AIDL原理分析
季春初始,天氣返暖,新冠漸去,正值學習好時機。在Android系統中,AIDL一直在Framework和應用層上扮演著很重要的角色,今日且將其原理簡單分析。(文2020.03.30)
一、開篇介紹
1.簡單介紹
Android系統中對原理的分析基本離不開對源碼的閱讀,我理解的原理分析:
原理分析 = 基本概念 + 源碼分析 + 實踐
正如創始人Linus Torvalds的名言:RTFSC(read the f**king source code)。本文也是按照上述結構來介紹AIDL的。
接下來先簡單提一提IPC、序列化和Parcel三個概念。
2.IPC
1)進程與執行緒
A. 執行緒是CPU調度的最小單元,同時執行緒是一種有限的系統資源。
B. 進程一般指一個執行單元,在PC和移動設備上指一個程式或一個應用
C. 一個進程可以包含多個執行緒,包含與被包含的關係。
D. Java默認只有一個執行緒,叫主執行緒,在android中也叫做UI線程
2)IPC
A. 定義:IPC(inter-process Commnication)跨進程的通訊,多進程之間的通訊。
B. 為什麼需要進程通訊:我們知道Android一般情況下一個應用是默認運行在一個進程中,但有可能一個應用中需要採用多進程模式(process屬性)來實現(比如獲取多份記憶體空間),或者兩個應用之間需要獲取彼此之間的數據,還有AMS(系統服務)對每個應用中四大組件的管理,系統服務是運行在一個單獨的進程中,這些統統需要IPC。
3)Android中的IPC
Android IPC方式:文件共享、ContentProvider(底層是Binder實現)、Socket、Binder(AIDL、Messenger)。
3.序列化
序列化是指將一個對象轉化為二進位或者是某種格式的位元組流,將其轉換為易於保存或網路傳輸的格式的過程,反序列化則是將這些位元組重建為一個對象的過程。Serializable和Parcelable介面可以完成對象的序列化。如下圖:
1)Serializable
Serializable是Java提供的序列化介面,使用時只需要實現Serializable介面並聲明一個serialVersionUID(用於反序列化)
2)Parcelable
A. writeToParcel:將對象序列化為一個Parcel對象,將類的數據寫入外部提供的Parcel中
B. describeContents:內容介面描述,默認返回0
C. 實例化靜態內部對象CREATOR實現介面Parcelable.Creator,需創建一個數組(newArray(int size)) 供外部類反序列化本類數組使用;createFromParcel創建對象
D. readFromParcel:從流里讀取對象,寫的順序和讀的順序必須一致。
Serializable使用簡單,但是開銷很大(大量I/O操作),Parcelable是Android中的序列化方式,使用起來麻煩,但是效率很高,是Android推薦的方式。Parcelable主要用在記憶體序列化上,如果要將對象序列化到存儲設備或者通過網路傳輸也是可以的,但是會比較複雜,這兩種情況建議使用Serializable。
4.Parcel
Parcel主要就是用來進行IPC通訊,是一種容器,他可以包含數據或者是對象引用,並且能夠用於Binder的傳輸。同時支援序列化以及跨進程之後進行反序列化,同時其提供了很多方法幫助開發者完成這些功能。Parcel的讀寫都是一個指針操作的,有writeInt(int val)、writeString(String val)、setDataPosition(int val)、readInt()、readString()、recycle()方法。
二、AIDL
1.定義
AIDL:Android interface definition Language,Android 介面定義語言。使用aidl可以發布以及調用遠程服務,實現跨進程通訊。將服務的aidl放到對應的src目錄,工程的gen目錄會生成相應的介面類。
2.語法
AIDL的語法十分簡單,與Java語言基本保持一致,主要規則有以下幾點:
1)AIDL文件以 .aidl 為後綴名
2)AIDL支援的數據類型分為如下幾種:
A. 八種基本數據類型:byte、char、short、int、long、float、double、boolean
String,CharSequence
B. 實現了Parcelable介面的數據類型
C. List 類型。List承載的數據必須是AIDL支援的類型,或者是其它聲明的AIDL對象
D. Map類型。Map承載的數據必須是AIDL支援的類型,或者是其它聲明的AIDL對象
3)AIDL文件可以分為兩類
A. 一類用來聲明實現了Parcelable介面的數據類型,以供其他AIDL文件使用那些非默認支援的數據類型。
B. 另一類是用來定義介面方法,聲明要暴露哪些介面給客戶端調用,定向Tag就是用來標註這些方法的參數值。
4)定向Tag
定向Tag表示在跨進程通訊中數據的流向,用於標註方法的參數值。
A. in 表示數據只能由客戶端流向服務端
B. out 表示數據只能由服務端流向客戶端
C. inout 則表示數據可在服務端與客戶端之間雙向流通。
如果AIDL方法介面的參數值類型是:基本數據類型、String、CharSequence或者其他AIDL文件定義的方法介面,那麼這些參數值的定向 Tag 默認是且只能是 in,所以除了這些類型外,其他參數值都需要明確標註使用哪種定向Tag。
5)明確導包
在AIDL文件中需要明確標明引用到的數據類型所在的包名,如java的import導入。
3.使用步驟
1)創建 AIDL
創建要操作的實體類,實現 Parcelable 介面,以便序列化/反序列化
新建 aidl 文件夾,在其中創建介面 aidl 文件以及實體類的映射 aidl 文件
build project ,生成 Binder 的 Java 文件
這裡定義了一個User對象,包含一個名字屬性,並定義了一個控制介面,添加和查詢。


