一步一步搭建 AIDL 跨進程的回調

關於有許多關於 AIDL 的文章,高質量的也很多,就不打算描述了,主要是想說說實際操作,如何在客戶端與服務端搭建一個回調(觀察者模式)。


搭建同進程中的通信

第一步先搭建一個服務端以及它的 Service ,新建一個 Project。

先彆着急寫代碼,思考下,假設我們新建一個 AIDL 實現兩個簡單的功能:

  • 算出兩個數字的和並返回
  • 對某個字符串取大寫並返回。

那是不是應該在 app(默認 module)中直接添加 AIDL 呢,當然是可以的。

不過我們還有更好的選擇,先說說 AIDL 的定義,Application Interface Defination Language(應用程序接口定義語言) 從本質上還是一個接口吧,服務端實現了該接口的方法,由客戶端調用該方法並傳入需要使用的參數。

那既然是個接口,說明是雙方都可以共用的吧,那麼我們是不是可以使用一個公共的 Library ,讓客戶端和服務端都來依賴這個 Library 呢 ?

那我們來導入一個 Library 。

在這裏插入圖片描述

然後在 main 目錄上右鍵,添加 AIDL 文件。

添加後,發現 main 目錄下多了一個 AIDL 目錄,並且裏面生成了我們剛輸入名字的 AIDL 文件,裏面已經有一個方法,和我們需要的不太一樣,我們把它刪除,然後寫入需要的方法。

interface IMyAidl {
    
    int onNumberPlus(int number1,int number2);

    String onUpperString(String strins);

}

並且點擊 Sync Project With Gradle Files。Android Studio 會爲我們自動生成對應的文件。

不過現在好像沒法知道是不是文件生成好了呢,先不着急,讓我們先讓服務端,即 app(Module) 依賴 commonlib (Library),在 app/build.gradle 文件中添加代碼。

implementation project (':commonlib')

然後在 app Module 中的 Acitivity 中寫一個 IMyAidl;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //測試 aidl 文件是否自動生成好。
        IMyAidl iMyAidl;
    }
}

點擊 IMyAidl 發現是自動生成好的文件,並且上面的提示也不建議我們進行編輯,咱們先暫時不看這個自動生成的,能看見這個文件,說明前面的操作沒有問題,咱們就開始動手編寫服務端的代碼。(如果想認真看看的話,建議使用代碼對齊後查看)

新建一個 Service 類來完成服務端的邏輯。

並且添加之前我們要實現的功能,對兩個數字求和返回與求一個字符串取大寫字符返回。

這裏我們是做的兩個方法的實現,解耦了調用與實現。

public class WorkService extends Service {
    
    private int addNumber(int a, int b){
        return a + b;
    }

    private String toUpperString(String s){
        if(s == null || s.equals("")){
            return null;
        }else{
            return s.toUpperCase();
        }
    }
}

我們的目的是讓客戶端能調用服務端的代碼,現在客戶端需要拿到服務端的一個 Service 實例,那麼肯定是需要在 Service 的 onBind() 中返回一個 IBinder 對象。

在 Service 中新建一個類,並且繼承 IMyAidl.Stub 。

public class WorkBinder extends IMyAidl.Stub{
    @Override
    public int onNumberPlus(int number1, int number2) throws RemoteException {
        //調用前面寫的實現
        return addNumber(number1,number2);
    }

    @Override
    public String onUpperString(String strins) throws RemoteException {
        //調用前面寫的實現
        return toUpperString(strins);
    }
}

這裏也需要實現兩個代碼的邏輯,不過我們在前面已經寫了,那麼直接把前面的代碼加進來, WorkBinder 只負責調用,真正的邏輯實現是在外部。

當然還需要在 WorkService 中增加一個 WorkBinder 的對象,不然我們返回哪個對象是吧,所以在 Service 的 onCreate 中,對WorkBinder 進行初始化,然後在連接成功時返回該 WorkBinder 實例。

然後整理好 WorkService 就是這樣的。

public class WorkService extends Service {
    
    private WorkBinder mWorkBinder;

    @Override
    public void onCreate() {
        super.onCreate();
        //初始化實例
        if(mWorkBinder == null){
            mWorkBinder = new WorkBinder();
        }
    }

    //加法實現
    private int addNumber(int a, int b){
        return a + b;
    }

    //大寫實現
    private String toUpperString(String s){
        if(s == null || s.equals("")){
            return null;
        }else{
            return s.toUpperCase();
        }
    }

