Android中實現IPC的幾種方式詳細分析及比較

1.使用Bundle   ----> 用於android四大組件間的進程間通訊
android的四大組件均可使用Bundle傳遞數據  因此若是要實現四大組件間的進程間通訊 徹底可使用Bundle來實現 簡單方便  

2.使用文件共享  ---->用於單線程讀寫
這種方式在單線程讀寫的時候比較好用 若是有多個線程併發讀寫的話須要限制線程的同步讀寫  
另外 SharePreference是個特例  它底層基於xml實現  可是系統對它的讀寫會基於緩存,也就是說再多進程模式下就變得不那麼可靠了,有很大概率丟失數據


3.使用Messenger   ---->用於可存放在message中的數據的傳遞
使用這個方式能夠在不一樣進程間傳遞message對象  這是一種輕量級的IPC方案  當傳遞的對象能夠放入message中時  能夠考慮用這種方式  可是msg.object最好不要放
由於不必定能夠序列化  
使用它的步驟以下:
假設這樣一個需求  須要在客戶端A發送消息給服務端B接受  而後服務端B再回復給客戶端A java

1. 首先是客戶端A發送消息給服務端B  因此在客戶端A中 聲明一個Handler用來接受消息  並建立一個Messenger對象 用Handler做爲參數構造  而後onBinder方法返回messenger.getBinder() 便可android

  1. public class MyServiceA extends Service {  
  2.   
  3.         private class MessageHandler extends Handler{  //建立的接受消息的handler  
  4.             @Override  
  5.             public void handleMessage(Message msg) {  
  6.                 switch (msg.what){  
  7.                     case 1:  
  8.                         Bundle bundle = msg.getData();  
  9.                         String str = bundle.getString("aaa");  
  10.                         System.out.println("----"+str);  
  11.                         Messenger replyTo = msg.replyTo; //此處往下是用來回復消息給客戶端A的     
  12.                         Message replyMsg = Message.obtain(null,2);  
  13.                         Bundle bundle1 = new Bundle();  
  14.                         bundle1.putString("bbb","remote222給主進程回覆消息啦");  
  15.                         replyMsg.setData(bundle1);  
  16.                         try {  
  17.                             replyTo.send(replyMsg);  
  18.                         } catch (RemoteException e) {  
  19.                             e.printStackTrace();  
  20.                         }  
  21.                         break;  
  22.                 }  
  23.                 super.handleMessage(msg);  
  24.             }  
  25.         }  
  26.         Messenger messenger = new Messenger(new MessageHandler());  
  27.         public MyServiceA() {  
  28.         }  
  29.           
  30.         public IBinder onBind(Intent intent) {  
  31.             return messenger.getBinder();  
  32.         }  
  33.     }  


 

 

2.在客戶端A天然是須要發送消息給服務端B的  因此須要在服務綁定完成以後  獲取到binder對象  以後用該對象構造一個Messenger對象  而後用messenger發送
消息給服務端便可  代碼以下  :sql

  1. public void onServiceConnected(ComponentName name, IBinder service) {  
  2.                 Messenger messenger = new Messenger(service);  
  3.                 Message msg = Message.obtain(null,1);  
  4.                 Bundle bundle = new Bundle();  
  5.                 bundle.putString("aaa", "主進程給remote22進程發消息啦");  
  6.                 msg.setData(bundle);  
  7.                 msg.replyTo = mmessenger; //這行代碼用於客戶端A接收服務端請求 設置的消息接收者   
  8.                 try {  
  9.                     messenger.send(msg);  
  10.                 } catch (RemoteException e) {  
  11.                     e.printStackTrace();  
  12.                 }  
  13.   
  14.             }  



3.因爲在服務端接收到了客戶端的消息還須要回覆  因此在服務端代碼中獲取 msg中的replyTo對象  用這個對象發送消息給 客戶端便可 
 在客戶端須要建立一個handler和Messenger  將發送的msg.replyTo設置成Messenger對象  就可  


4.AIDL android 接口定義語言  ---->主要用於調用遠程服務的方法的狀況 還能夠註冊接口 
使用方法很簡單  
在服務端定義aidl文件 自動生成java文件  而後在service中實現這個aidl  在onbind中返回這個對象  
在客戶端把服務端的aidl文件徹底複製過來  包名必須徹底一致   在onServiceConnected方法 中 把  Ibinder對象 用asInterface方法轉化成 aidl對象
而後調用方法便可  

