• AIDL使用解析


    之前面试的时候被问到这个问题,然而当时只有一个大致的印象,随GG,于是我就重新整理的一下。这里大力推荐《Android开发艺术探索》这本书,写的太好了!

    1.AIDL

    AIDL(Android Interface Define Language) 是IPC进程间通信方式的一种.用于生成可以在Android设备上两个进程之间进行进程间通信(interprocess communication, IPC)的代码.

    2.AIDL和Messenger的区别:

    1. Messenger不适用大量并发的请求:Messenger以串行的方式来处理客户端发来的消息,如果大量的消息同时发送到服务端,服务端仍然只能一个个的去处理。
    2. Messenger主要是为了传递消息:对于需要跨进程调用服务端的方法,这种情景不适用Messenger。
    3. Messenger的底层实现是AIDL,系统为我们做了封装从而方便上层的调用。
    4. AIDL适用于大量并发的请求,以及涉及到服务端端方法调用的情况

    3.使用AIDL的步骤:

    下面一个简单的例子来说明AIDL的使用:假设一个情景我们需要计算a+b,我们需要在客户端传递两个参数a和b,然后将参数传递给服务端(另一个进程)来进行计算,计算结果传递给客户端。

    目录结构

    1. 新建一个项目作为服务端,在项目中新建AIDL文件。这里我命名为:IImoocAIDL.aidl

    // IImoocAIDL.aidl
    package com.mecury.aidltest;
    // Declare any non-default types here with import statements
    interface IImoocAIDL {    
        //计算num1 + num2    
        int add(int num1,int num2);
    }

    点击同步按钮(一定要先同步),查看是否生成IImoocAIDL文件。

    aidl的生成.gif


    生成的文件如下,我写了详细的注释,相信你能够看懂:

    生成的AIDL文件#IImoocAIDL.java:

    这里来说一下AIDL通信的原理:首 先看这个文件有一个叫做proxy的类,这是一个代理类,这个类运行在客户端中,其实AIDL实现的进程间的通信并不是直接的通信,客户端和服务端都是通 过proxy来进行通信的:客户端调用的方法实际是调用是proxy中的方法,然后proxy通过和服务端通信将返回的结果返回给客户端。

    
    package com.mecury.aidltest;
    
    
    public interface IImoocAIDL extends android.os.IInterface {
        
        public static abstract class Stub extends android.os.Binder implements com.mecury.aidltest.IImoocAIDL {
            private static final java.lang.String DESCRIPTOR = "com.mecury.aidltest.IImoocAIDL"; 
    
            
            public Stub() {
                this.attachInterface(this, DESCRIPTOR);
            }
    
            
            
            
            public static com.mecury.aidltest.IImoocAIDL asInterface(android.os.IBinder obj) {
                if ((obj == null)) {
                    return null;
                }
                android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
                if (((iin != null) && (iin instanceof com.mecury.aidltest.IImoocAIDL))) {
                    return ((com.mecury.aidltest.IImoocAIDL) iin);
                }
                return new com.mecury.aidltest.IImoocAIDL.Stub.Proxy(obj);
            }
            
            @Override
            public android.os.IBinder asBinder() {
                return this;
            }
    
            
            @Override
            public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
                switch (code) {
                    case INTERFACE_TRANSACTION: {
                        reply.writeString(DESCRIPTOR);
                        return true;
                    }
                    case TRANSACTION_add: {
                        data.enforceInterface(DESCRIPTOR);
                        
                        int _arg0;
                        _arg0 = data.readInt();
                        int _arg1;
                        _arg1 = data.readInt();
                        
                        int _result = this.add(_arg0, _arg1);
                        
                        reply.writeNoException();
                        reply.writeInt(_result);
                        return true;
                    }
                }
                return super.onTransact(code, data, reply, flags); 
            }
    
            
            private static class Proxy implements com.mecury.aidltest.IImoocAIDL {
                private android.os.IBinder mRemote; 
    
                Proxy(android.os.IBinder remote) {
                    mRemote = remote;
                }
                
                @Override
                public android.os.IBinder asBinder() {
                    return mRemote;
                } 
    
                public java.lang.String getInterfaceDescriptor() {
                    return DESCRIPTOR;
                }
    
                
                @Override
                public int add(int num1, int num2) throws android.os.RemoteException {
                    android.os.Parcel _data = android.os.Parcel.obtain();
                    android.os.Parcel _reply = android.os.Parcel.obtain();
                    int _result;
                    try {
                        
                        _data.writeInterfaceToken(DESCRIPTOR);
                        _data.writeInt(num1);
                        _data.writeInt(num2);
                        
                        mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
                        _reply.readException();
                        _result = _reply.readInt();
                    } finally {
                        _reply.recycle();
                        _data.recycle();
                    }
                    return _result;
                }
            }
            
            static final int TRANSACTION_add = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        }
    
        
        public int add(int num1, int num2) throws android.os.RemoteException;
    }

    代码中的几个方法:
    DESCRIPTION
    Binderd的唯一标识,一般用当前的类名表示。
    asInterface(android.os.IBinder obj)
    用于将服务端的Binder对象转换为客户端需要的AIDL接口类型的对象,转换区分进程,客户端服务端位于同一进程,返回服务端的 //Stub对象本身;否则返回的是系统的封装后的Stub.proxy对象。
    asBInder
    返回Binder对象
    onTransact
    此方法运行在服务端中的Binder线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由此方法处理。
    Proxy#add
    此 方法运行在客户端,当客户端远程调用此方法时,它的内部实现是这样的:首先创建该方法所需要的输入型Parcel对象_data、输出型Parcel对象 _reple和返回值对象_result,然后将该方法的参数信息写入_data中;接着调用transact方法来发RPC请求,同时当前线程挂起;然 后服务端的onTransact方法会被调用,直到RPC过程返回后,当前线程继续执行,并从_reply中取出RPC过程返回的结果,写入 _result中。

    2.新建一个客户端File-》new--》new module--》phone & table module。这里我的命名为aidlclient.java
    同样要在客户端创建AIDL文件,里面的包名和所在位置要求完全一样。

    3.在服务端创建一个Service用来监听客户端的连接请求。

    public class IRemoteService extends Service {
    
        
        @Override
        public IBinder onBind(Intent intent) {
            return iBinder;
        }
    
        private IBinder iBinder = new IImoocAIDL.Stub(){
    
            @Override
            public int add(int num1, int num2) throws RemoteException {
                Log.e("TAG","收到了来自客户端的请求" + num1 + "+" + num2 );
                return num1 + num2;
            }
        };
    }

    最后,别忘记在AndroidManifest.xml中注册该Service。

    <service android:name=".IRemoteService"
                android:process=":remote"
                android:exported="true">
                <intent-filter>
                    <action android:name="com.mecury.aidltest.IRomoteService"/>
                </intent-filter>
    </service>

    4.客户端的编写
    activity_main.xml:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
    
        <EditText
            android:id="@+id/num1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="+"/>
        <EditText
            android:id="@+id/num2"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="="/>
        <TextView
            android:id="@+id/text"
            android:layout_width="match_parent"
            android:layout_height="30dp" />
        <Button
            android:id="@+id/button"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="计算"/>
    </LinearLayout>

    MainActivity.java

    package com.mecury.aidlclient;
    
    import android.content.ComponentName;
    import android.content.Context;
    import android.content.Intent;
    import android.content.ServiceConnection;
    import android.os.Bundle;
    import android.os.IBinder;
    import android.os.RemoteException;
    import android.support.v7.app.AppCompatActivity;
    import android.view.View;
    import android.widget.Button;
    import android.widget.EditText;
    import android.widget.TextView;
    
    import com.mecury.aidltest.IImoocAIDL;
    
    public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    
        private EditText num1;
        private EditText num2;
        private Button button;
        private TextView text;
    
        private IImoocAIDL iImoocAIDL;
    
        private ServiceConnection conn = new ServiceConnection() {
    
            
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                iImoocAIDL = IImoocAIDL.Stub.asInterface(service);
            }
    
            @Override
            public void onServiceDisconnected(ComponentName name) {
                iImoocAIDL = null;
            }
        };
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            bindService();
            initView();
    
        }
    
        private void initView() {
            num1 = (EditText) findViewById(R.id.num1);
            num2 = (EditText) findViewById(R.id.num2);
            button = (Button) findViewById(R.id.button);
            text = (TextView) findViewById(R.id.text);
    
            button.setOnClickListener(this);
        }
    
    
        @Override
        public void onClick(View v) {
            int num11 = Integer.parseInt(num1.getText().toString());
            int num22 = Integer.parseInt(num2.getText().toString());
    
            try {
                int res = iImoocAIDL.add(num11,num22);
                text.setText(num11 +"+"+ num22 +"="+ res);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    
        private void bindService() {
    
            Intent intent = new Intent();
            
            intent.setAction("com.mecury.aidltest.IRomoteService");
            
            intent.setComponent(new ComponentName("com.mecury.aidltest","com.mecury.aidltest.IRemoteService"));
            
            bindService(intent,conn, Context.BIND_AUTO_CREATE);
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            unbindService(conn);
        }
    }

    5.运行代码
    先启动服务端,在启动客户端。

    结果.gif

    小结:看完上面,是不是已经对于AIDL的用法有个大概的了解。下面来看一个更为复杂的例子,这是《android开发艺术探索》中的例子: 建立一个图书管理,能够添加图书、得到图书列表、使用观察者模式、当新书到达时通知所有观察者。

    4.AIDL高级示例

    1.先看Book.java。需要注意的是,AIDL能够传输的数据类型有限制,这里必须将book序列化才能够使用,同时Book类在客户端和服务端都要这样定义

    Book.java
    public class Book implements Parcelable {
        public int bookId;
        public String bookName;
    
        public Book(int bookId, String bookName) {
            this.bookId = bookId;
            this.bookName = bookName;
        }
    
        @Override
        public int describeContents() {
            return 0;
        }
    
        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeInt(bookId);
            dest.writeString(bookName);
        }
    
        protected Book(Parcel in) {
            bookId = in.readInt();
            bookName = in.readString();
        }
    
        public static final Creator<Book> CREATOR = new Creator<Book>() {
            @Override
            public Book createFromParcel(Parcel in) {
                return new Book(in);
            }
    
            @Override
            public Book[] newArray(int size) {
                return new Book[size];
            }
        };
    
        @Override
        public String toString() {
            return bookId + ":" + bookName;
        }
    }

    2.AIDL文件

    Book.aidl
    package com.mecury.aidltest2;
    parcelable Book;
    IOnNewBookArrivedListener.aidl
    package com.mecury.aidltest2;
    
    import com.mecury.aidltest2.book;
    interface IOnNewBookArrivedListener {
         void OnNewBookArrivedListener(in Book book);
    }
    IBookManager.aidl
    package com.mecury.aidltest2;
    
    import com.mecury.aidltest2.book;
    import com.mecury.aidltest2.IOnNewBookArrivedListener;
    
    interface IBookManager {
        List<Book> getBookList();
        void addBook(in Book book);
        void registerListener(IOnNewBookArrivedListener listener);
        void unregisterListener(IOnNewBookArrivedListener listener);
    }

    3.服务端

    BookManagerService.java
    public class BookManagerService extends Service {
    
        private static final String TAG = "BMS";
    
        private AtomicBoolean mIsServiceDestoryed = new AtomicBoolean(false);
        private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<Book>();
        private RemoteCallbackList<IOnNewBookArrivedListener> mListeners = new RemoteCallbackList<>();
    
        
        private Binder mBinder = new IBookManager.Stub(){
    
            @Override
            public List<Book> getBookList() throws RemoteException {
                return mBookList;
            }
    
            @Override
            public void addBook(Book book) throws RemoteException {
                mBookList.add(book);
            }
    
            @Override
            public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
                mListeners.register(listener);
            }
    
            @Override
            public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
                mListeners.unregister(listener);
            }
        };
    
        @Override
        public void onCreate() {
            super.onCreate();
            mBookList.add(new Book(1,"Android"));
            mBookList.add(new Book(2, "Ios"));
            new Thread(new serviceWork()).start();
        }
    
        @Nullable
        @Override
        public IBinder onBind(Intent intent) {
            return mBinder;
        }
        
        private void onNewBookArrived(Book book) throws RemoteException {
            mBookList.add(book);
            final int N = mListeners.beginBroadcast();
            Log.e("onNewBookArrived","registener listener size:" + N);
            for (int i = 0; i < N; i++){
                IOnNewBookArrivedListener l = mListeners.getBroadcastItem(i);
                if (l!=null){
                    l.OnNewBookArrivedListener(book);
                }
            }
            mListeners.finishBroadcast();
        }
        
        private class serviceWork implements Runnable{
    
            @Override
            public void run() {
                while (!mIsServiceDestoryed.get()){
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    int bookId = mBookList.size() + 1;
                    Book newBook = new Book(bookId,"new Book #" + bookId);
    
                    try {
                        onNewBookArrived(newBook);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
        @Override
        public void onDestroy() {
            mIsServiceDestoryed.set(true);
            super.onDestroy();
        }
    }
    在里面我们发现几处特殊的地方
    1.CopyOnWriteArrayList:支持并发的读写,这里我们使用它来进行自动的线程同步
    2.RemoteCallBackList:是系统专门提供的用于删除跨进程listener的接口。它的工作原理其实很简单:在它的内部有一个Map结构专门用来保存所有AIDL回调ArrayMap<IBinder, Callback> mCallback = new ArrayMap<IBinder, Callback>();,当客户端注册listener时,会把listener的信息注册到mCallBack中,其中key和value通过下面方式获得:IBinder key = listener.asBinder();Callback value = new Callback(listener, cookie)。另外一点我们需要知道:对 象是不能跨进程传输的,对象的跨进程传输过程实际是反序列化的过程,这是我们Book类为什么要实现Parcelable接口的原因。在跨进程传输 中,Binder会把客户端传递的对象重新转化并生成另一对象,当我们注册和解注册的过程中使用的是同一个客户端对象,但是通过Binder传递到服务端 却生成了两个不同的对象。而RemoteCallBackList就是用来解决这个问题的,虽然所多次跨进程传输客户端的同一个对象会在服务端 生成不同的对象,但在这些新生成的对象都有一个共同点,那就是他们底层的Binder对象是同一个,利用这个,就可以实现上面无法实现的功能。当客户端解 注册时,我们只要遍历所有的listener,找出那个和解注册listener具有相同Binder对象服务器listener并把他删除掉即可,这就 是RemoteCallbackList为我们做的事情。(对于这个看不明白的,可以看看《android 开发艺术探索》)

    4.客户端:

    MainActivity.java
    public class MainActivity extends AppCompatActivity {
    
        private static final String TAG = "BookManagerActivity";
        private IBookManager bookManager;
        private static final int MESSAGE_NEW_BOOK_ARRIVED = 1;
    
        private Handler mHandler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what){
                    case MESSAGE_NEW_BOOK_ARRIVED:
                        Log.e(TAG, "received new book:" + msg.obj);
                        break;
                    default:
                        super.handleMessage(msg);
                }
    
            }
        };
    
        private ServiceConnection mService = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                bookManager = IBookManager.Stub.asInterface(service);
                try {
                    List<Book> list = bookManager.getBookList();
                    Log.e(TAG, "query book list,list type:" + list.getClass().getCanonicalName());
                    Log.e(TAG, "query book list:" + list.toString());
                    Book newBook = new Book(3, "android进阶");
                    bookManager.addBook(newBook);
                    Log.e(TAG, "add book:" + newBook);
                    List<Book> newList =  bookManager.getBookList();
                    Log.e(TAG, "query book list:" + newList.toString());
                    bookManager.registerListener(mNewBookArrivedListener);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
    
            @Override
            public void onServiceDisconnected(ComponentName name) {
                bookManager = null;
                Log.e(TAG, "binder died.");
            }
        };
    
        private IOnNewBookArrivedListener mNewBookArrivedListener = new IOnNewBookArrivedListener.Stub() {
            @Override
            public void OnNewBookArrivedListener(Book book) throws RemoteException {
                mHandler.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED, book).sendToTarget();
            }
        };
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            bindService();
    
        }
    
        private void bindService() {
            Intent intent = new Intent();
            intent.setAction("com.mecury.aidltest2.BookManagerService");
            intent.setComponent(new ComponentName("com.mecury.aidltest2", "com.mecury.aidltest2.BookManagerService"));
            bindService(intent, mService, Context.BIND_AUTO_CREATE);
        }
    
        @Override
        protected void onDestroy() {
            if (bookManager != null && bookManager.asBinder().isBinderAlive()){
                Log.e(TAG, "unregister listener:" + mNewBookArrivedListener);
                try {
                    bookManager.unregisterListener(mNewBookArrivedListener);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
            unbindService(mService);
            super.onDestroy();
        }
    }

    目录结构:


    目录

    运行结果:

    客户端log:

    06-29 22:52:29.438 17007-17007/com.mecury.client E/BookManagerActivity: query book list,list type:java.util.ArrayList
    06-29 22:52:29.438 17007-17007/com.mecury.client E/BookManagerActivity: query book list:[1:Android, 2:Ios]
    06-29 22:52:29.439 17007-17007/com.mecury.client E/BookManagerActivity: add book:3:android进阶
    06-29 22:52:29.439 17007-17007/com.mecury.client E/BookManagerActivity: query book list:[1:Android, 2:Ios, 3:android进阶]
    06-29 22:52:33.487 17007-17007/com.mecury.client E/BookManagerActivity: received new book:4:new Book #4
    06-29 22:52:38.489 17007-17007/com.mecury.client E/BookManagerActivity: received new book:5:new Book #5
    06-29 22:52:43.491 17007-17007/com.mecury.client E/BookManagerActivity: received new book:6:new Book #6
    06-29 22:52:48.503 17007-17007/com.mecury.client E/BookManagerActivity: received new book:7:new Book #7

    服务端log:

    06-29 22:52:33.487 17027-17044/com.mecury.aidltest2:remote E/onNewBookArrived: registener listener size:1
    06-29 22:52:38.488 17027-17044/com.mecury.aidltest2:remote E/onNewBookArrived: registener listener size:1
    06-29 22:52:43.490 17027-17044/com.mecury.aidltest2:remote E/onNewBookArrived: registener listener size:1
    06-29 22:52:48.492 17027-17044/com.mecury.aidltest2:remote E/onNewBookArrived: registener listener size:1

    5.一些补充

    AIDL支持的数据类型
    • 基本数据类型(int、long、char 等)
    • String 和 CharSequence
    • List:只支持ArrayList,里面的每个元素都必须被AIDL支持。
    • Map: 只支持HashMap, 里面的每个元素都必须被AIDL支持。
    • Parcelable: 所有实现了Parcelable接口的对象
    • AIDL: 所有的AIDL接口本身也可以在AIDL文件中使用

    参考:
    1.《android开发艺术探索》

     
  • 相关阅读:
    WebAPi返回类型到底应该是什么才合适,这是个问题?
    NuGet程序包安装SQLite后完全抽离出SQLite之入门介绍及注意事项,你真的懂了吗?
    完全抽离WebAPi之特殊需求返回HTML、Css、JS、Image
    模板引擎Nvelocity实例
    C#由变量捕获引起对闭包的思考
    AngularJS之指令中controller与link(十二)
    AngularJS之ng-class(十一)
    AngularJS之WebAPi上传(十)
    AngularJS之代码风格36条建议【一】(九)
    两个List合并去重
  • 原文地址:https://www.cnblogs.com/mercuryli/p/5629971.html
Copyright © 2020-2023  润新知