AIDL使用以及IPC原理分析(進程間通信)
概要
為了大家能夠更好的理解android的進程間通信原理,以下將會從以下幾個方面講解跨進程通訊信:
必要了解的概念
為什么要使用aidl進程間通信
可能遇到的問題以及解決辦法
aidl的使用,通過android提供的aidl實現(xiàn)一個進程間通信
不使用aidl,手動編寫B(tài)inder實現(xiàn)進程間通信
分析aidl的原理,梳理andriod進程間通信相關(guān)知識
1.必要了解的概念
a.IPC
IPC是Inner-Process Communication,就是進程間通信。
b.AIDL
AUDL是Android Interface Define Language 安卓接口語言縮寫。
c.Binder
Binder是android中負責進程間通信的驅(qū)動類,Binder內(nèi)部設(shè)計十分復雜這里我們暫不做深入研究,這里我們只需要了解它是負責進程間通信的類即可。
d.Proxy代理模式
如果你不是很了解代理模式,可以去這里看看。
Proxy_Pattern
2.WHY?
a. 某些情況下遠端的服務(wù)更適合運算或者更適合執(zhí)行耗時操作,這時候我們會使用aidl請求遠程服務(wù);
b. android對單個應(yīng)用的內(nèi)存限制,當有需求需要突破這個限制的時候我們需要另啟進程擴大內(nèi)存。
實際使用情況還有很多,筆者遇到的情況還不是很多這里就不意義列舉了,反正aidl是一種很有效的IPC通信方式。
3.可能遇到的問題
我們都知道在android中一個應(yīng)用就對應(yīng)一個linux進程,或者說默認情況下所有的組件都是在同一個進程下的;我們也可以將不同的組件放在不同的進程中,這樣應(yīng)用就不止一個進程了,按照一個進程對應(yīng)一個虛擬機,也就是說我們應(yīng)用不止一個虛擬機了??赡艹霈F(xiàn)的問題我們來舉個栗子:
假設(shè)你的代碼里有一個單例,DemoSingletion;虛擬機1啟動時創(chuàng)建了這個單例,你在虛擬機中任何一個線程中使用都只有它一個對象,線程同步問題可以添加線程鎖解決。那么問題來了,虛擬機2啟動的時候還會再創(chuàng)建這個單例嗎?如果不創(chuàng)建的話和虛擬機1使用的是同一個單例嗎?
實際情況是每個虛擬機啟動的時候都會創(chuàng)建各自的單例,他們是不同的對象,在不同的地址空間上。那么問題又來了,兩個虛擬機操作的是不同的對象那么這個DemoSingletion怎么同步呢?無論是添加線程鎖還是對象鎖我們都無法做到同步,究其原因就是操作的是不同的對象。這就是多進程帶來的問題之一,接下來我們列舉會出現(xiàn)的問題并說明如何解決這些問題。
- 單例模式完全失效
- 靜態(tài)變量無法同步
4.AIDL的使用
注:由于基于eclipse的adt過于老舊這里不再講解操作,請使用android studio完成以下操作。
· 使用AIDL文件
a.新建aidl文件
在你想要創(chuàng)建aidl的包下新建aidl文件(這里我們命名為IDataManager),aidl文件的語法與java類似,默認生成的aidl會有一個demo方法
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,double aDouble, String aString);
系統(tǒng)生成的basicTypes這個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()這個方法中使用了自定義的數(shù)據(jù)類型,雖然我們在文件開頭寫了import但是還是無法通過編譯,我們需要在sdk的platform下修改framework.aidl,完整路徑如下:~/platforms/android-xx/framework.aidl,加入我們自己添加的類名即可:
// user define aidl parcelable data
parcelable org.github.lion.aidl_demo.Data;
這個路徑實際上是系統(tǒng)定義的Parcelable類~/platforms/android-xx/framework.aidl,這里我們不建議修改這個文件,另一種方式是aidl文件,定義如下:
// Data.aidl Data 類的完整包名為 org.github.lion.aidl_demo.Data,我們定義的aidl文件如下即可。
package org.github.lion.aidl_demo;
parcelable Data;
Data需實現(xiàn)Parcelable接口。
以下是android studio的默認實現(xiàn)。
/**
* Created by lion on 2016/10/11.
* 要通過Bundle傳遞的數(shù)據(jù)需要實現(xiàn)Parcelable接口,
* 一旦你實現(xiàn)了這個接口android studio會提示你幫
* 你快速實現(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下工程,編譯器會自動生成IDataManager.java文件。
該文件在工程的~/app/build/generated/source/aidl/debug/<package>/IDataManager.java,這里我們先不講解生成的這個類,先看下如何使用aidl。
d. 添加Service類(遠端服務(wù))
添加一個Service命名為DataManagerService我們在DataManagerService中實現(xiàn)一個靜態(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方法中返回這個Binder,這樣當我們調(diào)用Activity的bindService方法的時候就能返回這個binder對象了。
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
e.綁定服務(wù)并測試夸進程通信
在你需要調(diào)用的Activity中添加如下代碼:
/**
* data manager service 的遠程引用
*/
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;
}
};
當你的Activity啟動時綁定遠程服務(wù)
@Override
protected void onCreate(Bundle savedInstanceState) {
...
bindService(new Intent(this, DataManagerService.class), dataServiceConnection,
Context.BIND_AUTO_CREATE);
}
接下來我們編寫測試代碼,在button的回調(diào)函數(shù)中我們編寫如下測試代碼:
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.運行查看結(jié)果
·自己實現(xiàn)Binder
上面我們展示了如何使用AIDL文件實現(xiàn)進程間通信,為了能夠更好的理解進程間通信機制接下來將會展示如何手動編寫一個Binder實現(xiàn)IPC。
aidl生成類分析
將Android Studio切換到項目視圖,找到如下文件:
我們將這個接口文件簡化以下,看看系統(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
這個是我們定義的aidl接口,這個接口里面就要定義我們需要的要成服務(wù)能力的接口;
IDataManager.Stub
這個是一個繼承自Binder并且實現(xiàn)了IDataManager的抽象類;
IDataManager.Stub.Proxy
這個是一個私有內(nèi)部類,實現(xiàn)了IDataManager;
我們知道Binder是Android中的IPC通信驅(qū)動,從類結(jié)構(gòu)我們就可以看出最終的實際功能類是IDataManager.Stub.Proxy。具體的類方法我們暫時不做分析,接下來我們不使用aidl文件自己實現(xiàn)一個Binder驅(qū)動類,寫的過程中我們細細來分析各個函數(shù)的功能。
5.自己實現(xiàn)Binder驅(qū)動IPC通信
定義公共接口
從上面aidl生成的類我們看出需要實現(xiàn)IPC通信需要實現(xiàn)IInterface接口,并且繼承Binder類從中間驅(qū)動。所以首先我們先定義公共接口繼承IInterface接口。
//IDataManager.java
public interface IDataManager2 extends IInterface {
// 返回值為基本數(shù)據(jù)類型,定義接口時不需要做特殊處理
int getDataCount() throws RemoteException;
// 自定義的返回數(shù)據(jù)類型需要實現(xiàn)Parcelable接口,進程間通信不能直接共享內(nèi)存,需要將對象持久化。
// 所以自定義的類需要實現(xiàn)Parcelable接口
List<Data2> getData() throws RemoteException;
}
/**
* Data2.java
* Created by lion on 2016/10/11.
* 要通過Bundle傳遞的數(shù)據(jù)需要實現(xiàn)Parcelable接口,
* 一旦你實現(xiàn)了這個接口android studio會提示你幫
* 你快速實現(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并實現(xiàn)IDataManager2接口的類作為Binder的本體。
為了讓代碼邏輯更加清晰,這回我們的Binder類不再寫成內(nèi)部類。
public abstract class DataManagerNative extends Binder implements IDataManager2 {
// Binder描述符,唯一標識符
private static final String DESCRIPTOR = "com.github.onlynight.aidl_demo2.aidl.IDataManager2";
// 每個方法對應(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;
}
//同一進程內(nèi)直接返回
IInterface iin = binder.queryLocalInterface(DESCRIPTOR);
if ((iin != null) && (iin instanceof IDataManager2)) {
return (IDataManager2) iin;
}
//不在同一進程使用代理獲取遠程服務(wù)
return new Proxy(binder);
}
@Override
public IBinder asBinder() {
return this;
}
/**
* 我們查看Binder的源碼就可以看出實際上transact方法真正的執(zhí)行體
* 是這個onTransact方法。
*
* @param code 服務(wù)器回掉的方法ID,每一個方法都有一個唯一id,
* 這樣方法回調(diào)時可通過id判斷回調(diào)的方法。
* @param data 輸入的參數(shù),傳遞給服務(wù)端的參數(shù)
* @param reply 輸出的參數(shù),服務(wù)器返回的數(shù)據(jù)
* @param flags 默認傳入0
* @return
* @throws RemoteException 遠端服務(wù)器無響應(yīng)拋出該錯誤。
*/
@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描述符,唯一標識符,服務(wù)端和客戶端都可以通過該ID定位到Binder實例。
DataManagerNative.TRANSACTION_XXX
自定義的IInterface方法的唯一標識符。
DataManagerNative.asInterface
將Binder轉(zhuǎn)換為IInterface就可以直接調(diào)用我們自己定義的方法啦。
DataManagerNative.onTransact
根據(jù)不同的TRANSACTION_ID調(diào)用調(diào)用不同的方法。
DataManagerNative.Proxy
代理遠端Binder,對外提供IDataManager2的功能。
DataManagerNative.Proxy.transact
想調(diào)用遠端Binder的transact方法。
可以看到DataManagerNative是個抽象類,并沒有實現(xiàn)IDataManager2中的方法。所以我們需要在實例化這個類的時候?qū)崿F(xiàn)這些方法,這些操作都放到Service中去完成。
Service中Binder的實現(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);
}
// 可以看到我們在這里實現(xiàn)了這個Binder,這里才是這個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;
}
}
實際上我們自己修改過的類和編譯器自動生成的類基本上是一樣的,這里為了讓大家有更加深刻的認識,我們將其手動實現(xiàn)一次。
6.AIDL原理分析
相信經(jīng)過以上的分析大家應(yīng)該有個大概的認識了,但是要動起手來應(yīng)該會有很多地方卡住,接下來我們來個全面的分析,理清楚Binder的機制。
·運行原理圖
首先我們先來看下原理圖,讓大家有個感性的認識:
調(diào)用順序是這樣:Client->operate()->transact()->onTransact()->operation()->Server
我們能看到的源碼執(zhí)行順序就是這樣的,由于Binder內(nèi)部結(jié)構(gòu)很復雜,Binder內(nèi)部的如何進行數(shù)據(jù)交換如何定位服務(wù)端方法我們這里不再介紹,感興趣的朋友可以查看Android源碼。
有幾個比較有趣的地方我們單獨拿出來說說。
首先是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é)果賦值到這個引用中。我們只需要按照順序read其中的結(jié)果即可_reply.readInt()。
我們在看下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)用了實現(xiàn)IDataManager2接口的類的實現(xiàn)方法;注意上面大的例子看起來稍微有些復雜,DataManagerNative是個抽象類它并沒有實現(xiàn)IDataManager2中的方法,真正實現(xiàn)這些方法的DataManagerNative的實例在DataManagerService中,再結(jié)合上面的原理圖,相信你現(xiàn)在已經(jīng)很了解AIDL的通信機制了吧。