1. 程式人生 > >AIDL 客戶端與服務端的雙向通訊

AIDL 客戶端與服務端的雙向通訊

時隔一年半了,終於寫下了這個續篇,我發現我的很多部落格有頭無尾,都是有前面一點點,後面就沒寫去了,也正在想辦法都補上

今天聊聊的是客戶端和服務端的相互通訊,何謂雙向通訊,事實上,我們在上一篇的部落格中,只是講解了客戶端請求服務端的方法,然後服務端返回一個值給我們這樣,其實是最簡單的用法,但是常常在我們的開發過程中,如果呼叫了某些方法,比如網路請求,那麼就需要等待請求有結果了之後再回調給我們,這個回撥的過程就是服務端向客戶端通訊,作為ipc通訊的一種,如果你不會雙向通訊,那麼你可以比較low的用廣播,但是我還是建議你直接用一整套的AIDL複用,好的,那麼問題來了,我們怎麼下手呢?

服務端

我們新建兩個工程,一個叫ADILClient,一個叫AIDLService,分別代表的是客戶端和服務端

這裡寫圖片描述

我們現在開始編寫我們的aidl,這裡我需要編寫兩個AIDL檔案,一個是我們對外的方法,一個是我們對外的回撥方法,如圖

這裡寫圖片描述

這裡我做一下講解,首先我new了一個aidl的資料夾,在main下,和java同級,然後定義了一個公共的包名:com.android.openimpl,最後在裡面實現了兩個aidl檔案,我們來看下具體的檔案內容

IMyLifeStyleInterface

// IMyLifeStyleInterface.aidl
package com.android.openimpl;

import com.android.openimpl.IMyLifeStyleListener;