須要注意的地方: 
在aidl文件中並非支持全部類型 
僅支持以下6種類型:
基本數據類型---- int long  char  boolean double 
String  charSequence
List  只支持ArrayList  CopyOnWriteArrayList也能夠。。  裏面元素也必須被aidl支持
Map   只支持HashMap   ConCurrentHashMap也能夠  裏面元素也必須支持aidl
Parcelable  全部實現了此接口的對象 
AIDL  全部的AIDL接口   所以 若是須要使用接口 必須使用AIDL接口

其中自定義的類型和AIDL對象必須顯示import進來 不論是不是在一個包中  
若是AIDL文件中用到了自定義的Parcelable對象  必須建立同名的AIDL文件 並聲明爲Parcelable類型
AIDL文件中除了基本數據類型外 其餘類型必須標上方向  in  out  inout  
AIDL接口中只支持方法  不支持聲明靜態常量
在使用aidl時  最好把全部aidl文件都放在一個包中  這樣方便複製到客戶端 
其實全部的跨進程對象傳遞都是對象的序列化與反序列化  因此必須包名一致

如今加入有這樣一個需求 若是服務端是 圖書館添加和查看書的任務   客戶端能夠查看和添加書   這時候須要添加一個功能  當服務端每添加了一本書 
須要通知客戶端註冊用戶  有一本新書上架了   這個功能如何實現?
想一想可知  這是一個觀察者模式  若是在同一進程中很容易實現,只須要在服務端中的代碼中維護一個集合 裏面放的是註冊監聽的用戶  而後用戶須要實現一個新書到來的回調接口
當有新書上架時 遍歷這個集合  調用每一個註冊者的接口方法  便可實現  
如今咱們是跨進程通訊   因此天然不能如此簡單了  但也不是很複雜 想想  其實就是把以往的接口定義 變成了aidl接口定義  而後其餘的同樣便可  
可是這樣仍是存在一個問題  若是註冊了listener  咱們又想解除註冊  是否是在客戶端傳入listener對象 在服務端把它移除就能夠呢? 
實際上是不能夠的   由於這是跨進程的  因此對象並非真正的傳遞  只是在另外一個進程中從新建立了一個同樣的對象  內存地址不一樣 因此根本不是同一個對象
因此是不能夠的   若是要解決這個問題  須要使用RemoteCallbackList 類  不要使用CopyWriteArrayList  
在RemoteCallBackList中封裝了一個Map 用來保存全部的AIDL回調  key爲IBinder  value是CallBack   使用IBinder 來區別不一樣的對象  ,
由於跨進程傳輸時會產生不少個不一樣的對象  但這些對象的底層的Binder都是同一個對象  因此能夠  
在使用RemoteCallBackList時 add 變爲 register  remove 變爲 unregister  遍歷的時候須要先 beginBroadcast  這個方法同時也獲取集合大小 
獲取集合中對象使用 getBoardCastItem(i)  最後不要忘記finishBoardCast方法   

還有一個狀況  因爲onServiceConnected方法 是在主線程執行的  若是在這裏執行服務端的耗時代碼  會ANR  因此須要開啓一個子線程執行  
同理在服務端中 也不能夠運行客戶端的耗時程序  
總結起來就是 在執行其餘進程的耗時程序時  都須要開啓另外的線程防止阻塞UI線程  若是要訪問UI相關的東西  使用handler  

爲了程序的健壯性  有時候Binder可能意外死亡  這時候須要重連服務  有2種方法:
1.在onServiceDisconnected方法中  重連服務  
2. 給Binder註冊DeathRecipient監聽  當binder死亡時 咱們能夠收到回調  這時候咱們能夠重連遠程服務

最後有時候咱們不想全部的程序均可以訪問咱們的遠程服務  因此能夠給服務設置權限和過濾:
1.咱們在onbind中進行校驗 用某種方式 若是驗證不經過那麼就直接返回null 
2.咱們能夠在服務端的AndroidMiniFest.xml中  設置所需的權限  <permission android:name="aaaaaa" android:protectionLevel="normal"/>
而後在onbind中 檢查是否有這個權限了  若是沒有那麼直接返回null便可  判斷方法以下  :數組

  1. int check = checkCallingOrSelfPermission("aaa");  
  2.             if(check== PackageManager.PERMISSION_DENIED){  
  3.                 return null;  
  4.             }  



3.能夠在onTransact方法中 進行權限驗證  若是驗證失敗直接返回false  能夠採用permission方法驗證  還能夠用Uid和Pid驗證  不少方法  

其中聲明權限與 添加權限的方式 是   在Service所在的AndroidMinifest中 聲明權限 
好比    <permission android:name="com.yangsheng.ydzd_lb.myaidlpro.book" android:protectionLevel="normal"></permission>
而後在 須要遠程調用的 app中添加 這個權限 <uses-permission android:name="com.yangsheng.ydzd_lb.myaidlpro.book"/>  
這樣 就能夠在  onbind中驗證權限了緩存

至此 AIDL  大致介紹完了   之後須要在使用中提高了網絡

 

aidl demo 下載  :  http://download.csdn.NET/detail/u012760183/9520173多線程


5.ContentProvider方式  實現對另外一個應用進程開放provider數據的查詢
此方法使用起來也比較簡單  底層是對Binder的封裝 使之能夠實現進程間通訊  使用方法以下  
1. 在須要共享數據的應用進程中創建一個ContentProvider類 重寫它的CRUD 和getType方法  在這幾個方法中調用對本應用進程數據的調用 
  而後在AndroidMinifest.xml文件中聲明provider  併發

  1. <provider   
  2.             android:authorities="com.yangsheng.book"  //這個是用來標識provider的惟一標識  路徑uri也是這個  
  3.             android:name=".BookProdiver"  
  4.             android:process=":remote_provider"/>   //此句爲了建立多進程  正常不須要使用  




2. 在須要獲取共享數據的應用進程中調用getContentResolver().crud方法  便可實現數據的查詢  


須要注意的問題:
1.關於 sqlite crud的各個參數的意義 
query函數 參數 
Cursor query(boolean distinct, String table, String[] columns,
String selection, String[] selectionArgs, String groupBy,
String having, String orderBy, String limit)
第一個參數 distinct 英語單詞意思 獨特的   若是true 那麼返回的數據都是惟一的  意思就是實現查詢數據的去重 
第二個參數 table  表名
第三個參數 columns  要查詢的行的名字數組  例如  new String[]{"id","name","sex"} 
第四個參數 selection 選擇語句  sql語句中where後面的語句  值用?代替  例如  "id=? and sex=?"
第五個參數 selectionArgs  對應第四個參數的 ?  例如  new String[]{"1","男"}
第六個參數 groupBy 用於分組  
第七個參數 having  篩選分組後的數據 
第八個參數 orderby 用於排序  desc/asc  升序和降序  例如  id desc / id asc 
最後一個參數 limit  用於限制查詢的數據的個數  默認不限制
其餘幾個函數 根據query函數的參數猜測便可

2.因爲每次ipc操做 都是靠uri來區別 想要獲取的數據位置  因此provider在調取數據的時候根據uri並不知道要查詢的數據是在哪一個位置
 因此咱們能夠經過 UriMatcher 這個類來給每一個uri標上號 根據編號 對應適當的位置   例如:app

  1. public static final int BOOK_CODE = 0;  
  2.             public static final int USER_CODE = 1;  
  3.             public static UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);  
  4.   
  5.             static {  
  6.                 matcher.addURI("book uri", "book", BOOK_CODE);  
  7.                 matcher.addURI("user uri", "user", USER_CODE);  
  8.             }  
  9.             這樣咱們能夠經過 下面這個樣子來獲取位置(此處是表名 其餘類型也同樣)  
  10.             private String getTableName(Uri uri) {  
  11.                 switch (matcher.match(uri)) {  
  12.                     case BOOK_CODE:  
  13.                         return "bookTable";  
  14.                     case USER_CODE:  
  15.                         return "userTable";  
  16.                 }  
  17.                 return "";  
  18.             }  



 

3.另外ContentProvider除了crud四個方法外,還支持自定義調用  經過ContentProvider 和ContentResolver的 call方法  來實現 異步

 

ContentProviderdemo下載 : http://download.csdn.Net/detail/u012760183/9520175


