Android跨進程通信IPC之19——AIDL

移步系列Android跨進程通信IPC系列

  • 1 AIDL簡介
  • 2 為什么要設置AIDL
  • 3 AIDL的注意事項
  • 4 AIDL的使用
  • 5 源碼跟蹤
  • 6 AIDL的設計給我們的思考
  • 7 總結

1 AIDL簡介

  • AIDL是一個縮寫,全程是Android Interface Definition Language,也是android接口定義語言。
  • 準確的來說,它是用于定義客戶端/服務器通信接口的一種描述語言。它其實一種IDL語言,可以拿來生成用于IPC的代碼。從某種意義上說它其實是一個模板。

2 為什么要設置AIDL

2.1 IPC的角度

  • 設計這門語言的目的是為了實現(xiàn)進程間通信,尤其是在涉及多進程并發(fā)情況的下的進程間通信IPC。
  • 通過AIDL,可以讓本地調用遠程服務器的接口就像調用本地接口那么簡單,讓用戶無需關注內部細節(jié),只需要實現(xiàn)自己的業(yè)務邏輯接口,內部復雜的參數(shù)序列化發(fā)送、接收、客戶端調用服務端的邏輯,你都不需要去關心了。

2.2 方便角度

在Android process 之間不能用通常的方式去訪問彼此的內存數(shù)據(jù)。他們把需要傳遞的數(shù)據(jù)解析成基礎對象,使得系統(tǒng)能夠識別并處理這些對象。因為這個處理過程很難寫,所以Android使用AIDL來解決這個問題

3 使用場景

  • 多個客戶端,多個線程并發(fā)的情況下要使用AIDL
  • 如果你的IPC不需要適用多個客戶端,就用Binder。
  • 如果你想要IPC,但是不需要多線程,那就選擇Messager

4 AIDL支持以下數(shù)據(jù)類型

  • Java基本類型,即int、long、char等;
  • String;
  • CharSequence;
  • List
    • List中的所有元素都必須是AIDL支持的數(shù)據(jù)類型、其他AIDL接口或你之前聲明的Parcelable實現(xiàn)類。
  • Map
    • Map中的所有元素都必須是AIDL支持的數(shù)據(jù)類型、其他AIDL接口或你之前聲明的Parcelable實現(xiàn)類。
  • 其他類型,必須要有import語句,即使它跟.aidl是同一個包下。
  • 關于復雜對象
  • 傳遞的復雜對象必須要實現(xiàn)Parcelable接口,這是因為Parcelable允許Android系統(tǒng)將復雜對象分解成基本類型以便在進程間傳輸.
  • 若客戶端組件和服務分開在不同APP,必須要把該Parcelable實現(xiàn)類.java文件拷貝到客戶端所在的APP,包路徑要一致。
  • 另外,需要為這個Parcelable實現(xiàn)類定義一個相應的.aidl文件。與AIDL服務接口.aidl同理,客戶端所在APP的/src/<SourceSet>/aidl目錄下也要有這份副本。
TIM截圖20180821173719.png

5 AIDL中的方法和變量

  • 方法可有零、一或多個參數(shù),可有返回值或void。
  • 所有非基本類型的參數(shù)都需要標簽來表明這個數(shù)據(jù)的去向
    • in,表示此變量由客戶端設置;
    • out,表示此變量由服務端設置;
    • inout,表示此變量可由客戶端和服務端設置;
    • 基本類型只能是in。
  • AIDL中的定向tag表示在跨進程通信中數(shù)據(jù) 流向
  • in表示數(shù)據(jù)只能由客戶端流向服務端,out表示數(shù)據(jù)只能由服務端流行客戶端,而inout則表示數(shù)據(jù)可在服務端與客戶端之間雙向流通。
  • 其中,數(shù)據(jù)流向是針對客戶端中的那個傳入方法的對象而言。
  • in為定向tag的話,表現(xiàn)為服務端將會接受到一個那個對象的完整數(shù)據(jù),但是客戶端的那個對象不會因為服務端傳參修改而發(fā)生變動;
  • out的話表現(xiàn)為服務端將會接收到那個對象的參數(shù)為空的對象,但是在服務端對接收到的空對象有任何修改之后客戶端將會同步變動;
  • inout為定向tag的情況下,服務端將會接收到客戶端傳來對象的完整信息,并且客戶端將會同步服務對該對象的任何變動。