    public class WorkBinder extends IMyAidl.Stub{
        @Override
        public int onNumberPlus(int number1, int number2) throws RemoteException {
            return addNumber(number1,number2);
        }

        @Override
        public String onUpperString(String strins) throws RemoteException {
            return toUpperString(strins);
        }
    }

    //返回該 Service 實例時重要的方法。
    //如果是通過 startService 方法時,可以忽略此方法。
    @Override
    public IBinder onBind(Intent intent) {
        if(mWorkBinder == null)
            return null;
        }else{
            return mWorkBinder;
        }
    }
}

好了,我們在 Activity 當中,直接開啓 Service ,並且在連接建立後,嘗試調用AIDL定義的方法。

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "Server.MainActivity";
    //定義爲全局引用,方便後面調用
    IMyAidl myAidl;
    
    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.i(TAG, "onServiceConnected: ");
            myAidl = IMyAidl.Stub.asInterface(service);
            int sum;
            String string = null;
            try {
                sum = myAidl.onNumberPlus(10,20);
                string = myAidl.onUpperString("abcde");
            } catch (RemoteException e) {
                e.printStackTrace();
                sum = -1;
                string = "Error";
            }
            //試着打印下結果
            Log.i(TAG, "onServiceConnected: sum " + sum );
            Log.i(TAG, "onServiceConnected: String " + string );
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.i(TAG, "onServiceDisconnected: " );
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        bindService();
    }
    
    //建立連接
    private boolean bindService(){
        Intent intent = new Intent(this,WorkService.class);
        return bindService(intent,mServiceConnection,BIND_AUTO_CREATE);
    }
    
    @Override
    protected void onDestroy() {
        super.onDestroy();
        //結束時解除連接
        if(mServiceConnection != null){
            unbindService(mServiceConnection);
        }
    }
}

最後不要忘了,在 Manifest 文件中添加 Service。

<application
    ……
    <activity android:name=".MainActivity">
        ……
    </activity>
    <service android:name=".WorkService"/>
</application>

好了,我們啓動來,看看 Log.

com.rambopan.aidlcallback I/MainActivity: Server.onServiceConnected:

com.rambopan.aidlcallback I/MainActivity: Server.onServiceConnected: sum 30

com.rambopan.aidlcallback I/MainActivity: Server.onServiceConnected: String ABCDE

看來沒什麼問題,當然這個 AIDL 此時的作用只是在同一個進程當中。


搭建跨進程的通信

接下來改成跨進程的通訊,搭建客戶端,新建一個 Module 。比如叫 Client。同樣讓客戶端依賴 commonlib (Library)。

在這裏插入圖片描述

客戶端也類似服務端,唯一不同的就是開啓 Service 時需要隱式打開,我們先寫客戶端代碼。

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "Client.MainActivity";
    IMyAidl myAidl;
    
    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.i(TAG, "onServiceConnected: ");
            myAidl = IMyAidl.Stub.asInterface(service);
            int sum;
            String string = null;
            try {
                sum = myAidl.onNumberPlus(40,47);
                string = myAidl.onUpperString("client");
            } catch (RemoteException e) {
                e.printStackTrace();
                sum = -1;
                string = "Error";
            }
            Log.i(TAG, "onServiceConnected: sum " + sum );
            Log.i(TAG, "onServiceConnected: String " + string );
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.i(TAG, "onServiceDisconnected: " );
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        bindService();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //結束時解除連接
        if(mServiceConnection != null){
            unbindService(mServiceConnection);
        }
    }

    private void bindService() {
        Intent intent = new Intent("com.rambopan.commonlib.IMyAidl");
        bindService(intent,mServiceConnection,BIND_AUTO_CREATE);
    }
}

然後我們去服務端那邊,把隱式 Intent 所需的 Action 添加進 Manifest 文件。

<application
    ……
    <activity android:name=".MainActivity">
        ……
    </activity>
    
    <service android:name=".WorkService" >
        <intent-filter>
            <action android:name="com.rambopan.commonlib.IMyAidl"/>
        </intent-filter>
    </service>
    
</application>

這次我們先把之前服務端的 app 重新裝一次,因爲添加了隱式的 action ,然後運行客戶端。
嗯,然後一打開發現崩潰了,咱們來瞅瞅日誌。