interface
IMyLifeStyleInterface {
//計算 void sum(int a ,int b); //睡覺 void sleep(boolean isSleep); //註冊 void registerCallback(IMyLifeStyleListener il); //解綁 void unregisterCallback(IMyLifeStyleListener il); }

IMyLifeStyleListener

// IMyLifeStyleListener.aidl
package com.android.openimpl;

//回撥介面
interface IMyLifeStyleListener { //回撥方法 void OnCallBackSleep(String text); void OnCallBackSize(int size); }

這裡我定義了IMyLifeStyleInterface ,裡面有兩個方法,假設我定義的事一個人,他有基本的兩個本能,一個是計算,我傳兩個數字給他,他進行一系列的處理,那麼,問題來, 我不想用返回值,我想通過回撥知道,這是一點,另一個是睡覺,而在IMyLifeStyleListener,我也定義了兩個對應的回撥OnCallBackSleep和OnCallBackSize,好了,現在開始來實現我們的遠端Service服務

public class OpenImplService extends Service {

    private IMyLifeStyleListener lifeStyleListener;

    private IBinder mBinder = new IMyLifeStyleInterface.Stub() {
        @Override
        public void sum(int a, int b) throws RemoteException {
            //經過一系列的計算後將值告知客戶端
            int c = a * 2 + b;
            if (lifeStyleListener != null) {
                lifeStyleListener.OnCallBackSize(c);
            }
        }

        @Override
        public void sleep(boolean isSleep) throws RemoteException {
            //告訴客戶端我已經睡著
            if(isSleep){
                if (lifeStyleListener != null) {
                    lifeStyleListener.OnCallBackSleep("幫我關下燈,謝謝!");
                }
            }
        }

        @Override
        public void registerCallback(IMyLifeStyleListener il) throws RemoteException {
            if (il != null) {
                lifeStyleListener = il;
            }
        }

        @Override
        public void unregisterCallback(IMyLifeStyleListener il) throws RemoteException {
            if (il != null) {
                lifeStyleListener = null;
            }
        }
    };

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

這裡,我定義了一個遠端的服務OpenImplService,裡面我只是new了IMyLifeStyleInterface.Stub並且把Binder物件給了onBind方法,而在Binder內部,我做的操作相信大家都看的明白吧,很簡單,Ok,那我們的服務端就已經搞定了,我們來看下服務端的整體結構

這裡寫圖片描述

當然,別忘了在清單檔案中註冊

       <service
            android:name=".service.OpenImplService"
            android:enabled="true"
            android:exported="true" />

客戶端

好的,現在就開始來實現我們的客戶端,客戶端也需要同樣的AIDL檔案,所以我可以直接複製過去,但是要注意的是包名一定要相同,如圖

這裡寫圖片描述

這裡,我的客戶端就是app包下的東西,那麼我們來實現UI上的邏輯

<?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"
    android:padding="10dp">

    <EditText
        android:id="@+id/et_numer_1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="請輸入a" />

    <EditText
        android:id="@+id/et_numer_2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="請輸入b" />

    <Button
        android:id="@+id/btn_sum"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="計算"
        android:textAllCaps="false" />

    <Button
        android:id="@+id/btn_sleep"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="睡覺"
        android:textAllCaps="false" />

</LinearLayout>

這裡,我定義了兩個輸入框和有個按鈕,對應的計算和睡覺,好的,我們run一下

這裡寫圖片描述

我們來開始實現具體的邏輯了,這也是我們客戶端經常要乾的事情首先,我們initService來初始化了服務,在android5.0之後繫結遠端服務都需要完整的包名了,如下

    private void initService() {
        Intent i = new Intent();
        //Android 5.0 之後需要直接定義包名
        i.setComponent(new ComponentName("com.liuguilin.aidlservice", "com.liuguilin.aidlservice.service.OpenImplService"));
        bindService(i, mConnection, Context.BIND_AUTO_CREATE);
    }

這裡需要傳一個mConnection,這是一個對應的關係,如果現在bind,你銷燬的時候需要unBind

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(mConnection);
    }

好的,我們具體來看下mConnection的內容吧

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            Log.i(TAG, "onServiceConnected");
            //獲取AIDL物件
            iMyLifeStyleInterface = IMyLifeStyleInterface.Stub.asInterface(iBinder);
            //註冊介面
            try {
                iMyLifeStyleInterface.registerCallback(new OpenImpleListener());
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            isBind = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            Log.i(TAG, "onServiceDisconnected");
            try {
                iMyLifeStyleInterface.unregisterCallback(new OpenImpleListener());
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            iMyLifeStyleInterface = null;
            isBind = false;
        }
    };

他一共會重寫兩個方法,服務繫結onServiceConnected和服務解綁onServiceDisconnected,所以這裡我們用了一個標誌位isBind來標記繫結狀態,然後通過AIDL的asInterface方法來例項化對應的AIDL物件,然後就是註冊這個介面回調了

    class OpenImpleListener extends IMyLifeStyleListener.Stub {

        @Override
        public void OnCallBackSleep(String text) throws RemoteException {
            Toast.makeText(MainActivity.this, "text:" + text, Toast.LENGTH_SHORT).show();
        }

        @Override
        public void OnCallBackSize(int size) throws RemoteException {
            Toast.makeText(MainActivity.this, "size:" + size, Toast.LENGTH_SHORT).show();
        }
    }

這裡要注意的事需要繼承的是AIDL的Stub,最後就是我們的點選事件了

    @Override
    public void onClick(View v) {
        if (isBind) {
            switch (v.getId()) {
                case R.id.btn_sum:
                    String a = et_numer_1.getText().toString().trim();
                    String b = et_numer_2.getText().toString().trim();
                    try {
                        iMyLifeStyleInterface.sum(Integer.parseInt(a),Integer.parseInt(b));
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    break;
                case R.id.btn_sleep:
                    try {
                        iMyLifeStyleInterface.sleep(true);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    break;
            }
        } else {
            Toast.makeText(this, "服務未連線", Toast.LENGTH_SHORT).show();
        }
    }

這裡算是比較簡單的了,傳值呼叫即可,好的,下面貼上全部的程式碼

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private static final String TAG = "AIDL";

    private EditText et_numer_1;
    private EditText et_numer_2;
    private Button btn_sum;
    private Button btn_sleep;

    //繫結狀態
    private static boolean isBind = false;

    private IMyLifeStyleInterface iMyLifeStyleInterface;

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            Log.i(TAG, "onServiceConnected");
            //獲取AIDL物件
            iMyLifeStyleInterface = IMyLifeStyleInterface.Stub.asInterface(iBinder);
            //註冊介面
            try {
                iMyLifeStyleInterface.registerCallback(new OpenImpleListener());
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            isBind = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            Log.i(TAG, "onServiceDisconnected");
            try {
                iMyLifeStyleInterface.unregisterCallback(new OpenImpleListener());
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            iMyLifeStyleInterface = null;
            isBind = false;
        }
    };

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

        initService();
        initView();
    }

    private void initService() {
        Intent i = new Intent();
        //Android 5.0 之後需要直接定義包名
        i.setComponent(new ComponentName("com.liuguilin.aidlservice", "com.liuguilin.aidlservice.service.OpenImplService"));
        bindService(i, mConnection, Context.BIND_AUTO_CREATE);
    }

    private void initView() {
        et_numer_1 = (EditText) findViewById(R.id.et_numer_1);
        et_numer_2 = (EditText) findViewById(R.id.et_numer_2);
        btn_sum = (Button) findViewById(R.id.btn_sum);
        btn_sleep = (Button) findViewById(R.id.btn_sleep);

        btn_sum.setOnClickListener(this);
        btn_sleep.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        if (isBind) {
            switch (v.getId()) {
                case R.id.btn_sum:
                    String a = et_numer_1.getText().toString().trim();
                    String b = et_numer_2.getText().toString().trim();
                    try {
                        iMyLifeStyleInterface.sum(Integer.parseInt(a),Integer.parseInt(b));
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    break;
                case R.id.btn_sleep:
                    try {
                        iMyLifeStyleInterface.sleep(true);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    break;
            }
        } else {
            Toast.makeText(this, "服務未連線", Toast.LENGTH_SHORT).show();
        }
    }

    class OpenImpleListener extends IMyLifeStyleListener.Stub {

        @Override
        public void OnCallBackSleep(String text) throws RemoteException {
            Toast.makeText(MainActivity.this, "text:" + text, Toast.LENGTH_SHORT).show();
        }

        @Override
        public void OnCallBackSize(int size) throws RemoteException {
            Toast.makeText(MainActivity.this, "size:" + size, Toast.LENGTH_SHORT).show();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(mConnection);
    }
}

可以發現,程式碼很是簡潔,希望大家能夠學會,最後我們執行一遍

這裡寫圖片描述

這裡可以看到,點選睡覺,服務端會叫我們關下燈,而計算的話,我傳的是2和3,服務端計算了一下 2 * 2 + 3 = 7 並且返回回來了。

Demo下載:點選下載

我的公眾號,期待你的關注

weixin