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

Android跨進程通信IPC整體內容如下

本篇文章的內容如下:

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

一、AIDL簡介

AIDL是一個縮寫,全程是Android Interface Definition Language,也是android接口定義語言。準確的來說,它是用于定義客戶端/服務器通信接口的一種描述語言。它其實一種IDL語言,可以拿來生成用于IPC的代碼。從某種意義上說它其實是一個模板。為什么這么說?因為在我們使用中,實際起作用的并不是我們寫的AIDL代碼,而是系統根據它生成的一個IInterface的實例的代碼。而如果大家都生成這樣的幾個實例,然后它們拿來比較,你會發現它們都是有套路的——都是一樣的流程,一樣的結構,只是根據具體的AIDL文件的不同由細微變動。所以其實AIDL就是為了避免我們一遍遍的寫一些前篇一律的代碼而出現的一個模板

在詳解講解AIDL之前,大家想一想下面幾個問題?

  • 1、那么為什么安卓團隊要定義這一種語言。
  • 2、如果使用AIDL
  • 3、AIDL的原理

那我們開始圍繞這三個問題開始一次接待

二、為什么要設置AIDL

兩個維度來看待這個問題:

(一) IPC的角度

設計這門語言的目的是為了實現進程間通信,尤其是在涉及多進程并發情況的下的進程間通信IPC。每一個進程都有自己的Dalvik VM實例,都有自己的一塊獨立的內存,都在自己的內存上存儲自己的數據,執行著自己的操作,都在自己的那個空間里操作。每個進程都是獨立的,你不知我,我不知你。就像兩座小島之間的橋梁。通過這個橋梁,兩個小島可以進行交流,進行信息的交互。

通過AIDL,可以讓本地調用遠程服務器的接口就像調用本地接口那么接單,讓用戶無需關注內部細節,只需要實現自己的業務邏輯接口,內部復雜的參數序列化發送、接收、客戶端調用服務端的邏輯,你都不需要去關心了。

(二)方便角度

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

三、ADIL的注意事項

在定義AIDL之前,請意識到調用這些接口是direct function call。請不要認為call這些接口的行為是發生在另外一個線程里面的。具體的不同因為這個調用是發生在local process還是 remote process而異。

  • 發生在local process 里面的調用會跑在這個local process的thread里面。如果這是你的UI主線程,那么AIDL接口的調用也會發生在這個UIthread里面。如果這是發生在另外一個thread,那么調用會發生在service里面。因此,如果僅僅是發生在local process的調用,則你可以完全控制這些調用,當然這樣的話,就不需要AIDL了。因為你完全可以使用Bound Service的第一種方式去實現。
  • 發生在remote process 里面調用的會跑在你自己的process所維護的thread pool里面。那么你需要注意可能會在同一時刻接收到多個請求。所以AIDL的操作需要做到thread-safe。(每次請求,都交給Service,在線程池里面啟動一個thread去執行哪些請求,所以那些方法需要是線程安全的)
  • oneway關鍵字改變了remote cal的行為。當使用這個關鍵字時,remote call 不會被阻塞住,它僅僅是發送交互數據后再立即返回。IBinder thread pool之后會把它當做一個通常的remote call。

四、AIDL的使用

(一)、什么時候使用AIDL

前面我們介紹了,Binder機制,還有后面要講解的Messager,以及現在說的AIDL等,Android系統中有事先IPC的很多中方式,到底什么情況下應該使用AIDL?Android官方文檔給出了答案:

Note: Using AIDL is necessary only if you allow clients from different applications to access your service for IPC and want to handle multithreading in your service. If you do not need to perform concurrent IPC across different applications, you should create your interface by implementing a Binder or, if you want to perform IPC, but do not need to handle multithreading, implement your interface using a Messenger. Regardless, be sure that you understand Bound Services before implementing an AIDL.

所以使用AIDL只有在你先允許來自不同應用的客戶端跨進程通信訪問你的Service,并且想要在你的Service處理多線程的時候才是必要的。簡單的來說,就是多個客戶端,多個線程并發的情況下要使用AIDL。官方文檔還之處,如果你的IPC不需要適用多個客戶端,就用Binder。如果你想要IPC,但是不需要多線程,那就選擇Messager。

(二)、建立AIDL

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

1、創建AIDL文件夾

如下圖


創建AIDL文件夾.png
2、創建AIDL文件

