Android AIDL原理

HFW發表於2019-04-16

前言

本文參考自Android開發藝術探索第二章,AIDL(Android介面定義語言)是Android提供的一種程式間通訊機制,我們可以利用它定義客戶端與服務端相互通訊都認可的程式設計介面。先來看看AIDL的用法,然後通過原始碼理解下AIDL生成的java類檔案

一、基本用法

下面我以一個簡單的跨程式進行新增Book,檢視BookList。這裡使用兩個App為例,首先編寫一個Book類該類實現了Parcelable介面,程式碼如下

public class Book implements Parcelable {

    public int bookId;
    public String bookName;

    private Book(Parcel in) {
        bookId = in.readInt();
        bookName = in.readString();
    }
    public Book(int id, String name) {
        bookId = id;
        bookName = name;
    }
    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 int describeContents() {
        return 0;
    }
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(bookId);
        dest.writeString(bookName);
    }
}
複製程式碼

然後在新建一個Book.aidl,IBookManager.aidl原始碼如下

// Book.aidl
parcelable Book;

// IBookManager.aidl
interface IBookManager {
    List<Book> getBookList();
    void addBook(in Book book);
}
複製程式碼

然後把Book.java、Book.aidl、IBookManager.aidl分別複製到client、server兩個module,接著在server這個module裡面新建一個BookService,再在清單檔案中配置IntentFilter,程式碼如下

class BookService : Service() {

    // 由於addBook方法執行在Binder執行緒中併發呼叫可能會出問題因此使用CopyOnWriteArrayList
    private val mBookList = CopyOnWriteArrayList<Book>()

    override fun onBind(intent: Intent?): IBinder? {
        return object : IBookManager.Stub() {
            override fun registerListener(listener: IOnNewBookArrivedListener?) {
                // listener 為IOnNewBookArrivedListener.Stub.Proxy例項
                mListener.register(listener)
            }
            override fun unregisterListener(listener: IOnNewBookArrivedListener?) {
                mListener.unregister(listener)
            }
            override fun getBookList(): MutableList<Book> {
                return mBookList
            }
            override fun addBook(book: Book?) {
                mBookList.add(book)
            }
        }
    }
}
<service android:name=".BookService" android:exported="true" android:enabled="true">
    <intent-filter>
        <action android:name="com.hefuwei.testaidl.bookService" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</service>
複製程式碼

最後在client這個module的MainActivity與BookService進行通訊,MainActivity程式碼如下,需要注意的是Intent得設定將要啟動的應用程式的包名這裡就是Server這個module的包名

// 這裡不需要使用DeathRecipient因為onServiceConnected其實就是使用DeathRecipient實現的,兩者
// 的區別是onServiceDisconnected執行在主執行緒中,而DeathRecipient.binderDied執行在Binder執行緒池中
class MainActivity : AppCompatActivity() {
    private lateinit var mListener: IOnNewBookArrivedListener
    private var mManager: IBookManager? = null
    private var mBookID = 0
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        mConnection = object : ServiceConnection {
            override fun onServiceDisconnected(name: ComponentName?) {
                mManager = null
            }
            override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
                mManager = IBookManager.Stub.asInterface(service)
            }
        }
        val intent = Intent()
        intent.action = "com.hefuwei.aidl.BookService"
        intent.setPackage(packageName)
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE)
    }
    fun addBook(v: View) {
        mManager?.addBook(Book(++mBookID, "第${mBookID}本書"))
    }
    fun allBooks(v: View) {
        Log.d("AIDL", mManager?.bookList?.toString())
    }
    override fun onDestroy() {
        super.onDestroy()
        unbindService(mConnection)
    }
}
複製程式碼

至此每當點選Client就可以與Server進行通訊了,來總結下用法

  • 建立AIDL檔案 IBookManager.aidl
  • 將 IBookManager.aidl拷貝到Client和Server下
  • Server建立一個服務,在onBind的時候返回一個IBookManager.Stub的例項
  • Server的清單檔案中配置該服務(IntentFilter)
  • Client通過建立Intent(需要設定Server的包名)呼叫bindService進行繫結
  • ServerConnection的onServiceConnected回撥中呼叫IBookManager.Stub.asInterface拿到IBookManager.Stub.Proxy物件
  • 呼叫IBookManager.Stub.Proxy的指定方法,即可完成與Server的通訊

接下來看看系統為我們生成的IBookManager.java的原始碼

二、原始碼分析

生成的程式碼如下所示

