Android——IPC機制(二)程序間通訊方式
在上一章中,我們已經介紹了IPC的幾個基礎知識:序列化和Binder,本章將詳細介紹各種跨程序同行方式。具體的方式有很多,比如可以通過在Intent中附加extras來傳遞資訊,或者通過共享檔案的方式來共享資料,還可以採用Binder的方式來跨程序通訊,另外ContentProvider天生就是支援跨程序訪問的,隱藏我們也可以採用它來進行IPC。此外通過網路通訊也是可以實現資料傳遞的,所以Socket也可以實現IPC。上述所說的各種方法都能實現IPC,它們在使用方法和側重點上都有很大的區別,下面一一進行展開。
Android中的IPC方式
使用Bundle
我們知道,四大元件中的三大元件(Activity、Service、Receiver)都是支援在Intent中傳遞Bundle資料的,由於Bundle實現了Parcelable介面,所以它可以方便地在不同的程序間傳輸。基於這一點,當我們在一個程序中啟動了另一個程序的Activity、Service
、Receiver、我們就可以在Bundle中附加我們需要傳輸給遠端程序的資訊並通過Intent傳送出去。當然,我們傳輸的資料必須能夠被序列化,比如基本型別,實現了Parcelable介面的物件,實現了Serializable介面的物件以及一些Android支援的特殊物件,具體內容可以看Bundle這個類,就可以看到所有它支援的型別。Bundle不支援的型別我們無法通過它在程序間傳遞資料,這個相當簡單就不在做詳細介紹了。
使用檔案共享
共享檔案也是一種不錯的程序間通訊方式,兩個程序通過讀/寫同一個檔案來交換資料,比如A程序把資料寫入檔案,B程序通過讀取這個檔案來獲取資料。我們知道,在Window上,一個檔案如果被加了排斥鎖將會導致其他執行緒無法對其進行訪問,包括讀和寫,而由於Android系統基於Linux,使得其併發讀/寫可以沒有限制地進行,甚至兩個執行緒同時對同一個檔案進行寫操作都是允許的,儘管這樣可能會出問題,不過通過檔案交換資料確實很好用。
通過檔案共享這種方式對檔案格式是沒有具體要求的,比如可以是文字檔案,也可以是XML檔案,只要讀/寫雙方約定資料格式即可。通過檔案共享的方式也是有侷限性的,比如併發讀/寫的問題,如果真的出現了併發讀/寫,那麼我們讀出的內容就又可能不是最新的,如果是併發寫的話那就更嚴重了。因此我們要儘量避免併發寫這種情況的發生或者考慮使用執行緒同步來限制多個執行緒的寫操作。通過上面的分析,我們可以知道,檔案共享方式適合在對資料同步要求不高的程序之間進行通訊,並且要妥善處理併發讀/寫的問題。
當然,實際應用中我們用的比較多的還是SharedPreferences,而SharedPreferences是個特例,眾所周知,SharedPreferences是Android中提供的輕量級儲存方案,它通過鍵值對的方式來儲存資料,在底層實現上它採用XML檔案來儲存鍵值對,每個應用的SharedPreferences檔案都可以在當前包所在的data目錄下檢視到。一般來說,它的目錄位於data/data/package name/shared_prefs目錄下,其中package name表示的是當前應用的包名。從本質上來說,SharedPreferences也屬於檔案的一種,但是由於系統對它的讀/寫有一定的快取策略,即在記憶體中會有一份SharedPreferences檔案的快取,隱藏在多程序模式下,系統對它的讀/寫就變得不可靠,當面對高併發的讀/寫訪問,SharedPreferences有很大的機率會丟失資料,因此不建議在程序間通訊中使用SharedPreferences。
使用Messenger
Messenger可以翻譯為送信者,顧名思義,通過它可以在不同程序中傳遞Messenger物件,在Message中放入我們需要傳遞的資料,就可以輕鬆地實現資料的程序間通訊了。Messenger是一種輕量級的IPC方案,它的底層實現是Binder,為什麼這麼說呢,我們大致看一下Messenger這個類的構造方法就明白了。下面是Messenger的兩個構造方法,從構造方法的實現上我們就可以明顯看出Binder的痕跡,不管是IMessenger還是Stub.asInterface這種使用方法都表明它的底層是Binder。
/**
* Create a new Messenger pointing to the given Handler. Any Message
* objects sent through this Messenger will appear in the Handler as if
* {@link Handler#sendMessage(Message) Handler.sendMessage(Message)} had
* been called directly.
*
* @param target The Handler that will receive sent messages.
*/
public Messenger(Handler target) {
mTarget = target.getIMessenger();
}
/**
* Create a Messenger from a raw IBinder, which had previously been
* retrieved with {@link #getBinder}.
*
* @param target The IBinder this Messenger should communicate with.
*/
public Messenger(IBinder target) {
mTarget = IMessenger.Stub.asInterface(target);
}
在這裡我們
Messenger的使用方法非常簡單,而且由於Messenger一次只處理一個請求,因此在服務端我們不用考慮執行緒同步的問題,這是因為服務端中不存在併發執行的情形。實現一個Messenger有如下幾個步驟,分別為服務端和客戶端。
- 1、服務端程序
首先,我們需要在服務端建立一個Service來處理客戶端的連線請求,同時建立一個Handler並通過它來建立一個Messenger物件,然後在Service的onBind中返回這個Messenger物件底層的Binder物件即可。 - 2、客戶端程序
客戶端程序中,首先要繫結服務端的Service,繫結成功後用服務端返回的IBinder建立一個Messenger,通過這個Messenger就可以向伺服器傳送訊息了,發訊息型別為Message物件。如果需要服務端能夠迴應客戶端,就和服務端一樣,我們還需要建立一個Handler並建立一個新的Messenger,並把這個Messenger物件通過Message的replyTo引數傳遞給服務端,服務端通過這個replyTo引數就可以迴應客戶端。這聽起來可能有點抽象,下面我們會給出兩個例子,看一下我們就明白了。首先,我們先來做一個簡單點的例子,在這個例子中服務端無法迴應客戶端。
首先看服務端的程式碼,這是服務端的典型程式碼:
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 0:
Log.e(TAG, "receive msg form Client:" + msg.getData().getString("msg"));
break;
}
}
}
private final Messenger mMessenger = new Messenger(new MessengerHandler());
public MessengerService() {
}
@Override
public void onCreate() {
super.onCreate();
Log.e(TAG, mMessenger.getBinder() + "");
}
@Override
public IBinder onBind(Intent intent) {
Log.e(TAG, mMessenger.getBinder() + "");
return mMessenger.getBinder();
}
}
然後,註冊service,讓其執行在單獨的程序中:
<service
android:name=".MessengerService"
android:process=":remote"></service>
可以看到MessengerHandle用來處理客戶端傳送的訊息,並從訊息中取出客戶端發來的文字資訊。而mMessenger是一個Messenger物件,他和MessengerHandler相關聯,並在onBind方法中返回它裡面的Binder物件,可以看出,這裡Messenger的作用就是將客戶端傳送的訊息傳遞給MessengerHandler進行處理
這裡我們先不著急去實現客戶端程式碼,我們先來看下服務單Messenger的建立過程:
public Messenger(Handler target) {
mTarget = target.getIMessenger();
}
final IMessenger getIMessenger() {
synchronized (mQueue) {
if (mMessenger != null) {
return mMessenger;
}
mMessenger = new MessengerImpl();
return mMessenger;
}
}
private final class MessengerImpl extends IMessenger.Stub {
public void send(Message msg) {
msg.sendingUid = Binder.getCallingUid();
Handler.this.sendMessage(msg);
}
}
程式碼並不是很複雜,首先,我們是根據Messenger(Handler target)這個構造方法建立的Messenger物件,在Messenger構造方法內部,呼叫了Handler的getIMessenger()方法,而在getIMessenger()方法內部通過new MessengerImpl的例項返回(MessengerImpl其實是Handler的一個內部類,而且是一個繼承了IMessenger.Stub的Binder物件,)到這裡其實我們都差不多應該明白了 IMessenger其實就是一個系統生成的根據一個aidl檔案生成的Java類,那麼也就能夠解釋為什麼在服務端的onBind方法中返回的是 mMessenger.getBinder了,其實如果你看了mMessenger.getBinder方法的內部實現,可以發現它所做的操作其實很簡單,就是呼叫了IMessenger.Stub.asBinder方法返回了一個IBinder物件,好了服務端的程式碼我們就介紹到這裡,下面讓我們看實現下客戶端的邏輯:
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private Messenger mService;
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mService = new Messenger(service);
Message msg = Message.obtain(null, MyConstants.MSG_FROM_CLIENT);
Bundle data = new Bundle();
data.putString("msg", "Hello,this is Client.");
msg.setData(data);
try {
mService.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent(this, MessengerService.class);
bindService(intent, mConnection, 0);
}
@Override
protected void onDestroy() {
unbindService(mConnection);
super.onDestroy();
}
}
我們執行下程式,看一下log,很顯然,服務端成功收到了客戶端發來的問候語:“Hello,this is Client.”
08-28 13:16:15.283 22092-22092/com.layoutinflate.mk.www.messengerdemo E/MessengerService: receive msg form Client:Hello,this is Client.
通過上面的例子可以看出,在Messenger中進行資料傳遞必須將資料放入Message中,而Messenger和Message都實現了Parcelable介面,因此可以跨程序傳輸。簡單來說,Messenger中所支援的資料型別就是Messenger 所支援的傳輸型別。實際上,通過Messenger來傳輸Message,Message中能使用的載體只有what、arg1、arg2、Bundle以及replyTo。Message中的另一個欄位object在同一個程序中是很實用的,但是在程序間通訊的時候,在Android2.2.以前object欄位不支援跨程序傳輸,即便是2.2以後,也僅僅是系統提供的實現了Parcelable介面的時候才能通過它來傳輸。這就意味著我們自定義的Parcelable物件是無法通過object欄位進行傳輸的,大家可以試一下,非系統的Parcelable物件的確無法通過object欄位來傳輸,這也導致了object欄位的實用性大大降低,所幸我們還有Bundle,Bundle中可以支援大量的資料型別。
上面的例子演示瞭如何在服務端接受客戶端中傳送的資訊,但是有時候我們還需要能迴應客戶端,下面就介紹如何實現這種效果。還是採用上面的例子,但是稍微做一下修改,每當客戶端發來一條訊息,服務端就會自動恢復一條資訊過去。
前面我們已經說過,如果想要服務端響應客戶端,我們需要在客戶端中建立一個Handle並建立一個新的Messenger,並把這個Messenger物件通過Message的replyTo引數傳遞給服務端,服務端就可以通過這個replyTo引數就可以迴應客戶端。我們來看下具體實現:
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private Messenger mService;
//後加入程式碼 建立Handle物件
private static class MessengerHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
Log.e(TAG, "recevie msg from Service :" + msg.getData().getString("reply"));
break;
}
}
}
//後加入程式碼 根據建立的Handle建立Messenger
private Messenger mGetReplyMessenger = new Messenger(new MessengerHandler());
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.e(TAG, service + "");
mService = new Messenger(service);
Message msg = Message.obtain(null, 0);
Bundle data = new Bundle();
data.putString("msg", "Hello,this is Client.");
Log.e(TAG, "Hello,this is Client.");
msg.setData(data);
//TODO 後加入程式碼 將Messenger物件通過replyTo引數傳遞給服務端
msg.replyTo = mGetReplyMessenger;
try {
mService.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent(this, MessengerService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
unbindService(mConnection);
super.onDestroy();
}
}
客戶端的程式碼就是這麼簡單,下面我們來看下服務端的程式碼,服務端我們只需要修改MessengerHandler,當收到訊息後,取出客戶端給我傳遞過來的Messenger,然後呼叫send方法就可以了
private static class MessengerHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 0:
Log.e(TAG, "receive msg form Client:" + msg.getData().getString("msg"));
//後加入程式碼,獲取客戶端傳遞過來的Messenger物件,傳送訊息到客戶端
Messenger client = msg.replyTo;
Message replyMessage = Message.obtain(null, 1);
Bundle bundle = new Bundle();
bundle.putString("reply","訊息已收到,稍後回覆您!");
replyMessage.setData(bundle);
try {
client.send(replyMessage);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
}
}
}
通過上述修改,我們在執行程式,然後看一下log:
08-28 13:16:15.273 22092-22092/com.layoutinflate.mk.www.messengerdemo E/MessengerService: receive msg form Client:Hello,this is Client.
08-28 13:16:15.283 22092-22092/com.layoutinflate.mk.www.messengerdemo E/MainActivity: recevie msg from Service :訊息已收到,稍後回覆您!
很顯然,客戶端收到了服務端的回覆,這說明我們的功能已經完成。
到這裡,我們已經把採用Messenger進行程序間通訊的方法都介紹完了,下面給出一張Messenger的工作原理圖,以便更好的理解Messenger,如圖所示:
使用AIDL
前面我們已經介紹了使用Messenger來進行程序間通訊的方法,可以發現,Messenger是一序列的方式處理客戶端發來的訊息,如果大量的訊息同時傳送到服務端,服務端仍然只能一個個處理,如果有大量的併發請求,那麼用Messenger就不太合適了。同時,Messenger的作用主要是為了傳遞訊息,很多時候我們可能還需要跨程序呼叫服務端的方法,這種情形用Messenger就無法做到了,但是我們可以使用AIDL來完成跨程序的方法的呼叫。在上一篇部落格中我們已經介紹了Binder的概念,大家對Binder也有了一定的瞭解,在Binder的基礎上我們可以更加地容易理解AIDL。關於AIDL的用法這裡不在給出,有需要詳細瞭解AIDL的可以自行百度。
使用ContentProvider
ContentProvider是Android中專門用於不同應用間進行資料共享的方式,從這一點來看,它天生就適合程序間通訊。和Messenger一樣,ContentProvider的底層實現同樣也是Binder。和AIDL一樣這裡也不再對ContentProvider進行詳細介紹,有需要詳細瞭解的自行百度把,用法都是一樣的。
使用Socket
Socket也稱為“套接字”,是網路通訊中的概念,它分為流式套接字和使用者資料包套接字兩種,分別對應於網路的傳輸控制層中的TCP和UDP協議。TCP協議是面向連線的協議,提供穩定的雙向通訊功能,TCP的建立需要經過“三次握手”才能完成,為了提供穩定的資料傳輸功能,其本身提供了超時重傳機制,因此具有很高的穩定性;而UDP是面向無連線的,提供不穩定的單向通訊功能,當然UDP也可以實現雙向通訊功能,在效能上,UDP具有更好的效率,其缺點是不能保證資料一定能夠正確傳輸,尤其是在網路擁塞的情況下。Socket本身可以支援傳輸任意字元流,這裡為了簡單起見,僅僅傳輸文字資訊,很顯然,這是一種IPC方式;
使用Socket來進行通訊,有兩點需要注意,首先需要宣告許可權:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
下面就開始設計我們的聊天室程式了,比較簡單,首先在遠端Service建立一個TCP服務,然後在主介面中連線TCP服務,連線上了以後,就可以給服務端發訊息。對於我們傳送的每一條文字資訊,服務端都會隨機地迴應我們一句話。為了更好地展示Socket的工作機制,在服務端我們做了處理,使其能夠和多個客戶端同時建立連線並響應。
先看一下服務端的設計,當Service啟動時,會線上程中建立TCP服務,這裡監聽的是8688埠,然後就可以等待客戶端的連線請求。當有客戶端連線時,就會生成一個新的Socket,通過每次新建立的Socket就可以分別和不同的客戶端通訊了。服務端每收到一次客戶端訊息就會隨機回覆一句話給客戶端。當客戶端埠連線時,服務端這邊也會相應的關閉對應Socket並結束通話執行緒,這點是如何做到的呢?方法有很多,這裡是通過判斷服務端輸入流的返回值來確定的,當客戶端斷開連線後,服務端這邊的輸入流會返回null,這個時候我們就知道客戶端退出了。服務端的程式碼如下:
public class TCPServerService extends Service {
private boolean mIsServiceDestoryed = false;
private String[] mDefineDestoryed = new String[]{
"你好啊,哈哈",
"請問你叫什麼名字呀?",
"今天北京天氣不錯啊,shy",
"你知道嗎?我可以說可以和多個人同時聊天的哦",
"給你講個笑話吧;據說愛笑的人運氣不會太差,不知道真假"
};
public TCPServerService() {
}
@Override
public void onCreate() {
new Thread(new TCPServer()).start();
super.onCreate();
}
@Override
public void onDestroy() {
mIsServiceDestoryed = true;
super.onDestroy();
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
return null;
}
private class TCPServer implements Runnable {
@Override
public void run() {
ServerSocket serverSocket = null;
try {
//監聽本地8688埠
serverSocket = new ServerSocket(8688);
} catch (IOException e) {
System.out.print("establish tcp server failed, port:8688");
e.printStackTrace();
return;
}
while (!mIsServiceDestoryed) {
//接受客戶端請求
try {
final Socket client = serverSocket.accept();
System.out.print("accept");
new Thread() {
@Override
public void run() {
try {
responseClient(client);
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private void responseClient(Socket client) throws IOException {
//用於接收客戶端訊息
BufferedReader in = new BufferedReader(
new InputStreamReader(client.getInputStream()));
//用於向客戶端傳送訊息
PrintWriter out = new PrintWriter(
new BufferedWriter(new OutputStreamWriter(client.getOutputStream())),
true);
//向客戶端寫入訊息
out.println("歡迎來到聊天室");
while (!mIsServiceDestoryed) {
//獲取客戶端傳送過來的訊息
String msg = in.readLine();
System.out.print("msg form client:" + msg);
if (msg == null) {
//客戶端斷開連線
break;
}
int i = new Random().nextInt(mDefineDestoryed.length);
String s = mDefineDestoryed[i];
out.println(s);
System.out.print("send:" + s);
}
System.out.print("client quit");
//關閉流
in.close();
out.close();
client.close();
}
}
程式碼很簡單,不在詳細介紹,接著看一下客戶端,客戶端Activity啟動時,會在onCreate中開啟一個執行緒去連線伺服器Socket,至於為什麼用執行緒是因為存在耗時操作。為了確定能夠連線成功,這裡採用了超時重連策略,每次連線失敗後都會重新嘗試建立連線。為了降低重試機制的開銷,我們加入了休眠機制,即每次重試的時間間隔為1000毫秒。
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private static final int MESSAGE_RECEIVE_NEW_MSG = 1;
private static final int MESSAGE_SOCKET_CONNECTED = 2;
private Button mSendButton;
private TextView mMessageTextView;
private EditText mMessageEditText;
private PrintWriter mPrintWriter;
private Socket mClientSocket;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MESSAGE_RECEIVE_NEW_MSG:
mMessageTextView.setText(mMessageTextView.getText() + (String) msg.obj);
break;
case MESSAGE_SOCKET_CONNECTED:
mSendButton.setEnabled(true);
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mMessageTextView = (TextView) findViewById(R.id.msg_container);
mMessageEditText = (EditText) findViewById(R.id.msg);
mSendButton = (Button) findViewById(R.id.send);
mSendButton.setOnClickListener(this);
Intent service = new Intent(this, TCPServerService.class);
startService(service);
new Thread() {
@Override
public void run() {
connectTCPServer();
}
}.start();
}
@Override
protected void onDestroy() {
if (mClientSocket != null) {
try {
mClientSocket.shutdownInput();
mClientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
super.onDestroy();
}
private void connectTCPServer() {
Socket socket = null;
// while (socket == null) {
try {
socket = new Socket("10.0.2.2", 8888);
mClientSocket = socket;
mPrintWriter = new PrintWriter(
new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())),
true);
mHandler.sendEmptyMessage(MESSAGE_SOCKET_CONNECTED);
//接收服務端的訊息
BufferedReader in = new BufferedReader(
new InputStreamReader(socket.getInputStream()));
while (!MainActivity.this.isFinishing()) {
String msg = in.readLine();
if (msg != null) {
String time = formatDateTime(System.currentTimeMillis());
msg = "s erver" + time + ":" + msg + "\n";
mHandler.obtainMessage(MESSAGE_RECEIVE_NEW_MSG).sendToTarget();
}
}
mPrintWriter.close();
in.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
// }
}
private String formatDateTime(long l) {
return new SimpleDateFormat("(HH:mm:ss)").format(new Date(l));
}
@Override
public void onClick(View v) {
if (v == mSendButton) {
final String msg = mMessageEditText.getText().toString();
if (!TextUtils.isEmpty(msg) && mPrintWriter != null) {
mPrintWriter.println(msg);
mMessageEditText.setText("");
String time = formatDateTime(System.currentTimeMillis());
final String showedMsg = "self" + time + ":" + "\n";
mMessageTextView.setText(mMessageTextView.getText().toString() + showedMsg);
}
}
}
}
Binder連線池
上面我們介紹了不同的IPC方式,我們知道,不同的IPC方式有不同的特點和適用場景,在這裡我們要再次介紹AIDL,原因是AIDL是一種最常用的程序間通訊方式,是日常開發中涉及程序間通訊時的首選。
AIDL我們都會用,但是現在要考慮一種情況,比如說公司的專案越來越大了,現在有10個不同的業務模組都需要使用AIDL來進行程序間通訊,那麼我們該怎麼處理呢?也許你會說:“就按照AIDL的實現方式一個個來吧”,這是可以的,如果使用這種方法,首先我們需要建立10個Service,這好像有點多啊!如果有100個地方需要用到AIDL呢,先建立100個Service?到這裡,我們應該明白問題的所在了,隨著AIDL數量的增加,我們不能無限制地增加Service,Service是四大元件之一,本身就是一種系統資源。而且太多的Service會使得我們的應用看起來和重量級,因為正在執行的Service可以在應用詳情頁看到,當我們的應用詳情顯示10個服務正在執行時,這看起來並不是什麼耗時。針對上述問題,我們需要減少Service的數量,將所有的AIDL放在同一個Service中去管理。
在這種模式下,這個工作機制是這樣的:每個業務模組建立自己的AIDL介面並實現此介面,這個時候不同業務模組之間是不能耦合的,所有實現細節我們要單獨開來,然後向服務端提供自己的唯一標識和其對應的Binder物件;對於服務端來說,只需要一個Service就可以了,服務端提供一個queryBinder介面,這個介面能夠根據業務模組的特徵來返回相應的Binder物件給它們,不同的業務模組拿到所需的Binder物件後就可以進行遠端方法的呼叫了。由此可見,Binder連線池的主要作用就是將每個業務模組的Binder請求統一轉發到遠端Service中去執行,從而避免了重複建立Service的過程,它的工作原理如圖:
上面說了那麼多,可能還是不太好理解,下面我們來通過一個例子來對Binder連線池進行說明,首先,為了說明問題,我們提供了兩個AIDL介面(ISecurityCenter 和 ICompute)來模擬上面提到的多個業務模組都要使用AIDL的情況,其實ISecuityCenter 介面提送加密功能,宣告如下:
interface ISecurityCenter {
String encrypt(String content);
String decrypt(String password);
}
而ICompute介面提供計算加法的功能,宣告如下:
interface ICompute {
int add(int a, int b);
}
雖然說上面的兩個介面的功能都比較簡單,但是用於分析Binder連線池的工作原理已經足夠了。接著看一下上面兩個AIDL介面的實現,也比較簡單,程式碼如下:
public class SecurityCenterImpl extends ISecurityCenter.Stub {
private static final char SECRET_CODE = '^';
@Override
public String encrypt(String content) throws RemoteException {
char[] chars = content.toCharArray();
for (int i = 0; i < chars.length; i++) {
chars[i] ^= SECRET_CODE;
}
return new String(chars);
}
@Override
public String decrypt(String password) throws RemoteException {
return encrypt(password);
}
}
public class ComputeImpl extends ICompute.Stub {
@Override
public int add(int a, int b) throws RemoteException {
return a + b;
}
}
現在業務模組的AIDL介面定義和實現都已經完成了,注意這裡並沒有為介個模組的AIDL單獨建立Service,接下來就是服務端和Binder連線池的工作了。
首先,為Binder連線池建立AIDL介面IBinderPool.aidl,程式碼如下所示:
interface IBinderPool {
IBinder queryBinder(int binderCode);
}
接著,我們來實現一下IBinderPool這個AIDL介面的實現,程式碼如下:
public class BinderPoolImpl extends IBinderPool.Stub {
public static final int BINDER_COMPUTE = 0;
public static final int BINDER_SECURITY_CENTER = 1;
@Override
public IBinder queryBinder(int binderCode) throws RemoteException {
IBinder binder = null;
switch (binderCode) {
case BINDER_SECURITY_CENTER:
binder = new SecurityCenterImpl();
break;
case BINDER_COMPUTE:
binder = new ComputeImpl();
break;
}
return binder;
}
}
遠端Service的實現就比較簡單了,程式碼如下所示:
public class BinderPoolService extends Service {
private static final String TAG = "BinderPoolService";
private Binder mBinderPool = new BinderPoolImpl();
public BinderPoolService() {
}
@Override
public IBinder onBind(Intent intent) {
return mBinderPool;
}
}
下面還剩下Binder連線池的具體實現,在他的內部首先要去繫結遠端服務,繫結成功後,客戶單就可以通過它的queryBinder方法去獲取各自對應的Binder,拿到所需的Binder以後,不同業務模組就可以進行各自的操作了,Binder連線池的程式碼如下:
public class BinderPool {
private static final String TAG = "BinderPool";
public static final int BINDER_NONE = -1;
private Context mContext;
private IBinderPool mBinderPool;
private static volatile BinderPool sInstance;
private CountDownLatch mConnectBinderPoolCountDownLatch;
private ServiceConnection mBinderPoolConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mBinderPool = IBinderPool.Stub.asInterface(service);
try {
mBinderPool.asBinder().linkToDeath(mBindPoolDeathRecipent, 0);
} catch (RemoteException e) {
e.printStackTrace();
}
mConnectBinderPoolCountDownLatch.countDown();
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
private IBinder.DeathRecipient mBindPoolDeathRecipent = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
mBinderPool.asBinder().unlinkToDeath(mBindPoolDeathRecipent, 0);
mBinderPool = null;
connectBinderPoolService();
}
};
private BinderPool(Context context) {
mContext = context.getApplicationContext();
connectBinderPoolService();
}
private void getInsance(Context context) {
if (sInstance == null) {
synchronized (BinderPool.class) {
if (sInstance == null) {
sInstance = new BinderPool(context);
}
}
}
}
private synchronized void connectBinderPoolService() {
mConnectBinderPoolCountDownLatch = new CountDownLatch(1);
//開啟服務
Intent service = new Intent(mContext, BinderPoolService.class);
mContext.bindService(service, mBinderPoolConnection, Context.BIND_AUTO_CREATE);
}
//根據code獲取對應的Binder
public IBinder queryBinder(int binderCode) {
IBinder binder = null;
try {
if (mBinderPool != null) {
binder = mBinderPool.queryBinder(binderCode);
}
} catch (RemoteException e) {
e.printStackTrace();
}
return binder;
}
}
Binder連線池的具體實現就分析完了,它的好處是顯然易見的,針對上面的例子,我們只需要建立一個Service即可完成多個AIDL介面的工作,下面我們來驗證一下效果。新建立一個Activity,線上程中執行如下操作:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
BinderPool binderPool = BinderPool.getInsance(MainActivity.this);
IBinder securityBinder = binderPool.queryBinder(BinderPoolImpl.BINDER_SECURITY_CENTER);
ISecurityCenter mSecurityCenter = SecurityCenterImpl.asInterface(securityBinder);
String msg = "helloworld-安卓";
try {
String password = mSecurityCenter.encrypt(msg);
System.out.println("encrypt:" + password);
System.out.println(mSecurityCenter.decrypt(password));
} catch (RemoteException e) {
e.printStackTrace();
}
IBinder computeBinder = binderPool.queryBinder(BinderPoolImpl.BINDER_COMPUTE);
ICompute mCompute = ComputeImpl.asInterface(computeBinder);
try {
System.out.println("3 + 5 = " + mCompute.add(3, 5));
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
這個例子中用到了一個CountDownLatch 這個類到底是用來幹嘛的呢?可以參考下面這幾篇文章去了解下CountDownLatch:
關於例子中提到的 IBinder.DeathRecipient類的使用請參考:
選擇合適的IPC方式
1、Bundle
優點:簡單易用
缺點:只能傳輸Bundle支援的資料型別
適用場景:四大元件間的程序間通訊2、檔案共享
優點:簡單易用
缺點:不適合高併發場景,並且無法做到程序間的即時通訊
適用場景:無併發訪問情形,交換簡單的資料實時性不高的場景3、AIDL
優點:功能強大,支援一對多併發通訊,支援實時通訊
缺點:使用稍微複雜,需要處理號執行緒問題
適用場景:一對多通訊且RPC需求4、Messenger
優點:功能一般,支援一對多序列通訊,支援實時通訊
缺點:不能很好的處理高併發情形,不支援RPC,資料通過Message進行傳輸,因此只能傳輸Bundle支援的資料型別。
適用場景:低併發的一對多即時通訊,無RPC需求,或者無須要返回結果的RPC需求5、ContentProvider
優點:在資料來源訪問方面功能強大,支援一對多併發資料共享,可通過Call方法擴充套件其他操作
缺點:可以理解為受約束的AIDL,主要提供資料來源的CRUD操作
適用場景:一對多的程序間的資料共享5、Socket
優點:功能強大,可以通過網路傳輸位元組流,支援一對多併發實時通訊
缺點:實現細節稍微有點繁瑣,不支援直接的RPC
適用場景:網路資料的交換