如下圖


創建AIDL文件.png

文件名為 "IMyAidlInterface"

3、編輯和生成AIDL文件

增加一行代碼如下圖

   void connect();
編輯.png

這時候可以點擊"同步"按鈕,或者rebuild以下項目。然后去看下是否生成了相應的文件

如下圖


生成文件.png
4、編寫客戶端代碼

設置一個單例模式的PushManager,代碼如下:

public class PushManager {

    private static final String TAG = "GEBILAOLITOU";
    private int id=1;

    //定義為單例模式
    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);
    }

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

    }


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

        }

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

還有一個Server

public class PushService extends Service{

    private MyServer myServer=new MyServer();


    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return myServer;
    }
}
5、編寫服務器代碼

編寫服務端實現connect()、sendInMessage()方法

我自己寫一個MyServer.java類,代碼如下:

public class MyServer extends IMyAidlInterface.Stub {

    private static final String TAG = "GEBILAOLITOU";


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

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

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

再寫一個Service

public class PushService extends Service{

    private MyServer myServer=new MyServer();


    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return myServer;
    }
}

6、編寫傳遞的"數據"

之前講解Binder的時候,說過,跨進程調試的時候需要實現Parcelable接口。因為AIDL默認支持的類型保羅Java基本類型(int、long等)和(String、List、Map、CharSequence),如果要傳遞自定義的類型要實現android.os.Parcelable接口。

假設在兩個進程中傳遞的“數據”一般都是“消息”,我們編寫具體的載體,寫一個實體類public class Message implements Parcelable。代碼如下:

public class Message 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 Message(Parcel source){
          id=source.readLong();
          content=source.readString();
          time=source.readLong();
    }

    public Message(){

    }

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


    public static final Creator<Message> CREATOR=new Creator<Message>() {
        @Override
        public Message createFromParcel(Parcel source) {
            return new Message(source);
        }
        @Override
        public Message[] newArray(int size) {
            return new Message[size];
        }
    };
}

修改IMyAidlInterface.aidl,增加一個方法

// IMyAidlInterface.aidl
package com.gebilaolitou.android.aidl;

// Declare any non-default types here with import statements
import com.gebilaolitou.android.aidl.Message;

interface IMyAidlInterface {

    //連接
    void connect();

    void sendMessage(Message message);
}

7、進行調試

這時候我們rebuild項目,或者同步,會報錯

Information:Gradle tasks [:app:generateDebugSources, :app:generateDebugAndroidTestSources, :app:mockableAndroidJar, :app:prepareDebugUnitTestDependencies]
/Users/gebilaolitou/Downloads/AIDLDemo/app/src/main/aidl/com/gebilaolitou/android/aidl/IMyAidlInterface.aidl
Error:(5) couldn't find import for class com.gebilaolitou.android.aidl.Message

這是因為自定義類型不僅要定義實現android.os.Parcelable接口類,還的為該實現類定義個aidl文件。

Message.AIDL.png

代碼如下:

// Message.aidl.aidl
package com.gebilaolitou.android.aidl;

// Declare any non-default types here with import statements
import com.gebilaolitou.android.aidl.Message;

parcelable Message ;

注意:自定義類型aidl文件名字、路徑需要和自定義類名字,路徑保持一致。

編譯項目或者同步,還是報錯

