Android查缺補漏(IPC篇)-- Bundle、文件共享、ContentProvider、Messenger四種進程間通訊介紹

本文作者:CodingBlock 文章鏈接:http://www.cnblogs.com/codingblock/p/8387752.html

在Android中進程間通信的實現方式有多種,包括:Bundle、文件共享、ContentProvider、Messenger、AIDL、Socket等等,其各有各的優缺點,接下來就分別介紹一下上述各種進程間的通信方式及實現。

一、Bundle

Activity、Service、Receiver都支持Intent中傳遞Bundle數據,由於Bundle實現了Parcelable接口,所以它可以方便的在不同的進程間傳輸。

傳輸的數據必須能夠被序列化:

  • 基本類型
  • String類型
  • CharSequence
  • 實現了Parcelable接口的對象
  • 實現了Serializable接口的對象
  • List
  • IBinder類型
  • SparseArray

二、文件共享

文件共享作爲進程間通訊時,無法解決併發讀寫時所帶來的問題,所以只適合在對數據同步要求不高的進程間通訊。

其實SharedPreferences也屬於文件共享方式的一種,sp是android中提供的一種輕量級存儲方案,通過鍵值對的方式來存儲數據,底層用xml文件來存儲鍵值對。每個應用的sp文件放在當前包所在的data目錄下,位於/data/data/package_name/shared_prefs目錄下。由於系統對它的讀寫有一定的緩存策略,即在內存中會有一份sp文件的緩存,因此在多進程模式下,它變得不可靠。

三、ContentProvider

ContentProvider在前面介紹四大組件時就已經介紹過了,這裏就不多說了,詳見《Android查缺補漏--ContentProvider的使用》

四、Messenger

1、Messenger是什麼?

通過Messenger可以很方便的在不同的進程之間傳遞Messager對象,在Messager對象中就可以放入我們需要傳遞的數據。我們知道,跨進程傳輸數據有很多種方式,其中AIDL最爲強大也最爲常用,而Messenger即相當於AIDL的簡化版,其底層也是採用AIDL實現,是一種輕量級的跨進程傳輸方案。

所謂簡化版常常功能有限,Messenger也不例外,相對於AIDL它的功能確實弱化了不少,在方便使用的同時它一次只能處理一個請求。

2、實現一個簡單的Messenger通訊

實現一個Messenger需要在兩個進程中做以下操作:

爲了避免歧義,我們提前約定,以下所說的服務端若不做特別說明默認指Service所在的進程。
  • 在服務端:首先,創建一個Service充當服務端進程,然後在Service中創建一個Messenger,因爲創建Messenger時需要傳入一個Handler,所以還要創建一個Handler,接收數據的操作就在Handler中,最後在Service的onBind中返回這個Messenger的Binder即可。

示例代碼如下:

/**
 * Created by liuwei on 18/1/29.
 */
public class MessengerService extends Service {

    private static final String TAG = MessengerHandler.class.getSimpleName();
    
    // 創建一個Handler,處理消息用
    private class MessengerHandler extends Handler{
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case AppConstants.MSG_FROM_CLIENT:
                    // 接收客戶端發來的消息
                    Log.i(TAG, "handleMessage: MSG_FROM_CLIENT:" + msg.getData().getString("msg"));
                    break;
                default: super.handleMessage(msg);
            }
        }
    }
    
    // 創建Messenger
    private Messenger messenger = new Messenger(new MessengerHandler());

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        // 返回Messenger的Binder
        return messenger.getBinder();
    }
}

這裏要想讓Service跑在另外一個進程中需要在AndroidManifest文件中添加process節點:

<service
    android:name=".messager.MessengerService"
    android:process=":remote" />
  • 在客戶端:就像綁定一個普通的Service一樣,通過bindService方法鏈接Service即可,不同點是,需要在ServiceConnection接口中的onServiceConnected方法中通過參數中的IBinder創建一個Messenger對象,通過這個Messenger對象即可向服務端發送message消息。

示例代碼如下:

public class MessengerActivity extends AppCompatActivity {

    private final static String TAG = MessengerActivity.class.getSimpleName();