12-14 23:52:41.530 12552-12552/com.rambopan.client E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.rambopan.client, PID: 12552
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.rambopan.client/com.rambopan.client.MainActivity}: java.lang.IllegalArgumentException: Service Intent must be explicit: Intent { act=com.rambopan.commonlib.IMyAidl }
    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2315)
    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2375)
    at android.app.ActivityThread.access$900(ActivityThread.java:147)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1283)
    at android.os.Handler.dispatchMessage(Handler.java:102)
    at android.os.Looper.loop(Looper.java:135)
    at android.app.ActivityThread.main(ActivityThread.java:5254)
    at java.lang.reflect.Method.invoke(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:372)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:910)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:705)
 Caused by: java.lang.IllegalArgumentException: Service Intent must be explicit: Intent { act=com.rambopan.commonlib.IMyAidl }
    at android.app.ContextImpl.validateServiceIntent(ContextImpl.java:1686)
    at android.app.ContextImpl.bindServiceCommon(ContextImpl.java:1785)
    at android.app.ContextImpl.bindService(ContextImpl.java:1763)
    at android.content.ContextWrapper.bindService(ContextWrapper.java:548)
    at com.rambopan.client.MainActivity.bindService(MainActivity.java:52)
    at com.rambopan.client.MainActivity.onCreate(MainActivity.java:47)
    at android.app.Activity.performCreate(Activity.java:5993)
    at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1105)

說需要指定爲顯示的,我們明明要通過其他應用來開啓 Service ,肯定是需要隱式的。google 一下,發現是缺少一些信息,在 5.0 之後會拋出異常,找到一個解決方案,增加自定義類的包名。

具體分析可查看這篇文章:https://www.jianshu.com/p/08902fab84d4

然後修改 bindService 代碼,添加包名。

private void bindService() {
    Intent intent = new Intent("com.rambopan.commonlib.IMyAidl");
    //添加目標Service的包名
    intent.setPackage("com.rambopan.aidlcallback");
    bindService(intent,mServiceConnection,BIND_AUTO_CREATE);
}

修改之後我們先把之前服務端開啓的應用從後臺清除,再運行客戶端,觀察下日誌。

com.rambopan.client I/Client.MainActivity: onServiceConnected:

com.rambopan.client I/Client.MainActivity: onServiceConnected: sum 87

com.rambopan.client I/Client.MainActivity: onServiceConnected: String CLIENT

沒有啓動服務端,只啓動客戶端,也成功調起了服務端的服務。


搭建一個回調

現在已經實現了基本的調用操作,那麼 …… 如果我們想實現這樣一個功能:

開啓服務端,讓服務端不斷的執行數據處理,然後回調給客戶端,假設從 1 開始 ,每兩秒發送 +1 的結果給客戶端。

按照類似 setOnClickListener 的方式,客戶端給服務端設置一個監聽,由服務端把數據回調給客戶端,我們先來試一下。

然後新加入一個 AIDL 文件 CountListener 作爲回調的接口。

interface CountListener {

    //計數的接口
    void onGetNumber(int number);
}

先在 Library 中的 AIDL 加入四個新的方法:開始計數、是停止計數、註冊回調、反註冊回調。

interface IMyAidl {

    int onNumberPlus(int number1,int number2);

    String onUpperString(String strins);

    //開始計數
    boolean onStartCount();

    //停止計數
    boolean onStopCount();

    //註冊回調
    void registerListener(CountListener listener);

    //反註冊回調
    void unregisterListener(CountListener listener);
}

因爲對 CountListener 的引用,在 IMyAidl 文件中,對 CountListener 進行導入。注意觀察註釋,注意必須手動導入。

既然修改了 AIDL 文件,那麼服務端的實現邏輯肯定會變。

主要是增加了自動生成要傳遞的數字,以及註冊接口。

public class WorkService extends Service {
    private static final String TAG = "Server.WorkService";

    private WorkBinder mWorkBinder;
    //開啓線程來遞增數字
    private Thread mThread;
    //回調接口
    private CountListener mCountListener;
    //遞增數字
    private int mNumber = 0;
    //開始與結束線程
    private boolean isStart = false;

    @Override
    public void onCreate() {
        super.onCreate();
        if(mWorkBinder == null){
            mWorkBinder = new WorkBinder();
        }
    }

    private int addNumber(int a, int b){
        return a + b;
    }

    private String toUpperString(String s){
        if(s == null || s.equals("")){
            return null;
        }else{
            return s.toUpperCase();
        }
    }