Information:Gradle tasks [:app:generateDebugSources, :app:generateDebugAndroidTestSources, :app:mockableAndroidJar, :app:prepareDebugUnitTestDependencies]
Error:Execution failed for task ':app:compileDebugAidl'.
> java.lang.RuntimeException: com.android.ide.common.process.ProcessException: Error while executing process /Users/gebilaolitou/Library/Android/sdk/build-tools/25.0.2/aidl with arguments {-p/Users/gebilaolitou/Library/Android/sdk/platforms/android-25/framework.aidl -o/Users/gebilaolitou/Downloads/AIDLDemo/app/build/generated/source/aidl/debug -I/Users/gebilaolitou/Downloads/AIDLDemo/app/src/main/aidl -I/Users/gebilaolitou/Downloads/AIDLDemo/app/src/debug/aidl -I/Users/gebilaolitou/.android/build-cache/a1586d1e8ebbe7e662fad3571bc225fc0194aa31/output/aidl -I/Users/gebilaolitou/.android/build-cache/d52918cebdc5dd2a94851191b48afd011ca2b5c1/output/aidl -I/Users/gebilaolitou/.android/build-cache/a04d8c3e429534f503f1d5bbe860d0472d27613b/output/aidl -I/Users/gebilaolitou/.android/build-cache/83996cff7d35ba84168d1ed30e788df3aae82edd/output/aidl -I/Users/gebilaolitou/.android/build-cache/59c7b347cfeea50e258b0409d1f3d4eb91f58927/output/aidl -I/Users/gebilaolitou/.android/build-cache/7127c78b885aaac60b2b0712034851136a252f90/output/aidl -I/Users/gebilaolitou/.android/build-cache/d501962f472162456967742c491558ed0737c27c/output/aidl -I/Users/guochenli/.android/build-cache/13936966f3097ecab148b88871eeb79b0a9fe984/output/aidl -I/Users/gebilaolitou/.android/build-cache/fb883931c2e88ee11d0e77773aa01a2e67652940/output/aidl -I/Users/gebilaolitou/.android/build-cache/a0568698ab34df5d8fa827b197c443b9e1747f8e/output/aidl -d/var/folders/7d/z3snv_1n33xbf7l4vr2956fh0000gn/T/aidl2942334890792150517.d /Users/gebilaolitou/Downloads/AIDLDemo/app/src/main/aidl/com/gebilaolitou/android/aidl/IMyAidlInterface.aidl}

貌似什么看不出來,這時候我們看下Gradle Console,會發現

Executing tasks: [:app:generateDebugSources, :app:generateDebugAndroidTestSources, :app:mockableAndroidJar, :app:prepareDebugUnitTestDependencies]

Configuration on demand is an incubating feature.
Incremental java compilation is an incubating feature.
:app:preBuild UP-TO-DATE
:app:preDebugBuild UP-TO-DATE
:app:checkDebugManifest
:app:preReleaseBuild UP-TO-DATE
:app:prepareComAndroidSupportAnimatedVectorDrawable2531Library
:app:prepareComAndroidSupportAppcompatV72531Library
:app:prepareComAndroidSupportConstraintConstraintLayout102Library
:app:prepareComAndroidSupportSupportCompat2531Library
:app:prepareComAndroidSupportSupportCoreUi2531Library
:app:prepareComAndroidSupportSupportCoreUtils2531Library
:app:prepareComAndroidSupportSupportFragment2531Library
:app:prepareComAndroidSupportSupportMediaCompat2531Library
:app:prepareComAndroidSupportSupportV42531Library
:app:prepareComAndroidSupportSupportVectorDrawable2531Library
:app:prepareDebugDependencies
:app:compileDebugAidl
aidl E 19367 21417307 type_namespace.cpp:130] In file /Users/guochenli/Downloads/AIDLDemo/app/src/main/aidl/com/gebilaolitou/android/aidl/IMyAidlInterface.aidl line 12 parameter message (argument 1):
aidl E 19367 21417307 type_namespace.cpp:130]     'Message' can be an out type, so you must declare it as in, out or inout.

看重點

aidl E 19367 21417307 type_namespace.cpp:130] In file /Users/gebilaolitou/Downloads/AIDLDemo/app/src/main/aidl/com/gebilaolitou/android/aidl/IMyAidlInterface.aidl line 12 parameter message (argument 1):
aidl E 19367 21417307 type_namespace.cpp:130] 'Message' can be an out type, so you must declare it as in, out or inout.

這是因為AIDL不是Java。它是真的很接近,但是它不是Java。Java參數是沒有方向的概念,AIDL參數是有方向,參數可以從客戶端傳到服務端,再返回來。

所以如果sendMessage()方法的message參數是純粹的輸入參,這意味著是從客戶端到服務器的數據,你需要在ADIL聲明:

void sendMessage(in Message message);

如果sendMessage方法的message參數是純粹的輸出,這意味它的數據是通過服務器到客戶端的,使用:

void sendMessage(out Message message);

如果sendMessage方法的message參數是輸入也是輸出,客戶端的值在服務器可能會被修改,使用

void sendMessage(inout Message message);

所以,最終我們修改IMyAidlInterface.aidl文件如下

// IMyAidlInterface.aidl
package com.gebilaolitou.android.aidl;

// Declare any non-default types here with import statements
import com.gebilaolitou.android.aidl.Message;

