從AIDL看Android跨程式通訊
AIDL是Android實現IPC的一種重要的方式,理解它的原理對理解Android程式間通訊有很大的幫助。AIDL的定義,已經有很多介紹的文章了,這裡就不做詳解了。我們直接從例項入手來分析AIDL實現原理。
AIDL的使用
首先需要定義AIDL介面IMyService.aidl:
// IMyService.aidl
package com.chuck.aidldemo;
// Declare any non-default types here with import statements
interface IMyService {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
String getValue();
}
定義了getValue()方法,返回String。我們知道定義好aidl檔案後,IDE在編譯專案時會自動幫我們生成一個檔案IMyService.java檔案,稍後會詳細介紹。
接下來需要定義一個service,這裡定義MyService.java
public class MyService extends Service {
@Override
public void onCreate() {
super.onCreate();
Log.e("myService","onCreate" );
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return ims;
}
IMyService.Stub ims=new IMyService.Stub() {
@Override
public String getValue() throws RemoteException {
return "hello AIDL";
}
};
@Override
public void onDestroy() {
super.onDestroy();
Log.e("myService","onDestroy");
}
}
重寫onBind方法,返回IMyService.Stub物件ims,IMyService.Stub是定義在IMyService.java中,其實現了IBinder,所以這裡可以在OnBinder中作為返回物件。同時在AIDL中定義getValue方法的真正實現,就是在這裡。我們僅僅是返回一個”hello AIDL”字串。
為了實現跨程式,我們還需要在AndroidMenifast.xml檔案中設定process=”:Remote”,這樣service就和client不在同一個程式中了:
<service
android:name=".MyService"
android:process=":Remote" />
最後在定義client,為了方便我們就直接在MainActivity.java實現了:
public class MainActivity extends AppCompatActivity {
private IMyService ims;
private String text;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void onClick(View view) {
if (view.getId() == R.id.btn_bind) {
Intent intent = new Intent(this, MyService.class);
bindService(intent, sc, BIND_AUTO_CREATE);
}else if (view.getId()==R.id.btn_unbind){
unbindService(sc);
}
}
private ServiceConnection sc = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
ims = IMyService.Stub.asInterface(service);
try {
text = ims.getValue();
Log.e("text",text);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
}
這和平時bindService使用是一樣的,需要一個ServiceConnection例項,將該例項作為bindService引數傳入。這樣client就可以啟動並繫結MyService了。
一般我們使用AIDL的基本步驟就是這些,現在我們需要著重分析自動生成的IMyService.java了,如果不知道檔案位置,直接在IDE搜尋一下就可以了。先把程式碼貼出來:
/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: /Users/chenkai/Android/codeExcise/aidl/AidlDemo/app/src/main/aidl/com/chuck/aidldemo/IMyService.aidl
*/
package com.chuck.aidldemo;
// Declare any non-default types here with import statements
public interface IMyService extends android.os.IInterface {
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements com.chuck.aidldemo.IMyService {
private static final java.lang.String DESCRIPTOR = "com.chuck.aidldemo.IMyService";
/**
* Construct the stub at attach it to the interface.
*/
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.chuck.aidldemo.IMyService interface,
* generating a proxy if needed.
*/
public static com.chuck.aidldemo.IMyService asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.chuck.aidldemo.IMyService))) {
return ((com.chuck.aidldemo.IMyService) iin);
}
return new com.chuck.aidldemo.IMyService.Stub.Proxy(obj);
}
@Override
public android.os.IBinder asBinder() {
return this;
}
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_getValue: {
data.enforceInterface(DESCRIPTOR);
java.lang.String _result = this.getValue();
reply.writeNoException();
reply.writeString(_result);
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.chuck.aidldemo.IMyService {
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote) {
mRemote = remote;
}
@Override
public android.os.IBinder asBinder() {
return mRemote;
}
public java.lang.String getInterfaceDescriptor() {
return DESCRIPTOR;
}
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
@Override
public java.lang.String getValue() throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.lang.String _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getValue, _data, _reply, 0);
_reply.readException();
_result = _reply.readString();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
static final int TRANSACTION_getValue = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
}
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
public java.lang.String getValue() throws android.os.RemoteException;
}
程式碼結構很清晰,先來看一下類圖:
1.IInterface,我們定義的Binder介面都需要繼承自它。
2.我們定義的IMyService介面繼承自介面IInterface。
3.IBinder 遠端物件的介面。
4.Binder實現了IBinder,Android通過Binder進行IPC
5.BinderProxy是在定義在Binder.java中的,也是實現了IBinder介面,這裡只要知道它是Binder的代理就可以了。我們Client中拿到的就是這個類的物件,之後會有講。
6.IMyService.Stub繼承自Binder並且實現了IMyService。也就是說Stub其實是個Binder。還記得嗎?在前文MyService中我們生成了一個Stub的例項ims,Stub的本意是存根,這裡的用意就是Service的存根,通過它,我們在Service端就擁有了一個Binder。這樣我們就可以通過底層的Binder驅動進行跨程式通訊了。
7.IMyService.Proxy,顧名思義它是一個代理,主要是實現了IMyService介面,客戶端通過它發起遠端請求。
簡單的介紹了涉及到的類,下面來分析一下請求的流程。
我們知道,在client bindService(這是一個比較複雜的過程,涉及到AMS,本身也是一個跨程式的通訊)之後,如果遠端服務,這裡是MyService,處理完請求之後,通過一系列複雜的操作,client中的onServiceConnected方法將會回撥,為了方便我把前邊相關的部分程式碼放在這:
private ServiceConnection sc = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
ims = IMyService.Stub.asInterface(service);
try {
text = ims.getValue();
Log.e("text",text);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
第3行當回撥onServiceConnected時,會傳過來一個IBinder物件service,client就是需要通過它來發起遠端請求的,那麼service的具體實現到底是Binder還是BinderProxy呢,如果對Binder的機制比較清楚的都會知道其實是BinderProxy的,為了驗證這個我們可以除錯一下:
我們得到BinderProxy後會將其傳給IMyService.Stub.asInterface(service),asInterface接受一個IBinder物件作為引數,這裡就是剛才說的BinderProxy物件。具體看看asInterface做了什麼:
public static com.chuck.aidldemo.IMyService asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.chuck.aidldemo.IMyService))) {
return ((com.chuck.aidldemo.IMyService) iin);
}
return new com.chuck.aidldemo.IMyService.Stub.Proxy(obj);
}
看第5行,因為我們設定了MyService的process屬性,也就是說他和client是不在同一個程式的,所以obj.queryLocalInterface(DESCRIPTOR)這個方法返回為空,也就是iin為null,那麼將會執行第9行程式碼,對沒錯new了一個IMyService.Proxy物件。回到前邊onServiceConnected方法中第4行ims就是剛剛生成的IMyService.Proxy物件。第6行ims.getValue()方法執行的就是IMyService.Proxy中的getValue方法:
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
@Override
public java.lang.String getValue() throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.lang.String _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getValue, _data, _reply, 0);
_reply.readException();
_result = _reply.readString();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
第7.8行_data, _reply分別是進行遠端呼叫時的的請求和相應引數。第11行將DESCRIPTOR = “com.chuck.aidldemo.IMyService”作為Token寫入 _data.第12行mRemote.transact(Stub.TRANSACTION_getValue, _data, _reply, 0);mRemote是在IMyService.Proxy構造方法中被賦值的,也就是我們在new IMyService.Proxy物件時傳進來的BinderProxy物件。所以mRemote.transact(Stub.TRANSACTION_getValue, _data, _reply, 0)的實現應該是在BinderProxy中,這裡需要注意一下第一個引數Stub.TRANSACTION_getValue,他是標識需要呼叫方法的code,在後邊會有用到。我們看看他的原始碼:
public boolean transact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
Binder.checkParcel(this, code, data, "Unreasonably large binder buffer");
return transactNative(code, data, reply, flags);
}
最終會呼叫transactNative()方法,這是一個native方法,真正的實現是用c實現,他是實現在Android原始碼對應/frameworks/base/core/jni/android_util_Binder.cpp中的static jboolean android_os_BinderProxy_transact函式,將java層的transact轉換成c層transact,有興趣的可以去研究一下。
通過底層Binder的一系列操作,最終會回撥到遠端Stub的onTransact方法,至於怎麼呼叫的,需要去了解Binder機制。這裡只要知道會回撥onTransact()方法就好了。不過還是要注意的是,這裡的Stub是在遠端服務端的,他和client不是在同一個程式中的。他是在遠端服務的Binder執行緒池中。跟進去看onTransact()方法做了什麼處理:
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_getValue: {
data.enforceInterface(DESCRIPTOR);
java.lang.String _result = this.getValue();
reply.writeNoException();
reply.writeString(_result);
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
還記得在transact方法時提到的第一個引數Stub.TRANSACTION_getValue嗎,它就是這裡onTransact的第一個引數code。所以會執行8-13行程式碼,第10行java.lang.String _result = this.getValue();很關鍵這個this是Stub,還記得我們在哪兒有生成它的例項嗎?沒錯就是在MyService中,我們把它作為onBind方法的返回引數。所以這裡的this.getValue就是MyService中我們寫的那個getValue方法,會返回一個”hello AIDL”的字串。並把它賦值給_result。在第12行將其寫入_reply中。最後在通過Binder的一系列操作,我們將在client中得到這個字串。
總的來說AIDL本身使用比較簡單,理解起來也比較簡單,但是其內部的Binder機制還是比較複雜的,我們先理解Java層的AIDL對學習Binder也是有幫助的。並且Messager通訊是基於AIDL的,理解了AIDL也就理解了Messager。
相關文章
- 4-AIII–Service跨程式通訊:aidlAI
- Android程式間通訊,AIDL工作原理AndroidAI
- Android跨程式通訊Android
- [Android]你不知道的Android程式化(4)--程式通訊AIDL框架AndroidAI框架
- Aidl程式間通訊詳細介紹AI
- android AIDL程式間通訊(只介紹了簡單資料型別)AndroidAI資料型別
- 使用AIDL實現跨程式介面回掉AI
- Webview獨立程式並通過AIDL實現資料通訊WebViewAI
- Android AIDL原理AndroidAI
- 一行程式碼實現Android的跨程式呼叫與通訊行程Android
- Android 多程式通訊Android
- Android IPC 之AIDLAndroidAI
- Android 程式之間通訊Android
- [Android]程式通訊Andromeda框架Android框架
- Android AIDL使用詳解AndroidAI
- Android程式間通訊詳解Android
- 【React】元件通訊 - 跨層通訊React元件
- NEO從原始碼分析看網路通訊原始碼
- 詳解 CmProcess 跨程式通訊的實現
- 跨源通訊、跨域訪問跨域
- Windows程式通訊之一看就懂的匿名管道通訊Windows
- Android點將臺:金科玉律[-AIDL-]AndroidAI
- 藉助 AIDL 理解 Android Binder 機制——AIDL 的使用和原理分析AIAndroid
- Android程式間通訊(複習筆記)Android筆記
- 從PHP客戶端看MongoDB通訊協議TDPHP客戶端MongoDB協議
- Android Socket 通訊Android
- [Android]你不知道的Android程式化(6)--程式通訊Andromeda框架Android框架
- [Android]你不知道的Android程式化(5)--程式通訊Messenger框架AndroidMessenger框架
- Android 元件化之通訊(多模組,多程式)Android元件化
- Android多程式通訊之幾個基本問題Android
- Android 多程式通訊之幾個基本問題Android
- Android 串列埠通訊Android串列埠
- 不同頁面通訊與跨域跨域
- 瀏覽器跨標籤通訊瀏覽器
- 從原始碼看 Android 事件分發原始碼Android事件
- Android 11(R) Power HAL AIDL簡析 -- 基本介面AndroidAI
- 從Android到ReactNative開發(二、通訊與模組實現)AndroidReact
- 阿里Android開發規範:程式、執行緒與訊息通訊阿里Android執行緒
- 【rosbridge】ROS與Android通訊ROSAndroid