    //開始計數的實現 開啓線程,2秒+1
    private boolean startCount(){
        if(!isStart && mThread == null){
            mThread = new Thread(new Runnable() {
                @Override
                public void run() {
                    while(isStart){
                        mNumber++;
                        if(mCountListener != null){
                            try {
                                mCountListener.onGetNumber(mNumber);
                            } catch (RemoteException e) {
                                Log.w(TAG, "deliver number Error  " );
                                e.printStackTrace();
                            }
                        }
                        try {
                            Thread.sleep(2000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            });
            isStart = true;
            mThread.start();
            return true;
        }
        return false;
    }

    //停止計數的實現
    private boolean stopCount(){
        if(isStart && mThread != null){
            isStart = false;
            return true;
        }
        return false;
    }

    //註冊回調實現
    private void registerListenerImp(CountListener listener){
        Log.i(TAG, "registerListener: listener : " + listener);
        if(listener != null){
            mCountListener = listener;
        }
    }
    
    //反註冊回調實現
    private void unregisterListenerImp(CountListener listener){
        Log.i(TAG, "unregisterListenerImp: listener : " + listener);
        mCountListener = null;
    }

    //包裝一層,調用 Service 中的實現。
    public class WorkBinder extends IMyAidl.Stub{
        @Override
        public int onNumberPlus(int number1, int number2) throws RemoteException {
            return addNumber(number1,number2);
        }

        @Override
        public String onUpperString(String strins) throws RemoteException {
            return toUpperString(strins);
        }

        @Override
        public boolean onStartCount() throws RemoteException {
            return startCount();
        }

        @Override
        public boolean onStopCount() throws RemoteException {
            return stopCount();
        }

        @Override
        public void registerListener(CountListener listener) throws RemoteException {
           registerListenerImp(listener);
        }

        @Override
        public void unregisterListener(CountListener listener) throws RemoteException {
            unregisterListenerImp(listener);
        }
    }

    //返回該 Service 實例時重要的方法。
    //如果是通過 startService 方法時,可以忽略此方法。
    @Override
    public IBinder onBind(Intent intent) {
        if(mWorkBinder == null){
            return null;
        } else{
            return mWorkBinder;
        }
    }
}

服務端實現修改好了,我們繼續回到客戶端。
客戶端先增加兩個按鈕,方便我們點擊註冊與反註冊。

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

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!" />

    <Button
        android:id="@+id/btn_start_count"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Start_Count"
        />

    <Button
        android:id="@+id/btn_stop_count"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Stop_Count"/>

</LinearLayout>

MainActivity 當中主要是增加 CountListener.Stub 類,關於回調的傳遞、註冊、實現。

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "Client.MainActivity";
    IMyAidl myAidl;

    private CountClientStub mCountClientStub;
    //新建一個繼承 CountListener.Stub的類作爲傳入的對象。
    private class CountClientStub extends CountListener.Stub{
        @Override
        public void onGetNumber(int number) throws RemoteException {
            Log.i(TAG, "onGetNumber: ---> " + number);
        }
    }

    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.i(TAG, "onServiceConnected: ");
            myAidl = IMyAidl.Stub.asInterface(service);
            int sum;
            String string = null;
            try {
                sum = myAidl.onNumberPlus(40,47);
                string = myAidl.onUpperString("client");
            } catch (RemoteException e) {
                e.printStackTrace();
                sum = -1;
                string = "Error";
            }
            Log.i(TAG, "onServiceConnected: sum " + sum );
            Log.i(TAG, "onServiceConnected: String " + string );
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.i(TAG, "onServiceDisconnected: " );
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        bindService();

        //傳入一個 Listener
        if(mCountClientStub == null) {
            mCountClientStub = new CountClientStub();
        }
        Button btnStartCount = findViewById(R.id.btn_start_count);
        Button btnStopCount = findViewById(R.id.btn_stop_count);
        btnStartCount.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(myAidl != null){
                    try {
                        myAidl.registerListener(mCountClientStub);
                        myAidl.onStartCount();
                    } catch (RemoteException e) {
                        e.printStackTrace();
                        Log.w(TAG, "onClick: registerListener RemoteException" );
                    }
                }
            }
        });
        btnStopCount.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(myAidl != null){
                    try {
                        myAidl.onStopCount();
                        myAidl.unregisterListener(mCountClientStub);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                        Log.w(TAG, "onClick: unregisterListener RemoteException" );
                    }
                }
            }
        });
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //結束時解除連接
        if(mServiceConnection != null){
            unbindService(mServiceConnection);
        }
    }

    private void bindService() {
        Intent intent = new Intent("com.rambopan.commonlib.IMyAidl");
        intent.setPackage("com.rambopan.aidlcallback");
        bindService(intent,mServiceConnection,BIND_AUTO_CREATE);
    }
}