6.Socket方法實現Ipc   這種方式也能夠實現 可是不經常使用  
須要權限  
<uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
這種方式須要一個服務端socket 和一個客戶端socket  創建鏈接後 經過流循環獲取消息便可  
1.在服務端開啓一個serverSocket 不斷獲取客戶端鏈接  注意要在子線程中開啓 
 

  1. ServerSocket serverSocket = new ServerSocket(8688);  
  2.         while(isActive) { //表示服務生存着  
  3.                 try {  
  4.                     final Socket client = serverSocket.accept();  //不斷獲取客戶端鏈接  
  5.                     System.out.println("---服務端已獲取客戶端鏈接");  
  6.                     new Thread(){  
  7.                         @Override  
  8.                         public void run() {  
  9.                             try {  
  10.                                 dealWithMessageFromClient(client);  //處理客戶端的消息 就是開啓一個線程循環獲取out 和 in  流 進行通訊  
  11.                             } catch (IOException e) {  
  12.                                 e.printStackTrace();  
  13.                             }  
  14.                         }  
  15.                     }.start();  
  16.                 } catch (IOException e) {  
  17.                     e.printStackTrace();  
  18.                 }  
  19.             }  



2.在客戶端開啓一個線程 使用ip和端口號鏈接服務端socket  鏈接成功後 同樣 開啓子線程 循環獲取消息 處理

  1. Socket socket = null;  
  2.                 while(socket==null){  //失敗重連  
  3.                     try {  
  4.                         socket = new Socket("localhost",8688);  
  5.                         out = new PrintWriter(socket.getOutputStream(),true);  
  6.                         handler.sendEmptyMessage(1);  
  7.                         final Socket finalSocket = socket;  
  8.                         new Thread(){  
  9.                             @Override  
  10.                             public void run() {  
  11.                                 try {  
  12.                                     reader = new BufferedReader(new InputStreamReader(finalSocket.getInputStream()));  
  13.                                 } catch (IOException e) {  
  14.                                     e.printStackTrace();  
  15.                                 }  
  16.                                 while(!MainActivity.this.isFinishing()){  //循環獲取消息  這裏必須用 循環 不然 只能獲取一條消息 服務端也同樣  
  17.                                     try {  
  18.                                         String msg = reader.readLine();  
  19.                                         System.out.println("---"+msg);  
  20.                                         if (msg!=null){  
  21.                                             handler.sendMessage(handler.obtainMessage(2,msg));  
  22.                                         }  
  23.                                     } catch (IOException e) {  
  24.                                         e.printStackTrace();  
  25.                                     }  
  26.                                 }  
  27.                             }  
  28.                         }.start();  
  29.                     } catch (IOException e) {  
  30.                         SystemClock.sleep(1000);  
  31.                         e.printStackTrace();  
  32.                     }  
  33.                 }  




7.Binder 鏈接池的使用  很好用
咱們在android中進程間通訊 通常都使用 AIDL實現 由於它強大  可是普通的使用方法每次使用AIDL 都須要開啓一個服務  若是有多個AIDL請求 那豈不是要開啓不少個服務 
這明顯是不能夠的  好比你讓你用戶的手機 發現你這一個應用程序綁定了10個服務  那是極差的  因此 咱們在多個AIDL 請求的時候可使用Binder鏈接池技術 
只開啓一個服務  根據須要獲取的AIDL不一樣 轉化成須要的AIDL 接口 執行不一樣的方法  
實現的基本原理  就是在onbind中返回一個BinderPool 接口 這個接口有個方法 能夠根據不一樣的標誌位返回不一樣的aidl接口  這樣咱們在asInTerface以後調用哪一個方法
傳入標誌位便可返回須要的aidl接口 
提及來簡單 讓咱們來實現一個試試吧
1.假設原來有2個AIDL接口須要實現(能夠擴展成多個)  在服務端創建好AIDL文件  而且創建一個IBinderPool aidl接口  只有一個查詢binder的方法 用於查詢須要的binder
interface IBinderPool {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
IBinder queryBinder(int code);  //此方法返回Ibinder  用於轉化成須要的AIDL接口
}
2.在服務端 onbind方法中返回 IBinderPool的實現類  實現query方法 按照傳入的code 返回須要的ibinder
@Override
public IBinder onBind(Intent intent) {
return iBinderPool;
}


