Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android:IPC,Messenger,AIDL

Android:IPC,Messenger,AIDL

編輯:關於Android編程

簡介

IPC,即進程間通信。常見的IPC場景有兩種,一種是單個應用開啟多個進程,這些進程間需要通信;另外一種是不同應用間的進程間通信。

單個應用開啟多個進程並不復雜,只需要為四大組件聲明一個android:process屬性,這個組件便會運行在該聲明的進程上。而這個屬性的聲明方式有兩種:

以:號開頭,比如android:process=":remote",這時這個組件便運行在package_name:remote這個進程上。這種方式稱為私有進程,雖然名為私有進程,但是可以為其再聲明一個android:exported="true"屬性,然後增加intent-filter屬性(如果沒有intent-filter屬性,只能在本應用內使用),其他應用也可以關聯到這個進程。

以單個.號分割,比如android:process="com.leelit",這時這個組件便會運行在com.leelit這個進程上。這種方式稱為全局進程,假設有多個應用都有聲明了com.leelit全局進程的組件,也會產生多個名字相同但PID並不相同的進程,所以全局進程並非全局唯一的意思。全局進程的作用是,其他應用可以通過ShareUID的方式和這個全局進程跑在同一個進程上,從而共享資源。

IPC的基礎是數據結構的序列化與反序列化。Java平台簡單地實現Serializable,通過ObjectOutputStream以及ObjectInputStream即可完成對象的序列與反序列化。而Android平台上新增了一種效率更高的方式,Parcelable,其實現方式比較固定,所以也有一些插件可以直接生成代碼。如果說Serializable和Parcelable是實現IPC的“原料”,那麼Binder則可以視為“橋梁”,Binder是客戶端和服務端進行通信的媒介,當bindService時,客戶端可以得到一個可以“操縱”服務端的Binder對象。

Android平台上實現IPC的方式有很多種,比如常見的:

Intent和bundle 文件共享 Messenger AIDL ContentProvider Socket

這篇文章主要關注:Messenger和AIDL


Messenger

Messenger是系統為我們封裝好的一套IPC方案,它的底層是基於AIDL與Handler。如果了解Handler,Looper那一套東西就會知道,Handler每次只會處理一個Message,使用Messenger是不需要也無法考慮並發的情況的。

Messenger的構造方法有兩個:

    public Messenger(Handler target) {
        mTarget = target.getIMessenger();
    }

    public Messenger(IBinder target) {
        mTarget = IMessenger.Stub.asInterface(target);
    }

由構造方法也能看出它基於AIDL和Handler。

使用套路:

1、Service服務端實例一個Messenger來接收客戶端Messenger發來的message,onBind方法返回這個Messenger的Binder。如果有需要還可以通過客戶端發來的message攜帶的客戶端Messenger接收對象,返回message給客戶端。

2、客戶端bind到服務端的Service後,實例一個Messenger對象,便可以通過這個Messenger對象發送message到服務端。如果有需要還可以實例另外一個Messenger對象來接收服務端返回的message。

按照這個套路一共需要實例三個Messenger,客戶端兩個,一個用於發送給服務端,一個用於接收服務端;服務端一個用於接收客戶端,而返回給客戶端的Messenger在客戶端發來的message中攜帶。

服務端進程:

public class MessengerService extends Service {

    private static final String Messenger_TAG = "Messenger";

    private static class ServerMessengerHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case 0:
                    // 接收到客戶端的信息
                    Log.i(Messenger_TAG, msg.getData().getString("client-data") + " " + Thread.currentThread().toString());

                    // 接收服務端返回信息的客戶端Messenger
                    Messenger replyMessenger = msg.replyTo;

                    // 返回信息給客戶端
                    Message message = Message.obtain();
                    message.what = 1;
                    Bundle data = new Bundle();
                    data.putString("server-data", "hello client");
                    message.setData(data);
                    try {
                        replyMessenger.send(message);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }

    // 服務端Messenger
    private Messenger serverMessenger = new Messenger(new ServerMessengerHandler());

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        // 返回兩個Messenger溝通的Binder橋梁
        return serverMessenger.getBinder();
    }

}

客戶端進程:

public class MainActivity extends AppCompatActivity {
    private static final String Messenger_TAG = "Messenger";

    private Messenger clientMessenger;

    private Button button;

    // 接收服務端返回信息的Messenger
    private Messenger replyMessenger = new Messenger(new ClientMessengerHandler());

    private static class ClientMessengerHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case 1:
                    // 接收到服務端的信息
                    Log.i(Messenger_TAG, msg.getData().getString("server-data") + " " + Thread.currentThread().toString());
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }

    private ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // 客戶端Messenger
            clientMessenger = new Messenger(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        button = (Button) findViewById(R.id.messenger);

        // bind服務端Service
        Intent intent = new Intent(this, MessengerService.class);
        bindService(intent, serviceConnection, BIND_AUTO_CREATE);

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 發送消息到服務端
                Message message = Message.obtain();
                message.what = 0;
                Bundle data = new Bundle();
                data.putString("client-data", "hello server");
                message.setData(data);

                // 指定接收服務端返回信息的Messenger
                message.replyTo = replyMessenger;

                try {
                    clientMessenger.send(message);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });

    }


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

點擊Button後打印的信息如下:

10-21 21:44:28.557 29650-29650/com.example.kenjxli.ipc:messenger I/Messenger: hello server Thread[main,5,main]
10-21 21:44:28.567 29615-29615/com.example.kenjxli.ipc I/Messenger: hello client Thread[main,5,main]

處理的線程是Handler實例時所在的線程,所以兩個進程的Message處理都是在主線程。

這裡補充兩點bindService相關的內容:

bindService()是異步調用,會立即返回; 系統會在與服務連接上時回調onServiceConnected()方法,並傳遞服務的onBind() 方法返回的 IBinder;與服務的連接意外中斷時(例如當服務崩潰或被終止時)回調onServiceDisconnected()方法。當客戶端主動取消綁定時,系統“絕對不會”調用該方法。

AIDL

根據官方文檔,可以看出,底層AIDL和經過封裝的Messenger相比,最大的不同就是,AIDL是具備多線程處理能力的。

Messenger 會在單一線程中創建包含所有客戶端請求的隊列,以便服務一次接收一個請求。不過,如果您想讓服務同時處理多個請求,則可直接使用 AIDL。 在此情況下,您的服務必須具備多線程處理能力,並采用線程安全式設計。

使用AIDL的基本步驟:
1、確定服務端提供的服務,定義AIDL接口;
2、編寫服務端Service,onBind方法返回Binder;
3、客戶端bindService,在回調處將Binder轉化為AIDL接口,後續即可使用這個接口調用服務端的方法。
可以參考之前的一篇文章,鏈接。

這裡再補充幾個內容。

AIDL傳遞對象; 服務端回調客戶端; 調用時同步異步狀態及所在線程;

AIDL傳遞對象

如果使用AIDL傳遞基本數據類型,就比較簡單,只需要服務端定義好AIDL接口文件,並在Service中實現AIDL.Stub接口,返回binder。客戶端bindService後將binder重新轉化為AIDL接口,即可IPC調用。但是要傳遞對象,就需要額外的功夫。

需要用AIDL傳遞對象時,除了實際使用的AIDL接口之外,還需要該對象的類實現Parcelable接口,在同一個包內聲明該類的AIDL文件,並且AIDL接口中必須import該類,即便處在同一個包。

比如服務端AIDL接口,需要返回一個User對象。

interface MyAidl {
    User getUser();
}

首先需要創建一個實現Parcelable的User類;
然後在同一個包,新建一個AIDL文件;

// User.aidl
package com.example.kenjxli.ipc.aidl;

parcelable User;

最後需要在AIDL接口處import該類,就算處在同一個包也必須import。