interface IMyAidlInterface {
    //連接
    void connect();
    //發送消息  客戶端——> 服務器
    void sendInMessage(in Message message);
}

這時候重新編譯就可以了

8、補充完善

設計一個Activity,如下

public class MainActivity extends AppCompatActivity {

    private Button   btn,btnSend;
    private EditText et;
    private boolean isConnected=false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btn=(Button)this.findViewById(R.id.btn);
        et=(EditText)this.findViewById(R.id.et);
        btnSend=(Button)this.findViewById(R.id.send);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                PushManager.getInstance().connect();
                isConnected=true;
            }
        });
        btnSend.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(!isConnected){
                    Toast.makeText(MainActivity.this,"請連接",Toast.LENGTH_LONG).show();
                }
                if(et.getText().toString().trim().length()==0){
                    Toast.makeText(MainActivity.this,"請輸入",Toast.LENGTH_LONG).show();
                }
                PushManager.getInstance().sendString(et.getText().toString());
            }
        });
    }
}

對應的xml如下

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    >

    <Button
        android:id="@+id/btn"
        android:layout_width="368dp"
        android:layout_height="50dp"
        android:text="連接"
        />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal">
        <EditText
            android:layout_weight="1"
            android:id="@+id/et"
            android:layout_width="wrap_content"
            android:layout_height="50dp"
            />
        <Button
            android:id="@+id/send"
            android:text="發送"
            android:layout_width="50dp"
            android:layout_height="50dp" />
    </LinearLayout>

</LinearLayout>

別忘記manifest文件

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:name=".App"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <service
            android:name=".PushService"
            android:enabled="true"
            android:process=":push"
            android:exported="true" />
    </application>
9、效果顯示
(1) 沒有任何操作下顯示如下

無操作情況下,主進程log輸出如下

08-14 15:57:57.491 9575-9575/com.gebilaolitou.android.aidl I/GEBILAOLITOU: PushManager ***************成功連接***************
(2) 先點擊"連接"按鈕,主進程log輸出如下
08-14 16:00:20.394 9575-9575/com.gebilaolitou.android.aidl I/GEBILAOLITOU: PushManager ***************start Remote***************

這時候看下push進程log輸出如下

08-14 16:00:20.394 9605-9627/com.gebilaolitou.android.aidl:push I/GEBILAOLITOU: MyServer connect
(3) 在輸入框 輸入"123456",然后點擊"發送"按鈕,主進程log輸出如下
08-14 16:02:23.042 9575-9575/com.gebilaolitou.android.aidl I/GEBILAOLITOU: PushManager ***************sendString***************Message{id=2, content='123456', time=1502697743041}

這時候看下push進程log輸出如下

08-14 16:02:23.045 9605-9625/com.gebilaolitou.android.aidl:push I/GEBILAOLITOU: MyServer ** sendInMessage **Message{id=2, content='123456', time=1502697743041}

至此我們成功把一個Message對象通過AIDL傳遞到另外一個進程中。

五、源碼跟蹤

通過上面的內容,我們已經學會了AIDL的全部用法,接下來讓我們透過現象看本質,研究一下究竟AIDL是如何幫助我們進行跨進程通信的。

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

(一) .java文件位置

位置如下圖


位置.png

它的完整路徑是:app->build->generated->source->aidl->debug->com->gebilaolitou->android->aidl->IMyAidlInterface.java(其中 com.gebilaolitou.android.aidl
是包名,相對應的AIDL文件為 IMyAidlInterface.aidl )。在AndroidStudio里面目錄組織方式由默認的 android改為 Project 就可以直接按照文件夾結構訪問到它。

(二) IMyAidlInterface .java類分析

IMyAidlInterface .java 里面代碼如下:

/*
 * This file is auto-generated.  DO NOT MODIFY.
 * Original file: /Users/gebilaolitou/Downloads/AIDLDemo/app/src/main/aidl/com/gebilaolitou/android/aidl/IMyAidlInterface.aidl
 */
package com.gebilaolitou.android.aidl;

public interface IMyAidlInterface extends android.os.IInterface {
    /**
     * Local-side IPC implementation stub class.
     */
    public static abstract class Stub extends android.os.Binder implements com.gebilaolitou.android.aidl.IMyAidlInterface {
        private static final java.lang.String DESCRIPTOR = "com.gebilaolitou.android.aidl.IMyAidlInterface";