現在把服務端客戶端重新裝一次,再試一次。

12-15 16:09:57.543 25584-25584/com.rambopan.client I/Client.MainActivity: onServiceConnected:

12-15 16:09:57.543 25584-25584/com.rambopan.client I/Client.MainActivity: onServiceConnected: sum 87

12-15 16:09:57.543 25584-25584/com.rambopan.client I/Client.MainActivity: onServiceConnected: String CLIENT

12-15 16:10:11.276 25606-25623/com.rambopan.aidlcallback I/Server.WorkService: registerListener: listener : com.rambopan.commonlib.CountListenerStubStubProxy@cd84546

12-15 16:10:11.306 25584-25600/com.rambopan.client I/Client.MainActivity: onGetNumber: —> 1

12-15 16:10:13.308 25584-25601/com.rambopan.client I/Client.MainActivity: onGetNumber: —> 2

12-15 16:10:15.310 25584-25600/com.rambopan.client I/Client.MainActivity: onGetNumber: —> 3

12-15 16:10:17.312 25584-25601/com.rambopan.client I/Client.MainActivity: onGetNumber: —> 4

12-15 16:10:19.314 25584-25600/com.rambopan.client I/Client.MainActivity: onGetNumber: —> 5

12-15 16:10:21.316 25584-25601/com.rambopan.client I/Client.MainActivity: onGetNumber: —> 6


傳遞非基本類型數據

成功了,那麼我們如果回調,不只是傳基本類型,要是傳一些類的對象呢,那肯定是需要對該類實現序列化的接口。才能在一個進程中序列化寫入,另一個進程序列化讀出。

在 aidl 目錄下,新建一個新的文件,Person。只需要寫一句話。

parcelable Person;

然後在相同的包下,java 目錄下,新建一個類,也叫 Person。並且實現 Parcelable。

我們增加一個名字和一個年齡屬性,然後自動生成代碼。

實現 Parcelable 的目的,就是通過 Parcelable 能過將對象序列化和反序列化,可以從 protected Person(Parcel in) 看出。

public class Person implements Parcelable {

    private String mName;
    private int mAge;

    protected Person(Parcel in) {
        mName = in.readString();
        mAge = in.readInt();
    }

    //新增構造函數。
    public Person(String name,int age){
        mName = name;
        mAge = age;
    }
    
    public static final Creator<Person> CREATOR = new Creator<Person>() {
        @Override
        public Person createFromParcel(Parcel in) {
            return new Person(in);
        }

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

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

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(mName);
        dest.writeInt(mAge);
    }
    
    @Override
    public String toString() {
        return " mName : " + mName +
                " mAge : " + mAge;
    }
}

我們增加了一個兩個參數的構造函數。並且複寫了 toString 方法,方便一會打印參數。

Person 類準備好了,我們去 CountListener 當中新增加一個方法,並且加入需要導入的 Person類。

import com.rambopan.commonlib.Person;

interface CountListener {

    //計數的接口
    void onGetNumber(int number);

    //獲取每個人的信息
    void onGetPersonInfo(in Person person);
}

注意:Person 前面需要增加 in 關鍵字。

我們分別對服務端客戶端代碼進行修改。

服務端:增加構造 Person 類的方法。

Random mRandom = new Random();