// MyAidl.aidl
package com.example.kenjxli.ipc.aidl;

import com.example.kenjxli.ipc.aidl.User; //必須import

interface MyAidl {
    User getUser();
}

以上步驟缺一不可,否則編譯都將失敗。

另外,AIDL接口中的方法有一些非基本類型的參數,需要指定其“方向”。

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

假設有一個AIDL接口如下:

interface MyAidl {
    void inUser(in User user);
    void outUser(out User user);
    void inOutUser(inout User user);
}

則三個參數的含義如下:

in:客戶端輸入參數,此時服務端可以得到這個參數對象的內容,但是修改後無法同步回給客戶端。
out:客戶端輸入參數,即便這個對象是有內容的,到了服務端也會變成空值,服務端修改後能同步回給客戶端。
inout:則是上面兩者的集合,服務端既能得到客戶端對象的內容,修改後也能同步回給客戶端。

服務端回調

AIDL一般是客戶端進程調用服務端進程,但是有些時候是可能需要服務端回調客戶端,這時需要使用一個系統接口,RemoteCallbackList

具體使用步驟如下:
1、定義一個客戶端AIDL回調接口
2、在原有的AIDL接口增添注冊回調接口的方法
3、客戶端注冊時,將該接口添加到callbackList中;
4、回調時使用固定的代碼格式。

部分代碼如下:
1、定義一個AIDL回調接口

// OnServerCallBack.aidl
package com.example.kenjxli.ipc.aidl;

import com.example.kenjxli.ipc.aidl.User;
// Declare any non-default types here with import statements

interface OnServerCallBack {
    void onServerCallBack(in User user); // 此時我們的客戶端相當於是服務端的服務端了!
}

2、原有AIDL接口增添注冊回調的方法

// MyAidl.aidl
package com.example.kenjxli.ipc.aidl;

import com.example.kenjxli.ipc.aidl.User;
import com.example.kenjxli.ipc.aidl.OnServerCallBack;
// Declare any non-default types here with import statements

interface MyAidl {
    User getUser();
    void inUser(in User user);
    void outUser(out User user);
    void inOutUser(inout User user);
    void registerCallBack(in OnServerCallBack callbcak);
}

並在服務端Service中的AIDL.Stub中實現該方法

@Override
        public void registerCallBack(final OnServerCallBack callbcak) throws RemoteException {
        // 這裡可以添加一個cookie對象,方便回調時識別是哪個回調對象
            callbackList.register(callbcak, "cookie1");
        }

3、客戶端注冊

  myAidl.registerCallBack(new OnServerCallBack.Stub() {
                        @Override
                        public void onServerCallBack(User user) throws RemoteException {
                            Log.e("tag", "call back " + user.toString());
                        }

                    });

4、服務端回調代碼

private void callback() {
        int size = callbackList.beginBroadcast();
        for (int i = 0; i < size; i++) {
            OnServerCallBack callBack = callbackList.getBroadcastItem(i);
            if (callBack != null) {
                try {
                    Log.e("tag", (String) callbackList.getBroadcastCookie(i));
                    callBack.onServerCallBack(new User("callback user", 10000));
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }
        callbackList.finishBroadcast();
    }

這樣就是一個完整的服務端回調客戶端的流程了。上面沒有涉及到反注冊,其實也是大同小異的!值得一提的是,如果客戶端進程退出後,服務端會自動移除這個回調對象。


調用時同步異步狀態及所在線程

這一小節直接說明結論:

1、ServiceConnection的回調線程始終是在主線程;
2、AIDL調用是在Binder線程池,不管是客戶端調用服務端,還是服務端回調客戶端,方法體執行的地方都是在Binder線程池中;
3、AIDL調用是同步調用,不管是客戶端調用服務端,還是服務端回調客戶端。當調用某個遠程方法後,本地進程當前的線程會掛起,直到遠程方法返回後,本地進程的線程才能喚醒。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved