--- title: 'AIDL & Binder' disqus: kyleAlien --- AIDL & Binder === ## OverView of Content 如有引用參考請詳註出處,感謝 :smile: > Binder 是 Android 進程通訊的特色,包括 APP 的啟動 AMS 應用管理都有使用到 Binder,以下我們主要通過 AIDL 來了解 Binder [TOC] ## 多進程溝通 * Binder 是 Android 開發,多進程溝通的其中一種方式,不同進程之間是不能相互通訊的,而進程間溝通有也有其他的方式,每種方式所擁有的特性也不同,**Socket、信號、信號量、內存共享、管道** | 溝通方式 | 性能(工作方式) | 特點 | 安全性 | | -------- | -------- | -------- | - | | Socket | 須拷貝 2 次 | 使用 C/S 架構,擁有相同接口 | 依賴上層協議,訪問接口開放 | | 內存共享 | **無須拷貝** | 不易控制,需要作到許多的同步動作 | 依賴上層協議,訪問接口開放 | | Binder | 須拷貝 1 次 | 使用 C/S 架構,擁有相同接口 | **PID 是系統分配,較安全** | > **基於上層協議**,代表了是依靠上層來給予 Port / PID 這些 key,所以較不安全 ### 進程通訊功能 1. 空間:每個進程所擁有的空間是有限制的,要拓展其功能就必須使用到多進程 2. 體驗:減少程序崩潰導致 APP 整個無法運作的狀況,因為屬於不同的進程,令一個進程的崩潰不會導致主要的進程一起崩潰 ### 傳統進程共享 & Android Binder 1. 內存共享:**無須拷貝** > ![](https://i.imgur.com/L8Xr8U9.png) 2. Socket:**需要兩次拷貝** > ![](https://i.imgur.com/pENyPwO.png) 3. Binder:**需要拷貝一次** > ![](https://i.imgur.com/SBMKmKA.png) ## AIDL 概述 **AIDL 是 Android 基於 Binder 做出的功能,AIDL 有以下特點** 1. 文件類型:**副檔名為 .aidl** 2. 數據類型:**除了基礎類型以外的類型都需要 import 導入該類,就算在 ++同一資料夾內也需要++** :::info * AIDL 基礎 8 類 byte、short、int、long、float、double、boolean、char String 類型 CharSequence 類型 **集合類內部的元素**必須是 `aidl 支持的類型` or `aidl 生成的接口` or `定義為 parcelable` > List 類型,**可以使用泛型** Map 類型,**不可使用泛型** ::: 3. **方向標示 Tag**:有分為三種,^1^ in、^2^ out、^3^ inout,這些 Tag 是控制數據的流向 ^1^ **String & CharSequence 只能為 in**,**in 時數據是 ++使用拷貝++** ^2^ **out 時就會同步數據,服務端修改會影響客戶端** | 方向標示 Tag | 說明 | 備註 | | -------- | -------- | -------- | | in | 數據只能由 Binder Client 流向 Binder Server | **String & CharSequence 只能為 in** | | out | 數據只能由 Binder Server 端流向 Binder Client | | | inout | Binder Client & Binder Client 會同步資料 | | > ![](https://i.imgur.com/UQzGIzv.png) 4. 明確包名:在 AIDL 文件文件中需要明確聲明數據類型的包名,需要對照 Java 文件的資料結構,**也就是相同的包名底下**(後面會看到範例說明) ### AIDL 文件類型 - Parcelable * AIDL 文件可以分類為兩塊,^1^ 對非基礎的類實現 Parcelable 接口、^2^ 定義接口方法,聲明要暴露哪些接口給客戶端調用(使用定向 tag 設定) | 方向標示 Tag | Parcelable 的對應函數 | 備註 | | -------- | -------- | -------- | | in | writeToParcel | 預設 | | out | readFromParcel | 自己實現 | * Parcelable 是一個 interface,它內部有幾個一定要實現的方法,**==預設生成的模板類只支持 in 的方向標示==,因為它內部有一定要實現 writeToParcel 方法** ```java= // Parcelable interface public interface Parcelable { int CONTENTS_FILE_DESCRIPTOR = 1; int PARCELABLE_WRITE_RETURN_VALUE = 1; int describeContents(); // in void writeToParcel(Parcel var1, int var2); public interface ClassLoaderCreator<T> extends Parcelable.Creator<T> { T createFromParcel(Parcel var1, ClassLoader var2); } public interface Creator<T> { T createFromParcel(Parcel var1); T[] newArray(int var1); } } ``` * **如果要實現 out 方向標示就要自己寫一個 readFromParcel 方法,==讀取的++順序一定要和寫入的順序相同++==** ```java= // in 方向 @Override public void writeToParcel(Parcel p, int flags) { p.writeString(name); p.writeInt(age); } // out 方向 public void readFromParcel(Parcel p) { name = p.readString(); age = p.readInt(); } ``` * 如上實現定義後就可以使用 in / out / inout 方向 Tag ```java= public class Person implements Parcelable { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } protected Person(Parcel in) { this.name = in.readString(); this.age = in.readInt(); } public static final Creator<Person> CREATOR = new Creator<Person>() { @Override public Person createFromParcel(Parcel in) { return new Person(in); } @Override public Person[] newArray(int size) { return new Person[size]; } }; @Override public int describeContents() { return 0; } // in 方向 @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(name); dest.writeInt(age); } // out 方向 public void readFromParcel(Parcel p) { name = p.readString(); age = p.readInt(); } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", grade=" + grade + '}'; } @Override public String toString() { return "Name: " + name + ", age: " + age; } } ``` ### AIDL Service 創建 * 目前我這裡使用在相同的 Project 中呼叫在不同進程的 Server,所以這邊是使用創建另外一個 Service Module,並依賴在主 Module 中 * 主 Moudle gradle ```groovy= // 主 Moudle 依賴 Service Module dependencies { ... implementation project(":remote_service") } ``` * Service gradle(Module 取名為 remote_service) ```groovy= // 依賴 Module 必須修改為 library plugins { id 'com.android.library' } android { compileSdkVersion 30 buildToolsVersion "30.0.3" defaultConfig { // 必須移除 applicationId,因為一個應用只能有一個 applicationId // applicationId "com.example.mytest_aidl" minSdkVersion 21 targetSdkVersion 30 ... } ... } ``` * 在 src 資料夾上右鍵 -> new -> AIDL -> AIDL File 就可創建(自己手動創建也可以),之後**使用 rebuild 就可以自動創建** 需要的程式 1. 創建一個名為 IRemoteService.aidl 檔案 > ![](https://i.imgur.com/5EobkFv.png) 2. 讓 Java 層需要傳輸的物件實現 Parcelable 接口 ```java= // 這裡的包就很重要 package com.example.remote_service.info; public class BookData implements Parcelable { private String name; private int pageCount; private int starLevel; public String getName() { return name; } public BookData setName(String name) { this.name = name; return this; } public int getPageCount() { return pageCount; } public BookData setPageCount(int pageCount) { this.pageCount = pageCount; return this; } public int getStarLevel() { return starLevel; } public BookData setStarLevel(int starLevel) { this.starLevel = starLevel; return this; } @NonNull @Override public String toString() { return "Name: " + name + "\n" + "pageCount: " + pageCount + "\n" + "starLevel: " + starLevel; } public BookData() { } protected BookData(Parcel in) { readFromParcel(in); } public static final Creator<BookData> CREATOR = new Creator<BookData>() { @Override public BookData createFromParcel(Parcel in) { return new BookData(in); } @Override public BookData[] newArray(int size) { return new BookData[size]; } }; @Override public int describeContents() { return 0; } // tag: in, client to service @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(name); dest.writeInt(pageCount); dest.writeInt(starLevel); } // tag: out, service to client public void readFromParcel(Parcel in) { name = in.readString(); pageCount = in.readInt(); starLevel = in.readInt(); } } ``` 3. Server 創建另外一個 BookData.aidl 文件,並引入對應需要傳輸的 Java 類 ```java= // 這裡要注意使用對應的包名,需要與要對應的 Java 文件相同 package com.example.remote_service.info; parcelable BookData; ``` :::warning * aidl 資料夾下不可以 import java 檔案 > ![](https://i.imgur.com/6IeSmDQ.png) * 解法: 1. Aidl 資料結構要與 Java 相同,以下就是都放置在 info 資料夾下 > ![](https://i.imgur.com/Xe5B9CO.png) 2. 手動修改 package 路徑(該路徑指定 java 實現檔案的位置) > ![](https://i.imgur.com/hNXGDug.png) ::: 4. Server 在 IRemoteService.aidl 檔案中加入相對應要讓 Client 使用的方法 ```java= package com.example.remote_service; import com.example.remote_service.info.BookData; interface IRemoteService { List<BookData> getBookData(); void addBookData(inout BookData book); // 流向控制 void addBookDataIn(in BookData book); void addBookDataOut(out BookData book); } ``` :::warning * **由於取不同類名,aidl 找不到 .java 文件** > Android 是使用 gradle 的架構來建立 project 的,而 **Gradle 預設 Java 程式的訪問路徑就在 java 包下**,這也就是為何 .java 文件放置在 aidl 包下會找不到的原因,但**可以透過在 ++gradle 中設置 sourceSets++ 找查路徑來修正 (不建議)** ```groovy= // 讓 gradle 在 aidl 的路徑下找尋 .java 文件,sourceSets 必須設置在 app gradle 中 android { ... sourceSets { main { java.srcDirs = ['src/main/java', 'src/main/aidl'] } } } ``` ::: ### AIDL 生成檔案 * **其實可以把 AIDL 檔案當成是一個 ++Script(腳本) 檔案++**,把它稱為腳本是因為,經過編譯過經過後就會產生新的檔案給使用者使用 * 在上面的 AIDL Service 創建 完成後,點擊 Build 後 IDE 就會生成新的檔案(build/generated/aidl_source_output_dir 資料夾下) > ![](https://i.imgur.com/l4RNRsl.png) * 以下是 IDE 自動生成的檔案,列出幾個重點 & UML 關係圖 > ![](https://i.imgur.com/w0cxGKX.png) ```java= // IDE 自動生成的檔案 public interface IRemoteService extends android.os.IInterface { ... // Server 端需要實現 public static abstract class Stub extends android.os.Binder implements com.example.remote_service.IRemoteService { private static final java.lang.String DESCRIPTOR = "com.example.remote_service.IRemoteService"; /** Construct the stub at attach it to the interface. */ public Stub() { this.attachInterface(this, DESCRIPTOR); } public static com.example.remote_service.IRemoteService asInterface(android.os.IBinder obj) { if ((obj==null)) { return null; } android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); if (((iin!=null)&&(iin instanceof com.example.remote_service.IRemoteService))) { return ((com.example.remote_service.IRemoteService)iin); } return new com.example.remote_service.IRemoteService.Stub.Proxy(obj); } @Override public android.os.IBinder asBinder() { return this; } // 複寫 Binder 的 onTransact 方法 @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_getBookData: { data.enforceInterface(descriptor); java.util.List<com.example.remote_service.info.BookData> _result = this.getBookData(); reply.writeNoException(); reply.writeTypedList(_result); return true; } case TRANSACTION_addBookData: { data.enforceInterface(descriptor); com.example.remote_service.info.BookData _arg0; if ((0!=data.readInt())) { _arg0 = com.example.remote_service.info.BookData.CREATOR.createFromParcel(data); } else { _arg0 = null; } this.addBookData(_arg0); reply.writeNoException(); if ((_arg0!=null)) { reply.writeInt(1); _arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE); } else { reply.writeInt(0); } return true; } ... } } // 與底層 C、Cpp 溝通的代理類(跟 ProcessState 通訊) private static class Proxy implements com.example.remote_service.IRemoteService { 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.example.remote_service.info.BookData> getBookData() throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); java.util.List<com.example.remote_service.info.BookData> _result; try { _data.writeInterfaceToken(DESCRIPTOR); boolean _status = mRemote.transact(Stub.TRANSACTION_getBookData, _data, _reply, 0); if (!_status && getDefaultImpl() != null) { return getDefaultImpl().getBookData(); } _reply.readException(); _result = _reply.createTypedArrayList(com.example.remote_service.info.BookData.CREATOR); } finally { _reply.recycle(); _data.recycle(); } return _result; } @Override public void addBookData(com.example.remote_service.info.BookData 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); } boolean _status = mRemote.transact(Stub.TRANSACTION_addBookData, _data, _reply, 0); if (!_status && getDefaultImpl() != null) { getDefaultImpl().addBookData(book); return; } _reply.readException(); if ((0!=_reply.readInt())) { book.readFromParcel(_reply); } } finally { _reply.recycle(); _data.recycle(); } } ... public static com.example.remote_service.IRemoteService sDefaultImpl; } static final int TRANSACTION_getBookData = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); static final int TRANSACTION_addBookData = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1); static final int TRANSACTION_addBookDataIn = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2); static final int TRANSACTION_addBookDataOut = (android.os.IBinder.FIRST_CALL_TRANSACTION + 3); ... 省略部份 } // 以下為 AIDL 編寫的抽象方法 public java.util.List<com.example.remote_service.info.BookData> getBookData() throws android.os.RemoteException; public void addBookData(com.example.remote_service.info.BookData book) throws android.os.RemoteException; public void addBookDataIn(com.example.remote_service.info.BookData book) throws android.os.RemoteException; public void addBookDataOut(com.example.remote_service.info.BookData book) throws android.os.RemoteException; } ``` ### Service 端實現 * 創建一個 Service 並記得在 Service 端的 AndroidManifest 中聲明 1. 實現 Service 服務:創建一個 BookManager 類 ```java= public class BookManager extends Service { private static final String TAG = BookManager.class.getSimpleName(); // Service 端儲存的資料 private final List<BookData> bookDataList = new ArrayList<BookData>(){ { add(new BookData().setName("Android").setPageCount(300).setStarLevel(5)); add(new BookData().setName("Design OOP").setPageCount(280).setStarLevel(5)); } }; private final IRemoteService.Stub stub = new IRemoteService.Stub() { @Override public List<BookData> getBookData() throws RemoteException { return bookDataList; } @Override public void addBookData(BookData book) throws RemoteException { bookDataList.add(book); } @Override public void addBookDataIn(BookData book) throws RemoteException { if (book != null) { book.setStarLevel(1); bookDataList.add(book); Log.e(TAG, book.getName() + " In: BookManager reset star to 1"); } else { Log.e(TAG, "In: addBookDataIn get null object."); } } @Override public void addBookDataOut(BookData book) throws RemoteException { if (book != null) { Log.e(TAG, book.getName() + " Out: Client book star"); book.setStarLevel(7); Log.e(TAG, book.getName() + " Service change start to: 7"); bookDataList.add(book); } else { Log.e(TAG, "Out: addBookDataOut get null object."); } } }; @Nullable @Override public IBinder onBind(Intent intent) { return stub; } } ``` 2. 在 AndroidManifest 中聲明該 Service,有關詳細設定可以參照 [**官網對 service 的說明**](https://developer.android.com/guide/topics/manifest/service-element?hl=zh-cn),這邊只給幾個會用到的設定值 | Service 設定 | 說明 | | -------- | -------- | | enabled | 是否開啟 | | exported | 是否對外開放讓其他的應用使用該 Service | | **process** | 由於是在不同程,所以必須以 `:` 開頭設定,這樣才會跑在不同進程 | ```xml= <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.remote_service"> <application android:allowBackup="true" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/Theme.MyTest_aidl"> <!-- 設定開啟並對外開放 --> <!-- 這裡是同 Module 其實可以不用設定 exported = true --> <service android:name=".info.BookManager" android:enabled="true" android:exported="true" android:process=":remote"> <intent-filter> <action android:name="com.example.remote_service"/> <category android:name="android.intent.category.DEFAULT"/> </intent-filter> </service> </application> </manifest> ``` ### AIDL Client 調用 Service * aidl_client : 使用者,透過 Intent 呼叫 Service,並且使用 [**bindService**](https://hackmd.io/qwwq4oLMTmaw2UksbpPOUg#bindService--unbindService) 方式獲取對服務的操作 :::info * **asInterface 功能** 會依照你是否在同一個進程中創建不同的對象,**若是不同進程就需要創建 Proxy 類** ::: ```java= public class MainActivity extends AppCompatActivity { private static final String TAG = MainActivity.class.getSimpleName(); private IRemoteService remoteService; private final ServiceConnection serviceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { remoteService = IRemoteService.Stub.asInterface(service); try { remoteService.addBookData(new BookData().setName("HelloWorld").setPageCount(123).setStarLevel(2)); Log.i(TAG, "-----------------------------------------------------------"); for(BookData book : remoteService.getBookData()) { Log.i(TAG, "Book: " + book.toString()); } } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { Log.i(TAG, "onServiceDisconnected"); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); startRemoteService(); } private void startRemoteService() { Intent intent = new Intent(this, BookManager.class); bindService(intent, serviceConnection, BIND_AUTO_CREATE); } } ``` :::success * 若服務在不同應用 必須明確寫出包名 & Action ```java= private void startRemoteService() { Intent = new Intent(); // ComponentName(<pkg 包名>, <完整 class 名>) ComponentName name = new ComponentName("com.example.aidl_service", "com.example.aidl_service.AlienService"); intent.setComponent(name); // 或是以下用法 // intent.setAction("com.example.aidl_service.AlienService"); // intent.setPackage("com.example.aidl_service"); bindService(intent, connection, BIND_AUTO_CREATE); } ``` ::: ### AIDL 程式分析 ```java= // 簡單架構概念 public interface IRemoteService extends android.os.IInterface { public abstract class Stub extends android.os.Binder implements IRemoteService { public static class Proxy implements IRemoteService { } } } ``` * 關係圖 > ![](https://i.imgur.com/c4c30Ao.png) ```java= public interface IRemoteService extends android.os.IInterface { /** * Default implementation for IRemoteService. */ ... 忽略 Default /** * Local-side IPC implementation stub class. */ // "1. " public static abstract class Stub extends android.os.Binder implements com.example.remote_service.IRemoteService { private static final java.lang.String DESCRIPTOR = "com.example.remote_service.IRemoteService"; /** * Construct the stub at attach it to the interface. */ public Stub() { this.attachInterface(this, DESCRIPTOR); } public static com.example.remote_service.IRemoteService asInterface(android.os.IBinder obj) { if ((obj == null)) { return null; } // "3. " android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); if (((iin != null) && (iin instanceof com.example.remote_service.IRemoteService))) { return ((com.example.remote_service.IRemoteService) iin); } return new com.example.remote_service.IRemoteService.Stub.Proxy(obj); } @Override public android.os.IBinder asBinder() { return this; } // 與 transact 的方法有相同的引數 @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_getBookData: { data.enforceInterface(descriptor); java.util.List<com.example.remote_service.info.BookData> _result = this.getBookData(); reply.writeNoException(); reply.writeTypedList(_result); return true; } case TRANSACTION_addBookData: { data.enforceInterface(descriptor); com.example.remote_service.info.BookData _arg0; if ((0 != data.readInt())) { _arg0 = com.example.remote_service.info.BookData.CREATOR.createFromParcel(data); } else { _arg0 = null; } this.addBookData(_arg0); reply.writeNoException(); if ((_arg0 != null)) { reply.writeInt(1); _arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE); } else { reply.writeInt(0); } return true; } case TRANSACTION_addBookDataIn: { data.enforceInterface(descriptor); com.example.remote_service.info.BookData _arg0; if ((0 != data.readInt())) { _arg0 = com.example.remote_service.info.BookData.CREATOR.createFromParcel(data); } else { _arg0 = null; } this.addBookDataIn(_arg0); reply.writeNoException(); return true; } case TRANSACTION_addBookDataOut: { data.enforceInterface(descriptor); com.example.remote_service.info.BookData _arg0; _arg0 = new com.example.remote_service.info.BookData(); this.addBookDataOut(_arg0); reply.writeNoException(); if ((_arg0 != null)) { reply.writeInt(1); _arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE); } else { reply.writeInt(0); } return true; } default: { return super.onTransact(code, data, reply, flags); } } } // "2. " private static class Proxy implements com.example.remote_service.IRemoteService { 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.example.remote_service.info.BookData> getBookData() throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); java.util.List<com.example.remote_service.info.BookData> _result; try { _data.writeInterfaceToken(DESCRIPTOR); boolean _status = mRemote.transact(Stub.TRANSACTION_getBookData, _data, _reply, 0); if (!_status && getDefaultImpl() != null) { return getDefaultImpl().getBookData(); } _reply.readException(); _result = _reply.createTypedArrayList(com.example.remote_service.info.BookData.CREATOR); } finally { _reply.recycle(); _data.recycle(); } return _result; } @Override public void addBookData(com.example.remote_service.info.BookData 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); } boolean _status = mRemote.transact(Stub.TRANSACTION_addBookData, _data, _reply, 0); if (!_status && getDefaultImpl() != null) { getDefaultImpl().addBookData(book); return; } _reply.readException(); if ((0 != _reply.readInt())) { book.readFromParcel(_reply); } } finally { _reply.recycle(); _data.recycle(); } } @Override public void addBookDataIn(com.example.remote_service.info.BookData 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); } boolean _status = mRemote.transact(Stub.TRANSACTION_addBookDataIn, _data, _reply, 0); if (!_status && getDefaultImpl() != null) { getDefaultImpl().addBookDataIn(book); return; } _reply.readException(); } finally { _reply.recycle(); _data.recycle(); } } @Override public void addBookDataOut(com.example.remote_service.info.BookData 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); boolean _status = mRemote.transact(Stub.TRANSACTION_addBookDataOut, _data, _reply, 0); if (!_status && getDefaultImpl() != null) { getDefaultImpl().addBookDataOut(book); return; } _reply.readException(); if ((0 != _reply.readInt())) { book.readFromParcel(_reply); } } finally { _reply.recycle(); _data.recycle(); } } public static com.example.remote_service.IRemoteService sDefaultImpl; } // 5. static final int TRANSACTION_getBookData = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); static final int TRANSACTION_addBookData = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1); static final int TRANSACTION_addBookDataIn = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2); static final int TRANSACTION_addBookDataOut = (android.os.IBinder.FIRST_CALL_TRANSACTION + 3); ... } public java.util.List<com.example.remote_service.info.BookData> getBookData() throws android.os.RemoteException; public void addBookData(com.example.remote_service.info.BookData book) throws android.os.RemoteException; public void addBookDataIn(com.example.remote_service.info.BookData book) throws android.os.RemoteException; public void addBookDataOut(com.example.remote_service.info.BookData book) throws android.os.RemoteException; } ``` 1. **==Stub 類是給 服務端 使用==**,所以**在 Service 內匿名實現抽象類 Stub**,而 Stub 實現了 IPerson 的方法,所以在匿名抽象類中也要實做 ```java= public class BookManager extends Service { ... private final IRemoteService.Stub stub = new IRemoteService.Stub() { @Override public List<BookData> getBookData() throws RemoteException { return bookDataList; } @Override public void addBookData(BookData book) throws RemoteException { bookDataList.add(book); } @Override public void addBookDataIn(BookData book) throws RemoteException { if (book != null) { book.setStarLevel(1); bookDataList.add(book); Log.e(TAG, book.getName() + " In: BookManager reset star to 1"); } else { Log.e(TAG, "In: addBookDataIn get null object."); } } @Override public void addBookDataOut(BookData book) throws RemoteException { if (book != null) { Log.e(TAG, book.getName() + " Out: Client book star"); book.setStarLevel(7); Log.e(TAG, book.getName() + " Service change start to: 7"); bookDataList.add(book); } else { Log.e(TAG, "Out: addBookDataOut get null object."); } } }; } ``` 2. **==Proxy 類是給 客戶端 使用==**,透過 asInterface 取得內部類的實體化,它就像是一個[**代理類**](https://hackmd.io/UK0V9uc9SDyLvVAyCpnuDA) 透過它來連接遠端的服務 ```java= public class MainActivity extends AppCompatActivity { private static final String TAG = MainActivity.class.getSimpleName(); private IRemoteService remoteService; private final ServiceConnection serviceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { remoteService = IRemoteService.Stub.asInterface(service); try { remoteService.addBookData(new BookData().setName("HelloWorld").setPageCount(123).setStarLevel(2)); // remoteService.addBookDataIn(new BookData().setName("AAA").setPageCount(777).setStarLevel(3)); // remoteService.addBookDataOut(new BookData().setName("BBB").setPageCount(888).setStarLevel(4)); Log.i(TAG, "-----------------------------------------------------------"); for(BookData book : remoteService.getBookData()) { Log.i(TAG, "Book: " + book.toString()); } } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { Log.i(TAG, "onServiceDisconnected"); } }; ... } ``` 3. Stub 中的 asInterface 方法會判斷目前的服務是否是同一個進程的,如果是同一進程就直接返回,不須創建代理類 Stub.Proxy > 這裡是客戶端使用 asInterface 所以可以看出絕對不是本地的服務,所以**客戶端所調用的 Service 方法都會跑到 Stub.Proxy 類** ```java= public static com.example.remote_service.IRemoteService asInterface(android.os.IBinder obj) { if ((obj == null)) { return null; } // 同進程 android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); if (((iin != null) && (iin instanceof com.example.remote_service.IRemoteService))) { return ((com.example.remote_service.IRemoteService) iin); } // 不同進程,創建 Proxy return new com.example.remote_service.IRemoteService.Stub.Proxy(obj); } ``` 4. 現在來看客戶端調用方法 getBookData(),它內部會調用 transact 方法傳遞,最終會傳遞到 Stub 類中的 onTransact 方法 下圖 Class > ![](https://i.imgur.com/MduWeZq.png) 5. 可以看到傳輸方法時它使用了 `Stub.TRANSACTION_getPersonList`,其實就是使用兩邊 (Service & Client) 持有相同的方法 & 順序,所以**使用了一系列的 ++串列數字去標明 Function++** ```java= // 參 4 boolean _status = mRemote.transact( Stub.TRANSACTION_getPersonList, // 協定方法 _data, _reply, 0 // 如果是 0 則代表了雙向流通, 1 是單向 ); // IBinder public interface IBinder { int FIRST_CALL_TRANSACTION = 0x00000001; ... } // 協定 1 方法 static final int TRANSACTION_getBookData = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); // 協定 2 方法 static final int TRANSACTION_addBookData = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1); // 協定 3 方法 static final int TRANSACTION_addBookDataIn = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2); // 協定 4 方法 static final int TRANSACTION_addBookDataOut = (android.os.IBinder.FIRST_CALL_TRANSACTION + 3); ``` * **在 ++客戶端 client++ 可以得到以下重點** :::success 1. 使用 Parcel.obtain() 獲得 **\_data(保存客戶端數據)**、**\_reply(儲存服務器返回的數據)** 兩個數據儲存容器 2. 透過 transact() 方法傳輸數據到服務端,寫入數據到 \_data (數據拷貝) 3. 讀取 \_reply 來接收服務端返回的數據 ::: * **在 ++服務端 Service++ 可以得到以下重點** :::success 1. 服務端會用 onTransact() 方法接收客戶端資料 2. 判斷 function id 來執行操作呼叫服務端代碼 3. **將要回傳的數據寫入 \_reply 引數** (數據拷貝) ::: ## 服務源碼 - 分析 分析是用 Android 7 Source Code,使用 sourceInsight 閱讀 ### 客戶端 bindService * 從客戶端下手,觀察他是**如何綁定 ++不同進程間++ 的服務** ```java= // 綁定監聽 private void bindService() { Intent = new Intent(); // ComponentName(<pkg 包名>, <完整 class 名>) ComponentName name = new ComponentName("com.example.aidl_service", "com.example.aidl_service.AlienService"); intent.setComponent(name); bindService(intent, connection, BIND_AUTO_CREATE); } ``` * 從 bindService 的流程就可以看出來它**使用的機制與 AIDL 相同** 1. 客戶端呼叫代理 ActivityManagerProxy 2. **++透過 ActivityManagerProxy 的 transact 方法呼叫到 ActivityManagerNative 的 onTransact 方法++** 3. 這就可以看出如何從客戶端 Client 調用到 Server 端的服務,其道理與 AIDL 相同 **--流程圖--** > ![](https://i.imgur.com/Up1H61V.png) * 與上面實做的 AIDL 做比較 | 類型 | Proxy 代理 | 抽象 Service 服務 | | -------- | -------- | - | | AIDL 生成 | ActivityManagerProxy | ActivityManagerNative | | SDK 源碼 | IPerson.Stub.Proxy | IPerson.Stub | ### AMS 啟動服務 bindService * 啟動服務因為有兩個變數,所以又**分為四種狀況**,**有關到 ++進程 Process++、++服務 Service++、++綁定與否++** > 1. Process 未啟動 , Service 未啟動 (APP 未創建) > 2. Process 啟動 , Service 未啟動 (APP 創建) > 3. Process 啟動 , Service 啟動 , 未綁定 > 4. Process 啟動 , Service 啟動 , 已綁定 > > 以下介紹順序相同 **第一**、進程未啟動 > 缺少進程會進入 ActivityManagerService 中創建,先來看看流程圖 :D ![](https://i.imgur.com/OE35xPZ.png) ```java= // ActiveServices.java private String bringUpServiceLocked(ServiceRecord r, int intentFlags, boolean execInFg, boolean whileRestarting, boolean permissionsReviewRequired) throws TransactionTooLargeException { ... if (app == null && !permissionsReviewRequired) { // 啟動 & 綁定 startProcessLocked 往下追 if ((app=mAm.startProcessLocked(procName, r.appInfo, true, intentFlags, "service", r.name, false, isolated, false)) == null) { String msg = "Unable to launch app " + r.appInfo.packageName + "/" + r.appInfo.uid + " for service " + r.intent.getIntent() + ": process is bad"; Slog.w(TAG, msg); bringDownServiceLocked(r); return msg; } if (isolated) { r.isolatedProc = app; } } } // startProcessLocked 在 AMS.java 中 private final void startProcessLocked(ProcessRecord app, String hostingType, String hostingNameStr, String abiOverride, String entryPoint, String[] entryPointArgs) { ... // start new process !!! if (entryPoint == null) entryPoint = "android.app.ActivityThread"; Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Start proc: " + app.processName); checkTime(startTime, "startProcess: asking zygote to start proc"); // 注意 entryPoint,需要傳入應用包包名 Process.ProcessStartResult startResult = Process.start(entryPoint, app.processName, uid, uid, gids, debugFlags, mountExternal, app.info.targetSdkVersion, app.info.seinfo, requiredAbi, instructionSet, app.info.dataDir, entryPointArgs); ... } ``` **第二**、進程啟動 && 服務未啟動 > 這種狀況就缺少服務未啟動,我們先來看看啟動過程圖 **--流程圖--** > ![](https://i.imgur.com/vUSnjrB.png) ```java= // ActiveServices.java private String bringUpServiceLocked(ServiceRecord r, int intentFlags, boolean execInFg, boolean whileRestarting, boolean permissionsReviewRequired) throws TransactionTooLargeException { ... ProcessRecord app; if (!isolated) { ... // app.thread 進程啟動 if (app != null && app.thread != null) { try { app.addPackage(r.appInfo.packageName, r.appInfo.versionCode, mAm.mProcessStats); // 綁定服務,往下跟 !!! realStartServiceLocked(r, app, execInFg); return null; } catch (TransactionTooLargeException e) { throw e; } catch (RemoteException e) { Slog.w(TAG, "Exception when starting service " + r.shortName, e); } } else { ... } } private final void realStartServiceLocked(ServiceRecord r, ProcessRecord app, boolean execInFg) throws RemoteException { if (app.thread == null) { throw new RemoteException(); } ... // record service 紀錄 final boolean newService = app.services.add(r); bumpServiceExecutingLocked(r, execInFg, "create"); mAm.updateLruProcessLocked(app, false, null); mAm.updateOomAdjLocked(); boolean created = false; try { ... synchronized (r.stats.getBatteryStats()) { // 做線程同步 r.stats.startLaunchedLocked(); } ... // create Service,往下跟 !!! app.thread.scheduleCreateService(r, r.serviceInfo, mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo), app.repProcState); r.postNotification(); created = true; } catch (DeadObjectException e) { Slog.w(TAG, "Application dead when creating service " + r); mAm.appDiedLocked(app); throw e; } finally { ... } ... } // scheduleCreateService 跳轉到 ActivityThread.java public final void scheduleCreateService(IBinder token, ServiceInfo info, CompatibilityInfo compatInfo, int processState) { ... // 使用內部類 H Handler 傳送 CREATE_SERVICE 訊息 sendMessage(H.CREATE_SERVICE, s); } // CREATE_SERVICE 最終會掉用到 handleCreateService 方法 private void handleCreateService(CreateServiceData data) { ... try { java.lang.ClassLoader cl = packageInfo.getClassLoader(); // 這個服務就是創建的我們需要的服務 !!! service = (Service) cl.loadClass(data.info.name).newInstance(); } catch (Exception e) { ... } ... try { if (localLOGV) Slog.v(TAG, "Creating service " + data.info.name); ... Application app = packageInfo.makeApplication(false, mInstrumentation); // attach 方法 service.attach(context, this, data.info.name, data.token, app, ActivityManagerNative.getDefault()); // 調用 Service 的 onCreate 方法 service.onCreate(); // 儲存服務 mServices.put(data.token, service); try { // 調用到客戶端,表示服務已經運行 ActivityManagerNative.getDefault().serviceDoneExecuting( data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } catch (Exception e) { if (!mInstrumentation.onException(service, e)) { throw new RuntimeException( "Unable to create service " + data.info.name + ": " + e.toString(), e); } } } ``` :::info 1. 判斷過程中 **app(ProcessRecord) 代表了進程的紀錄**,而該類中有一個屬性 **app.thread 代表了真正的進程** > ![](https://i.imgur.com/g4pvWF4.png) 2. **該 thread 在 ActivityThread 這個類的內部類 ApplicationThread 中,而 ActivityThread 有 main() 方法、並且有 ApplicationThread 這個屬性** > 代表了開始運行 ActivityThread 這個類,Application 就一定不為空 ```java= public final class ActivityThread { final ApplicationThread mAppThread = new ApplicationThread(); ... public static void main(String[] args) { ... ActivityThread thread = new ActivityThread(); ... } } ``` ::: **第三/四**、Process, Service 都已經創建,差別在綁定與否 > ![](https://i.imgur.com/CUXlVKc.png) ```java= // ActiveService.java int bindServiceLocked(IApplicationThread caller, IBinder token, Intent service, String resolvedType, final IServiceConnection connection, int flags, String callingPackage, final int userId) throws TransactionTooLargeException { ... if (s.app != null && b.intent.received) { ... try { c.conn.connected(s.name, b.intent.binder); } catch (Exception e) { Slog.w(TAG, "Failure sending service " + s.shortName + " to connection " + c.conn.asBinder() + " (in " + c.binding.client.processName + ")", e); } ... if (b.intent.apps.size() == 1 && b.intent.doRebind) { // 差在最後一個參數 already bind !!! requestServiceBindingLocked(s, b.intent, callerFg, true); } } else if (!b.intent.requested) { // 差在最後一個參數 not bind !!! requestServiceBindingLocked(s, b.intent, callerFg, false); } } private final boolean requestServiceBindingLocked(ServiceRecord r, IntentBindRecord i, boolean execInFg, boolean rebind) throws TransactionTooLargeException { if (r.app == null || r.app.thread == null) { // If service is not currently running, can't yet bind. return false; } if ((!i.requested || rebind) && i.apps.size() > 0) { try { bumpServiceExecutingLocked(r, execInFg, "bind"); r.app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_SERVICE); // 綁定的關鍵函數 !!! r.app.thread.scheduleBindService(r, i.intent.getIntent(), rebind, r.app.repProcState); if (!rebind) { i.requested = true; } i.hasBound = true; i.doRebind = false; } catch (TransactionTooLargeException e) { ... } catch (RemoteException e) { ... } } return true; } /** * 前面有說過 r.app.thread 指的就是 ApplicationThread * 所以會呼叫到 ApplicationThread 中的方法 */ // ActivityThread.java public final void scheduleBindService(IBinder token, Intent intent, boolean rebind, int processState) { updateProcessState(processState, false); ... // 對 Handler 發送訊息 sendMessage(H.BIND_SERVICE, s); } // 最終會到 handleUnbindService private void handleBindService(BindServiceData data) { Service s = mServices.get(data.token); if (DEBUG_SERVICE) Slog.v(TAG, "handleBindService s=" + s + " rebind=" + data.rebind); if (s != null) { try { data.intent.setExtrasClassLoader(s.getClassLoader()); data.intent.prepareToEnterProcess(); try { if (!data.rebind) { // 調用 Service 的 onBind 方法 IBinder binder = s.onBind(data.intent); // ActivityManagerNative.getDefault() == ActivityManagerProxy 類 ActivityManagerNative.getDefault().publishService( data.token, data.intent, binder); } else { // 調用 Service 的 reBind 方法 s.onRebind(data.intent); ActivityManagerNative.getDefault().serviceDoneExecuting( data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0); } ensureJitEnabled(); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } } catch (Exception e) { if (!mInstrumentation.onException(s, e)) { throw new RuntimeException( "Unable to bind to service " + s + " with " + data.intent + ": " + e.toString(), e); } } } } /** * ActivityManagerService.java * ActivityManagerNative 的實作類就是 AMS 類 */ // // 回傳給 Proxy 知道 publishService or serviceDoneExecuting public void publishService(IBinder token, Intent intent, IBinder service) throws RemoteException { ... mRemote.transact(PUBLISH_SERVICE_TRANSACTION, data, reply, 0); } ``` ### AMS 客戶端 & 服務端 * 經過上面的分析,我們再多加上一行,真正服務在處理的類 | 類型 | Proxy 代理 | 抽象 Service 服務 | 實現服務的類 | | -------- | -------- | - | - | | AIDL 程式 | ActivityManagerProxy | ActivityManagerNative | ActivityManagerService(AMS) | | SDK 源碼 | IPerson.Stub.Proxy | IPerson.Stub | AlienService(繼承 Service) | * 上面看起來就有一點的混淆,現在來整理一下,畢竟**不同的應用都有相同的 AMS**,先來整理何時是調用客戶端,何時調用服務端 1. 調用 ActivityManagerService 的 **++onTransact 方法++** 區塊,就是屬於操控到 Server 端的動作,這就是屬於服務端的 AMS > **綁定服務** 為例子 (==黃色區塊即為 AMS 服務端==) > ![](https://i.imgur.com/xkOfU5r.png) 2. 在確定綁定後又會調用客戶端的 publishService,publishService 在調用 onTransact 調回服務端 > ![](https://i.imgur.com/F2EhyWE.png) 3. 到這裡為止 Server 分析差不多完成,接下來會分析 Server 如何調用 Client 端的內部匿名類 ServiceConnection ### Server 端服務回調 * 還記得我們在使用服務綁定時有傳入 3 個參數,而第 2 個參數就是 Server 監聽器,當 Server 完成所有準備工作後就會呼叫回調 ```java= // Client 端 private void bindService() { Intent = new Intent(); // ComponentName(<pkg 包名>, <完整 class 名>) ComponentName name = new ComponentName("com.example.aidl_service", "com.example.aidl_service.AlienService"); intent.setComponent(name); bindService(intent, connection, BIND_AUTO_CREATE); } // Client 監聽 Server,完成準備時就會回調 private ServiceConnection connection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { iPerson = IPerson.Stub.asInterface(service); Log.e("AIDL", "Connected to Service"); } @Override public void onServiceDisconnected(ComponentName name) { // 報錯崩潰 iPerson = null; } }; ``` * 在這邊可以看到 **依監看 Server 的角度而言,++Client 這端反轉成為了 Server++**,提供給 原本的服務端 調用 **--流程圖--** ![](https://i.imgur.com/1yAPCx1.png) ```java= /** * ContextImpl * * 先看 IServiceConnection 是如何調用到 ServiceConnection */ private boolean bindServiceCommon(Intent service, ServiceConnection conn, int flags, Handler handler, UserHandle user) { IServiceConnection sd; if (conn == null) { throw new IllegalArgumentException("connection is null"); } if (mPackageInfo != null) { // 往下分析! mPackageInfo 是 LoadedApk 這個類 sd = mPackageInfo.getServiceDispatcher(conn, getOuterContext(), handler, flags); } else { throw new RuntimeException("Not supported in system context"); } ... } // LoadedApk 類 public final IServiceConnection getServiceDispatcher(ServiceConnection c, Context context, Handler handler, int flags) { synchronized (mServices) { LoadedApk.ServiceDispatcher sd = null; ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher> map = mServices.get(context); if (map != null) { sd = map.get(c); } if (sd == null) { // 往下分析 sd = new ServiceDispatcher(c, context, handler, flags); if (map == null) { map = new ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher>(); mServices.put(context, map); } map.put(c, sd); } else { sd.validate(context, handler); } // 這是最終會得到的結果 return sd.getIServiceConnection(); } } /** * LoadedApk#ServiceDispatcher (內部類) * ServiceDispatcher 建構,觀察其屬性 */ static final class ServiceDispatcher { // InnerConnection private final ServiceDispatcher.InnerConnection mIServiceConnection; private final ServiceConnection mConnection; ... } // ServiceDispatcher 獲取內部屬性 mIServiceConnection IServiceConnection getIServiceConnection() { return mIServiceConnection; } /** * LoadedApk#ServiceDispatcher#InnerConnection 內部類 * * 這邊可以看到 IServiceConnection.Stub,InnerConnection 這就是我們服務的實體類 !!! */ private static class InnerConnection extends IServiceConnection.Stub { final WeakReference<LoadedApk.ServiceDispatcher> mDispatcher; InnerConnection(LoadedApk.ServiceDispatcher sd) { mDispatcher = new WeakReference<LoadedApk.ServiceDispatcher>(sd); } public void connected(ComponentName name, IBinder service) throws RemoteException { // 最外部 class LoadedApk.ServiceDispatcher sd = mDispatcher.get(); if (sd != null) { // 給客戶端調用 sd.connected(name, service); } } } /** * ServiceDispatcher class */ public void connected(ComponentName name, IBinder service) { if (mActivityThread != null) { mActivityThread.post(new RunConnection(name, service, 0)); } else { doConnected(name, service); } } public void doConnected(ComponentName name, IBinder service) { ... // If there was an old service, it is now disconnected. if (old != null) { // 回調到使用者端,通知使用者 mConnection.onServiceDisconnected(name); } // If there is a new service, it is now connected. if (service != null) { // 回調到使用者端,通知使用者 mConnection.onServiceConnected(name, service); } } ``` ## 結論 * 可以看的出來由於使用跨進程服務通訊,所以**不能直接通訊,轉而使用了[代理模式](https://hackmd.io/UK0V9uc9SDyLvVAyCpnuDA?view)** | 使用者 | 使用方法 | | -------- | -------- | | 客戶端 | 透過 **asInterface 取得代理的對象接口** (AIDL **Proxy**),進而實現操做 | | 服務端 | 創建 Service 並**繼承實現抽象類(實做)** (AIDL **Stub** 的抽象類) | * Proxy(代理)、Stub(實作) 類**都會實現共同方法接口 (AIDL 的方法)**,差別在一個是代理,一個是實作 > ![](https://i.imgur.com/OMXZBXE.png) * 剛開始通訊實 A 為客戶端(Client),Server 為服務端(Service),**等服務創建完成後通知服務完成連線,兩者個關係就會==相反==過來,A 為服務端(Service)、B 為客戶端(Client)** :::success Binder 簡易的概念是 1. 使用 ActivityManagerNative 的 getDefault 方法得到單例的 Proxy 對象 2. Proxy 對象會透過 transact 呼叫 Native 的 onTransact 方法 3. Native 又會呼叫實際對象 (繼承 ActivityManagerNative 的對象) 的方法 ::: ## Appendix & FAQ :::info ::: ###### tags: `Android 進階` `binder` `aidl`