詳解 Android 中的 IPC 機制:基礎篇

Rickon發表於2019-03-12

前言

本文主要介紹 Android 中的 IPC 機制,具體實現的方式有很多,比如可以通過在 Intent 中附加 extras 來傳遞資訊,或者通過共享檔案的方式來共享資料。Android 開發中,還經常用到 Binder 方式來實現跨程式通訊。四大元件之一 ContentProvider 天生就是支援跨程式訪問的,所以我們也可以用它來進行 IPC。通過網路通訊也可以實現資料傳遞,所以 Socket 也可以用來進行 IPC。以上幾種方法都能實現 IPC,我們需要根據具體的情況選擇不同的方式實現 IPC。

使用 Bundle

Android 開發四大元件中的三大元件(Activity、Service、Receiver)都是支援在 Intent 中傳遞 Bundle 資料的,因為 Bundle 實現了 Parcelable 介面,所以它可以方便地在不同的程式間傳輸。我們可以在 Bundle 中附加我們需要跨程式傳遞的資料並通過 Intent 傳送出去。使用 Bundle 傳遞的資料必須是能夠被序列化的,比如基本型別、實現 Parcelable 介面的物件、實現了 Serializable 介面的物件等。這是最簡單的程式間通訊方式。

使用共享檔案

兩個程式可以通過對同一個檔案進行讀寫的方式來交換資料,比如程式 A 將想傳遞的資料寫入一個檔案,程式 B 通過讀取同一個檔案來獲取資料。這種方式需要注意一個問題就是 Android 中,兩個執行緒可以同時對一個檔案進行讀寫,這樣可能會出現問題。但是因為這種 IPC 方式很簡單,所以只要在開發時注意一下這種併發操作的情況即可。不僅如此,我們還可以通過一個程式將一個物件序列化之後寫入檔案,另一個程式讀取檔案之後恢復物件。儘管這兩個物件並不是同一個物件,但是內容是一樣的。

使用 Messenger

Messenger 是一種輕量級的 IPC 方案,它的底層是現實 AIDL,這一點我們可以通過觀察其原始碼驗證。Messenger 的兩個構造方法程式碼如下,可以很明顯地看出來 AIDL 的痕跡。

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

public Messenger(IBinder target) {
        mTarget = IMessenger.Stub.asInterface(target);
}
複製程式碼

Messenger 對 AIDL 做了封裝,我們可以很簡單的使用它進行程式間通訊。 實現一個Messenger有多個步驟,分為服務端和客戶端:

服務端:建立一個Service來處理客戶端的連線請求,同時建立一個Handle並通過它來建立一個Messenger物件,然後再Service的onBind中返回這個Messenger物件的底層Binder即可。

public class MessengerService extends Service {
    private static final String TAG = "MessengerService";
    private static class MessengerHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
            case MyConstants.MSG_FROM_CLIENT:
                Log.i(TAG, "receive msg from Client:" + msg.getData().getString("msg"));
                Messenger client = msg.replyTo;
                Message relpyMessage = Message.obtain(null, MyConstants.MSG_FROM_SERVICE);
                Bundle bundle = new Bundle();
                bundle.putString("reply", "收到訊息啦");
                relpyMessage.setData(bundle);
                try {
                    client.send(relpyMessage);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
                break;
            default:
                super.handleMessage(msg);
            }
        }
    }

    private final Messenger mMessenger = new Messenger(new MessengerHandler());

    @Override
    public IBinder onBind(Intent intent) {
        return mMessenger.getBinder();
    }

    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return super.onStartCommand(intent, flags, startId);
    }

}
複製程式碼

從服務端的程式碼中可以看到,MessageHandler用來處理客戶端發過來的訊息,並從客戶端中取出文字資訊,而 mMessenger 是和客戶端關聯在一起的,在 onBind 方法中返回它裡面的Binder物件,這裡的 Messenger 的作用是將客戶端傳送的訊息轉交給 MessengerHandler 處理。

客戶端:首先繫結服務端的 Service,用返回的 IBinder 建立一個 Messenger,通過這個 Messenger 像伺服器傳送 Message 型別的資料,如果需要服務端能夠迴應客戶端,同時還需要建立一個Handle並建立一個新的 Messenger,把這個 Messenger 物件通過 Message 的 replyTo 傳遞給服務端。

public class MessengerActivity extends Activity {
    private static final String TAG = "MessengerActivity";
    private Messenger mService;
    private Messenger mGetReplyMessenger = new Messenger(new MessengerHandler());

    private static class MessengerHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
            case MyConstants.MSG_FROM_SERVICE:
                Log.i(TAG, "receive msg from Service:" + msg.getData().getString("reply"));
                break;
            default:
                super.handleMessage(msg);
            }
        }
    }

    private ServiceConnection mConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className, IBinder service) {
            mService = new Messenger(service);
            Log.d(TAG, "bind service");
            Message msg = Message.obtain(null, MyConstants.MSG_FROM_CLIENT);
            Bundle data = new Bundle();
            data.putString("msg", "hello, this is client.");
            msg.setData(data);
            msg.replyTo = mGetReplyMessenger;
            try {
                mService.send(msg);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        public void onServiceDisconnected(ComponentName className) {
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_messenger);
        Intent intent = new Intent("com.ryg.MessengerService.launch");
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onDestroy() {
        unbindService(mConnection);
        super.onDestroy();
    }
}
複製程式碼

從例子看來,在 Messenger 中進行資料傳遞必須是將資料放到 Message 中,實際上,Messenger 來傳輸 Message,Message 中能使用的載體有 what,arg1,arg2,Bundle 以及 replyTo。Message 的 object 在同一個程式中的使用是很實用的。

使用AIDL

上面介紹的 Messenger 是以序列的方式處理客戶端發來的訊息,如果有大量訊息傳送到服務端,服務端仍然只能一個個處理,如果有大量的併發請求,Messenger 就不能勝任了。此外,我們可能需要跨程式呼叫服務端的方法,這種情形用 Messenger 就無法做到了,這時候就需要使用 AIDL 來實現跨程式的方法呼叫。

  • 服務端
    服務端首先要建立一個 Service 用來監聽客戶端的請求連線,然後建立一個 AIDL 檔案,將暴露給客戶端的介面在這個 AIDL 檔案中宣告,在 Service 中實現這個介面。

  • 客戶端
    首先繫結好服務端的 Service,將服務端返回的 Binder 物件轉成 AIDL 介面所屬的型別,接著呼叫 AIDL 中的方法。

使用 ContentProvider

ContentProvider 是 Android 提供的專門用於不同應用間資料共享的方式,所以它天生就能夠用於程式間通訊。ContentProvider 使用起來要比 AIDL 容易得多。系統預置了很多 ContentProvider,比如通訊錄資訊、本地媒體庫。我們還可以通過實現自定義的 ContentProvider,並在不同程式中進行讀寫來實現跨程式通訊。

使用 Socket

Socket 也叫做“套接字”,是網路通訊裡的概念。兩個程式可以通過 Socket 來實現資訊的傳輸,Socket 本身可以支援傳輸任意位元組流,這樣就實現了 IPC。

相關文章