AIDL使用以及IPC原理分析(進(jìn)程間通信)
概要
為了大家能夠更好的理解android的進(jìn)程間通信原理,以下將會(huì)從以下幾個(gè)方面講解跨進(jìn)程通訊信:
- 必要了解的概念
- 為什么要使用aidl進(jìn)程間通信
- 可能遇到的問題以及解決辦法
- aidl的使用,通過android提供的aidl實(shí)現(xiàn)一個(gè)進(jìn)程間通信
- 不使用aidl,手動(dòng)編寫B(tài)inder實(shí)現(xiàn)進(jìn)程間通信
- 分析aidl的原理,梳理andriod進(jìn)程間通信相關(guān)知識(shí)
1.必要了解的概念
a.IPC
IPC是Inner-Process Communication,就是進(jìn)程間通信。
b.AIDL
AUDL是Android Interface Define Language 安卓接口語言縮寫。
c.Binder
Binder是android中負(fù)責(zé)進(jìn)程間通信的驅(qū)動(dòng)類,Binder內(nèi)部設(shè)計(jì)十分復(fù)雜這里我們暫不做深入研究,這里我們只需要了解它是負(fù)責(zé)進(jìn)程間通信的類即可。
d.Proxy代理模式
如果你不是很了解代理模式,可以去這里看看。
Proxy_Pattern
2.WHY?
a. 某些情況下遠(yuǎn)端的服務(wù)更適合運(yùn)算或者更適合執(zhí)行耗時(shí)操作,這時(shí)候我們會(huì)使用aidl請(qǐng)求遠(yuǎn)程服務(wù);
b. android對(duì)單個(gè)應(yīng)用的內(nèi)存限制,當(dāng)有需求需要突破這個(gè)限制的時(shí)候我們需要另啟進(jìn)程擴(kuò)大內(nèi)存。
實(shí)際使用情況還有很多,筆者遇到的情況還不是很多這里就不意義列舉了,反正aidl是一種很有效的IPC通信方式。
3.可能遇到的問題
我們都知道在android中一個(gè)應(yīng)用就對(duì)應(yīng)一個(gè)linux進(jìn)程,或者說默認(rèn)情況下所有的組件都是在同一個(gè)進(jìn)程下的;我們也可以將不同的組件放在不同的進(jìn)程中,詳情請(qǐng)查看我的另外一篇文章Multi_Process_Component,這樣應(yīng)用就不止一個(gè)進(jìn)程了,按照一個(gè)進(jìn)程對(duì)應(yīng)一個(gè)虛擬機(jī),也就是說我們應(yīng)用不止一個(gè)虛擬機(jī)了??赡艹霈F(xiàn)的問題我們來舉個(gè)栗子:
假設(shè)你的代碼里有一個(gè)單例,DemoSingletion;虛擬機(jī)1啟動(dòng)時(shí)創(chuàng)建了這個(gè)單例,你在虛擬機(jī)中任何一個(gè)線程中使用都只有它一個(gè)對(duì)象,線程同步問題可以添加線程鎖解決。那么問題來了,虛擬機(jī)2啟動(dòng)的時(shí)候還會(huì)再創(chuàng)建這個(gè)單例嗎?如果不創(chuàng)建的話和虛擬機(jī)1使用的是同一個(gè)單例嗎?
實(shí)際情況是每個(gè)虛擬機(jī)啟動(dòng)的時(shí)候都會(huì)創(chuàng)建各自的單例,他們是不同的對(duì)象,在不同的地址空間上。那么問題又來了,兩個(gè)虛擬機(jī)操作的是不同的對(duì)象那么這個(gè)DemoSingletion怎么同步呢?無論是添加線程鎖還是對(duì)象鎖我們都無法做到同步,究其原因就是操作的是不同的對(duì)象。這就是多進(jìn)程帶來的問題之一,接下來我們列舉會(huì)出現(xiàn)的問題并說明如何解決這些問題。
- 單例模式完全失效
- 靜態(tài)變量無法同步
4.AIDL的使用
注:由于基于eclipse的adt過于老舊這里不再講解操作,請(qǐng)使用android studio完成以下操作。
· 使用AIDL文件
a.新建aidl文件
在你想要?jiǎng)?chuàng)建aidl的包下新建aidl文件(這里我們命名為IDataManager),aidl文件的語法與java類似,默認(rèn)生成的aidl會(huì)有一個(gè)demo方法
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,double aDouble, String aString);
系統(tǒng)生成的basicTypes這個(gè)demo方法告訴我們能夠傳遞那些類型的數(shù)據(jù)。
b.添加自定義方法
// 無論應(yīng)用的類是否和aidl文件在同一包下,都需要顯示import
import org.github.lion.aidl_demo.Data;
// Declare any non-default types here with import statements
interface IDataManager {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,double aDouble, String aString);
int getDataTypeCount();
List<Data> getData();
String getUrlContent(String url);
}
List<Data> getData()這個(gè)方法中使用了自定義的數(shù)據(jù)類型,雖然我們?cè)谖募_頭寫了import但是還是無法通過編譯,我們需要在sdk的platform下修改framework.aidl,完整路徑如下:~/platforms/android-xx/framework.aidl,加入我們自己添加的類名即可:
// user define aidl parcelable data
parcelable org.github.lion.aidl_demo.Data;
這個(gè)路徑實(shí)際上是系統(tǒng)定義的Parcelable類~/platforms/android-xx/framework.aidl,這里我們不建議修改這個(gè)文件,另一種方式是aidl文件,定義如下:
// Data.aidl Data 類的完整包名為 org.github.lion.aidl_demo.Data,我們定義的aidl文件如下即可。
package org.github.lion.aidl_demo;
parcelable Data;
Data需實(shí)現(xiàn)Parcelable接口。
以下是android studio的默認(rèn)實(shí)現(xiàn)。
/**
* Created by lion on 2016/10/11.
* 要通過Bundle傳遞的數(shù)據(jù)需要實(shí)現(xiàn)Parcelable接口,
* 一旦你實(shí)現(xiàn)了這個(gè)接口android studio會(huì)提示你幫
* 你快速實(shí)現(xiàn)帶有Parcel的構(gòu)造函數(shù)。
*/
public class Data implements Parcelable {
...
protected Data(Parcel in) {
...
}
public static final Creator<Data> CREATOR = new Creator<Data>() {
@Override
public Data createFromParcel(Parcel in) {
return new Data(in);
}
@Override
public Data[] newArray(int size) {
return new Data[size];
}
};
}
c. 編譯aidl文件
到這里aidl的編寫就完成了,我們build下工程,編譯器會(huì)自動(dòng)生成IDataManager.java文件。
該文件在工程的~/app/build/generated/source/aidl/debug/<package>/IDataManager.java,這里我們先不講解生成的這個(gè)類,先看下如何使用aidl。
d. 添加Service類(遠(yuǎn)端服務(wù))
添加一個(gè)Service命名為DataManagerService我們?cè)?code>DataManagerService中實(shí)現(xiàn)一個(gè)靜態(tài)的IDataManager.Stub的類
private static final IDataManager.Stub mBinder = new IDataManager.Stub() {
@Override
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
}
@Override
public int getDataTypeCount() throws RemoteException {
// todo return some data
return 0;
}
@Override
public List<Data> getData() throws RemoteException {
// todo return some data
return null;
}
@Override
public String getUrlContent(String url) throws RemoteException {
// todo return some data
return null;
}
};
在onBind方法中返回這個(gè)Binder,這樣當(dāng)我們調(diào)用Activity的bindService方法的時(shí)候就能返回這個(gè)binder對(duì)象了。
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
e.綁定服務(wù)并測(cè)試夸進(jìn)程通信
在你需要調(diào)用的Activity中添加如下代碼:
/**
* data manager service 的遠(yuǎn)程引用
*/
private IDataManager dataManagerService = null;
/**
* 創(chuàng)建Service Connection用于監(jiān)聽service鏈接與斷開鏈接
*/
private ServiceConnection dataServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
dataManagerService = IDataManager.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
dataManagerService = null;
}
};
當(dāng)你的Activity啟動(dòng)時(shí)綁定遠(yuǎn)程服務(wù)
@Override
protected void onCreate(Bundle savedInstanceState) {
...
bindService(new Intent(this, DataManagerService.class), dataServiceConnection,
Context.BIND_AUTO_CREATE);
}
接下來我們編寫測(cè)試代碼,在button的回調(diào)函數(shù)中我們編寫如下測(cè)試代碼:
public void callService(View view) {
try {
System.out.println(dataManagerService.getDataTypeCount());
StringBuilder sb = new StringBuilder();
for (Data data : dataManagerService.getData()) {
System.out.println(data.toString());
sb.append(data.toString()).append("\n");
}
textData.setText(sb.toString());
new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println(dataManagerService.getUrlContent("http://www.baidu.com"));
} catch (RemoteException e) {
e.printStackTrace();
}
}
}).start();
} catch (RemoteException e) {
e.printStackTrace();
}
}
f.運(yùn)行查看結(jié)果
·自己實(shí)現(xiàn)Binder
上面我們展示了如何使用AIDL文件實(shí)現(xiàn)進(jìn)程間通信,為了能夠更好的理解進(jìn)程間通信機(jī)制接下來將會(huì)展示如何手動(dòng)編寫一個(gè)Binder實(shí)現(xiàn)IPC。
aidl生成類分析
將Android Studio切換到項(xiàng)目視圖,找到如下文件:
我們將這個(gè)接口文件簡(jiǎn)化以下,看看系統(tǒng)多給我們做了些什么。
public interface IDataManager extends android.os.IInterface {
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements org.github.lion.aidl_demo.IDataManager {
private static class Proxy implements org.github.lion.aidl_demo.IDataManager {}
}
}
IDataManager
這個(gè)是我們定義的aidl接口,這個(gè)接口里面就要定義我們需要的要成服務(wù)能力的接口;
IDataManager.Stub
這個(gè)是一個(gè)繼承自Binder并且實(shí)現(xiàn)了IDataManager的抽象類;
IDataManager.Stub.Proxy
這個(gè)是一個(gè)私有內(nèi)部類,實(shí)現(xiàn)了IDataManager;
我們知道Binder是Android中的IPC通信驅(qū)動(dòng),從類結(jié)構(gòu)我們就可以看出最終的實(shí)際功能類是IDataManager.Stub.Proxy。具體的類方法我們暫時(shí)不做分析,接下來我們不使用aidl文件自己實(shí)現(xiàn)一個(gè)Binder驅(qū)動(dòng)類,寫的過程中我們細(xì)細(xì)來分析各個(gè)函數(shù)的功能。
5.自己實(shí)現(xiàn)Binder驅(qū)動(dòng)IPC通信
定義公共接口
從上面aidl生成的類我們看出需要實(shí)現(xiàn)IPC通信需要實(shí)現(xiàn)IInterface接口,并且繼承Binder類從中間驅(qū)動(dòng)。所以首先我們先定義公共接口繼承IInterface接口。
//IDataManager.java
public interface IDataManager2 extends IInterface {
// 返回值為基本數(shù)據(jù)類型,定義接口時(shí)不需要做特殊處理
int getDataCount() throws RemoteException;
// 自定義的返回?cái)?shù)據(jù)類型需要實(shí)現(xiàn)Parcelable接口,進(jìn)程間通信不能直接共享內(nèi)存,需要將對(duì)象持久化。
// 所以自定義的類需要實(shí)現(xiàn)Parcelable接口
List<Data2> getData() throws RemoteException;
}
/**
* Data2.java
* Created by lion on 2016/10/11.
* 要通過Bundle傳遞的數(shù)據(jù)需要實(shí)現(xiàn)Parcelable接口,
* 一旦你實(shí)現(xiàn)了這個(gè)接口android studio會(huì)提示你幫
* 你快速實(shí)現(xiàn)帶有Parcel的構(gòu)造函數(shù)。
*/
public class Data2 implements Parcelable {
private int id;
private String content;
public Data2() {
}
protected Data2(Parcel in) {
id = in.readInt();
content = in.readString();
}
public static final Creator<Data2> CREATOR = new Creator<Data2>() {
@Override
public Data2 createFromParcel(Parcel in) {
return new Data2(in);
}
@Override
public Data2[] newArray(int size) {
return new Data2[size];
}
};
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(id);
dest.writeString(content);
}
@Override
public String toString() {
return "id = " + id + " content = " + content;
}
}
繼承Binder并實(shí)現(xiàn)IDataManager2接口的類作為Binder的本體。
為了讓代碼邏輯更加清晰,這回我們的Binder類不再寫成內(nèi)部類。
public abstract class DataManagerNative extends Binder implements IDataManager2 {
// Binder描述符,唯一標(biāo)識(shí)符
private static final String DESCRIPTOR = "com.github.onlynight.aidl_demo2.aidl.IDataManager2";
// 每個(gè)方法對(duì)應(yīng)的ID
private static final int TRANSACTION_getDataCount = IBinder.FIRST_CALL_TRANSACTION;
private static final int TRANSACTION_getData = IBinder.FIRST_CALL_TRANSACTION + 1;
public DataManagerNative() {
attachInterface(this, DESCRIPTOR);
}
/**
* 將Binder轉(zhuǎn)化為IInterface接口
*
* @param binder
* @return
*/
public static IDataManager2 asInterface(IBinder binder) {
if (binder == null) {
return null;
}
//同一進(jìn)程內(nèi)直接返回
IInterface iin = binder.queryLocalInterface(DESCRIPTOR);
if ((iin != null) && (iin instanceof IDataManager2)) {
return (IDataManager2) iin;
}
//不在同一進(jìn)程使用代理獲取遠(yuǎn)程服務(wù)
return new Proxy(binder);
}
@Override
public IBinder asBinder() {
return this;
}
/**
* 我們查看Binder的源碼就可以看出實(shí)際上transact方法真正的執(zhí)行體
* 是這個(gè)onTransact方法。
*
* @param code 服務(wù)器回掉的方法ID,每一個(gè)方法都有一個(gè)唯一id,
* 這樣方法回調(diào)時(shí)可通過id判斷回調(diào)的方法。
* @param data 輸入的參數(shù),傳遞給服務(wù)端的參數(shù)
* @param reply 輸出的參數(shù),服務(wù)器返回的數(shù)據(jù)
* @param flags 默認(rèn)傳入0
* @return
* @throws RemoteException 遠(yuǎn)端服務(wù)器無響應(yīng)拋出該錯(cuò)誤。
*/
@Override
protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
switch (code) {
case TRANSACTION_getDataCount: {
data.enforceInterface(DESCRIPTOR);
int _result = this.getDataCount();
reply.writeNoException();
reply.writeInt(_result);
return true;
}
case TRANSACTION_getData: {
data.enforceInterface(DESCRIPTOR);
List<Data2> _result = this.getData();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
/**
* 代理類,調(diào)用transact方法。
*/
private static class Proxy implements IDataManager2 {
private IBinder remote;
Proxy(IBinder remote) {
this.remote = remote;
}
public String getInterfaceDescriptor() {
return DESCRIPTOR;
}
@Override
public int getDataCount() throws RemoteException {
// 輸入?yún)?shù)
Parcel _data = Parcel.obtain();
//輸出參數(shù)
Parcel _reply = Parcel.obtain();
int _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
remote.transact(TRANSACTION_getDataCount, _data, _reply, 0);
_reply.readException();
_result = _reply.readInt();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override
public List<Data2> getData() throws RemoteException {
Parcel _data = Parcel.obtain();
Parcel _reply = Parcel.obtain();
List<Data2> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
remote.transact(TRANSACTION_getData, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(Data2.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override
public IBinder asBinder() {
return remote;
}
}
}
DataManagerNative.DESCRIPTER
Binder描述符,唯一標(biāo)識(shí)符,服務(wù)端和客戶端都可以通過該ID定位到Binder實(shí)例。
DataManagerNative.TRANSACTION_XXX
自定義的IInterface方法的唯一標(biāo)識(shí)符。
DataManagerNative.asInterface
將Binder轉(zhuǎn)換為IInterface就可以直接調(diào)用我們自己定義的方法啦。
DataManagerNative.onTransact
根據(jù)不同的TRANSACTION_ID調(diào)用調(diào)用不同的方法。
DataManagerNative.Proxy
代理遠(yuǎn)端Binder,對(duì)外提供IDataManager2的功能。
DataManagerNative.Proxy.transact
想調(diào)用遠(yuǎn)端Binder的transact方法。
可以看到DataManagerNative是個(gè)抽象類,并沒有實(shí)現(xiàn)IDataManager2中的方法。所以我們需要在實(shí)例化這個(gè)類的時(shí)候?qū)崿F(xiàn)這些方法,這些操作都放到Service中去完成。
Service中Binder的實(shí)現(xiàn)我們和上一次使用同樣的代碼。
public class DataManagerService extends Service {
private static List<Data2> data = new ArrayList<>();
static {
Data2 data1 = new Data2();
data1.setId(1);
data1.setContent("data1");
data.add(data1);
Data2 data2 = new Data2();
data2.setId(2);
data2.setContent("data2");
data.add(data2);
Data2 data3 = new Data2();
data3.setId(3);
data3.setContent("data3");
data.add(data3);
Data2 data4 = new Data2();
data4.setId(4);
data4.setContent("data4");
data.add(data4);
Data2 data5 = new Data2();
data5.setId(5);
data5.setContent("data5");
data.add(data5);
}
// 可以看到我們?cè)谶@里實(shí)現(xiàn)了這個(gè)Binder,這里才是這個(gè)Binder的本體。
private static DataManagerNative binder = new DataManagerNative() {
@Override
public int getDataCount() throws RemoteException {
return data.size();
}
@Override
public List<Data2> getData() throws RemoteException {
return data;
}
};
public DataManagerService() {
}
@Override
public IBinder onBind(Intent intent) {
return binder;
}
}
實(shí)際上我們自己修改過的類和編譯器自動(dòng)生成的類基本上是一樣的,這里為了讓大家有更加深刻的認(rèn)識(shí),我們將其手動(dòng)實(shí)現(xiàn)一次。
6.AIDL原理分析
相信經(jīng)過以上的分析大家應(yīng)該有個(gè)大概的認(rèn)識(shí)了,但是要?jiǎng)悠鹗謥響?yīng)該會(huì)有很多地方卡住,接下來我們來個(gè)全面的分析,理清楚Binder的機(jī)制。
·運(yùn)行原理圖
首先我們先來看下原理圖,讓大家有個(gè)感性的認(rèn)識(shí):
調(diào)用順序是這樣:
Client->operate()->transact()->onTransact()->operation()->Server
我們能看到的源碼執(zhí)行順序就是這樣的,由于Binder內(nèi)部結(jié)構(gòu)很復(fù)雜,Binder內(nèi)部的如何進(jìn)行數(shù)據(jù)交換如何定位服務(wù)端方法我們這里不再介紹,感興趣的朋友可以查看Android源碼。
有幾個(gè)比較有趣的地方我們單獨(dú)拿出來說說。
首先是transact方法
public int getDataCount() throws RemoteException {
// 輸入?yún)?shù)
Parcel _data = Parcel.obtain();
//輸出參數(shù)
Parcel _reply = Parcel.obtain();
int _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
remote.transact(TRANSACTION_getDataCount, _data, _reply, 0);
_reply.readException();
_result = _reply.readInt();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
其中_data是調(diào)用函數(shù)傳入的參數(shù),_reply是調(diào)用函數(shù)返回的結(jié)果。通過形參的方式返回在java中不常見,這里需要理解下,_reply即是函數(shù)執(zhí)行完將結(jié)果賦值到這個(gè)引用中。我們只需要按照順序read其中的結(jié)果即可_reply.readInt()。
我們?cè)诳聪?code>transact方法的源碼
/**
* Default implementation rewinds the parcels and calls onTransact. On
* the remote side, transact calls into the binder to do the IPC.
*/
public final boolean transact(int code, Parcel data, Parcel reply,
int flags) throws RemoteException {
if (false) Log.v("Binder", "Transact: " + code + " to " + this);
if (data != null) {
data.setDataPosition(0);
}
boolean r = onTransact(code, data, reply, flags);
if (reply != null) {
reply.setDataPosition(0);
}
return r;
}
很明顯onTransact才是真正的方法執(zhí)行體,而onTransact方法調(diào)用了實(shí)現(xiàn)IDataManager2接口的類的實(shí)現(xiàn)方法;注意上面大的例子看起來稍微有些復(fù)雜,DataManagerNative是個(gè)抽象類它并沒有實(shí)現(xiàn)IDataManager2中的方法,真正實(shí)現(xiàn)這些方法的DataManagerNative的實(shí)例在DataManagerService中,再結(jié)合上面的原理圖,相信你現(xiàn)在已經(jīng)很了解AIDL的通信機(jī)制了吧。
以上都是個(gè)人理解進(jìn)行的分析,如果哪里有問題歡迎指出,最后希望這篇文章能夠幫到你。