        /**
         * Construct the stub at attach it to the interface.
         */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        /**
         * Cast an IBinder object into an com.gebilaolitou.android.aidl.IMyAidlInterface interface,
         * generating a proxy if needed.
         */
        public static com.gebilaolitou.android.aidl.IMyAidlInterface asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.gebilaolitou.android.aidl.IMyAidlInterface))) {
                return ((com.gebilaolitou.android.aidl.IMyAidlInterface) iin);
            }
            return new com.gebilaolitou.android.aidl.IMyAidlInterface.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_connect: {
                    data.enforceInterface(DESCRIPTOR);
                    this.connect();
                    reply.writeNoException();
                    return true;
                }
                case TRANSACTION_sendInMessage: {
                    data.enforceInterface(DESCRIPTOR);
                    com.gebilaolitou.android.aidl.Message _arg0;
                    if ((0 != data.readInt())) {
                        _arg0 = com.gebilaolitou.android.aidl.Message.CREATOR.createFromParcel(data);
                    } else {
                        _arg0 = null;
                    }
                    this.sendInMessage(_arg0);
                    reply.writeNoException();
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }

        private static class Proxy implements com.gebilaolitou.android.aidl.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;
            }
//連接

            @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();
                }
            }
//發送消息  客戶端——> 服務器

            @Override
            public void sendInMessage(com.gebilaolitou.android.aidl.Message 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_sendInMessage, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }
        }

        static final int TRANSACTION_connect = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_sendInMessage = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    }
//連接

    public void connect() throws android.os.RemoteException;
//發送消息  客戶端——> 服務器

    public void sendInMessage(com.gebilaolitou.android.aidl.Message message) throws android.os.RemoteException;
}

們可以看到編譯的后IMyAidlInterface.java文件是一個接口,繼承自android.os.IInterface,仔細觀察IMyAidlInterface接口我們可以發現IMyAidlInterface內部代碼主要分成兩部分,一個是抽象類Stub原來aidl聲明的connect()和sendInMessage()方法

如下圖


IMyAidlInterface結構.png

重點在于Stub類,下面我們來分析一下。從Stub類中我們可以看到是繼承自Binder,并且實現了IMyAidlInterface接口。如下圖:

Stub類.png

Stub類基本結構如下:

  • 靜態方法 asInterface(android.os.IBinder obj)
  • 靜態內部類 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

那我們就依次來分析下,我們先從 asInterface(android.os.IBinder obj) 方法入手

1、靜態方法 asInterface(android.os.IBinder obj)
        /**
         * Cast an IBinder object into an com.gebilaolitou.android.aidl.IMyAidlInterface interface,
         * generating a proxy if needed.
         */
        public static com.gebilaolitou.android.aidl.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.gebilaolitou.android.aidl.IMyAidlInterface))) {
                return ((com.gebilaolitou.android.aidl.IMyAidlInterface) iin);
            }
            //如果沒有,則構造一個IMyAidlInterface.Stub.Proxy對象
            return new com.gebilaolitou.android.aidl.IMyAidlInterface.Stub.Proxy(obj);
        }

上面的代碼可以看到,主要的作用就是根據傳入的Binder對象轉換成客戶端需要的IMyAidlInterface接口。通過之前學過的Binder內容,我們知道:如果客戶端和服務端處于同一進程,那么queryLocalInterface()方法返回就是服務端Stub對象本身;如果是跨進程,則返回一個封裝過的Stub.Proxy,也是一個代理類,在這個代理中實現跨進程通信。那么讓我們來看下Stub.Proxy類

2、onTransact()方法解析

onTransact()方法是根據code參數來處理,這里面會調用真正的業務實現類

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

3、靜態類Stub.Proxy

代碼如下:

private static class Proxy implements com.gebilaolitou.android.aidl.IMyAidlInterface {
            private android.os.IBinder mRemote;

            Proxy(android.os.IBinder remote) {
                mRemote = remote;
            }
            //Proxy的asBinder()返回位于本地接口的遠程代理
            @Override
            public android.os.IBinder asBinder() {
                return mRemote;
            }

            public java.lang.String getInterfaceDescriptor() {
                return DESCRIPTOR;
            }
//連接
            
            @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();
                }
            }
//發送消息  客戶端——> 服務器

            @Override
            public void sendInMessage(com.gebilaolitou.android.aidl.Message 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_sendInMessage, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }
        }