public interface IBookManager extends android.os.IInterface {
    public static abstract class Stub extends android.os.Binder implements com.hefuwei.testaidl.aidl.IBookManager {
        // 1
        private static final java.lang.String DESCRIPTOR = "com.hefuwei.testaidl.aidl.IBookManager";
        // 2
        public Stub() {
            this.attachInterface(this, DESCRIPTOR); 
        }
        // 3
        public static com.hefuwei.testaidl.aidl.IBookManager asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.hefuwei.testaidl.aidl.IBookManager))) {
                return ((com.hefuwei.testaidl.aidl.IBookManager) iin);
            }
            return new com.hefuwei.testaidl.aidl.IBookManager.Stub.Proxy(obj);
        }

        @Override
        public android.os.IBinder asBinder() {
            return this;
        }
        // 6
        @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) 
        throws android.os.RemoteException {
            java.lang.String descriptor = DESCRIPTOR;
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(descriptor);
                    return true;
                }
                case TRANSACTION_getBookList: {
                    data.enforceInterface(descriptor);
                    java.util.List<com.hefuwei.testaidl.aidl.Book> _result = this.getBookList();
                    reply.writeNoException();
                    reply.writeTypedList(_result);
                    return true;
                }
                case TRANSACTION_addBook: {
                    data.enforceInterface(descriptor);
                    com.hefuwei.testaidl.aidl.Book _arg0;
                    if ((0 != data.readInt())) {
                        _arg0 = com.hefuwei.testaidl.aidl.Book.CREATOR.createFromParcel(data);
                    } else {
                        _arg0 = null;
                    }
                    this.addBook(_arg0);
                    reply.writeNoException();
                    return true;
                }
                default: {
                    return super.onTransact(code, data, reply, flags);
                }
            }
        }

        private static class Proxy implements com.hefuwei.testaidl.aidl.IBookManager {
            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 java.util.List<com.hefuwei.testaidl.aidl.Book> getBookList() throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                java.util.List<com.hefuwei.testaidl.aidl.Book> _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.createTypedArrayList(com.hefuwei.testaidl.aidl.Book.CREATOR);
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
            // 4
            @Override
            public void addBook(com.hefuwei.testaidl.aidl.Book book) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    if ((book != null)) {
                        _data.writeInt(1);
                        book.writeToParcel(_data, 0);
                    } else {
                        _data.writeInt(0);
                    }
                    mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }
        }
        // 7
        static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    }

    public java.util.List<com.hefuwei.testaidl.aidl.Book> getBookList() throws android.os.RemoteException;
    public void addBook(com.hefuwei.testaidl.aidl.Book book) throws android.os.RemoteException;
}
複製程式碼

重點程式碼解釋如下

  1. 描述符,該值為IBookManager的全類名
  2. 根據AIDL的使用流程,Server會在onBind的時候返回一個IBookManager.Stub例項,呼叫了Stub的構造器內部呼叫Binder的attachInterface方法將當前例項以及描述符存到Binder例項中
  3. Client在onServiceConnect回撥中會呼叫IBookManager.asInterface,該方法內部呼叫queryLocalInterface如果該方法返回不為null,那麼就能證明Server和Client屬於同一個程式,那麼就直接強轉,不然就返回一個IBookManager.Stub.Proxy物件
  4. Client呼叫asInterface方法後拿到了一個IBookManager.Stub.Proxy例項,然後會呼叫addBook方法,如果相同程式會直接呼叫,不同程式就會呼叫到註釋5處
  5. 先獲取兩個Parcel例項一個用於儲存引數另一個儲存返回值(Parcel內部維護了一個Parcel陣列,陣列中有元素時會取陣列中的沒有會建立一個),然後在_data中先寫入當前的描述符,接著如果book不為null在_data中寫入1和book其中1表示資料為一個,如果為null就寫入0表示沒有資料,然後呼叫transact方法將_data、_reply傳入,該方法會掛起當前的執行緒(如果方法沒有被設定為oneWay),然後系統會在Binder執行緒池中呼叫Server端對應的Binder例項的onTransact方法,也就是註釋6的地方
  6. 方法內部從data這個Parcel例項中讀取一個int,如果為0那麼表示沒有引數直接呼叫Server端實現類的addBook將null傳入,如果不是0就根基data建立一個Book例項,然後呼叫addBook將建立的例項傳入,然後呼叫reply.writeNoExceptio指明沒有異常發生,由於addBook沒有返回值所有就直接結束了。getBookList其實流程也差不多就不解釋了
  7. 常量,用來標誌呼叫的是哪個方法

三、註冊監聽