6 AIDL的使用(復雜對象為例)

6.1 服務端

我們以在Android Studio為例進行講解

6.1.1 創(chuàng)建復雜對象

public class MessageData implements Parcelable {

    public long id;  //消息的id
    public String content; //消息的內容
    public long time;  //時間

    @Override
    public String toString() {
        return "Message{" +
                "id=" + id +
                ", content='" + content + '\'' +
                ", time=" + time +
                '}';
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeLong(id);
        dest.writeString(content);
        dest.writeLong(time);
    }

    public MessageData(Parcel source) {
        id = source.readLong();
        content = source.readString();
        time = source.readLong();
    }

    public MessageData() {

    }

    public void readFromParcel(Parcel in) {
        id = in.readLong();
        content = in.readString();
        time = in.readLong();
    }


    public static final Creator<MessageData> CREATOR = new Creator<MessageData>() {
        @Override
        public MessageData createFromParcel(Parcel source) {
            return new MessageData(source);
        }

        @Override
        public MessageData[] newArray(int size) {
            return new MessageData[size];
        }
    };
}

6.1.2 創(chuàng)建AIDL文件夾

TIM截圖20180821160304.png

6.1.3 創(chuàng)建AIDL文件(復雜對象及服務)

復雜對象
[MessageData.aidl]

// MessageData.aidl
package com.kai.ling.myapplication;

// Declare any non-default types here with import statements
parcelable MessageData;

服務

// IMyAidlInterface.aidl
package com.kai.ling.myapplication;
// Declare any non-default types here with import statements
import com.kai.ling.myapplication.MessageData;

