季春初始,天氣返暖,新冠漸去,正值學習好時機。在Android系統中,AIDL一直在Framework和應用層上扮演着很重要的角色,今日且將其原理簡單分析。(文2020.03.30)java
Android系統中對原理的分析基本離不開對源碼的閱讀,我理解的原理分析:android
原理分析 = 基本概念 + 源碼分析 + 實踐git
正如創始人Linus Torvalds的名言:RTFSC(read the f**king source code)。本文也是按照上述結構來介紹AIDL的。數據庫
接下來先簡單提一提IPC、序列化和Parcel三個概念。編程
A. 線程是CPU調度的最小單元,同時線程是一種有限的系統資源。
B. 進程通常指一個執行單元,在PC和移動設備上指一個程序或一個應用
C. 一個進程能夠包含多個線程,包含與被包含的關係。
D. Java默認只有一個線程,叫主線程,在android中也叫作UI線程api
A. 定義:IPC(inter-process Commnication)跨進程的通訊,多進程之間的通訊。
B. 爲何須要進程通訊:咱們知道Android通常狀況下一個應用是默認運行在一個進程中,但有可能一個應用中須要採用多進程模式(process屬性)來實現(好比獲取多分內存空間),或者兩個應用之間須要獲取彼此之間的數據,還有AMS(系統服務)對每一個應用中四大組件的管理,系統服務是運行在一個單獨的進程中,這些通通須要IPC。數組
Android IPC方式:文件共享、ContentProvider(底層是Binder實現)、Socket、Binder(AIDL、Messenger)。安全
序列化是指將一個對象轉化爲二進制或者是某種格式的字節流,將其轉換爲易於保存或網絡傳輸的格式的過程,反序列化則是將這些字節重建爲一個對象的過程。Serializable和Parcelable接口能夠完成對象的序列化。以下圖:網絡
Serializable是Java提供的序列化接口,使用時只須要實現Serializable接口並聲明一個serialVersionUID(用於反序列化)數據結構
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。
Parcel主要就是用來進行IPC通訊,是一種容器,他能夠包含數據或者是對象引用,而且可以用於Binder的傳輸。同時支持序列化以及跨進程以後進行反序列化,同時其提供了不少方法幫助開發者完成這些功能。Parcel的讀寫都是一個指針操做的,有writeInt(int val)、writeString(String val)、setDataPosition(int val)、readInt()、readString()、recycle()方法。
AIDL:Android interface definition Language,Android 接口定義語言。使用aidl能夠發佈以及調用遠程服務,實現跨進程通訊。將服務的aidl放到對應的src目錄,工程的gen目錄會生成相應的接口類。
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導入。
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 }
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 }
複製上述兩個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 };
實現 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 };
原理分析分析離不開代碼,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 }
1)客戶端與服務端aidl包名要一致
2)定向Tag:
A. InOut 類型,服務端對數據的改變同時也同步到了客戶端,所以能夠說二者之間數據是雙向流動的
B. In 類型的表現形式是:數據只能由客戶端傳向服務端,服務端對數據的修改不會影響到客戶端
C. Out類型的表現形式是:數據只能由服務端傳向客戶端,即便客戶端向方法接口傳入了一個對象,該對象中的屬性值也是爲空的,即不包含任何數據,服務端獲取到該對象後,對該對象的任何操做,就會同步到客戶端這邊
3)oneway
此關鍵字用於修改遠程調用的行爲。對客戶端不會有任何影響,調用還是同步調用。使用oneway時,遠程服務端不會阻塞,它只是發送事務數據並當即返回(異步調用);不使用則爲同步調用。而且方法必須是void類型的。
4)服務端方法是運行在Binder線程池中,要考慮好線程同步。
1)雙向通訊,服務端向客戶端主動發起(可採用觀察者模式產生回調實現,RemoteCallbackList取消回調)
2)Binder鏈接池(aidl不少的狀況下)
後續找時間研究這兩個方面的內容,提到AIDL就不得不提Binder,更況且要對其原理進行分析,接下來看看Binder的簡單介紹。
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。以下圖:
IPC原理圖 - 圖源
* ioctl(input/output control)是一個專用於設備輸入輸出操做的系統調用。
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之間的通訊。
對於Binder的理解大多來自其餘大佬的博客,其中的原理和相關角色都介紹得很詳細、系統。貼一下源碼目錄:
/framework/base/core/java/ (Java) /framework/base/core/jni/ (JNI) /framework/native/libs/binder (Native) /framework/native/cmds/servicemanager/ (Native) /kernel/drivers (Driver)
上述對Binder的敘述不多,只是簡單羅列了下相關概念。主要核心仍是在實際場景中如何運用上面的知識去理解所遇到的問題、或者解決新的需求。因爲項目是在Mstar芯片上的Android系統電視,而且這次所要解決的問題涵蓋了系統的多個層次,因此有必要記錄一下。雖然是電視系統,但仍是基於Android的,其中的原理都是同樣的,只是Mstar在某些地方加入或者修改了本身的東西。
在Mstar的芯片方案上,電視系統在歐洲某國家出現了自動搜臺後有LCN(Logic Channel Number,邏輯節目號)衝突的節目,即LCN重複。查看打印信息,在搜臺完成出現衝突節目後,會發送一個STATUS_LCN_CONFLICT,而在實際的分支代碼中去除了對這個事件的處理,因此彈出的進度條提示沒有消失,導致界面卡死,而且衝突節目的問題也沒有解決。
1)初步處理:首先直接引入Mstar對衝突事件的處理,跑出來看到最後搜完臺會彈窗讓用戶選擇是否自動解決衝突節目,不然不解決,彈窗消失;是則調用resolveLCNConflictAuto()接口自動解決。
2)需求更改:上述初步處理已基本解決問題。接着更改了修改需求:在彈出選擇是否自動處理衝突節目時,若選否,須要將衝突節目列出,並由用戶手動選擇保留的節目直至全部衝突節目選擇完成。
3)需求處理:查看Mstar中Framework的代碼,發現沒有提供能夠知足需求的接口。需找Supernova Engineer協助解決(溝經過程略 ......)。
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 }
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 }
能夠看到兩個數據類都實現了Parcelable接口,須要在各層次之間傳遞(跨進程),必須得實現此接口,還須要對應的定義出AIDL文件(很簡單,不貼代碼了)。接着進入解決流程:
因爲電視系統架構在了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;
能夠看到此處就用到了第一章節提到的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 的方法。
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的內容不少,這裏只簡單加了個接口,依葫蘆畫瓢,詳細原理待後續研究 .....
此處只分析在Android與Supernova之間的Binder原理,Android framework層的Binder機制表現爲AIDL形式,使用方式及原理已在aidl處分析。
性能方面更加高效。Binder數據拷貝只須要一次,而管道、消息隊列、Socket都須要2次,共享內存方式一次內存拷貝都不須要,但實現方式比較複雜。安全方面,Binder機制從協議自己就支持對通訊雙方作身份校檢,於是大大提高了安全性。
使用簡單。客戶端和服務端進行通信,若直接使用Binder需先將請求轉換成序列化的數據,而後調用transact()函數發送給服務端,而且控制參數順序,服務端和客戶端都必須一致,不然就會出錯。這樣的過程很麻煩,若是有上百個接口,而且都須要手動編寫傳輸接口,那可就很麻煩了。AIDL調用服務端方法如調用自身方法同樣簡單快捷煩。
封裝、繼承、多態
1)Binder鏈接池及服務端主動通知客戶端方法
2)HIDL原理
3)Binder啓動
1.https://www.jianshu.com/p/4920c7781afe
2.http://www.imooc.com/article/17958
3.https://www.jianshu.com/p/29999c1a93cd
4.https://blog.csdn.net/lei7143/article/details/80931412
5.https://www.jianshu.com/p/4920c7781afe
6.https://blog.csdn.net/xude1985/article/details/9232049
7.http://gityuan.com/2015/10/31/binder-prepare/
8.《MStar 開發指導》.P113
9.《Android開發藝術探索》.任玉剛 .P42
10.《MStar Android網絡電視總結》