Android AIDL SERVICE 雙向通訊 詳解

希爾瓦娜斯女神發表於2016-01-25

http://www.cnblogs.com/punkisnotdead/p/5062631.html

起因 是這個blog 提到了 用webview 的時候 用開啟子程式的方式 可以極大避免記憶體洩露。然後有很多人私信我 這種情況下

如何 相互通訊的問題。當然廣播是一個比較不錯的選擇,只不過廣播的方法 能夠傳遞的值比較有限。messenger 也只能做

單向傳遞訊息。(當然你如果用2個 是可以雙向的。單獨的messenger是隻能單向的)。

so,這裡給出一個簡單的小例子,教你如何處理 AIDL service雙向通訊的問題.

首先來建立一下這個例子的 模型,

1.我們假定有一個service 執行在 獨立程式上,這個程式 就好像是餐廳一樣。

2.我們的主程式呢,就好像是一個個顧客, 每次我們進入餐廳的時候 ,餐廳都會告訴我們 誰誰誰 進入了餐廳。

3.上述2條 我們注意看一下 餐廳的容量是有限的,所以我們的顧客進去以後吃完了就必須要出來。不然 餐廳的資源就有可能會被浪費 其他顧客就無法進入。

4.所以有一種場景是 當你的主程式也就是顧客 進入餐廳以後,萬一你的主程式因為某種原因被殺死了,(比如退到後臺的時候 記憶體不夠 被kill掉)那你的service程式就必須要保證

把這個顧客移出掉,不然有限的資源 遲早會被耗盡,而且邏輯上也說不通。

下面就來實現這個需求。

 

首先看下 專案結構:

 

然後看一下我們的aidl檔案:

 1 // RestaurantAidlInterface.aidl
 2 package com.example.administrator.aidlmessageexample;
 3 import com.example.administrator.aidlmessageexample.NotifyCallBack;
 4 // Declare any non-default types here with import statements
 5 //這個就是aidl檔案
 6 interface RestaurantAidlInterface {
 7 
 8     //新來了一個顧客
 9     void join(IBinder token,String name);
10     //走了一個顧客
11     void leave();
12     //註冊回撥介面
13     void registerCallBack(NotifyCallBack cb);
14     void unregisterCallBack(NotifyCallBack cb);
15 }
1 // NotifyCallBack.aidl
2 package com.example.administrator.aidlmessageexample;
3 
4 // Declare any non-default types here with import statements
5 
6 interface NotifyCallBack {
7     void notifyMainUiThread(String name,boolean joinOrLeave);
8 }

 