通過上面的代碼,我們知道了幾個重點

  • 1、Proxy 實現了 com.gebilaolitou.android.aidl.IMyAidlInterfac接口,所以他內部有IMyAidlInterface接口的兩個抽象方法
  • 2、Proxy的asBinder()方法返回的mRemote,而這個mRemote是什么時候被賦值的?是在構造函數里面被賦值的。
3.1、靜態類Stub.Proxy的connect()方法和sendInMessage()方法
3.1.1connect()方法解析
  • 1、 通過閱讀靜態類Stub.Proxy的connect()方法,我們容易分析出來里面的兩個android.os.Parcel_data_reply是用來進行跨進程傳輸的"載體"。而且通過字面的意思,很容易猜到,_data用來存儲 客戶端流向服務端 的數據,_reply用來存儲 服務端流向客戶端 的數據。
  • 2、通過mRemote. transact()方法,將_data_reply傳過去
  • 3、通過_reply.readException()來讀取服務端執行方法的結果。
  • 4、最后通過finally回收l_data_reply
3.1.2的相關參數

關于Parcel,這部分我已經在前面,講解過了,如果有不明白的,請看Android跨進程通信IPC之4——AndroidIPC基礎1中的第四部分Parcel類詳解

關于 transact()方法:這是客戶端和和服務端通信的核心方法,也是IMyAidlInterface.Stub繼承android.os.Binder而重寫的一個方法。調起這個方法之后,客戶端將會掛起當前線程,等候服務端執行完相關任務后,通知并接收返回的_reply數據流。關于這個方法的傳參,有注意兩點

  • 1 方法ID:transact()方法第一個參數是一個方法ID,這個是客戶端和服務端約定好的給方法的編碼,彼此一一對應。在AIDL文件轉話為.java時候,系統會自動給AIDL里面的每一個方法自動分配一個方法ID。而這個ID就是咱們說的常量TRANSACTION_connectTRANSACTION_sendInMessage這些常量生成了遞增的ID,是根據你在aidl文件的方法順序而來,然后在IMyAidlInterface.Stub中的onTransact()方法里面switch根據第一個參數code即我們說的ID而來。
  • 2最后的一個參數:transact()方法最后一個參數是一個int值,代表是單向的還是雙向的。具體大家請參考我們前面的文章Android跨進程通信IPC之5——Binder的三大接口中關于IBinder部分。我這里直接說結論:0表示雙向流通,即_reply可以正常的攜帶數據回來。如果為1的話,那么數據只能單向流程,從服務端回來的數據_reply不攜帶任何數據。注意:AIDL生成的.java文件,這個參數均為0

sendInMessage()方法和connect()方法大同小異,這里就不詳細說明了。

3.2 AIDL中關于定向tag的理解

定向tag是AIDL語法的一部分,而in,out,intout,是三個定向的tag

3.2.1 android官方文檔中關于AIDL中定向tag的介紹

https://developer.android.com/guide/components/aidl.html1. Create the .aidl file 里面,原文內容截圖了一部分如下:

All non-primitive parameters require a directional tag indicating which way the data goes . Either in , out , or inout . Primitives are in by default , and connot be otherwise .

翻譯過來就是:所有非基本參數都需要一個定向tag來指出數據流通的方向,不管是in,out,inout。基本參數的定向tag默認是并且只能是in
所以按照上面翻譯的理解,in和out代表客戶端與服務端兩條單向的數據流向,而inout則表示兩端可雙向流通數據的。

3.2.2 總結

AIDL中的定向tag表示在跨進程通信中數據 流向,其中in表示數據只能由客戶端流向服務端,out表示數據只能由服務端流行客戶端,而inout則表示數據可在服務端與客戶端之間雙向流通。其中,數據流向是針對客戶端中的那個傳入方法的對象而言。in為定向tag的話,表現為服務端將會接受到一個那個對象的完整數據,但是客戶端的那個對象不會因為服務端傳參修改而發生變動;out的話表現為服務端將會接收到那個對象的參數為空的對象,但是在服務端對接收到的空對象有任何修改之后客戶端將會同步變動;inout為定向tag的情況下,服務端將會接收到客戶端傳來對象的完整信息,并且客戶端將會同步服務對該對象的任何變動。

4.asBinder()方法

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

(三) IMyAidlInterface .java流程分析

