概述
最近在學習Replugin源碼時,遇到了其中的多進程部分。由于太久沒使用,有點生疏,剛好重拾總結下。
AIDL是一個縮寫,全稱是Android Interface Definition Language,也就是Android接口定義語言。設計這門語言的目的是為了實現進程間通信。當然對于上層間應用想實現簡單的進程間通信還可以使用Message,當然底層還是通過aidl實現,但操作起來會簡單挺多,參見 Android中的Service:Binder,Messenger,AIDL(2)
源碼分析
編寫如下的BookManager.aidl
文件
interface BookManager {
//所有的返回值前都不需要加任何東西,不管是什么數據類型
// 帶有特定parcelable的情況會涉及到tag及序列化和反序列化,但總的原理一樣,本文只是為了通過源碼復習原理,故簡單處理
String getName();
void setName(String name);
}
gradle編譯時通過compileXXXXAidl
task后會在工程build\generated\source\aidl\{FlavorName}\{BuildType}\{package}
下生成對一個BookManager.java
文件如下:
public interface BookManager extends android.os.IInterface
{
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.liyuange.liyuange.BookManager
{
private static final java.lang.String DESCRIPTOR = "com.liyuange.liyuange.BookManager";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.liyuange.liyuange.BookManager interface,
* generating a proxy if needed.
*/
public static com.liyuange.liyuange.BookManager asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.liyuange.liyuange.BookManager))) {
return ((com.liyuange.liyuange.BookManager)iin);
}
return new com.liyuange.liyuange.BookManager.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_getName:
{
data.enforceInterface(DESCRIPTOR);
java.lang.String _result = this.getName();
reply.writeNoException();
reply.writeString(_result);
return true;
}
case TRANSACTION_setName:
{
data.enforceInterface(DESCRIPTOR);
java.lang.String _arg0;
_arg0 = data.readString();
this.setName(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.liyuange.liyuange.BookManager
{
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;
}
//所有的返回值前都不需要加任何東西,不管是什么數據類型
@Override public java.lang.String getName() 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_getName, _data, _reply, 0);
_reply.readException();
_result = _reply.readString();
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override public void setName(java.lang.String name) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeString(name);
mRemote.transact(Stub.TRANSACTION_setName, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
}
static final int TRANSACTION_getName = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_setName = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
//所有的返回值前都不需要加任何東西,不管是什么數據類型
public java.lang.String getName() throws android.os.RemoteException;
public void setName(java.lang.String name) throws android.os.RemoteException;
}
生成了很多模板性代碼,其實真正實行多進程通信的就是這個生成的java文件。就算我們不寫AIDL文件,直接按照它生成的 .java 文件那樣寫一個 .java 文件出來,在服務端和客戶端中也可以照常使用這個 .java 類來進行跨進程通信,所以其實aidl可以理解為,只是幫android開發者節省了寫模板性代碼而已。
接下來我們分別從服務端和客戶端看下是怎么利用這個文件進行通信的。
服務端 即將 生成的IBinder通過onbind返回給客戶端
public class AIDLService extends Service {
//由AIDL文件生成的BookManager
private final BookManager.Stub mBookManager = new BookManager.Stub() {
@Override
public String getName() throws RemoteException {//aidl所定義的接口方法
return null;
}
@Override
public void setName(String name) throws RemoteException {//aidl所定義的接口方法
...
}
};
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBookManager.asBinder();
}
}
客戶端 即將返回的binder 調用asInterface()
方法,獲得我們自定義的接口,然后調用對應方法。這樣即可實行進程間通信
public class AIDLActivity extends AppCompatActivity {
//由AIDL文件生成的Java類
private BookManager mBookManager = null;
//標志當前與服務端連接狀況的布爾值,false為未連接,true為連接中
private boolean mBound = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_aidl);
}
/**
* 嘗試與服務端建立連接
*/
private void attemptToBindService() {
Intent intent = new Intent();
intent.setAction("XXX");//androidmenifest中設置的service的action
intent.setPackage("XXXX");//androidmenifest中設置的service的服務端package
bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onStart() {
super.onStart();
if (!mBound) {
attemptToBindService();
}
}
@Override
protected void onStop() {
super.onStop();
if (mBound) {
unbindService(mServiceConnection);
mBound = false;
}
}
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mBookManager = BookManager.Stub.asInterface(service);
mBound = true;
if (mBookManager != null) {
try {
String name= mBookManager.getName();
Log.e(getLocalClassName(), "get book name for remote server, name = "+ name);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
mBound = false;
}
};
}
重點從生成的代碼中看下asInterface
方法如下
public static com.liyuange.liyuange.BookManager asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.liyuange.liyuange.BookManager))) {
return ((com.liyuange.liyuange.BookManager)iin);
}
return new com.liyuange.liyuange.BookManager.Stub.Proxy(obj);
}
BookManager.Stub
,根據生成的代碼可看出集成了Binder同時實現了我們自定義的接口public static abstract class Stub extends android.os.Binder implements com.liyuange.liyuange.BookManager
,當服務端與客戶端同在一個進程時iin!=null即 返回的是服務端創建的BookManager.Stub本身,但我們這種情況是在2個進程,故返回的是com.liyuange.liyuange.BookManager.Stub.Proxy
.根據生成的代碼,該類代碼如下
private static class Proxy implements com.liyuange.liyuange.BookManager//實現我們自定義接口
{
private android.os.IBinder mRemote; //我們傳入的ibinder
Proxy(android.os.IBinder remote)
{
mRemote = remote;
}
@Override public android.os.IBinder asBinder()
{
return mRemote;
}
public java.lang.String getInterfaceDescriptor()
{
return DESCRIPTOR;
}
//所有的返回值前都不需要加任何東西,不管是什么數據類型
@Override public java.lang.String getName() 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_getName, _data, _reply, 0);
_reply.readException();
// 如果需要讀取,服務端返回數據,從_reply中取出服務端執行方法的結果
_result = _reply.readString();
}
finally {
_reply.recycle();
_data.recycle();
}
//需要返回結果則將結果返回
return _result;
}
@Override public void setName(java.lang.String name) throws android.os.RemoteException
{
//很容易可以分析出來,_data用來存儲流向服務端的數據流,
//_reply用來存儲服務端流回客戶端的數據流
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
//如果需要傳參參流向服務端,則寫入
_data.writeString(name);
//調用 transact() 方法將方法id和兩個 Parcel 容器傳過去
mRemote.transact(Stub.TRANSACTION_setName, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
}
static final int TRANSACTION_getName = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_setName = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
大概流程大同小異,注釋打在了代碼上做分析。大致做的事就是,因為在不同進程不能直接讀取對方內存對象,所以需要進行序列化和反序列化(通過Parcelable實現),通過binder進行傳遞通信。
關于 _data 與 _reply 對象:一般來說,我們會將方法的傳參的數據存入_data 中,而將方法的返回值的數據存入 _reply 中——在沒涉及定向 tag 的情況下。如果涉及了定向 tag ,情況將會變得稍微復雜些,具體是怎么回事請參見這篇博文:你真的理解AIDL中的in,out,inout么?
關于 Parcel :簡單的來說,Parcel 是一個用來存放和讀取數據的容器。我們可以用它來進行客戶端和服務端之間的數據傳輸,當然,它能傳輸的只能是可序列化的數據。具體 Parcel 的使用方法和相關原理可以參見這篇文章:Android中Parcel的分析以及使用
-
關于 transact() 方法:這是客戶端和服務端通信的核心方法。調用這個方法之后,客戶端將會掛起當前線程,等候服務端執行完相關任務后通知并接收返回的 _reply 數據流。關于這個方法的傳參,這里有兩點需要說明的地方:
- 方法 ID :transact() 方法的第一個參數是一個方法 ID ,這個是客戶端與服務端約定好的給方法的編碼,彼此一一對應。在AIDL文件轉化為 .java 文件的時候,系統將會自動給AIDL文件里面的每一個方法自動分配一個方法 ID。本例子中生成ID如下
static final int TRANSACTION_getName = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_setName = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
* 第四個參數:transact() 方法的第四個參數是一個 int 值,它的作用是設置進行 IPC 的模式,為 0 表示數據可以雙向流通,即 _reply 流可以正常的攜帶數據回來,如果為 1 的話那么數據將只能單向流通,從服務端回來的 _reply 流將不攜帶任何數據。
注:AIDL生成的 .java 文件的這個參數均為 0。
接下來看下客戶端是如何響應的?調用 mRemote.transact
后,服務端就會調用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_getName:
{
data.enforceInterface(DESCRIPTOR);
java.lang.String _result = this.getName();
reply.writeNoException();
//將方法執行的結果寫入 reply
reply.writeString(_result);
return true;
}
case TRANSACTION_setName:
{
data.enforceInterface(DESCRIPTOR);
java.lang.String _arg0;
_arg0 = data.readString();
//調用服務端中`BookManager.Stub`本身setName方法, 即我們服務端service中`onbind`方法返回的的`BookManager.Stub`子類方法。
this.setName(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
直接通過客戶端調用的方法傳入ID 進行 switch 判斷是掉用戶服務端哪個方法,然后調用我們服務端service中onbind
方法返回的的BookManager.Stub
子類方法。
我們沒有看到關于將 reply 回傳到客戶端的相關代碼,也沒有看到它將相關參數傳向服務端的相關代碼——它只是把這些參數都傳入了一個方法,其中過程同樣是對我們隱藏的——服務端也同樣,在執行完 return true 之后系統將會把 reply 流傳回客戶端,具體是怎么做的就在于IBinder的源碼了。如果想深入研究IBinder機制,可參考老羅及其它大神對該機制更底層的分析。
通過隱藏了這些細節,我們在 transact() 與 onTransact() 之間的調用以及數據傳送看起來就像是發生在同一個進程甚至同一個類里面一樣。我們的操作就像是在一條直線上面走,根本感受不出來其中原來有過曲折——也許這套機制在設計之初,就是為了達到這樣的目的。
總結
一圖以辟之