上面只說到了客戶端呼叫服務端的方法,但是假設客戶端想要監聽服務端書籍數量的變化,一種做法是一直不停的輪詢去請求,另一種做法是使用觀察者模式註冊監聽,這裡講講怎麼註冊觀察者,首先建立IOnNewBookArrivedListener.aidl檔案,程式碼如下

interface IOnNewBookArrivedListener {
    void onBookArrived(in Book book);
}
複製程式碼

然後修改下IBookManager.aidl新增註冊和解除註冊兩個方法

interface IBookManager {
    List<Book> getBookList();
    void addBook(in Book book);
    void registerListener(IOnNewBookArrivedListener listener);
    void unregisterListener(IOnNewBookArrivedListener listener);
}
複製程式碼

接著將這兩個aidl檔案同步給Server和Client,然後修改Client的MainActivity的程式碼

class MainActivity : AppCompatActivity() {
    private lateinit var mConnection: ServiceConnection
    private lateinit var mListener: IOnNewBookArrivedListener
    private var mManager: IBookManager? = null
    private var mBookID = 0
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        mConnection = object : ServiceConnection {
            override fun onServiceDisconnected(name: ComponentName?) {
                mManager = null
            }
            override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
                mManager = IBookManager.Stub.asInterface(service)
            }
        }
        mListener = object : IOnNewBookArrivedListener.Stub() {
            override fun onBookArrived(book: Book?) {
                // 執行在客戶端的Binder執行緒池中
                runOnUiThread {
                    Toast.makeText(this@MainActivity, "${book?.id}", Toast.LENGTH_SHORT).show()
                }
            }
        }
        val intent = Intent()
        intent.action = "com.hefuwei.aidl.BookService"
        intent.setPackage(packageName)
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE)
    }
    fun addBook(v: View) {
        mManager?.addBook(Book(++mBookID, "第${mBookID}本書"))
    }
    fun allBooks(v: View) {
        Log.d("AIDL", mManager?.bookList?.toString())
    }
    fun register(v: View) {
        mManager?.registerListener(mListener)
        Toast.makeText(this, "註冊", Toast.LENGTH_SHORT).show()
    }
    fun unregister(v: View) {
        mManager?.unregisterListener(mListener)
        Toast.makeText(this, "解除註冊", Toast.LENGTH_SHORT).show()
    }
    override fun onDestroy() {
        super.onDestroy()
        unbindService(mConnection)
    }
}
複製程式碼

最後修改下服務端的BookService程式碼

class BookService : Service() {
    private val mBookList = CopyOnWriteArrayList<Book>()
    private val mListener = RemoteCallbackList<IOnNewBookArrivedListener>()
    override fun onBind(intent: Intent?): IBinder? {
        return object : IBookManager.Stub() {
            override fun registerListener(listener: IOnNewBookArrivedListener?) {
                // listener 為IOnNewBookArrivedListener.Stub.Proxy例項
                mListener.register(listener)
            }
            override fun unregisterListener(listener: IOnNewBookArrivedListener?) {
                mListener.unregister(listener)
            }
            override fun getBookList(): MutableList<Book> {
                return mBookList
            }
            override fun addBook(book: Book?) {
                mBookList.add(book)
            }
        }
    }
    override fun onCreate() {
        super.onCreate()
        Executors.newScheduledThreadPool(1).scheduleWithFixedDelay({
            val n = mListener.beginBroadcast()
            for (i in 0 until n) {
                mListener.getBroadcastItem(i).onBookArrived(Book(12, "12書"))
            }
            mListener.finishBroadcast()
        }, 0, 3, TimeUnit.SECONDS)
    }
}
複製程式碼

注意這裡需要使用RemoteCallbackList來儲存listener,因為registerListener的入參是一個IOnNewBookArrivedListener.Stub.Proxy物件,每次IPC都會建立一個新的物件,這樣就沒法解除註冊了。RemoteCallbackList內部維護了一個Key為Binder例項的Map,雖然每次會建立一個新的Proxy物件,但是這個Proxy物件的mRemote成員一直是同一個因此可以進行區分。而當服務端的資料傳送變化後就可以從RemoteCallbackList取出Proxy物件呼叫客戶端的方法

注意:如果通過AIDL傳遞一個Binder物件會先呼叫Parcel.writeStrongBinder將Binder寫入,然後在目標執行緒取出時會呼叫Parcel.readStrongBinder將Binder讀出,如果寫入端和讀出端位於兩個程式,讀取到的時候會是一個BinderProxy物件而不是原先的Binder物件

相關文章