通過閱讀上面源碼,我們來看下AIDL的流程,以上面的例子為例,那么先從客戶端開始

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 ***************連接已經斷開***************");
        }
    };

客戶端中通過Intent去綁定一個服務端的Service。在** onServiceConnected(ComponentName name, IBinder service)**方法中通過返回service可以得到AIDL接口的實例。這是調用了asInterface(android.os.IBinder) 方法完成的。

1.1、asInterface(android.os.IBinder)方法
        /**
         * Cast an IBinder object into an com.gebilaolitou.android.aidl.IMyAidlInterface interface,
         * generating a proxy if needed.
         */
        public static com.gebilaolitou.android.aidl.IMyAidlInterface asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.gebilaolitou.android.aidl.IMyAidlInterface))) {
                return ((com.gebilaolitou.android.aidl.IMyAidlInterface) iin);
            }
            return new com.gebilaolitou.android.aidl.IMyAidlInterface.Stub.Proxy(obj);
        }

在asInterface(android.os.IBinder)我們知道是調用的new com.gebilaolitou.android.aidl.IMyAidlInterface.Stub.Proxy(obj)構造的一個Proxy對象。

所以可以這么說在PushManager中的變量IMyAidlInterface其實是一個IMyAidlInterface.Stub.Proxy對象。

2、調用connect()方法

上文我們說過了PushManager類中的iMyAidlInterface其實IMyAidlInterface.Stub.Proxy對象,所以調用connect()方法其實是IMyAidlInterface.Stub.Proxy的connect()方法。代碼如下:

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

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

2服務端流程

在前面幾篇文章中我們知道Binder傳輸中,客戶端調用transact()對應的是服務端的onTransact()函數,我們在IMyAidlInterface.java中看到

1、獲取IMyAidlInterface.Stub的transact()方法
        @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_connect: {
                    data.enforceInterface(DESCRIPTOR);
                    this.connect();
                    reply.writeNoException();
                    return true;
                }
                case TRANSACTION_sendInMessage: {
                    data.enforceInterface(DESCRIPTOR);
                    com.gebilaolitou.android.aidl.Message _arg0;
                    if ((0 != data.readInt())) {
                        _arg0 = com.gebilaolitou.android.aidl.Message.CREATOR.createFromParcel(data);
                    } else {
                        _arg0 = null;
                    }
                    this.sendInMessage(_arg0);
                    reply.writeNoException();
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }

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

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

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

整體流程如下:

AIDL流程.png

六、AIDL的設計給我們的思考

通過研究我們知道AIDL是基于Binder實現的跨進程通信,但是為什么要設計成AIDL,這么麻煩?

在程序的設計領域,任何的解決方案,無非都是需求和性能兩方面的綜合考量。性能又包括可維護性和可拓展性等。

我們知道android已經有了Binder實現跨進程通信,但是里面涉及很多Service在啟動的時候在ServiceManager中進行注冊,但是在application層面做到這一步基本是無法實現的。所以很有以必要研究一套application層的IPC通信機制。因為android已經提供了Binder機制,如果能重復利用Binder機制豈不是更好,所以就有了現在的AIDL。android是操作系統,要利于開發者去方便操作,所以就應該是設計出一套模板。這樣就很方便了。

由于是跨進程通信,所以我們就需要有一種途徑去訪問它們,在這時候,代理—樁的設計理念就初步成型了。為了達到我們的目的,我們可以在客戶端建立一個服務端的代理,在服務端建立一個客戶端的樁,這樣一來,客戶端有什么需求可以直接和代理通信,代理說你等下,代理就把信息發送給樁,樁把信息給服務端進行處理,處理結束后,服務器告訴樁,樁告訴代理,代理告訴客戶端。這樣一來,客戶端以為代理就是服務端,并且事實是它也只是和代理進行交互,服務端也是也是如此。

類似的跨進程通信機制,我知道還有一個是Hermes,大家有空可以去了解下。

七、總結

AIDL是Android IPC機制中很重要的一部分,AIDL主要是通過Binder來實現進程通信的,其實另一種IPC方式的Message底層也是通過AIDL來實現的。所以AIDL的重要性就不言而喻了。后面我將講解Message.

為了讓大家更好的理解AIDL,我下面補上了三張圖,分別是類圖、流程圖和原理圖。

設計的類圖如下:

類圖.png
AIDL流程圖.png
原理圖.png
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容