    Button btn_bind_messenger;
    Intent intent;
    Messenger messenger;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_messenger);
        btn_bind_messenger = ViewUtils.findAndOnClick(this, R.id.btn_bind_messenger, mOnClickListener);
        intent = new Intent(this, MessengerService.class);
    }

    private View.OnClickListener mOnClickListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            switch (v.getId()){
                case R.id.btn_bind_messenger:
                    // 綁定Service
                    bindService(intent, connection, BIND_AUTO_CREATE);
                    break;
            }
        }
    };

    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // 鏈接messenger成功,利用service創建一個Messenger
            messenger = new Messenger(service);

            // 向服務端發送一條消息
            Message message = Message.obtain(null, AppConstants.MSG_FROM_CLIENT);
            Bundle bundle = new Bundle();
            bundle.putString("msg", "client bind messenger succeed!");
            message.setData(bundle);
            try {
                messenger.send(message);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            messenger = null;
        }
    };

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

上面代碼中ViewUtils.findAndOnClick()是博主封裝的一個方法,功能只是綁定控件併爲控件添加onClick監聽器,無需重點關注,接下來運行工程點擊按鈕,在Android Monitor中將切換到remote進程,查看log如下:

.../cn.codingblock.ipc:remote I/MessengerHandler: handleMessage: MSG_FROM_CLIENT:client bind messenger succeed!

3、服務端如何迴應客戶端?

到上面這一步一個簡單的Messenger通訊就完成了,接下在MessengerActivity中我們就可以使用Messenger對象向服務端發送數據了,但是如何才能得到服務端的迴應呢,或者服務端想向客戶端發送數據怎麼辦?

這個其實也很簡單,我們只需要在客戶端這裏也創建一個Messenger,然後再向服務端發送數據時在Message的replyTo指向客戶單的Messenger對象即可,如下:

message.replyTo = clientMessenger;

然後再服務端通過獲取Message.replyTo,就可以獲取到客戶端的Messenger:

Messenger client = msg.replyTo;

具體實現如下:

  • 在上面的MessengerActivity中增加一個Handler,並通過Handler創建一個Messenger,在向服務端發送一條消息時告訴服務器接收回復的messenger:
// 新增一個MessengerHandler
private class MessengerHandler extends Handler {
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case AppConstants.MSG_FROM_SERVICE:
                Log.i(TAG, "handleMessage: MSG_FROM_SERVICE:" + msg.getData().getString("msg"));
                break;
            default:super.handleMessage(msg);
        }
    }
}
// 創建一個clientMessenger
private Messenger clientMessenger = new Messenger(new MessengerHandler());

private ServiceConnection connection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        // 鏈接messenger成功,利用service創建一個Messenger
        messenger = new Messenger(service);

        // 向服務端發送一條消息
        Message message = Message.obtain(null, AppConstants.MSG_FROM_CLIENT);
        Bundle bundle = new Bundle();
        bundle.putString("msg", "client bind messenger succeed!");
        message.setData(bundle);

        // 關鍵點,告訴服務器接收回復的messenger
        message.replyTo = clientMessenger;

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

    @Override
    public void onServiceDisconnected(ComponentName name) {
        messenger = null;
    }
};
  • 在上面的MessengerService中獲取客戶端的Messenger,並通過獲取的這個Messenger對象向客戶端迴應消息:
private class MessengerHandler extends Handler{
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case AppConstants.MSG_FROM_CLIENT:
                // 接收客戶端發來的消息
                Log.i(TAG, "handleMessage: MSG_FROM_CLIENT:" + msg.getData().getString("msg"));

                // 服務端收到消息後,給客戶端發送迴應
                Messenger client = msg.replyTo;
                Message replyMsg = Message.obtain(null, AppConstants.MSG_FROM_SERVICE);
                Bundle bundle = new Bundle();
                bundle.putString("msg", "ok,I will reply you soon!");
                replyMsg.setData(bundle);
                try {
                    client.send(replyMsg);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
                break;
            default: super.handleMessage(msg);
        }
    }
}