1 public class User implements Parcelable { 2 3 private String name; 4 5 public User(String name) { 6 this.name = name; 7 } 8 9 protected User(Parcel in) { 10 name = in.readString(); 11 } 12 13 public String getName() { 14 return name; 15 } 16 17 public void setName(String name) { 18 this.name = name; 19 } 20 21 public static final Creator<User> CREATOR = new Creator<User>() { 22 @Override 23 public User createFromParcel(Parcel in) { 24 return new User(in); 25 } 26 27 @Override 28 public User[] newArray(int size) { 29 return new User[size]; 30 } 31 }; 32 33 @Override 34 public int describeContents() { 35 return 0; 36 } 37 38 @Override 39 public void writeToParcel(Parcel dest, int flags) { 40 dest.writeString(name); 41 } 42 43 public void readFromParcel(Parcel in) { 44 this.name = in.readString(); 45 } 46 }
View Code
1 // UserControl.aidl 2 package com.haybl.aidl_test; 3 4 import com.haybl.aidl_test.User; 5 6 // Declare any non-default types here with import statements 7 8 interface UserController { 9 10 List<User> getUserList(); 11 12 void addUserInOut(inout User user); 13 }
2)服務端
複製上述兩個AIDL文件至服務端程式碼。創建 Service,在其中創建上面生成的 Binder 對象實例,實現介面定義的方法在 onBind() 中返回。
1 @Override 2 public IBinder onBind(Intent intent) { 3 Log.d(TAG, "onBind"); 4 return stub; 5 } 6 7 private UserController.Stub stub = new UserController.Stub() { 8 @Override 9 public List<User> getUserList() throws RemoteException { 10 Log.d(TAG, "getUserList"); 11 return mUserList; 12 } 13 14 @Override 15 public void addUserInOut(User user) throws RemoteException { 16 if (user != null) { 17 Log.d(TAG, "Server receive a new user by InOut = " + user.getName()); 18 // 服務端改變數據,通過InOut Tag會同步到客戶端。數據是雙向流動的 19 user.setName("I'm changed"); 20 mUserList.add(user); 21 } else { 22 Log.d(TAG, "Server receive a null by InOut"); 23 } 24 } 25 };
3)客戶端
實現 ServiceConnection 介面,在其中拿到 AIDL 類,bindService()調用 AIDL 類中定義好的操作請求
1 private void bindService() { 2 Log.d(TAG, "bindService"); 3 Intent intent = new Intent(); 4 // android 5.0以後直設置action不能啟動相應的服務,需要設置packageName或者Component 5 intent.setPackage("com.haybl.aidl_server"); 6 intent.setAction("com.haybl.aidl_server.action"); 7 bindService(intent, connection, BIND_AUTO_CREATE); 8 } 9 10 private ServiceConnection connection = new ServiceConnection() { 11 @Override 12 public void onServiceConnected(ComponentName name, IBinder service) { 13 Log.d(TAG, "onServiceConnected, name = " + name.getPackageName()); 14 mUserController = UserController.Stub.asInterface(service); 15 isConnected = true; 16 } 17 18 @Override 19 public void onServiceDisconnected(ComponentName name) { 20 isConnected = false; 21 Log.d(TAG, "onServiceDisconnected, name = " + name.getPackageName()); 22 } 23 };
4.程式碼分析
原理分析分析離不開程式碼,aidl在build之後會生成一個對應的介面java文件,aidl文件本身的作用就是生成這個java文件,後續的操作都在這個java介面上進行的。直接放生成的文件,根據其中的注釋可以很好的理解的原理:


1 package com.haybl.aidl_test; 2 3 /** 4 * 所有在Binder中傳輸的介面都必須實現IInterface介面 5 */ 6 public interface UserController extends android.os.IInterface { 7 /** 8 * 本地IPC實施存根類:為內部靜態類,繼承android.os.Binder、實現com.haybl.aidl_test.UserController(本介面) 9 */ 10 public static abstract class Stub extends android.os.Binder implements com.haybl.aidl_test.UserController { 11 /** 12 * Binder的唯一標識 13 */ 14 private static final java.lang.String DESCRIPTOR = "com.haybl.aidl_test.UserController"; 15 16 /** 17 * 介面中的方法標誌,個數與定義的方法個數一致且一一對應,在onTransact()中使用 18 */ 19 static final int TRANSACTION_getUserList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); 20 static final int TRANSACTION_addUserInOut = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1); 21 22 /** 23 * 構造方法,將其附加到介面 24 * 25 * attachInterface()方法將特定介面與Binder相關聯。 26 * 調用後,可通過queryLocalInterface()方法,在當請求符合描述符(DESCRIPTOR)時,返回該IInterface。 27 */ 28 public Stub() { 29 this.attachInterface(this, DESCRIPTOR); 30 } 31 32 /** 33 * 將IBinder對象轉換為com.haybl.aidl_test.UserController介面。 34 * 用於將服務端的Binder對象轉換為客戶端所需要的介面對象,該過程區分進程, 35 * 如果進程一樣,就返回服務端Stub對象本身,否則就返回封裝後的Stub.Proxy對象。 36 */ 37 public static com.haybl.aidl_test.UserController asInterface(android.os.IBinder obj) { 38 if ((obj == null)) { 39 return null; 40 } 41 42 /* 43 * 針對此obj Binder對象查詢介面的本地實現。 44 * 如果返回null,則需要實例化代理類,通過transact()方法封裝調用。非同一進程 45 * 如果提供的資訊與請求的描述符匹配,則返回關聯的IInterface。同一進程 46 */ 47 android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); 48 if (((iin != null) && (iin instanceof com.haybl.aidl_test.UserController))) { 49 return ((com.haybl.aidl_test.UserController) iin); 50 } 51 return new com.haybl.aidl_test.UserController.Stub.Proxy(obj); 52 } 53 54 /** 55 * android.os.IInterface介面方法實現 56 */ 57 @Override 58 public android.os.IBinder asBinder() { 59 return this; 60 } 61 62 /** 63 * <服務端調用> 64 * android.os.Binder的onTransact方法實現 65 * 如果要調用此函數,需調用transact()。transact()實現對onTransact上調用。在遠端,將調用到Binder中以進行IPC。 66 * 該方法是運行在服務端的Binder執行緒中的,當客戶端發起遠程請求後,在底層封裝後會交由此方法來處理。 67 * 68 * @param code 要執行的動作標誌。在IBinder.FIRST_CALL_TRANSACTION 和 IBinder.LAST_CALL_TRANSACTION之間 69 * @param data 從調用方接收到的數據。 70 * @param reply 如果調用方需要返回結果,則應將其從此處返回。 71 * @param flags 附加操作標誌。正常RPC為0,one-way類型的RPC為1。 72 * 73 * @return return 是否成功 true 返回成功 74 * false 客戶端的請求失敗(可以用來做許可權控制) 75 */ 76 @Override 77 public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException { 78 switch (code) { 79 // IBinder協議事務程式碼,寫入標準介面描述符DESCRIPTOR。 80 case INTERFACE_TRANSACTION: { 81 reply.writeString(DESCRIPTOR); 82 return true; 83 } 84 // getUserList 方法 85 case TRANSACTION_getUserList: { 86 data.enforceInterface(DESCRIPTOR); 87 // 服務端調用具體實現 this.getUserList 88 java.util.List<com.haybl.aidl_test.User> _result = this.getUserList(); 89 reply.writeNoException(); 90 reply.writeTypedList(_result); 91 return true; 92 } 93 // addUserInOut 方法 94 case TRANSACTION_addUserInOut: { 95 data.enforceInterface(DESCRIPTOR); 96 com.haybl.aidl_test.User _arg0; 97 if ((0 != data.readInt())) { 98 _arg0 = com.haybl.aidl_test.User.CREATOR.createFromParcel(data); 99 } else { 100 _arg0 = null; 101 } 102 // 服務端調用具體實現 this.addUserInOut 103 this.addUserInOut(_arg0); 104 reply.writeNoException(); 105 if ((_arg0 != null)) { 106 reply.writeInt(1); 107 _arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE); 108 } else { 109 reply.writeInt(0); 110 } 111 return true; 112 } 113 } 114 return super.onTransact(code, data, reply, flags); 115 } 116 117 /* 118 * com.haybl.aidl_test.UserController代理類 119 * <客戶端調用> 120 */ 121 private static class Proxy implements com.haybl.aidl_test.UserController { 122 private android.os.IBinder mRemote; 123 124 Proxy(android.os.IBinder remote) { 125 mRemote = remote; 126 } 127 128 @Override 129 public android.os.IBinder asBinder() { 130 return mRemote; 131 } 132 133 /** 134 * 返回描述符DESCRIPTOR,在Binder的onTransact方法中需要寫入此描述:case INTERFACE_TRANSACTION 135 */ 136 public java.lang.String getInterfaceDescriptor() { 137 return DESCRIPTOR; 138 } 139 140 @Override 141 public java.util.List<com.haybl.aidl_test.User> getUserList() throws android.os.RemoteException { 142 android.os.Parcel _data = android.os.Parcel.obtain(); 143 android.os.Parcel _reply = android.os.Parcel.obtain(); 144 java.util.List<com.haybl.aidl_test.User> _result; 145 try { 146 _data.writeInterfaceToken(DESCRIPTOR); 147 // 遠程調用 148 mRemote.transact(Stub.TRANSACTION_getUserList, _data, _reply, 0); 149 _reply.readException(); 150 _result = _reply.createTypedArrayList(com.haybl.aidl_test.User.CREATOR); 151 } finally { 152 _reply.recycle(); 153 _data.recycle(); 154 } 155 return _result; 156 } 157 158 @Override 159 public void addUserInOut(com.haybl.aidl_test.User user) throws android.os.RemoteException { 160 android.os.Parcel _data = android.os.Parcel.obtain(); 161 android.os.Parcel _reply = android.os.Parcel.obtain(); 162 try { 163 _data.writeInterfaceToken(DESCRIPTOR); 164 if ((user != null)) { 165 _data.writeInt(1); 166 user.writeToParcel(_data, 0); 167 } else { 168 _data.writeInt(0); 169 } 170 /* 171 * 遠程調用 172 * 客戶端會被掛起等待服務端執行完成才繼續其他程式碼執行,即同步調用 173 * 若使用oneway關鍵字修飾此介面或整個UserController,則不會被掛起,即非同步調用 174 */ 175 mRemote.transact(Stub.TRANSACTION_addUserInOut, _data, _reply, 0); 176 _reply.readException(); 177 178 // InOut定向Tag生成的對客戶端對象修改的程式碼 179 if ((0 != _reply.readInt())) { 180 user.readFromParcel(_reply); 181 } 182 } finally { 183 _reply.recycle(); 184 _data.recycle(); 185 } 186 } 187 } 188 } 189 190 /** 191 * 定義的方法,在實現Stub 的時候需要實現這些方法 192 */ 193 public java.util.List<com.haybl.aidl_test.User> getUserList() throws android.os.RemoteException; 194 195 public void addUserInOut(com.haybl.aidl_test.User user) throws android.os.RemoteException; 196 }
View Code
5.注意
1)客戶端與服務端aidl包名要一致
2)定向Tag:
A. InOut 類型,服務端對數據的改變同時也同步到了客戶端,因此可以說兩者之間數據是雙向流動的
B. In 類型的表現形式是:數據只能由客戶端傳向服務端,服務端對數據的修改不會影響到客戶端
C. Out類型的表現形式是:數據只能由服務端傳向客戶端,即使客戶端向方法介面傳入了一個對象,該對象中的屬性值也是為空的,即不包含任何數據,服務端獲取到該對象後,對該對象的任何操作,就會同步到客戶端這邊
3)oneway
此關鍵字用於修改遠程調用的行為。對客戶端不會有任何影響,調用仍是同步調用。使用oneway時,遠程服務端不會阻塞,它只是發送事務數據並立即返回(非同步調用);不使用則為同步調用。並且方法必須是void類型的。
4)服務端方法是運行在Binder執行緒池中,要考慮好執行緒同步。
6.其他
1)雙向通訊,服務端向客戶端主動發起(可採用觀察者模式產生回調實現,RemoteCallbackList取消回調)
2)Binder連接池(aidl很多的情況下)
後續找時間研究這兩個方面的內容,提到AIDL就不得不提Binder,更何況要對其原理進行分析,接下來看看Binder的簡單介紹。
三、Binder
1.簡介
1)Binder是一個很深入的話題,很複雜
2)Binder是android中的一個類,實現了IBinder介面
3)Framework角度,Binder是ServiceManger連接各種Manger(AM、WM)和MangerService的橋樑
4)應用層角度,Binder是客戶端和服務端進行通訊的媒介,通過bindService可獲得一個Binder對象,進而獲得服務端提供的各種服務介面(包括普通服務和AIDL服務)
5)IPC角度看Binder是一種跨進程通訊方式
6)Binder還是一種虛擬的物理設備,驅動為 /dev/binder。如下圖:
2.IPC再現——IPC原理
IPC原理圖 – 圖源
* ioctl(input/output control)是一個專用於設備輸入輸出操作的系統調用。
3.Binder原理
Binder原理圖 – 圖源
1)Binder通訊採用C/S架構,包含Client、Server、ServiceManager以及binder驅動四個組件,其中ServiceManager用於管理系統中的各種服務(native C++層)。
2)Binder 驅動:binder驅動與硬體設備沒有關係,但是它的工作方式與設備驅動程式是一樣的,工作在內核態,提供open(),mmap(),ioctl等標準文件操作,用戶可以通過/dev/binder來訪問它,驅動負責進程之間binder通訊的建立,傳遞,計數管理以及數據的傳遞交互等底層支援。
3)ServiceManager:將Binder名字轉換為client中對該binder的引用,使得client可以通過binder名字獲得server中binder實體的引用。
4)Client和Server在Binder驅動和Service Manager提供的基礎設施上,進行Client-Server之間的通訊。
4.源碼目錄
對於Binder的理解大多來自其他大佬的部落格,其中的原理和相關角色都介紹得很詳細、系統。貼一下源碼目錄:
/framework/base/core/java/ (Java) /framework/base/core/jni/ (JNI) /framework/native/libs/binder (Native) /framework/native/cmds/servicemanager/ (Native) /kernel/drivers (Driver)
四、Android TV實踐
上述對Binder的敘述很少,只是簡單羅列了下相關概念。主要核心還是在實際場景中如何運用上面的知識去理解所遇到的問題、或者解決新的需求。由於項目是在Mstar晶片上的Android系統電視,並且此次所要解決的問題涵蓋了系統的多個層次,所以有必要記錄一下。雖然是電視系統,但還是基於Android的,其中的原理都是一樣的,只是Mstar在某些地方加入或者修改了自己的東西。
1.問題場景
在Mstar的晶片方案上,電視系統在歐洲某國家出現了自動搜台後有LCN(Logic Channel Number,邏輯節目號)衝突的節目,即LCN重複。查看列印資訊,在搜台完成出現衝突節目後,會發送一個STATUS_LCN_CONFLICT,而在實際的分支程式碼中去除了對這個事件的處理,所以彈出的進度條提示沒有消失,致使介面卡死,並且衝突節目的問題也沒有解決。
1)初步處理:首先直接引入Mstar對衝突事件的處理,跑出來看到最後搜完台會彈窗讓用戶選擇是否自動解決衝突節目,否則不解決,彈窗消失;是則調用resolveLCNConflictAuto()介面自動解決。
2)需求更改:上述初步處理已基本解決問題。接著更改了修改需求:在彈出選擇是否自動處理衝突節目時,若選否,需要將衝突節目列出,並由用戶手動選擇保留的節目直至所有衝突節目選擇完成。
3)需求處理:查看Mstar中Framework的程式碼,發現沒有提供可以滿足需求的介面。需找Supernova Engineer協助解決(溝通過程略 ……)。
2.解決流程
Supernova 工程師給出如下介面:
1 // 獲取衝突節目列表 2 bool ScanManagerImplService::Client::getConflictProgramList(Parcel *reply) 3 4 //設置單個衝突節目 5 bool ScanManagerImplService::Client::setConflictLCNServiceListIndex(uint8_t listindex, uint8_t selectindex)
圖中注釋已經說明了,實際解決問題需要這兩個介面,所以後續的工作基本圍繞這兩個介面來展開。按照Android的體系結構,首先定義出數據Model,在應用層根據數據結構,編寫符合需求的邏輯,還包括實現UI效果等;接著在Framework層添加應用層需要的介面(此問題需按照Mstar的層次邏輯添加),並解析相關數據;最後打通Supernova中程式碼邏輯。可以看到,基本思路就是應用層往下走的方向,因為對應用層更熟悉一些,所以更容易入手問題。相當於我假設已經拿到這些數據(還有數據解決方法),進而實現我的介面邏輯;接下來再思考怎樣拿到這些數據,怎樣與其他更底層的邏輯交互。首先來看下介面中需要用到的數據格式是怎麼樣的:
1 /// Define conflict program struct 2 typedef struct 3 { 4 /// state of LCN resolved 5 U8 u8ResolveState; 6 /// number of total list 7 U16 u16ListNum; 8 /// the service of one list that gets allocated LCN. Be pair with VctConfProg. 9 U16 u16AllocLCNService[MAX_CONFLICT_PROGRAM_LIST_NUM]; 10 /// Program information 11 list<ST_CONFLICT_PROGRAM_INFO> ConfProgList[MAX_CONFLICT_PROGRAM_LIST_NUM]; 12 } ST_CONFLICT_PROGRAM; 13 14 /// Define conflict program info struct 15 typedef struct 16 { 17 /// program information number 18 U16 u16Number; 19 /// program index of DB 20 U32 u32DbIndex; 21 /// Service ID 22 U16 u16ServiceID; 23 /// program information service type 24 U8 u8ServiceType; 25 /// program information service name 26 string sServiceName; 27 } ST_CONFLICT_PROGRAM_INFO;
從數據結構中可以看出,getConflict返回的這個數據體就包含了所有衝突的節目List<>,其中的每一個節目又有一個數組來保存其衝突情況(ST_CONFLICT_PROGRAM_INFO)。拿到數據後先安裝Android的做法定義出Java版本的數據,因為這樣才能給上層應用調用。如下:


1 /* 2 * @author Haybl 3 * @version 1.0 4 */ 5 import android.os.Parcel; 6 import android.os.Parcelable; 7 8 import java.util.ArrayList; 9 /** 10 * ConflictProgram Information 11 */ 12 public class ConflictProgram implements Parcelable { 13 /** state of LCN resolved */ 14 public int resolveState; 15 /** number of total list */ 16 public int listNum; 17 /** the service of one list that gets allocated LCN. Be pair with VctConfProg */ 18 public int[] allocLCNService = new int[Constants.MAX_CONFLICT_PROGRAM_LIST_NUM]; 19 /** Program information */ 20 public List[] confProgList = new List[Constants.MAX_CONFLICT_PROGRAM_LIST_NUM]; 21 22 public ConflictProgram() { 23 resolveState = 0; 24 listNum = 0; 25 for(int i = 0; i < this.allocLCNService.length; ++i) { 26 this.allocLCNService[i] = 0; 27 } 28 for (int i = 0; i < confProgList.length; ++i) { 29 confProgList[i] = new ArrayList<ConflictProgramInfo>(); 30 } 31 } 32 33 public ConflictProgram(Parcel in) { 34 resolveState = in.readInt(); 35 listNum = in.readInt(); 36 allocLCNService = in.createIntArray(); 37 for(int i = 0; i < this.confProgList.length; ++i) { 38 in.createTypedArrayList(ConflictProgramInfo.CREATOR); 39 } 40 } 41 42 @Override 43 public void writeToParcel(Parcel dest, int flags) { 44 dest.writeInt(resolveState); 45 dest.writeInt(listNum); 46 dest.writeIntArray(allocLCNService); 47 for(int i = 0; i < this.confProgList.length; ++i) { 48 dest.writeTypedList(this.confProgList[i]); 49 } 50 } 51 52 @Override 53 public int describeContents() { 54 return 0; 55 } 56 57 public static final Creator<ConflictProgram> CREATOR = new Creator<ConflictProgram>() { 58 @Override 59 public ConflictProgram createFromParcel(Parcel in) { 60 return new ConflictProgram(in); 61 } 62 63 @Override 64 public ConflictProgram[] newArray(int size) { 65 return new ConflictProgram[size]; 66 } 67 }; 68 }
View Code


1 /* @author Haybl 2 * @version 1.0 3 */ 4 import android.os.Parcel; 5 import android.os.Parcelable; 6 7 /** 8 * ConflictProgram Information 9 */ 10 public class ConflictProgramInfo implements Parcelable { 11 /** program information of program number */ 12 public int number; 13 /** program information of program index */ 14 public int index; 15 /** ServiceID */ 16 public int serviceID; 17 /** program information of program service type */ 18 public int serviceType; 19 /** program information of program service name */ 20 public String serviceName; 21 22 public static final Creator<ConflictProgramInfo> CREATOR = new Creator<ConflictProgramInfo>() { 23 public ConflictProgramInfo createFromParcel(Parcel in) { 24 return new ConflictProgramInfo(in); 25 } 26 27 public ConflictProgramInfo[] newArray(int size) { 28 return new ConflictProgramInfo[size]; 29 } 30 }; 31 32 public ConflictProgramInfo(Parcel in) { 33 number = (int) in.readInt(); 34 index = in.readInt(); 35 serviceID = (int) in.readInt(); 36 serviceType = in.readInt(); 37 serviceName = in.readString(); 38 } 39 40 public ConflictProgramInfo(int number, int index, int serviceID, int serviceType, String serviceName) { 41 super(); 42 this.number = number; 43 this.index = index; 44 this.serviceID = serviceID; 45 this.serviceType = serviceType; 46 this.serviceName = serviceName; 47 } 48 49 public ConflictProgramInfo() { 50 number = 0; 51 index = 0; 52 serviceID = 0; 53 serviceType = 0; 54 serviceName = ""; 55 } 56 57 public ConflictProgramInfo(int index) { 58 number = 0; 59 index = 0; 60 serviceID = 0; 61 serviceType = 0; 62 serviceName = ""; 63 } 64 65 @Override 66 public int describeContents() { 67 return 0; 68 } 69 70 @Override 71 public void writeToParcel(Parcel dest, int flags) { 72 dest.writeInt(number); 73 dest.writeInt(index); 74 dest.writeInt(serviceID); 75 dest.writeInt(serviceType); 76 dest.writeString(serviceName); 77 } 78 }
View Code
可以看到兩個數據類都實現了Parcelable介面,需要在各層次之間傳遞(跨進程),必須得實現此介面,還需要對應的定義出AIDL文件(很簡單,不貼程式碼了)。接著進入解決流程:
1)Android 6.0:
由於電視系統架構在了M和O兩個Android版本之上,並且考慮到版本之間的差異,需要依據具體版本具體實現介面添加。首先看下Android M的流程:
A. 應用層:應用層只是基本邏輯添加,較為簡單,主要注意自定義UI風格、循環處理衝突節目以及數據下發格式三個方面,此處略。
B. Framework層:分為tv及api兩層
I. ../tv2/
java層:IChannel.aidl(介面聲明),ChannelManager.java中添加對應介面,此處為第一次Binder機制,以AIDL形式呈現。IChannel定義了對節目操作的介面,ChannelManager作為客戶端調用IChannel中的方法,這些方法都在服務端實現。
1 /** 2 * Set conflict to resolve LCN . 3 * 4 * @param listindex int 5 * @param selectindex int 6 * @return boolean 7 */ 8 public boolean setConflictLCNListIndex(int listindex, int selectindex) { 9 try { 10 Log.d(TAG, "setConflictLCNListIndex(), listindex = " + listindex + ", selectindex = " + selectindex); 11 return mService.setConflictLCNListIndex(listindex, selectindex); 12 } catch (RemoteException e) { 13 e.printStackTrace(); 14 } 15 return false; 16 }
MService層: ChannelService.java中添加對應介面(注意方法名稱校驗添加)。此處即為服務端,實現了IChannel.aidl中所定義的介面,其具體實現又依賴於api層邏輯。
1 @Override 2 public boolean setConflictLCNListIndex(int listindex, int selectindex) throws RemoteException { 3 boolean result = false; 4 try { 5 if (Manager.getScanManager() != null) { 7 result = Manager.getScanManager().setConflictLCNServiceListIndex(listindex, selectindex); 9 } 10 } catch (CommonException e) { 11 e.printStackTrace(); 12 } 13 return result; 14 }
II. ../api/
java層:ScanManager.java,ScanManagerImpl.java 中添加對應介面,並在包中導入定義的數據類型Bean。ScanManagerImpl中多為jni調用(native修飾),對於setConflictLCNServiceListIndex介面,由於沒有返回節目數據,可直接調用jni的native實現;對於getConflictProgramList介面,需要對數據手動編碼實現反序列化。