然後看看我們的service:

  1 package com.example.administrator.aidlmessageexample;
  2 
  3 import android.app.Service;
  4 import android.content.Intent;
  5 import android.os.IBinder;
  6 import android.os.IBinder.DeathRecipient;
  7 import android.os.RemoteCallbackList;
  8 import android.os.RemoteException;
  9 import android.support.annotation.Nullable;
 10 
 11 import java.util.ArrayList;
 12 import java.util.List;
 13 import java.util.Random;
 14 
 15 /**
 16  * Created by Administrator on 2016/1/25.
 17  */
 18 public class RestaurantService extends Service {
 19 
 20     //這個list 就是用來儲存當前餐廳有多少顧客 注意我們為什麼沒有用顧客的名字來儲存?
 21     //而是用了這個CustomerClient的類 看這個類的註釋即可明白
 22     private List<CustomerClient> mClientsList = new ArrayList<>();
 23 
 24     //上面用CustomerClient 的原因是因為害怕客戶端異常銷燬時,伺服器收不到訊息 造成資源浪費等異常
 25     //同樣的 我們在服務端通知客戶端訊息的時候 也害怕 服務端 會異常銷燬 導致客戶端收不到訊息
 26     //好在谷歌早就為我們考慮到這種情況  提供了RemoteCallbackList 來完成對應的功能
 27     //避免我們再重複一遍上述的過程
 28     private RemoteCallbackList<NotifyCallBack> mCallBacks = new RemoteCallbackList<>();
 29 
 30 
 31     private final RestaurantAidlInterface.Stub mBinder = new RestaurantAidlInterface.Stub() {
 32 
 33 
 34         @Override
 35         public void join(IBinder token, String name) throws RemoteException {
 36             CustomerClient cl = new CustomerClient(token, name);
 37             mClientsList.add(cl);
 38             notifyCallBack(name, true);
 39         }
 40 
 41         @Override
 42         public void leave() throws RemoteException {
 43             //顧客離開的時候 我們隨機讓他離開一個就行了
 44             int length = mClientsList.size();
 45             int randomIndex = new Random().nextInt(length-1);
 46             mClientsList.remove(randomIndex);
 47             notifyCallBack(mClientsList.get(randomIndex).mCustomerName, false);
 48         }
 49 
 50         @Override
 51         public void registerCallBack(NotifyCallBack cb) throws RemoteException {
 52             mCallBacks.register(cb);
 53         }
 54 
 55         @Override
 56         public void unregisterCallBack(NotifyCallBack cb) throws RemoteException {
 57             mCallBacks.unregister(cb);
 58         }
 59     };
 60 
 61     private void notifyCallBack(String customerName, boolean joinOrLeave) {
 62         final int len = mCallBacks.beginBroadcast();
 63         for (int i = 0; i < len; i++) {
 64             try {
 65                 // 通知回撥
 66                 mCallBacks.getBroadcastItem(i).notifyMainUiThread(customerName, joinOrLeave);
 67             } catch (RemoteException e) {
 68                 e.printStackTrace();
 69             }
 70         }
 71         mCallBacks.finishBroadcast();
 72     }
 73 
 74 
 75     @Override
 76     public void onDestroy() {
 77         //銷燬回撥資源 否則要記憶體洩露
 78         mCallBacks.kill();
 79         super.onDestroy();
 80     }
 81 
 82     @Nullable
 83     @Override
 84     public IBinder onBind(Intent intent) {
 85         return mBinder;
 86     }
 87 
 88     //http://developer.android.com/intl/zh-cn/reference/android/os/Binder.html#linkToDeath(android.os.IBinder.DeathRecipient, int)
 89     //實際上 這個介面 就是用來 當客戶端自己發生崩潰時, 我們的服務端也能收到這個崩潰的訊息
 90     //並且會呼叫binderDied 這個回撥方法,所以你看這個內部類的程式碼 就明白了 無非就是保證當客戶端異常銷燬的時候
 91     //我們服務端也要保證收到這個訊息 然後做出相應的應對
 92     final class CustomerClient implements DeathRecipient {
 93 
 94         public final IBinder mToken;
 95 
 96         public CustomerClient(IBinder mToken, String mCustomerName) {
 97             this.mToken = mToken;
 98             this.mCustomerName = mCustomerName;
 99         }
100 
101         public final String mCustomerName;
102 
103         @Override
104         public void binderDied() {
105             //我們的應對方法就是當客戶端 也就是顧客異常消失的時候 我們要把這個list裡面 的物件也移出掉
106             if (mClientsList.indexOf(this) >= 0) {
107                 mClientsList.remove(this);
108             }
109 
110         }
111     }
112 }
 1 <!-- 這個地方用開啟子程式的方式來實現這個service 注意你們可以把主程式關閉以後 看看這個子程式
 2         service list裡面持有的那些物件能否收到 這個異常關閉的訊息-->
 3         <service
 4             android:name=".RestaurantService"
 5             android:enabled="true"
 6             android:exported="true"
 7             android:process="com.android.test.process">
 8 
 9             <intent-filter>
10                 <action android:name="com.example.administrator.aidlmessageexample.RestaurantAidlInterface" />
11             </intent-filter>
12 
13         </service>