//開始計數的實現 開啓線程,2秒+1
private boolean startCount(){
    if(!isStart && mThread == null){
        mThread = new Thread(new Runnable() {
            @Override
            public void run() {
                while(isStart){
                    mNumber++;
                    if(mCountListener != null){
                        try {
                            mCountListener.onGetNumber(mNumber);

                            //隨機生成 A - Z 的字母作爲名字
                            String name = Character.toString((char)(mRandom.nextInt(26) + 65));
                            //隨機生成 0 - 20 的數字作爲年齡
                            int age = mRandom.nextInt(20);
                            Person person = new Person(name,age);

                            mCountListener.onGetPersonInfo(person);
                        } catch (RemoteException e) {
                            Log.w(TAG, "deliver number Error  " );
                            e.printStackTrace();
                        }
                    }
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        isStart = true;
        mThread.start();
        return true;
    }
    return false;
}

客戶端增加打印的消息。

private class CountClientStub extends CountListener.Stub{
    @Override
    public void onGetNumber(int number) throws RemoteException {
        Log.i(TAG, "onGetNumber: ---> " + number);
    }

    @Override
    public void onGetPersonInfo(Person person) throws RemoteException {
        Log.i(TAG, "onGetPersonInfo: ---> Person : " + person);
    }
}

好,現在來運行一下。

12-15 16:49:46.764 26999-27017/com.rambopan.aidlcallback I/Server.WorkService: registerListener: listener : com.rambopan.commonlib.CountListenerStubStubProxy@cd84546

12-15 16:49:46.764 26963-26988/com.rambopan.client I/Client.MainActivity: onGetNumber: —> 1

12-15 16:49:46.774 26963-26988/com.rambopan.client I/Client.MainActivity: onGetPersonInfo: —> Person : mName : I mAge : 15

12-15 16:49:48.776 26963-26988/com.rambopan.client I/Client.MainActivity: onGetNumber: —> 2

12-15 16:49:48.786 26963-26990/com.rambopan.client I/Client.MainActivity: onGetPersonInfo: —> Person : mName : F mAge : 8

12-15 16:49:50.788 26963-26988/com.rambopan.client I/Client.MainActivity: onGetNumber: —> 3

12-15 16:49:50.788 26963-26990/com.rambopan.client I/Client.MainActivity: onGetPersonInfo: —> Person : mName : P mAge : 7

12-15 16:49:52.789 26963-26988/com.rambopan.client I/Client.MainActivity: onGetNumber: —> 4

12-15 16:49:52.789 26963-26990/com.rambopan.client I/Client.MainActivity: onGetPersonInfo: —> Person : mName : M mAge : 11

12-15 16:49:54.801 26963-26988/com.rambopan.client I/Client.MainActivity: onGetNumber: —> 5

12-15 16:49:54.801 26963-26990/com.rambopan.client I/Client.MainActivity: onGetPersonInfo: —> Person : mName : K mAge : 8

嗯,成功了。


更方便的使用

現在可以把 commonlib 共享出去,給需要使用的其他工程依賴。那麼依賴很麻煩,有沒有更簡單的方法,比如打個 jar 包,然後讓其他應用直接丟進 lib 文件夾依賴。

當然是可以的,我們可以先 clean 下工程,再 rebuid ,在這個目錄下可以看見生成的 jar 文件。(不同 Android Studio 版本可能有差異)

/commonlib/build/intermediates/packaged-classes/debug/

在這裏插入圖片描述

然後把 jar 文件分別放進客戶端與服務端,再分別修改 build.gradle :註釋掉之前依賴的 commonlib。並確保下一行代碼存在。

//implementation project (':commonlib')
//依賴 libs 目錄下所有 jar 類型文件。
implementation fileTree(dir: 'libs', include: ['*.jar'])

在這裏插入圖片描述

替換了文件,然後重新同步下工程,再點擊 IMyAidl ,已經提示是通過 .class 文件反編譯的,無法修改了,說明是 jar 包生效了。

在這裏插入圖片描述

這樣保證了工程在共享過程中不會被隨意改動。


那還有沒有什麼可以優化的地方呢,要是每個客戶端用的時候,都要寫一次綁定服務的代碼,是不是也可以把這重複的部分抽出來呢 ? 當然是可以的啦。

來創建一個輔助單例類 ConnectAssist,把綁定,解綁定等做成公共的部分(當然確保客戶端不會添加新的功能)。

  • 把連接相關的邏輯放進來。
  • 把 AIDL 的方法包裝一層,或者做一個獲取 AIDL 的公共方法。爲了簡單,就用獲取 AIDL 好了。

在這裏插入圖片描述

public class ConnectAssist {
    private static final String TAG = "ConnectAssist";
    private static ConnectAssist connectAssist;
    private IMyAidl myAidl;
    private Context mContext;

    private ConnectAssist(Context context){
        mContext = context;
    }

    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.i(TAG, "onServiceConnected: ");
            myAidl = IMyAidl.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.i(TAG, "onServiceDisconnected: " );
        }
    };

    public static synchronized ConnectAssist getInstance(Context context){
        if(connectAssist == null){
            synchronized (ConnectAssist.class){
                if(connectAssist == null){
                    connectAssist = new ConnectAssist(context.getApplicationContext());
                }
            }
        }
        return connectAssist;
    }

    public boolean bindService() {
        Intent intent = new Intent("com.rambopan.commonlib.IMyAidl");
        intent.setPackage("com.rambopan.aidlcallback");
        return mContext.bindService(intent,mServiceConnection,BIND_AUTO_CREATE);
    }
    
    public boolean unbindService(){
        if(mServiceConnection != null){
            mContext.unbindService(mServiceConnection);
            return true;
        }else{
            return false;
        }
    }

    //獲取 AIDL 接口
    public IMyAidl getMyAidl(){
        return myAidl;
    }
}

重新生成 jar 文件,然後替換客戶端 jar 包,替換 jar 包後同步下工程,簡單調整客戶點綁定代碼。

調整後如圖,其實如果不是兩個 OnClickListener 和 CountClientStub 類 ,還是挺簡潔的 ……

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "Client.MainActivity";
    IMyAidl myAidl;

    private CountClientStub mCountClientStub;
    //新建一個繼承 CountListener.Stub的類作爲傳入的對象。
    private class CountClientStub extends CountListener.Stub{
        @Override
        public void onGetNumber(int number) throws RemoteException {
            Log.i(TAG, "onGetNumber: ---> " + number);
        }

        @Override
        public void onGetPersonInfo(Person person) throws RemoteException {
            Log.i(TAG, "onGetPersonInfo: ---> Person : " + person);
        }
    }

    ConnectAssist connectAssist;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //bindService();

        connectAssist = ConnectAssist.getInstance(this);
        connectAssist.bindService();

        //傳入一個 Listener
        if(mCountClientStub == null) {
            mCountClientStub = new CountClientStub();
        }
        Button btnStartCount = findViewById(R.id.btn_start_count);
        Button btnStopCount = findViewById(R.id.btn_stop_count);
        btnStartCount.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //嘗試在使用之前判斷一次,如果爲 null 就取一次
                if(myAidl == null){
                    myAidl = connectAssist.getMyAidl();
                }
                if(myAidl != null){
                    try {
                        myAidl.registerListener(mCountClientStub);
                        myAidl.onStartCount();
                    } catch (RemoteException e) {
                        e.printStackTrace();
                        Log.w(TAG, "onClick: registerListener RemoteException" );
                    }
                }
            }
        });
        btnStopCount.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //嘗試在使用之前判斷一次,如果爲 null 就取一次
                if(myAidl == null){
                    myAidl = connectAssist.getMyAidl();
                }
                if(myAidl != null){
                    try {
                        myAidl.onStopCount();
                        myAidl.unregisterListener(mCountClientStub);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                        Log.w(TAG, "onClick: unregisterListener RemoteException" );
                    }
                }
            }
        });
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //結束時解除連接
        connectAssist.unbindService();
    }
}