再次運行工程,點擊按鈕,將進程切換到主進程後log如下:

.../cn.codingblock.ipc I/MessengerActivity: handleMessage: MSG_FROM_SERVICE:ok,I will reply you soon!

至此,通過Messenger實現的一個完整的兩個進程之間的交互過程完成了,上面的兩個進程雖然都在同一個App中,但其效果同在兩個App中幾乎一致,只是如果是兩個App的話在需要稍微修改一下綁定Service時的Intent並將Service的export屬性設爲true。

4、通過Messenger實現兩個App通訊

接下來試驗一下將兩個進程中放入兩個App中:

  • 新建一個名爲IpcClient的Module,將MessengerActivity拷貝過去,在bindService時,不要忘記將Intent稍作修改一下,因爲現在MessengerService在另外一個工程中,無法直接傳入到Intent中,需改爲如下方式:
intent = new Intent();
intent.setComponent(new ComponentName("cn.codingblock.ipc", "cn.codingblock.ipc.messager.MessengerService"));

爲了能夠更加方便的區分是哪個工程傳到Service端的消息,在發送消息時加入如下信息:

bundle.putString("msg", "client bind messenger succeed!(from ipc_client)");

其他代碼不變。

  • 然後再原來的工程,將配置文件中的MessengerService的exported屬性設置爲true,否則外界將無法調用此Service。
<service
    android:name=".messager.MessengerService"
    android:exported="true"
    android:process=":remote" />

此時,工程結構如下:

運行IpcClient工程,點擊按鈕,首先在Android Monitor中切換到cn.codingblock.ipc:remote進程查看log如下:

.../cn.codingblock.ipc:remote I/MessengerHandler: handleMessage: MSG_FROM_CLIENT:client bind messenger succeed!(from ipc_client)

然後將進程切換到cn.codingblock.ipcclient中log如下:

.../cn.codingblock.ipcclient I/MessengerActivity: handleMessage: MSG_FROM_SERVICE:ok,I will reply you soon!

5、Messenger可以傳輸的數據類型包括:

簡單來說,Messenger可以傳輸Message可承載的數據類型,而Message中能使用的載體有:what、arg1、arg2、Bundle和replyTo,其實Message中還有一個Object類型的載體,這個載體在同一個進程中非常使用,但是在Android2.2之前object字段不支持跨進程傳輸,在2.2之後也僅支持系統提供的實現Parcelable接口的對象。所以總結起來,Messenger在跨進程時可傳遞的類型如下:

  • Bundle類型
  • Messenger類型

小結

本篇介紹了四種比較簡單的跨進程通信方式,這四種實現起來相對方便,但功能也非常有限,在後續的博文中將介紹AIDL和Socket的使用。下面的表格爲以上四種跨進程通信方式的比較:

名稱

優點

缺點

場景

Bundle

使用簡單

1、只能傳輸Bundle支持的類型 2、不支持RPC

四大組件間的通信

文件共享

使用簡單

1、不適合併發 2、做不到即時通信

無併發訪問、不要求實時通信的場景

ContentProvider

1、在數據源訪問方面功能強大 2、支持一對多 3、可通過call方法擴展其他操作

受約束的AIDL、主要提供數據的CRUD操作

一對多的進程間數據共享

Messenger

1、支持一對多串行通信 2、支持實時通信

1、只能串行通信 2、只能傳輸Bundle支持的類型 3、不支持RPC

低併發一對多即時通信、無RPC需求


最後想說的是,本系列文章爲博主對Android知識進行再次梳理,查缺補漏的學習過程,一方面是對自己遺忘的東西加以複習重新掌握,另一方面相信在重新學習的過程中定會有巨大的新收穫,如果你也有跟我同樣的想法,不妨關注我一起學習,互相探討,共同進步!

參考文獻:

  • 《Android開發藝術探索》

源碼地址:本系列文章所對應的全部源碼已同步至github,感興趣的同學可以下載查看,結合代碼看文章會更好。源碼傳送門

本文作者:CodingBlock 文章鏈接:http://www.cnblogs.com/codingblock/p/8387752.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章