AIDL:Android Interface Definition Language,即 Android 介面定義語言。 AIDL 是什麼 Android 系統中的進程之間不能共用記憶體,因此,需要提供一些機制在不同進程之間進行數據通信。 為了使其他的應用程式也可以訪問本應用程式提供的服務,Andro ...
AIDL:Android Interface Definition Language,即 Android 介面定義語言。
AIDL 是什麼
Android 系統中的進程之間不能共用記憶體,因此,需要提供一些機制在不同進程之間進行數據通信。
為了使其他的應用程式也可以訪問本應用程式提供的服務,Android 系統採用了遠程過程調用(Remote Procedure Call,RPC)方式來實現。與很多其他的基於 RPC 的解決方案一樣,Android 使用一種介面定義語言(Interface Definition Language,IDL)來公開服務的介面。我們知道 Android 四大組件中的 3 種(Activity、BroadcastReceiver和ContentProvider)都可以進行跨進程訪問,另外一種 Android 組件 Service 同樣可以。因此,可以將這種可以跨進程訪問的服務稱為 AIDL(Android Interface Definition Language)服務。
在介紹 AIDL 的使用以及其它特性前,我們先來瞭解下 AIDL 的核心——Binder。
Android 的 Binder 機制淺析
看過一些關於 Binder 的文章,總得來說 Binder 機制的底層實現很複雜,相當複雜,要完全搞清楚,得花大量的時間。從某種角度來說,個人覺得,對於 Binder,我們只需要瞭解其上層原理以及使用方法即可。
直觀來看,從代碼的角度來說,Binder 是 Android 系統源碼中的一個類,它實現了 IBinder 介面;從 IPC 角度來說,Binder 是 Android 中的一種跨進程通信方式;從 Android Framework 角度來講,Binder 是 ServiceManager 連接各種 Manager(ActivityManager、WindowManager 等等)和相應 ManagerService 的橋梁;從 Android 應用層來說,Binder 是客戶端和服務端進行通信的媒介,當 bindService 的時候,服務端會返回一個包含了服務端業務調用的 Binder 對象,通過這個 Binder 對象,客戶端就可以和服務端進行通信,這裡的服務包括普通服務和基於 AIDL 的服務。
接下來,我們通過一個 AIDL 示例,來分析 Binder 的工作機制。在工程目錄中新建一個名為 aidl 的 package,然後新建 Book.Java、Book.aidl(創建此文件時,as 會提示已存在,需要先用其它命令,創建成功後再重命名為 Book.aidl )和 IBookManager.aidl,代碼如下:
// Book.java package com.cy.ipcsample.aidl; import android.os.Parcel; import android.os.Parcelable; /** * 數據類 * @author cspecialy * @version v1.0.0 * @date 2018/5/14 21:38 */ public class Book implements Parcelable { public int bookId; public String bookName; public Book(int bookId, String bookName) { this.bookId = bookId; this.bookName = 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 int describeContents() { return 0; } @Override public void writeToParcel(Parcel parcel, int i) { parcel.writeInt(bookId); parcel.writeString(bookName); } @Override public String toString() { return "Book{" + "bookId=" + bookId + ", bookName='" + bookName + '\'' + '}'; } }
1 // Book.aidl 2 package com.cy.ipcsample.aidl; 3 4 parcelable Book;/
1 // IBookManager.aidl 2 package com.cy.ipcsample.aidl; 3 4 import com.cy.ipcsample.aidl.Book; 5 import com.cy.ipcsample.aidl.IOnNewBookArrivedListener; 6 7 interface IBookManager { 8 List<Book> getBookList(); 9 void addBook(in Book book); 10 void registerListener(IOnNewBookArrivedListener listener); 11 void unRegisterListener(IOnNewBookArrivedListener listener); 12 }
創建好三個文件之後,編譯一下,as 會在app/build/generated/source/aidl/debug
目錄下的 com.cy.ipcsample 包中生成一個名為 IBookManager.Java 的類,如下圖所示:
這是系統生成的 Binder 類,接下來我們要利用這個類來分析 Binder 的工作原理。其代碼如下:(生成的代碼格式很亂,可以格式化代碼之後看)
1 /* 2 * This file is auto-generated. DO NOT MODIFY. 3 * Original file: G:\\Android\\Github\\Bugly-Android-Demo\\sample\\ipcsample\\src\\main\\aidl\\com\\cy\\ipcsample 4 * \\aidl\\IBookManager.aidl 5 */ 6 package com.cy.ipcsample.aidl; 7 8 public interface IBookManager extends android.os.IInterface { 9 public java.util.List<com.cy.ipcsample.aidl.Book> getBookList() throws android.os.RemoteException; 10 11 public void addBook(com.cy.ipcsample.aidl.Book book) throws android.os.RemoteException; 12 13 /** Local-side IPC implementation stub class. */ 14 public static abstract class Stub extends android.os.Binder implements com.cy.ipcsample.aidl.IBookManager { 15 static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); 16 static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1); 17 private static final java.lang.String DESCRIPTOR = "com.cy.ipcsample.aidl.IBookManager"; 18 19 /** Construct the stub at attach it to the interface. */ 20 public Stub() { 21 this.attachInterface(this, DESCRIPTOR); 22 } 23 24 /** 25 * Cast an IBinder object into an com.cy.ipcsample.aidl.IBookManager interface, 26 * generating a proxy if needed. 27 */ 28 public static com.cy.ipcsample.aidl.IBookManager asInterface(android.os.IBinder obj) { 29 if ((obj == null)) { 30 return null; 31 } 32 android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); 33 if (((iin != null) && (iin instanceof com.cy.ipcsample.aidl.IBookManager))) { 34 return ((com.cy.ipcsample.aidl.IBookManager) iin); 35 } 36 return new com.cy.ipcsample.aidl.IBookManager.Stub.Proxy(obj); 37 } 38 39 @Override 40 public android.os.IBinder asBinder() { 41 return this; 42 } 43 44 @Override 45 public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws 46 android.os.RemoteException { 47 switch (code) { 48 case INTERFACE_TRANSACTION: { 49 reply.writeString(DESCRIPTOR); 50 return true; 51 } 52 case TRANSACTION_getBookList: { 53 data.enforceInterface(DESCRIPTOR); 54 java.util.List<com.cy.ipcsample.aidl.Book> _result = this.getBookList(); 55 reply.writeNoException(); 56 reply.writeTypedList(_result); 57 return true; 58 } 59 case TRANSACTION_addBook: { 60 data.enforceInterface(DESCRIPTOR); 61 com.cy.ipcsample.aidl.Book _arg0; 62 if ((0 != data.readInt())) { 63 _arg0 = com.cy.ipcsample.aidl.Book.CREATOR.createFromParcel(data); 64 } else { 65 _arg0 = null; 66 } 67 this.addBook(_arg0); 68 reply.writeNoException(); 69 return true; 70 } 71 } 72 return super.onTransact(code, data, reply, flags); 73 } 74 75 private static class Proxy implements com.cy.ipcsample.aidl.IBookManager { 76 private android.os.IBinder mRemote; 77 78 Proxy(android.os.IBinder remote) { 79 mRemote = remote; 80 } 81 82 public java.lang.String getInterfaceDescriptor() { 83 return DESCRIPTOR; 84 } @Override 85 public android.os.IBinder asBinder() { 86 return mRemote; 87 } 88 89 @Override 90 public java.util.List<com.cy.ipcsample.aidl.Book> getBookList() throws android.os.RemoteException { 91 android.os.Parcel _data = android.os.Parcel.obtain(); 92 android.os.Parcel _reply = android.os.Parcel.obtain(); 93 java.util.List<com.cy.ipcsample.aidl.Book> _result; 94 try { 95 _data.writeInterfaceToken(DESCRIPTOR); 96 mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0); 97 _reply.readException(); 98 _result = _reply.createTypedArrayList(com.cy.ipcsample.aidl.Book.CREATOR); 99 } finally { 100 _reply.recycle(); 101 _data.recycle(); 102 } 103 return _result; 104 } 105 106 @Override 107 public void addBook(com.cy.ipcsample.aidl.Book book) throws android.os.RemoteException { 108 android.os.Parcel _data = android.os.Parcel.obtain(); 109 android.os.Parcel _reply = android.os.Parcel.obtain(); 110 try { 111 _data.writeInterfaceToken(DESCRIPTOR); 112 if ((book != null)) { 113 _data.writeInt(1); 114 book.writeToParcel(_data, 0); 115 } else { 116 _data.writeInt(0); 117 } 118 mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0); 119 _reply.readException(); 120 } finally { 121 _reply.recycle(); 122 _data.recycle(); 123 } 124 } 125 126 127 } 128 } 129 }
可見,系統為我們生成了一個 IBookManager 介面,它繼承了 IInterface 這個介面,所以這裡要註意下,所有可以在 Binder 中傳輸的介面,都需要繼承 IInterface 介面。
接下來分析下,該類的工作機制。仔細看,可以發現,該類主要分成三個部分:
- 定義自身方法( getBookList 方法和 addBook 方法);
- 內部靜態類-Stub,該類繼承 Binder,同時實現 IBookManager 介面
- Stub的內部代理類-Proxy,也實現 IBookManager 介面
第一部分我們不用管它,主要看 Stub 類和 Proxy 類。在 Stub 中,首先聲明瞭兩個用於標識 IBookManager 方法的整型變數,這兩個變數用於標識在 transact 過程中客戶端所請求的是哪個方法。接著,是 asInterface 方法,該方法用於將服務端的 Binder 對象轉換成客戶端所需的 AIDL 介面類型的對象,該方法通過調用 Binder 的 queryLocalInterface 方法,判斷客戶端和服務端是否處於同一進程,如果客戶端、服務端處於同一進程,那麼此方法直接返回服務端的 Stub,否則,返回 Stub 的代理對象 Proxy。queryLocalInterface 實現如下:
/** * Use information supplied to attachInterface() to return the * associated IInterface if it matches the requested * descriptor. */ public @Nullable IInterface queryLocalInterface(@NonNull String descriptor) { if (mDescriptor.equals(descriptor)) { return mOwner; } return null; }
mOwner 是在 Stub 構造函數中傳進去的 this 參數。
接下來是 Stub 的代理類 Stub.Proxy,由上面的分析可知,Stub.Proxy 是運行在客戶端的(由 asInterface 方法返回給客戶端的對象),Stub.Proxy對象創建後,持有服務端的 Binder 對象,用於客戶端請求時調用服務端方法進行遠程調用。客戶端在向服務端發起請求時,調用 Stub.Proxy 的相應方法,Stub.Proxy 方法的流程如下:
- 首先創建該方法所需要的輸入型 Parcel 對象 _data、輸出型對象 _reply 和返回值對象(如果有);
- 然後把該方法的參數信息寫入 _data 中(如果有);
- 接著調用 transact 方法進行 RPC(遠程過程調用)請求,同時當前線程掛起;
- 然後在 transact 方法通過服務端的 Binder 對象調用服務端的 onTransact 方法,即 Stub 中的 onTransact 方法;
- onTransact 方法返回後,當前線程繼續執行,並從 _reply 中取出 RPC 過程返回的結果;
- 最後返回 _reply 中的數據(如果客戶端請求方法需要返回值)。
以上,就是系統生成的 IBookManager 的工作過程,需要註意下,服務端的 onTransact 方法是運行在 Binder 線程池中的。由於 IBookManager.Stub 類繼承 Binder,所以上述分析即 Binder 的工作機制,簡單總結下:
- 在客戶端和服務端連接時(一般通過 bindService 方法),服務端通過 asInterface 方法給客戶端返回一個 IInterface 介面類型的對象(如果客戶端和服務端在同一個進程,則返回服務端的 Binder 對象,否則返回 服務端 Binder 對象的代理);
- 客戶端通過該對象向服務端進行請求,如果客戶端和服務端不在同一個進程,則通過該對象所代理的 Binder 對象進行 RPC 請求,否則,直接通過 Binder 對象調用服務端相應方法。
或者參考下圖理解下:
由此可見,Binder 在 AIDL 中承載著重要的職能,是 AIDL 的核心,理解了 Binder 的工作機制,其實在很多方面都很有用。
AIDL 的使用
一套 AIDL 服務搭建的步驟如下:
- 創建 .aidl 文件,系統生成相應的繼承 IInterface 的介面類(暴露給客戶端的介面)。
- 創建一個 Service(服務端),實現 .aidl 文件中的介面。
- 創建客戶端,綁定服務端的 Service。
- 客戶端綁定服務端成功後,將服務端返回的 Binder 對象轉成 AIDL 介面所屬的 IInterface 類型。調用 AIDL 中的方法,實現和服務端的通信。
接下來,我們使用上面的 IBookManager 來實現 AIDL。
- 創建 .aidl 文件
直接使用上面創建好的 Book.aidl、IBookManager.aidl 文件即可 -
創建服務端
創建一個 Service,命名為 BookManagerService,代碼如下:1 package com.cy.ipcsample.aidl 2 3 import android.app.Service 4 import android.content.Intent 5 import android.os.IBinder 6 import android.os.RemoteCallbackList 7 import android.util.Log 8 import java.util.concurrent.CopyOnWriteArrayList 9 10 class BookManagerService : Service() { 11 private val TAG = "BookManagerService" 12 13 private val mBookList = CopyOnWriteArrayList<Book>() 14 private val mListenerList = RemoteCallbackList<IOnNewBookArrivedListener>() 15 16 /** 17 * 實現 AIDL 介面的 Binder 對象,客戶端綁定服務端時,直接返回此 Binder 對象 18 */ 19 private val mBinder = object : IBookManager.Stub() { 20 21 override fun getBookList(): MutableList<Book> { 22 return mBookList 23 } 24 25 override fun addBook(book: Book?) { 26 mBookList.add(book) 27 } 28 29 } 30 31 override fun onBind(intent: Intent): IBinder { 32 return mBinder 33 } 34 35 override fun onCreate() { 36 super.onCreate() 37 38 // 創建兩本圖書 39 mBookList.add(Book(1, "Android")) 40 mBookList.add(Book(2, "iOS")) 41 } 42 }
然後在 AndroidManifest 中註冊 Service,註意啟動多進程:
<service android:name=".aidl.BookManagerService" android:process="com.cy.ipcsample.bookManagerService"> </service>
-
客戶端綁定服務端的 Service
客戶端的創建,直接使用 Activity 即可,綁定遠程服務的代碼如下:private val mConnection = object : ServiceConnection { override fun onServiceDisconnected(name: ComponentName?) { } /** * 連接遠程服務成功的回調 */ override fun onServiceConnected(name: ComponentName?, service: IBinder?) { } } // 綁定遠程服務 bindService(Intent(this, BookManagerService::class.java), mConnection, Context.BIND_AUTO_CREATE)
-
與服務端通信
與伺服器綁定成功後,首先在 ServiceConnection 的回調中,將服務端返回的 Binder 對象轉換成 AIDL 介面所屬的對象,就可以調用相應方法和服務端通信了,代碼如下:// 將服務端返回的 Binder 對象轉換成 IBookManager 對象 val bookManager = IBookManager.Stub.asInterface(service) // 與服務端通信 try { // 獲取圖書列表 val list = bookManager.bookList Log.i(TAG, "query book list, list type: ${list.javaClass.canonicalName}") Log.i(TAG, "query book list: $list") // 添加一本圖書 val book = Book(3, "Android開發藝術探索") bookManager.addBook(book) Log.i(TAG, "add book: $book") // 獲取圖書列表 val newList = bookManager.bookList Log.i(TAG, "query book list: $newList") } catch (e: RemoteException) { e.printStackTrace() }
代碼中,我們先查詢了服務端的圖書列表,接著向服務端添加一本書
Android藝術開發探索
,然後再次查詢看是否添加成功。運行下看 log,如下圖所示:可見,運行結果和預期結果一致。完整的客戶端代碼如下: 1 package com.cy.ipcsample.aidl 2 3 import android.content.ComponentName 4 import android.content.Context 5 import android.content.Intent 6 import android.content.ServiceConnection 7 import android.os.Bundle 8 import android.os.IBinder 9 import android.os.RemoteException 10 import android.support.v7.app.AppCompatActivity 11 import android.util.Log 12 import com.cy.ipcsample.R 13 14 class BookManagerActivity : AppCompatActivity() { 15 16 private val TAG = "BookManagerActivity" 17 18 private val mConnection = object : ServiceConnection { 19 20 override fun onServiceDisconnected(name: ComponentName?) { 21 Log.d(TAG, "binder died.") 22 } 23 24 /** 25 * 連接遠程服務成功的回調 26 */ 27 override fun onServiceConnected(name: ComponentName?, service: IBinder?) { 28 // 將服務端返回的 Binder 對象轉換成 IBookManager 對象 29 val bookManager = IBookManager.Stub.asInterface(service) 30 31 // 與服務端通信 32 try { 33 // 獲取圖書列表 34 val list = bookManager.bookList 35 Log.i(TAG, "query book list, list type: ${list.javaClass.canonicalName}") 36 Log.i(TAG, "query book list: $list") 37 38 // 添加一本圖書 39 val book = Book(3, "Android開發藝術探索") 40 bookManager.addBook(book) 41 Log.i(TAG, "add book: $book") 42 43 // 獲取圖書列表 44 val newList = bookManager.bookList 45 Log.i(TAG, "query book list: $newList") 46 } catch (e: RemoteException) { 47 e.printStackTrace() 48 } 49 } 50 51 } 52 53 override fun onCreate(savedInstanceState: Bundle?) { 54 super.onCreate(savedInstanceState) 55 setContentView(R.layout.activity_book_manager) 56 57 // 綁定遠程服務 58 bindService(Intent(this, BookManagerService::class.java), 59 mConnection, Context.BIND_AUTO_CREATE) 60 } 61 62 override fun onDestroy() { 63 unbindService(mConnection) 64 super.onDestroy() 65 } 66 }
到此,一個簡單的 AIDL 示例就完成了,當然,AIDL 的使用遠沒有那麼簡單,還有很多情景需要考慮的,比如:客戶端需要隨時服務端在狀態變化時同時客戶端,類似觀察這模式,那麼訂閱與反訂閱怎麼實現;Binder 意外死亡,怎麼重連等等,更多內容,可以參考《Android藝術開發探索》,電子書下載
AIDL 的非同步調用
AIDL 的調用過程是同步還是非同步的?
這個問題其實看這一節的標題大家都知道了,AIDL 的調用過程是同步的。同時,上面分析 Binder 的機制時,也提到了,客戶端進行遠程 RPC 請求時,線程會掛起,等待結果,由此也可知,AIDL 的調用過程是同步的,下麵來驗證下。
首先,在服務端的 BookManagerService 中實現的 Binder 對象的 getBookList 方法添加延時執行,如下圖所示:
然後,在客戶端的 BookManagerActivity 中添加一個按鈕,點擊按鈕時調用服務端 Binder 的 getBookList 做 RPC 請求。代碼如下:
運行後,連續點擊按鈕,結果如下圖所示:
由圖所知,連續點擊按鈕過後一段時間,出現了無響應錯誤( ANR ),由此可知,客戶端向服務端做 RPC 請求時,是同步的,也就是說:AIDL 的調用過程是同步的。
AIDL 的調用過程是同步的,當我們需要服務端做耗時操作時,肯定是不能使用同步調用的,否則輕者影響用戶體驗,重者直接 ANR 或者應用崩潰。那麼如何使 AIDL 的調用過程是非同步的呢?
其實也很簡單,只需要把調用放到非 UI 線程即可,如果要對調用的返回做 UI 更新的話,再通過 Handler 處理即可。如下圖所示:
本文參考
- 《Android開發藝術探索》 第二章