終於圓滿了,當然在操作過程中也碰到一些需要思考的地方,比如。

  • WorkBinder 爲什麼要繼承 IMyAidl.Stub ?

那我們先把 commonlib 中的 IMyAidl 點開,看看是什麼結構。

public interface IMyAidl extends android.os.IInterface {
    
    ……
    
    public static abstract class Stub extends android.os.Binder implements com.rambopan.commonlib.IMyAidl {
        
        ……
        
        private static class Proxy implements com.rambopan.commonlib.IMyAidl {
            
            ……
        }
    }
}


public class Binder implements IBinder {
    ……
}

可以看到 IMyAidl.Stub 繼承了 Binder 類,Binder 類對 IBinder 進行了實現。所以 Stub 可以作爲 IBinder 類型返回。

看看 服務端 ServiceConnection 代碼。

private ServiceConnection mServiceConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        Log.i(TAG, "onServiceConnected: ");
        myAidl = IMyAidl.Stub.asInterface(service);
        ……
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        Log.i(TAG, "onServiceDisconnected: " );
    }
};

從名字上可以看出,是把 IBinder 類型作爲定義的接口類型返回,而 Stub 類,就是不光繼承了 IBinder 類,同時也實現了定義的 AIDL 接口類 IMyAidl 。

    public static com.rambopan.commonlib.IMyAidl asInterface(android.os.IBinder obj) {
        if ((obj == null)) {
            return null;
        }
        android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
        if (((iin != null) && (iin instanceof com.rambopan.commonlib.IMyAidl))) {
            return ((com.rambopan.commonlib.IMyAidl) iin);
        }
        return new com.rambopan.commonlib.IMyAidl.Stub.Proxy(obj);
    }

點開 asInterface() 方法,可以看出,是對 IBinder 類進行判斷。

  • 如果在本地能查詢到該類型 IInterface ,則向上轉型。

  • 如果查詢不到,則使用代理類,對 IBinder 類進行代理,返回一個該類型 IInterface。

      private static class Proxy implements com.rambopan.commonlib.IMyAidl {
          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 int onNumberPlus(int number1, int number2) throws android.os.RemoteException {
              android.os.Parcel _data = android.os.Parcel.obtain();
              android.os.Parcel _reply = android.os.Parcel.obtain();
              int _result;
              try {
                  _data.writeInterfaceToken(DESCRIPTOR);
                  _data.writeInt(number1);
                  _data.writeInt(number2);
                  mRemote.transact(Stub.TRANSACTION_onNumberPlus, _data, _reply, 0);
                  _reply.readException();
                  _result = _reply.readInt();
              } finally {
                  _reply.recycle();
                  _data.recycle();
              }
              return _result;
          }
      
          ……
      
      }
    

點擊 Proxy 類,能看到持有一個 IBInder 類型引用,幾個方法都基本類似,我們就只分析一個 onNumberPlus。

Proxy 類當中的各種方法,就是將數據變成序列化之後,對每個方法加上唯一的描述符,然後調用 mRemote 引用中的 transact() 方法,把需要調用該方法的數據傳入,等待 mRemote 調用結束之後,再從回覆當中讀取數據,就完成了一次跨進程的調用。

此處的 mRemote ,就是外層的 Stub 類,來看看 Stub 類中對應的方法,發現並沒有找到 transact 方法,那去它的父類 IBinder 類中看看。

public final boolean transact(int code, @NonNull Parcel data, @Nullable 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;
}

觀察 transact 執行的邏輯,主要還是在 onTransact 方法中,雖然 IBinder 當中是存在 onTransact 的,不過 Stub 類中已經重寫了該類。在我們定義的這幾種操作的情況下,會執行我們寫的邏輯,其他情況下調用 super 。

public static abstract class Stub extends android.os.Binder implements com.rambopan.commonlib.IMyAidl {
    private static final java.lang.String DESCRIPTOR = "com.rambopan.commonlib.IMyAidl";

    ……

    @Override
    public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
        java.lang.String descriptor = DESCRIPTOR;
        switch (code) {
            case INTERFACE_TRANSACTION: {
                reply.writeString(descriptor);
                return true;
            }
            case TRANSACTION_onNumberPlus: {
                data.enforceInterface(descriptor);
                int _arg0;
                _arg0 = data.readInt();
                int _arg1;
                _arg1 = data.readInt();
                int _result = this.onNumberPlus(_arg0, _arg1);
                reply.writeNoException();
                reply.writeInt(_result);
                return true;
            }
            
            ……
            
            default: {
                return super.onTransact(code, data, reply, flags);
            }
        }
    }
    ……
}

可以看到執行的邏輯,先判斷對應的 transact 當時寫入的每個操作對應的唯一描述符,先從序列化中讀出數據,再調用 this.onNumberPlus() ,再把結果寫入。

而 this 此時就是指每個 Stub 對象,而我們在 WorkBinder 定義當中已經複寫這幾個操作的邏輯,所以現在繞了一圈,所有線索都拼接上了。也算是把 AIDL 流程簡單熟悉了下。


  • 爲什麼要在 myAidl 使用前進行一次 (myAidl == null) 的判斷?在 onCreate 當中加入獲取不行麼?

當然是可以的,不過如果直接在 bindService 之後去獲取的話,可能是 null 。

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        connectAssist = ConnectAssist.getInstance(this);
        connectAssist.bindService();
        //測試
        myAidl = connectAssist.getMyAidl();
        Log.i(TAG, "ConnectAssist: ---> myAidl : " + myAidl);
        ……
    }

我們加入 log 測試,查看一下。

11:32:13.418 12674-12674/com.rambopan.client I/Client.MainActivity: ConnectAssist: —> myAidl : null

11:32:13.448 12674-12674/com.rambopan.client I/ConnectAssist: onServiceConnected:

從結果來看,Service 連接成功還需要一點時間,所以 bindService 是異步執行的,所以等沒有等待連接成功或失敗之後再執行剩下的代碼。

Demo 地址:https://github.com/RamboPan/demo_aidlcallback

  • 技術淺薄,如果存在任何問題或錯誤,歡迎指出,會予以確認與改正。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章