[TOC]
文章結構
-
1.使用多程式原因
-
2.如何開啟多程式
-
3.多程式可能會引起的問題
-
4.序列化
-
5.常用的IPC通訊機制(主要介紹AIDL,附帶介紹一下匿名共享記憶體)
- 5.4 AIDL
- 5.7 匿名共享記憶體
-
6.Binder的基本原理
可參考文章
1.使用多程式原因
- 一種是由於某些模組的特殊原因需要執行在單獨程式,或為了增大一個應用可使用的最大記憶體需要通過多程式獲取多份記憶體空間。
- 另外一種是當前應用需要向其他應用獲取資料。
系統為每個程式分配的記憶體是有限的,比如在以前的低端手機上常見是 16M,現在的機器記憶體更大一些,32M、48M,甚至更高。但是,總是有限的,畢竟一個手機出廠之後 RAM 的大小就定了,總是無法滿足所有應用的需求。
也可以在 Application 中通過使用 largeHeap 屬性為自己應用所處的程式爭取分配更大的記憶體,就像這樣,但也存在不夠用的可能。
<application
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:largeHeap="true">
......
</application>
複製程式碼
此部分可參考《Android開發藝術探索》或Android 中的多程式,你值得了解的一些知識
2.開啟多程式
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.sparkfengbo.app" >
<service
android:name=".android.aidltest.AService"
android:process=":remote"/>
<service
android:name=".android.aidltest.BService"
android:process="com.sparkfengbo.test.hh"/>
...
複製程式碼
預設程式的程式名是包名.
AService所在程式是com.sparkfengbo.app:remote
BService所在程式是com.sparkfengbo.test.hh
可以用過 asb shell ps | grep com.sparkfengbo
的指令檢視程式。
程式名以:
開頭的程式屬於當前應用的私有程式,其他應用的元件不可以和它跑在同一個程式中,而程式名不以:
開頭的程式屬於全域性程式,其他應用可以通過相同的ShareUID和相同的簽名和它跑在同一個程式。
如果有相同的ShareUID和相同的簽名,兩個應用能夠互相訪問對方的私有資料。
3.多程式可能會引起的問題
- 1.靜態成員和單例模式失效
- 2.執行緒同步機制失效
- 3.SharedPreference可靠性下降
- 4.Application會多次重建
1、2是因為記憶體空間不一樣了,鎖或者全域性類都不能保證執行緒同步,不同程式鎖的不是同一個物件;3是因為SharedPreference不支援兩個程式同時去進行寫操作(底層讀寫XML,容易出現問題);4是因為當一個元件跑在一個新程式中時,由於系統要建立新的程式同時分配獨立的虛擬機器,所以這個過程實際就是啟動一個應用的過程。
Android應用為每個應用分配獨立的虛擬機器,或者為每個程式都分配獨立的虛擬機器,這樣不同程式的元件會擁有獨立的虛擬機器、Application和記憶體空間
4.序列化
參考《序列化備忘》筆記。
5.常用的IPC通訊機制
Android是基於Linux的。
Linux提供的IPC機制有
- 1.管道(Pipe)
- 2.訊號(Signal)
- 3.訊息佇列(Message)
- 4.共享記憶體(Share Memory)
- 5.插口(Socket)
下面是Android能夠進行IPC的方法的羅列,字號較大的是比較好的實現方式
5.1 Bundle
同一應用內啟動Service時,通過Bundle將資訊包在Intent中
5.2 使用檔案共享
5.3 使用Messenger
參考例項程式碼 AndroidCodeDemoTest 中的MessengerTestActivity
大致的結構是Messenger + Handler
Messenger是以序列的方式處理訊息,如果大量的訊息同時傳送到服務端,服務端仍然只能一個個處理,如果有大量的併發請求,那麼用Messenger就不太合適。
Messenger的作用主要是傳遞訊息,如果需要跨程式呼叫服務端的方法,Messenger就不行了。
底層其實和AIDL是一個原理,還是看看接下來看看AIDL的實現吧。
5.4 使用AIDL
參考例項程式碼 AndroidCodeDemoTest 中的AIDLTestActivity
5.4.1 需要注意的是
1. 當客戶端發起遠端請求時,客戶端執行緒被掛起,直至服務端程式返回資料,而服務端被呼叫的方法執行在服務端的Binder執行緒池中。所以如果一個遠端方法很耗時,那麼不能在UI線層發起遠端呼叫。
**另外,由於服務端的方法執行在Binder執行緒池中,切記不要在服務端方法中開非同步執行緒。**處理客戶端的回撥方法也是同樣的道理(Server的遠端方法執行在Server的Binder執行緒池中,客戶端的被Server呼叫的回撥方法執行在客戶端的Binder執行緒池中)(RemoteCallback相關的程式碼)。
2. 由於服務端的Binder方法執行在Binder執行緒池中,所以Binder方法不管是否耗時都應該採用同步的方法去實現,因為它已經執行在一個執行緒中了。
3. AIDL中不是所有資料型別都可以使用,能夠使用的資料型別有:基本資料型別
、String、CharSequence
、ArrayList
、HashMap
、Parcelable
(必須顯式的import進來,如果用到自定義的Parcelable則必須新建同名的aidl檔案,並宣告型別)、AIDL
。
Parcelable(必須顯式的import進來,如果用到自定義的Parcelable則必須新建同名的aidl檔案,並宣告型別)這裡可以參考 AndroidCodeDemoTest
AIDL中除了基本型別,其他型別必須標上in、out、inout(不能隨便使用,因為在底層實現中有開銷)
4. AIDL介面中只支援方法,不支援靜態變數
5. AIDL的包結構在服務端和客戶端中必須保持一致,因為客戶端需要反序列化服務端中和AIDL介面相關的所有類。建議把AIDL相關的類和檔案全部放在同一個包中
6. Server可以連線很多個Client
7. Server的遠端方法執行在Server的Binder執行緒池中,客戶端的被Server呼叫的回撥方法執行在客戶端的Binder執行緒池中。
為什麼可以使用CopyOnWriteArrayList和ConcurrentHashMap?AIDL中支援ArrayList和HashMap,但是AIDL所支援的是List介面,雖然服務端返回的是CopyOnWriteArrayList,但是Binder中會按照List的規範訪問資料並最終形成一個ArrayList傳遞給客戶端。
建議看看AIDL生成的程式碼
5.4.2 介面執行在Server的Binder的死亡通知
方法1
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
if (iBookManagerInterface == null) {
return;
}
TLog.e("DeathRecipient binderDied");
iBookManagerInterface.asBinder().unlinkToDeath(mDeathRecipient, 0);
iBookManagerInterface = null;
tryBindService();
}
};
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
TLog.e("service connencted " + name.getPackageName());
iBookManagerInterface = IBookManagerInterface.Stub.asInterface(service);
try {
service.linkToDeath(mDeathRecipient, 0);
...
複製程式碼
方法2
onServiceDisconnected
區別:
onServiceDisconnected在UI執行緒呼叫而binderDied在客戶端的執行緒池中呼叫。
5.4.3 有關AIDL中使用介面型別
多程式的回撥問題。請參考《Android開發藝術探索》 第2章79-87頁,需要重點關注 RemoteCallback
1.宣告介面,並建立AIDL檔案
xxxListener.aidl
interface xxxListener {
//Method
}
IBookManager.aidl
void registerListenr(xxxListener listener) ;
複製程式碼
2.Client端
ServiceConnection/onServiceConnected
{
IBookManager.registerListener(mLisner);
}
onDestory
{
unregisterListener();
}
複製程式碼
3.Server端
private RemoteCallbackList<xxxListener> mListener = new RemoteCallbackList<xxxListener> ;
mListener.beginBroadcast();
mListener.getBroadcastItem(i);
mListener.finishBroadcast();
複製程式碼
5.4.5 安全性:驗證許可權
預設情況下,遠端服務任何人都可以連線,所以必須進行許可權的驗證。這部分《Android開發藝術探索》 第2章 90頁
5.5 使用ContentProvider
底層的實現也是Binder,可以參考《Android開發藝術探索》 第2章91頁
5.6 使用Socket
參考《Android開發藝術探索》 第2章103頁,此處不贅述了
5.7 匿名共享記憶體
此部分參考《Android系統原始碼情景分析》(羅昇陽)記錄。部落格地址
在Android系統中,提供了獨特的匿名共享記憶體子系統Ashmem(Anonymous Shared Memory),它以驅動程式的形式實現在核心空間中。它有兩個特點,一是能夠輔助記憶體管理系統來有效地管理不再使用的記憶體塊,二是它通過Binder程式間通訊機制來實現程式間的記憶體共享。
在Android應用程式框架層,提供了一個MemoryFile介面來封裝了匿名共享記憶體檔案的建立和使用,它實現在frameworks/base/core/java/android/os/MemoryFile.java檔案中。
詳細例項請參考例項程式碼 AndroidCodeDemoTest 中的AshmemActivty。
6.Binder基本原理
此部分參考《Android系統原始碼情景分析》(羅昇陽)記錄。
6.1 Binder的優勢
Android是基於Linux的,但是為什麼需要新設計一個通訊機制,Binder?
Binder的特點和優勢
傳統的跨程式通訊機制如Socket,開銷大且效率不高,而管道和佇列拷貝次數多,更重要的是對於移動裝置來說,安全性非常重要,傳統的通訊機制安全性低,大部分情況下接受方無法得到傳送方程式的可信PID/UID,難以甄別身份。
Binder只需要進行一次拷貝操作。
Binder是在OpenBinder的基礎上實現的。
6.2 Binder通訊過程
涉及的物件 Client程式和Server程式的一次通訊涉及四種型別物件,分別是
- 位於Binder驅動程式中的Binder實體物件(binder_node)
- 位於Binder驅動程式中的Binder引用物件(binder_ref)
- Binder庫中的Binder本地物件(BBinder)
- Binder庫中的Binder代理物件(BpBinder)
過程
- 1.執行在Client程式中的Binder代理物件通過Binder驅動程式向執行在Server程式中的Binder本地物件發出程式間通訊請求,Binder驅動程式接著就根據Client程式傳遞來的Binder代理物件的控制程式碼值找到對應的Binder引用物件
- 2.Binder驅動程式根據前面找到的Binder引用物件找到對應的Binder實體物件,並建立一個事物(binder_transaction)來描述傳遞過來的通訊資料傳送給它處理
- 3.Binder驅動程式根據前面找到的Binder實體物件來找到執行在Server程式中的Binder本地物件,並且將Client程式傳遞過來的通訊資料傳送給它處理
- 4.Binder本地物件處理完成Client程式的通訊請求後就將通訊結果返回給Binder驅動程式,Binder驅動程式接著找到前面所建立的一個事物
- 5.Binder驅動程式根據前面找到的事物的相關屬性來找到發出通訊請求的Client程式,並且通知Client程式將通訊結果返回給對應的Binder代理物件處理
可以看到涉及的四種物件型別都有相互依賴的關係,所以為了維護這些Binder物件的依賴關係,Binder通訊機制採用引用計數技術維護每一個Binder物件宣告週期。
使用Binder在程式間傳遞資料的時候,有時候會丟擲TransactionTooLargeException這個異常,這個異常的產生是因為Binder驅動對記憶體的限制引起的。也就是說,我們不能通過Binder傳遞太大的資料。官方文件裡有說明,最大通常限制為1M。參見TransactionTooLargeException。可以通過MemoryFile解決,參考Android匿名共享記憶體和MemoryFile
下面是未整理的部分 (TODO 底層原理待補充,太複雜了 )
1.Binder程式間通訊機制框架
2.相關的知識路線
-
Binder驅動程式
-
Binder的驅動程式實現在核心空間中,目錄介面是
複製程式碼
-
~/Android/kernel/goldfish ----drivers ----staging ----android ----binder.h ----binder.c ```
- 裝置檔案 /dev/binder的開啟(open)、記憶體對映(mmap)、核心緩衝區管理等
複製程式碼
-
Service Manager的啟動過程
-
Service Manager代理物件的獲取
-
Service元件的啟動過程
-
Service代理物件的獲取過程
-
Binder程式間通訊機制的Java介面