interface IMyAidlInterface {
    /**
     * 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);

    void connect();

    void sendMessage(inout MessageData message);
}

編譯后會生成對應的aidl文件


TIM截圖20180821174715.png

6.1.4 書寫服務端server繼承IMyAidlInterface.Stub

public class MyServer extends IMyAidlInterface.Stub {

    private static final String TAG = "GEBILAOLITOU";


    @Override
    public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {

    }

    @Override
    public void connect() throws RemoteException {
        Log.i(TAG, "connect");
    }

    @Override
    public void sendMessage(MessageData message) throws RemoteException {
        Log.i(TAG, "MyServer ** sendInMessage **" + message.toString());
    }

}

6.1.5 service中返回server 并設置多進程

public class PushService extends Service {

    private MyServer myServer=new MyServer();


    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return myServer;
    }
}
        <service
            android:name=".server.PushService"
            android:enabled="true"
            android:exported="true"
            android:process=":push"/>

6.2 客戶端

管理類

public class PushManager {

    private static final String TAG = "GEBILAOLITOU";
    
    private PushManager() {
    }

    private IMyAidlInterface iMyAidlInterface;
    
    private static PushManager instance = new PushManager();
    
    //單例
    public static PushManager getInstance() {
        return instance;
    }


    //綁定服務
    public void init(Context context) {
        //定義intent
        Intent intent = new Intent(context, PushService.class);
        //綁定服務
        context.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
    }

    private ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //成功連接
            Log.d(TAG, "pushManager ***************成功連接***************");
            //通過asInterface獲取
            iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);

        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            //斷開連接調用
            Log.d(TAG, "pushManager ***************連接已經(jīng)斷開***************");
        }
    };

    //遠程調用connect()方法
    public void connect() {
        try {
            Log.d(TAG, "pushManager ***************start Remote***************");
            iMyAidlInterface.connect();
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    //遠程調用sendMessage()方法
    public void sendMessage(String content) {
        MessageData messageData = new MessageData();
        messageData.content = content;
        try {
            iMyAidlInterface.sendMessage(messageData);
        } catch (RemoteException e) {
            e.printStackTrace();
            Log.d(TAG, "pushManager ***************RemoteException***************");
        }
    }
}
public class MainActivity extends AppCompatActivity {

    private boolean isConnected;
    private EditText content;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //初始化
        PushManager.getInstance().init(this);

        content = findViewById(R.id.content);

        findViewById(R.id.connect).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                PushManager.getInstance().connect();
                isConnected = true;
            }
        });

        findViewById(R.id.send).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (!isConnected) {
                    Toast.makeText(MainActivity.this, "請連接", Toast.LENGTH_LONG).show();
                }
                if (TextUtils.isEmpty(content.getText().toString().trim())) {
                    Toast.makeText(MainActivity.this, "請輸入", Toast.LENGTH_LONG).show();
                }
                PushManager.getInstance().sendMessage(content.getText().toString().trim());
            }
        });
    }
}

7 源碼跟蹤

  • 在寫完AIDL文件后,編譯器會幫我們自動生成一個同名的.java文件。
  • 在我們實際編寫客戶端和服務端代碼的過程中,真正協(xié)助我們工作的其實就是這個文件,而.aidl文件從頭到尾都沒有出現(xiàn)過。
  • 這個.aidl文件就是為了生成這個對應的.java文件。事實上,就算我們不寫AIDL文件,直接按照它生成的.java文件這樣寫一個.java文件出來。在服務端和客戶端也可以照常使用這個.java類進行跨進程通信。
  • AIDL語言只是在簡化我們寫這個.java文件而已,而要研究AIDL是符合幫助我們進行跨境進程通信的,其實就是研究這個生成的.java文件是如何工作的

7.1 .java文件位置

TIM截圖20180822155143.png
  • 它的完整路徑是:app->build->generated->source->aidl->debug->com->gebilaolitou->android->aidl->IMyAidlInterface.java(其中 com.gebilaolitou.android.aidl
    是包名,相對應的AIDL文件為 IMyAidlInterface.aidl )。

7.2 IMyAidlInterface .java類分析

結構圖

TIM截圖20180822155826.png

  • 編譯的后IMyAidlInterface.java文件是一個接口,繼承自android.os.IInterface
  • IMyAidlInterface內部代碼主要分成兩部分,一個是抽象類Stub 和 原來aidl聲明的basicTypes()、connect()和sendInMessage()方法

7.3 Stub類分析

TIM截圖20180822160212.png
  • Stub類基本結構
  • 靜態(tài)方法 asInterface(android.os.IBinder obj)
  • 靜態(tài)內部類 Proxy
  • 方法 onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)
  • 方法 asBinder()
  • private的String類型常量DESCRIPTOR
  • private的int類型常量TRANSACTION_connect
  • private的int類型常量TRANSACTION_sendInMessage

7.3.1 靜態(tài)方法 asInterface(android.os.IBinder obj)

public static com.kai.ling.myapplication.IMyAidlInterface asInterface(android.os.IBinder obj) {
    //非空判斷
    if ((obj == null)) {
        return null;
    }
    // DESCRIPTOR是常量為"com.gebilaolitou.android.aidl.IMyAidlInterface"
    // queryLocalInterface是Binder的方法,搜索本地是否有可用的對象
    android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
    //如果有,則強制類型轉換并返回
    if (((iin != null) && (iin instanceof com.kai.ling.myapplication.IMyAidlInterface))) {
        return ((com.kai.ling.myapplication.IMyAidlInterface) iin);
    }
    //如果沒有,則構造一個IMyAidlInterface.Stub.Proxy對象
    return new com.kai.ling.myapplication.IMyAidlInterface.Stub.Proxy(obj);
}
  • 主要的作用就是根據(jù)傳入的Binder對象轉換成客戶端需要的IMyAidlInterface接口。
    • 如果客戶端和服務端處于同一進程,那么queryLocalInterface()方法返回就是服務端Stub對象本身;
    • 如果是跨進程,則返回一個封裝過的Stub.Proxy,也是一個代理類,在這個代理中實現(xiàn)跨進程通信。那么讓我們來看下Stub.Proxy類

7.3.2 onTransact()方法解析

onTransact()方法是根據(jù)code參數(shù)來處理,這里面會調用真正的業(yè)務實現(xiàn)類

  • 在onTransact()方法中,根據(jù)傳入的code值回去執(zhí)行服務端相應的方法。其中常量TRANSACTION_connect和常量TRANSACTION_sendInMessage就是code值(在AIDL文件中聲明了多少個方法就有多少個對應的code)。
  • 其中data就是服務端方法需要的的參數(shù),執(zhí)行完,最后把方法的返回結果放入reply中傳遞給客戶端。如果該方法返回false,那么客戶端請求失敗。

7.3.3 靜態(tài)類Stub.Proxy

private static class Proxy implements com.kai.ling.myapplication.IMyAidlInterface {
    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 void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) 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.writeInt(anInt);
            _data.writeLong(aLong);
            _data.writeInt(((aBoolean) ? (1) : (0)));
            _data.writeFloat(aFloat);
            _data.writeDouble(aDouble);
            _data.writeString(aString);
            mRemote.transact(Stub.TRANSACTION_basicTypes, _data, _reply, 0);
            _reply.readException();
        } finally {
            _reply.recycle();
            _data.recycle();
        }
    }

    @Override
    public void connect() throws android.os.RemoteException {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        try {
            _data.writeInterfaceToken(DESCRIPTOR);
            mRemote.transact(Stub.TRANSACTION_connect, _data, _reply, 0);
            _reply.readException();
        } finally {
            _reply.recycle();
            _data.recycle();
        }
    }
    //發(fā)送消息  客戶端——> 服務器
    @Override
    public void sendMessage(com.kai.ling.myapplication.MessageData message) throws android.os.RemoteException {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        try {
            _data.writeInterfaceToken(DESCRIPTOR);
            if ((message != null)) {
                _data.writeInt(1);
                message.writeToParcel(_data, 0);
            } else {
                _data.writeInt(0);
            }
            mRemote.transact(Stub.TRANSACTION_sendMessage, _data, _reply, 0);
            _reply.readException();
            if ((0 != _reply.readInt())) {
                message.readFromParcel(_reply);
            }
        } finally {
            _reply.recycle();
            _data.recycle();
        }
    }
}

  • 1、Proxy 實現(xiàn)了 com.gebilaolitou.android.aidl.IMyAidlInterfac接口,所以他內部有IMyAidlInterface接口的兩個抽象方法
  • 2、Proxy的asBinder()方法返回的mRemote,而這個mRemote是什么時候被賦值的?是在構造函數(shù)里面被賦值的。

7.3.3.1 靜態(tài)類Stub.Proxy的connect()方法

  • 1、 通過閱讀靜態(tài)類Stub.Proxy的connect()方法,我們容易分析出來里面的兩個android.os.Parcel_data_reply是用來進行跨進程傳輸?shù)?strong>"載體"。而且通過字面的意思,很容易猜到,*_data用來存儲 客戶端流向服務端 的數(shù)據(jù),_reply用來存儲 服務端流向客戶端 的數(shù)據(jù)。
  • 2、通過mRemote. transact()方法,將_data和_reply傳過去
  • 3、通過_reply.readException()來讀取服務端執(zhí)行方法的結果。
  • 4、最后通過finally回收l_data和_reply

7.3.3.2 connect() 相關參數(shù)

  • 關于 transact()方法:這是客戶端和和服務端通信的核心方法,也是IMyAidlInterface.Stub繼承android.os.Binder而重寫的一個方法。
  • 調起這個方法之后,客戶端將會掛起當前線程,等候服務端執(zhí)行完相關任務后,通知并接收返回的_reply數(shù)據(jù)流。
  • 1 方法ID:transact()方法第一個參數(shù)是一個方法ID,這個是客戶端和服務端約定好的給方法的編碼,彼此一一對應。在AIDL文件轉話為.java時候,系統(tǒng)會自動給AIDL里面的每一個方法自動分配一個方法ID。而這個ID就是咱們說的常量TRANSACTION_connect和TRANSACTION_sendInMessage這些常量生成了遞增的ID,是根據(jù)你在aidl文件的方法順序而來,然后在IMyAidlInterface.Stub中的onTransact()方法里面switch根據(jù)第一個參數(shù)code即我們說的ID而來。
  • 2最后的一個參數(shù):transact()方法最后一個參數(shù)是一個int值,代表是單向的還是雙向的。具體大家請參考我們前面的文章Android跨進程通信IPC之8——Binder的三大接口中關于IBinder部分。我這里直接說結論:0表示雙向流通,即_reply可以正常的攜帶數(shù)據(jù)回來。如果為1的話,那么數(shù)據(jù)只能單向流程,從服務端回來的數(shù)據(jù)_reply不攜帶任何數(shù)據(jù)。注意:AIDL生成的.java文件,這個參數(shù)均為0

7.3.4 asBinder()方法

該方法就是返回當前的Binder方法

8 IMyAidlInterface .java流程分析

以上面的例子為例,那么先從客戶端開始

8.1 客戶端

8.1.1 獲取IMyAidlInterface對象

public void init(Context context){
        //定義intent
        Intent intent = new Intent(context,PushService.class);
        //綁定服務
        context.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
    }

    private ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //成功連接
            Log.i(TAG,"PushManager ***************成功連接***************");
            iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);

        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            //斷開連接調用
            Log.i(TAG,"PushManager ***************連接已經(jīng)斷開***************");
        }
    };
  • 客戶端中通過Intent去綁定一個服務端的Service。
  • onServiceConnected(ComponentName name, IBinder service)方法中通過返回service可以得到AIDL接口的實例。
  • 這是調用了asInterface(android.os.IBinder) 方法完成的。在asInterface(android.os.IBinder)我們知道是調用的new com.gebilaolitou.android.aidl.IMyAidlInterface.Stub.Proxy(obj)構造的一個Proxy對象。
  • 所以可以這么說在PushManager中的變量IMyAidlInterface其實是一個IMyAidlInterface.Stub.Proxy對象。

8.1.2 調用connect()方法

PushManager類中的iMyAidlInterface其實IMyAidlInterface.Stub.Proxy對象,所以調用connect()方法其實是IMyAidlInterface.Stub.Proxy的connect()方法。

  • 1、這里面主要是生成了_data和_reply數(shù)據(jù)流,并向_data中存入客戶端的數(shù)據(jù)。
  • 2、通過 transact()方法將他們傳遞給服務端,并請求服務指定的方法
  • 3、接收_reply數(shù)據(jù),并且從中讀取服務端傳回的數(shù)據(jù)。

通過上面客戶端的所有行為,我們會發(fā)現(xiàn),其實通過ServiceConnection類中onServiceConnected(ComponentName name, IBinder service)中第二個參數(shù)service很重要,因為我們最后是用它的transact() 方法,將客戶端的數(shù)據(jù)和請求發(fā)送給服務端去。從這個角度來看,這個service就像是服務端在客戶端的代理一樣,而IMyAidlInterface.Stub.Proxy對象更像一個二級代理,我們在外部通過調用這個二級代理來間接調用service這個一級代理

8.2 服務端流程

在前面幾篇文章中我們知道Binder傳輸中,客戶端調用transact()對應的是服務端的onTransact()函數(shù),我們在IMyAidlInterface.java中看到
查看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_basicTypes: {
                    data.enforceInterface(DESCRIPTOR);
                    int _arg0;
                    _arg0 = data.readInt();
                    long _arg1;
                    _arg1 = data.readLong();
                    boolean _arg2;
                    _arg2 = (0 != data.readInt());
                    float _arg3;
                    _arg3 = data.readFloat();
                    double _arg4;
                    _arg4 = data.readDouble();
                    java.lang.String _arg5;
                    _arg5 = data.readString();
                    this.basicTypes(_arg0, _arg1, _arg2, _arg3, _arg4, _arg5);
                    reply.writeNoException();
                    return true;
                }
                case TRANSACTION_connect: {
                    data.enforceInterface(DESCRIPTOR);
                    this.connect();
                    reply.writeNoException();
                    return true;
                }
                case TRANSACTION_sendMessage: {
                    data.enforceInterface(DESCRIPTOR);
                    com.kai.ling.myapplication.MessageData _arg0;
                    if ((0 != data.readInt())) {
                        _arg0 = com.kai.ling.myapplication.MessageData.CREATOR.createFromParcel(data);
                    } else {
                        _arg0 = null;
                    }
                    this.sendMessage(_arg0);
                    reply.writeNoException();
                    if ((_arg0 != null)) {
                        reply.writeInt(1);
                        _arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
                    } else {
                        reply.writeInt(0);
                    }
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }

在收到客戶端的 transact()方法后,直接調用了switch選擇,根據(jù)ID執(zhí)行不同操作,因為我們知道是調用的connect()方法,所以對應的code是TRANSACTION_connect,所以我們下case TRANSACTION_connect:的內容,如下:

case TRANSACTION_connect: {
    data.enforceInterface(DESCRIPTOR);
    this.connect();
    reply.writeNoException();
    return true;
}

這里面十分簡單了,就是直接調用服務端的connect()方法。

9 整體流程如下:

5713484-298c59e740ccf7e3.png

5713484-2c51ce26ad1e82a9.png

參考

Android跨進程通信IPC之11——AIDL

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容