private Binder iBinderPool = new IBinderPool.Stub() {


@Override
public IBinder queryBinder(int code) throws RemoteException {
switch (code) {
case 1:
return new IBookManger.Stub() {


@Override
public void getBook() throws RemoteException {
System.out.println("--->book");
}
};
case 2:
return new IPersonManager.Stub() {


@Override
public void getPerson() throws RemoteException {
System.out.println("---->person");
}
};


}
return null;
}
};
3.客戶端實現一個BinderPool類  這個類主要是封裝了 AIDL的一些實現方法 方便調用罷了 其中 涉及到一個能夠實現同步機制的類
CountDownLatch  這個類 當他的值 不是0的時候  執行了await方法後會使方法一直停在await處  不進行 直到他的值變成了0 才能夠繼續執行  
也就是說 當執行了await方法  這個線程就會阻塞 等待這個數值變到0後繼續執行  
而在BinderPool中的應用場景是這樣的   
 private void connectService(){
countDownLatch = new CountDownLatch(1);  //實現同步機制 
Intent intent = new Intent();
intent.setClass(ctx,MyService.class);
ctx.bindService(intent,connection,Context.BIND_AUTO_CREATE);
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
首先爲何在這裏要使用同步機制  咱們要搞清楚 讓咱們看這個方法調用的時機 :
binderPool = BinderPool.getInstance(MainActivity.this);  //connectService方法 是在這個方法中調用的  
                IBinder iBinder = binderPool.queryBinder(2);
                iPersonManager = IPersonManager.Stub.asInterface(iBinder);
                try {
                    iPersonManager.getPerson();
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
由於咱們最終的目的是在bind服務  鏈接到遠程服務以後獲取到 binderPool對象調用它的 binderPool.queryBinder(2) 方法  若是不加同步機制
異步執行  就有可能在 connectService方法 執行完以後  執行IBinder iBinder = binderPool.queryBinder(2);這行代碼的時候binderPool對象還
沒有被賦值  這樣就會產生問題  因此咱們讓 connectService方法 阻塞  當BinderPool中的 binderPool對象賦值以後 讓CountDownLatch的值countDown到0

這樣 connectService方法就會繼續執行 而後執行下一行代碼了  

BinderPool demo下載 : http://download.csdn.net/detail/u012760183/9520188

 


最後 總結了這麼多IPC通訊方式  那咱們該如何選擇合適的IPC方式呢  針對這幾種IPC通訊方式分析一下優缺點 1.bundle : 簡單易用  可是隻能傳輸Bundle支持的對象 經常使用於四大組件間進程間通訊  2.文件共享: 簡單易用  但不適合在高併發的狀況下 而且讀取文件須要時間 不能即時通訊   經常使用於併發程度不高 而且實時性要求不高的狀況 3.AIDL : 功能強大 支持一對多併發通訊 支持即時通訊   可是使用起來比其餘的複雜 須要處理好多線程的同步問題  經常使用於一對多通訊 且有RPC 需求的場合(服務端和客戶端通訊) 4.Messenger : 功能通常 支持一對多串行通訊 支持實時通訊  可是不能很好處理高併發狀況 只能傳輸Bundle支持的類型  經常使用於低併發的無RPC需求一對多的場合  5.ContentProvider : 在數據源訪問方面功能強大 支持一對多併發操做 可擴展call方法  能夠理解爲約束版的AIDL  提供CRUD操做和自定義函數  經常使用於一對多的數據共享場合 6.Socket : 功能強大 能夠經過網絡傳輸字節流 支持一對多併發操做  可是實現起來比較麻煩 不支持直接的RPC   經常使用於網絡數據交換 總結起來   當僅僅是跨進程的四大組件間的傳遞數據時 使用Bundle就能夠  簡單方便   當要共享一個應用程序的內部數據的時候  使用ContentProvider實現比較方便   當併發程度不高  也就是偶爾訪問一次那種 進程間通訊 用Messenger就能夠   當設計網絡數據的共享時  使用socket  當需求比較複雜  高併發 而且還要求實時通訊 並且有RPC需求時  就得使用AIDL了  文件共享的方法用於一些緩存共享 之類的功能

相關文章
相關標籤/搜索