然後再看看 客戶端 也就是主程式的編寫:

  1 package com.example.administrator.aidlmessageexample;
  2 
  3 import android.content.ComponentName;
  4 import android.content.DialogInterface;
  5 import android.content.Intent;
  6 import android.content.ServiceConnection;
  7 import android.os.Binder;
  8 import android.os.Bundle;
  9 import android.os.IBinder;
 10 import android.os.IInterface;
 11 import android.os.Parcel;
 12 import android.os.RemoteException;
 13 import android.support.design.widget.FloatingActionButton;
 14 import android.support.design.widget.Snackbar;
 15 import android.support.v7.app.AppCompatActivity;
 16 import android.support.v7.widget.Toolbar;
 17 import android.view.View;
 18 import android.view.Menu;
 19 import android.view.MenuItem;
 20 import android.widget.Button;
 21 import android.widget.TextView;
 22 import android.widget.Toast;
 23 
 24 import org.w3c.dom.Text;
 25 
 26 import java.io.FileDescriptor;
 27 import java.util.Random;
 28 
 29 public class MainActivity extends AppCompatActivity implements View.OnClickListener {
 30 
 31     private Button bt, bt2, bt3, bt4;
 32 
 33     private RestaurantAidlInterface mService;
 34 
 35     private TextView tv;
 36 
 37 
 38     private ServiceConnection mServiceConnection = new ServiceConnection() {
 39         @Override
 40         public void onServiceConnected(ComponentName name, IBinder service) {
 41             mService = RestaurantAidlInterface.Stub.asInterface(service);
 42             try {
 43                 //我們這個demo裡面 只註冊了一個回撥 實際上可以註冊很多個回撥 因為service裡面 我們存的是list callback
 44                 mService.registerCallBack(mNotifyCallBack);
 45             } catch (RemoteException e) {
 46                 e.printStackTrace();
 47             }
 48 
 49         }
 50 
 51         @Override
 52         public void onServiceDisconnected(ComponentName name) {
 53             try {
 54                 mService.unregisterCallBack(mNotifyCallBack);
 55             } catch (RemoteException e) {
 56                 e.printStackTrace();
 57             }
 58             mService = null;
 59         }
 60     };
 61 
 62 
 63     private NotifyCallBack mNotifyCallBack = new NotifyCallBack.Stub() {
 64 
 65         @Override
 66         public void notifyMainUiThread(String name, boolean joinOrLeave) throws RemoteException {
 67             String toastStr = "";
 68             if (joinOrLeave) {
 69                 toastStr = name + "進入了餐廳";
 70             } else {
 71                 toastStr = name + "離開了餐廳";
 72             }
 73             tv.setText(toastStr);
 74         }
 75     };
 76 
 77 
 78     @Override
 79     protected void onCreate(Bundle savedInstanceState) {
 80         super.onCreate(savedInstanceState);
 81         setContentView(R.layout.activity_main);
 82         bt = (Button) this.findViewById(R.id.bt);
 83         bt2 = (Button) this.findViewById(R.id.bt2);
 84         bt3 = (Button) this.findViewById(R.id.bt3);
 85         bt4 = (Button) this.findViewById(R.id.bt4);
 86         tv = (TextView) this.findViewById(R.id.tv);
 87         bt.setOnClickListener(this);
 88         bt2.setOnClickListener(this);
 89         bt3.setOnClickListener(this);
 90         bt4.setOnClickListener(this);
 91 
 92         Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
 93         setSupportActionBar(toolbar);
 94 
 95         FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
 96         fab.setOnClickListener(new View.OnClickListener() {
 97             @Override
 98             public void onClick(View view) {
 99                 Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
100                         .setAction("Action", null).show();
101             }
102         });
103     }
104 
105     @Override
106     public boolean onCreateOptionsMenu(Menu menu) {
107         // Inflate the menu; this adds items to the action bar if it is present.
108         getMenuInflater().inflate(R.menu.menu_main, menu);
109         return true;
110     }
111 
112     @Override
113     public boolean onOptionsItemSelected(MenuItem item) {
114         // Handle action bar item clicks here. The action bar will
115         // automatically handle clicks on the Home/Up button, so long
116         // as you specify a parent activity in AndroidManifest.xml.
117         int id = item.getItemId();
118 
119         //noinspection SimplifiableIfStatement
120         if (id == R.id.action_settings) {
121             return true;
122         }
123 
124         return super.onOptionsItemSelected(item);
125     }
126 
127     @Override
128     public void onClick(View v) {
129         switch (v.getId()) {
130             case R.id.bt:
131                 bindService();
132                 break;
133             case R.id.bt2:
134                 unbindService();
135                 break;
136             case R.id.bt3:
137                 addCustomer();
138                 break;
139             case R.id.bt4:
140                 leaveCustomer();
141                 break;
142         }
143 
144     }
145 
146     private void bindService() {
147         Intent intent = new Intent(RestaurantAidlInterface.class.getName());
148         bindService(intent, mServiceConnection, BIND_AUTO_CREATE);
149     }
150 
151     private void unbindService() {
152         unbindService(mServiceConnection);
153     }
154 
155     private void leaveCustomer() {
156         try {
157             // mService.registerCallBack(mNotifyCallBack);
158             mService.leave();
159         } catch (RemoteException e) {
160             e.printStackTrace();
161         }
162     }
163 
164     private void addCustomer() {
165         try {
166             mService.join(new Binder(), getRandomString(6));
167         } catch (RemoteException e) {
168             e.printStackTrace();
169         }
170     }
171 
172     public static String getRandomString(int length) { //length表示生成字串的長度
173         String base = "abcdefghijklmnopqrstuvwxyz0123456789";
174         Random random = new Random();
175         StringBuffer sb = new StringBuffer();
176         for (int i = 0; i < length; i++) {
177             int number = random.nextInt(base.length());
178             sb.append(base.charAt(number));
179         }
180         return sb.toString();
181     }
182 }

 

最後跑一下效果(客戶端程式異常結束 服務端程式收到訊息 無法演示在gif裡面,你們可以回去自己演示 看log日誌 即可。直接用adb shell 命令 結束客戶端程式 就行了)

 

相關文章