1 public native final boolean setConflictLCNServiceListIndex(int listindex, int selectindex) throws TvCommonException; 2 3 public final ConflictProgram getConflictProgramList() throws CommonException { 4 Parcel reply = Parcel.obtain(); 5 native_getConflictProgramList(reply); 6 7 DtvConflictProgram result = new DtvConflictProgram(); 8 int ret = reply.readInt(); // not 0: get conflict program list success; 0: failure. 9 if (ret == 0) { 10 result.listNum = 0; 11 } else { 12 result.resolveState = reply.readInt(); 13 result.listNum = reply.readInt(); 14 // the max conflict program list num is 30 15 for (int i = 0; i < result.listNum && i < Constants.MAX_CONFLICT_PROGRAM_LIST_NUM; i++) { 16 result.allocLCNService[i] = reply.readInt(); 17 } 18 for (int i = 0; i < result.listNum; i++) { 19 int listIdsize = reply.readInt(); 20 if (listIdsize == 0) { 21 continue; 22 } 23 for (int j = 0; j < listIdsize; j++) { 24 ConflictProgramInfo cpi = new ConflictProgramInfo(); 25 cpi.number = reply.readInt(); 26 cpi.index = reply.readInt(); 27 cpi.serviceID = reply.readInt(); 28 cpi.serviceType = reply.readInt(); 29 cpi.serviceName = reply.readString(); 30 result.confProgList[i].add(cpi); 31 } 32 } 33 } 34 reply.recycle(); 35 return result; 36 } 37 38 public native final boolean native_getConflictProgramList(Parcel reply) throws CommonException;
View Code
可以看到此處就用到了第一章節提到的Parcel數據容器,至於其中的數據為何這樣解析,需要獲取到如何封裝的數據,稍後會給出。
JNI層:com_android_api_impl_ScanManagerImpl.cpp中添加對應介面。此處直接調用Supernova層的介面(C++)。為何能直接調用?因為在第一次編譯Android源碼的時候,需要將Sn軟鏈接過來:ln -s ../../../xxx,即可直接調用。
1 /* 2 * Class: com_android_api_impl_ScanManagerImpl 3 * Method: setConflictLCNServiceListIndex 4 * Signature: (II)Z 5 */ 6 jboolean com_android_api_impl_ScanManagerImpl_setConflictLCNServiceListIndex(JNIEnv *env, jobject thiz, jint listindex, jint selectindex) { 8 ALOGI("setConflictLCNServiceListIndex"); 9 sp<ScanManagerImpl> ms = getScanManagerImpl(env, thiz); 10 if (ms == NULL) { 11 ALOGI("setConflictLCNServiceListIndex:ms == NULL"); 12 jniThrowException(env, "com/android/api/common/exception/IpcException", "can not connect to server"); 13 return 0; 14 } 15 ALOGI("setConflictLCNServiceListIndex:ms != NULL"); 16 return ms->setConflictLCNServiceListIndex(listindex, selectindex); 17 }
C. Supernova層:
ScanManagerImpl.cpp被Android層直接調用,發生的第二次Binder機制調用,作為客戶端通過Binder與ScanManagerImplService.cpp通訊。ScanManagerImplService作為服務,具體實現了上述兩個介面。IScanManagerImpl.cpp中實現了遠程調用。
首先調到到此方法,此處發起調用請求:
1 bool ScanManagerImpl::setConflictLCNServiceListIndex(int32_t listindex, int32_t selectindex) 2 { 3 ALOGV("ScanManagerImpl getConflictProgramList\n"); 4 /* 5 * mScanManagerImpl是一個BpBinder(IScanManagerImpl的一個代理BpScanManagerImpl) 6 * BpBinder是與BnBinder通訊用,也即是BpScanManagerImpl與BnScanManagerImpl通訊 7 * BnScanManagerImpl: public BnInterface<IScanManagerImpl> 8 * BpScanManagerImpl: public BpInterface<IScanManagerImpl> 9 * Bp端通過remote->transact()將client端請求發給Bn端,Bn端則通過onTransact()處理接收到的請求 10 */ 11 return mScanManagerImpl->setConflictLCNServiceListIndex(listindex, selectindex); 12 }
過程 – Binder客戶端:
1 virtual bool setConflictLCNServiceListIndex(int32_t listindex, int32_t selectindex) 2 { 3 bool ret = false; 4 printf("Send SCANMANAGER_SET_CONFLICT_SERVICE_LIST_INDEX\n"); 5 Parcel data, reply; 6 data.writeInterfaceToken(IScanManagerImpl::getInterfaceDescriptor()); 7 data.writeInt32(listindex); 8 data.writeInt32(selectindex); 9 // 發起遠程調用 10 remote()->transact(SCANMANAGER_SET_CONFLICT_SERVICE_LIST_INDEX, data, &reply); 11 ret = static_cast<bool>(reply.readInt32()); 12 return ret; 13 }
Binder – 服務端:
1 case SCANMANAGER_SET_CONFLICT_SERVICE_LIST_INDEX: 2 printf("Receive SCANMANAGER_SET_CONFLICT_SERVICE_LIST_INDEX\n"); 3 CHECK_INTERFACE(IScanManagerImpl, data, reply); 4 int32_t listindex = (int32_t)data.readInt32(); 5 int32_t selectindex = (int32_t)data.readInt32(); 6 // 接收到請求,調用具體實現(ScanManagerImplService.cpp中實現) 7 reply->writeInt32(setConflictLCNServiceListIndex(listindex, selectindex)); 8 break;
具體實現在ScanManagerImplService.cpp中,終於解開神秘的面紗了,這裡的方法調用了在掃描時存入的衝突數據(資料庫形式),設置衝突也就相當於修改資料庫了:
1 ST_CONFLICT_PROGRAM *pstConfProg = pMsrvD->GetConflictProgramList(); 2 3 pPlayer->SetConflictLCNServiceListIndex(listindex, selectindex);
自此Android 6.0的問題基本已解決完成,接下來看看8.0 的方法。
2)Android 8.0:
Android 8 中增加了一層hidl層,在Mstar平台上表現出也相對複雜些。
HIDL:HAL interface definition language(硬體抽象層介面定義語言),在此之前Android有AIDL,架構在Android binder 之上,用來定義Android 基於Binder通訊的Client 與Service之間的介面。HIDL也是類似的作用,只不過定義的是Android Framework與Android HAL實現之間的介面。
8.0 jni以上都與Android6一致,在api中新增了hidl包裝層。
B. Framework層:
II. ../api/
hidl_wrapper:ScanManagerImpl.cpp、ScanManagerImpl.h中添加對應介面。.h聲明介面,.cpp具體實現。具體實現中調用了vendor/mstar/中的hardware層的程式碼。
III. vendor/mstar/
interfaces:IMstarInput.hal、Input.h、Input_ScanManagerImpl.cpp、mstarInput_ScanManagerImpl_d.h中添加對應介面。
1 bool mstar_input_ScanManagerImpl::setConflictLCNServiceListIndex(int32_t listindex, int32_t selectindex) { 2 return g_pScanManagerImplImpl->setConflictLCNServiceListIndex(listindex, selectindex); 3 }
tv_input:mstar_input_ScanManagerImpl.cpp、mstar_nput_ScanManagerImpl.h中添加對應介面。在mstar_input.cpp中統一註冊並連接服務端。
Supernova層與6.0一致。關於HIDL的內容很多,這裡只簡單加了個介面,依葫蘆畫瓢,詳細原理待後續研究 …..
3.原理分析
此處只分析在Android與Supernova之間的Binder原理,Android framework層的Binder機製表現為AIDL形式,使用方式及原理已在aidl處分析。
五、總結
1.為什麼要使用Binder?
性能方面更加高效。Binder數據拷貝只需要一次,而管道、消息隊列、Socket都需要2次,共享記憶體方式一次記憶體拷貝都不需要,但實現方式比較複雜。安全方面,Binder機制從協議本身就支援對通訊雙方做身份校檢,因而大大提升了安全性。
2.為什麼需要AIDL?
使用簡單。客戶端和服務端進行通訊,若直接使用Binder需先將請求轉換成序列化的數據,然後調用transact()函數發送給服務端,並且控制參數順序,服務端和客戶端都必須一致,否則就會出錯。這樣的過程很麻煩,如果有上百個介面,並且都需要手動編寫傳輸介面,那可就很麻煩了。AIDL調用服務端方法如調用自身方法一樣簡單快捷煩。
3.面向介面編程
封裝、繼承、多態
4.接下來學習方向
1)Binder連接池及服務端主動通知客戶端方法
2)HIDL原理
3)Binder啟動
六、參考鏈接
1.//www.jianshu.com/p/4920c7781afe
2.//www.imooc.com/article/17958
3.//www.jianshu.com/p/29999c1a93cd
4.//blog.csdn.net/lei7143/article/details/80931412
5.//www.jianshu.com/p/4920c7781afe
6.//blog.csdn.net/xude1985/article/details/9232049
7.//gityuan.com/2015/10/31/binder-prepare/
8.《MStar 開發指導》.P113
9.《Android開發藝術探索》.任玉剛 .P42
10.《MStar Android網路電視總結》