使用Bundle
我們知道泉瞻,四大組件中的三大組件(Activity莽囤,Service,Broadcast)都是支持在Intent中傳輸Bundle數(shù)據(jù)的房官,由于Bundle實現(xiàn)了Parcelable接口趾徽,所以他可以很方便的在進程間傳輸,通過Bundle我們不就可以傳輸基本類型翰守,也可以傳輸實現(xiàn)了Parcelable接口或者Serializable接口的對象
使用文件共享
- 在Windows上附较,一個文件如果被加了排斥鎖將會導(dǎo)致其他線程無法對其進行訪問,包括讀和寫
- 而Android由于是基于Linux系統(tǒng)潦俺,使得這個并發(fā)讀寫操作可以無限制的進行拒课,盡管這可能出問題
- 但是這種方式卻是解決了不同進程數(shù)據(jù)傳輸?shù)膯栴}
接下來簡單的舉個栗子示范一下在本進程中寫數(shù)據(jù):
Data data = new Data(10,"這是主進程寫的數(shù)據(jù)");
try {
ObjectOutputStream out = new ObjectOutputStream(
new FileOutputStream("/storage/emulated/0/1/sina/sdadas.txt"));
out.writeObject(data);
out.close();
} catch (IOException e) {
e.printStackTrace();
Log.d(TAG, "ssss: 序列化的時候失敗" + e.toString());
}
在另一進程中讀數(shù)據(jù)
Data data = null;
try {
ObjectInputStream in = new ObjectInputStream(
new FileInputStream("/storage/emulated/0/1/sina/sdadas.txt"));
data = (Data) in.readObject();
}catch (IOException e){
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
if(data != null){
Log.d(TAG, "onClick: 讀取成功 , data.id = " + data.id + "data.name = " + data.name);
}else {
Log.d(TAG, "onClick: 錯誤");
}
這是正常并且操作成功的事示,說明這種方式是可行的早像,不過我們這里雖然說的是傳輸數(shù)據(jù),只不過我們保證的是內(nèi)容一樣肖爵,他們實際上還是兩個不同的對象卢鹦,通過這種方式,我們應(yīng)盡量控制讀寫時的時序問題
使用Messenger
Messenger可以翻譯為信使,通過它可以在不同進程中傳遞Messenger對象冀自,在Messenger中可以放入我們需要傳遞的對象揉稚,就可以輕松跨進程傳輸
-
Messenger是一種輕量級的IPC方案,他的底層實現(xiàn)是AIDL熬粗,為什么這么說呢搀玖,我們?nèi)タ匆幌滤臉?gòu)造方法
public Messenger(Handler target) { mTarget = target.getIMessenger(); } public Messenger(IBinder target) { mTarget = IMessenger.Stub.asInterface(target); }
從上構(gòu)造方法我們可以很容易想到底層是AIDl的
Messenger的用法非常簡單,他對AIDL做了封裝驻呐,使得我們可以更簡單的跨進程通信
同時灌诅,由于他一次處理一個請求,因此在服務(wù)端我們不同考慮線程同步的問題含末,這是因為服務(wù)端不承諾在并發(fā)執(zhí)行的問題猜拾,下面我們來看看怎么實現(xiàn)一個Messenger
我們首先來看看服務(wù)端進程中怎么實現(xiàn)
只需要在服務(wù)端創(chuàng)建一個Service來處理客戶端的連接請求,并通過它來創(chuàng)建一個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 1:
Log.d(TAG, "handleMessage: receive msg from Client " + msg.getData().getString("msg"));
break;
}
super.handleMessage(msg);
}
}
private final Messenger mMessenger = new Messenger(new Messengerhandler());
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
}
可以看到挎袜,因為我們在上面的Messenger構(gòu)造器中發(fā)現(xiàn)Messenger的構(gòu)造器需要傳入一個handler 類型的參數(shù),很明顯這個Handler就是用來處理客戶端發(fā)來的Message消息的
接下來看看客戶端的代碼
客戶端首先要綁定Service肥惭,然后發(fā)送Message類型的消息
void bindMyMessengerService(){
ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mService = new Messenger(service);
sendData();
}
@Override
public void onServiceDisconnected(ComponentName name) {
mService = null;
}
};
Intent intent = new Intent(this,MessengerService.class);
bindService(intent,mConnection, Context.BIND_AUTO_CREATE);
}
void sendData(){
Message msg = Message.obtain(null,1);
Bundle data = new Bundle();
data.putString("msg","這是客戶端的消息");
msg.setData(data);
try{
mService.send(msg);
}catch (RemoteException e) {
e.printStackTrace();
}
}
這段代碼相信沒什么難度盯仪,不過這里問題又來了?誒务豺?我們只是發(fā)送了消息給服務(wù)端磨总,可是通信,通信笼沥,不是應(yīng)該能互相發(fā)的嗎蚪燕?客戶端是怎么接收服務(wù)端的呢?
接下來我們看看服務(wù)端怎么給客戶端發(fā)送消息奔浅,先來看看服務(wù)端怎么修改
private static class Messengerhandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case 1:
Log.d(TAG, "handleMessage: receive msg from Client " + msg.getData().getString("msg"));
Messenger client = msg.replyTo;
Message clientData = Message.obtain(null,1);
Bundle bundle = new Bundle();
bundle.putString("reply","嗯馆纳,我收到了你的消息,我是服務(wù)端");
clientData.setData(bundle);
try {
client.send(clientData);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
}
super.handleMessage(msg);
}
}
這里我將服務(wù)端接收消息的handler的Hand可Message方法改變了一下汹桦,讓他能在接收到消息之后直接再給客戶端發(fā)送一個消息
這里可以看到鲁驶,我們發(fā)送消息的Messenger是從客戶端發(fā)送過來msg中得到的,能猜到吧舞骆,客戶端將自己進程所在的Messenger通過Messenger發(fā)送給服務(wù)端钥弯,然后服務(wù)端就可以拿到客戶端進程的Messenger句柄給客戶端發(fā)送消息了
-
看一下客戶端的代碼
private Messenger mCilentMessenger = new Messenger(new ClientMessenger());
private static class ClientMessenger extends Handler{
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case 1:
Log.d(TAG, "handleMessage: 接收到服務(wù)端的消息 = " +msg.getData().getString("reply"));
break;
}super.handleMessage(msg); }
}
void sendData(){
Message msg = Message.obtain(null,1);
msg.replyTo = mCilentMessenger;
Bundle data = new Bundle();
data.putString("msg","這是客戶端的消息");
msg.setData(data);
try{
mService.send(msg);
}catch (RemoteException e) {
e.printStackTrace();
}
}
- 這里我添加了一個Messenger對象負責(zé)作為發(fā)送消息時候的replyTo參數(shù),然后將這個參數(shù)在發(fā)送消息時添加到replyTo參數(shù)上即可
- 具體代碼其實也沒啥難度督禽,不太清楚的小伙伴認真分析一下即可
Messenger總結(jié)
- 我們發(fā)現(xiàn)Messenger的核心就是使用本進程的Messenger在另外一個線程發(fā)送(Message)消息就可
使用AIDL
Binder之間通信還是分為客戶端和服務(wù)端
先來看服務(wù)端脆霎,還是創(chuàng)建一個Service來監(jiān)聽客戶端的連接請求,然后創(chuàng)建一個AIDL文件狈惫,將暴露給客戶端的接口在這個AIDL文件中聲明睛蛛,最后在Service中實現(xiàn)這個AIDL接口即可
-
先看看服務(wù)端AIDL接口的創(chuàng)建吧
// IBookManager.aidl package com.example.learnretrofit.LearnMoreProcess; import com.example.learnretrofit.LearnMoreProcess.Book; interface IBookManager { List<Book> getBookList(); void addBook(in Book book); }
創(chuàng)建AIDL文件的方法上篇文章講過,在文章最開始有鏈接
在AIDL文件中,并不是所有類型都可使用忆肾,他所能使用的數(shù)據(jù)類型如下
基本數(shù)據(jù)類型
- String和CharSequence
- List:只支持ArrayList荸频,里面每個元素都必須能被AIDL支持
- Map:只支持HashMap,里面每個元素都必須能被AIDL支持
- Parcelable:所有實現(xiàn)了Parcelable接口的對象客冈,使用時需要手動import
- AIDL:所有的AIDL接口本身也可以在AIDL文件中使用旭从,使用時需要手動import
這里由于在AIDL接口中使用了Book類,所以必須在前面import郊酒,另外如果AIDL文件用到了自定義的Parcelable對象遇绞,必須新建一個和他同名的AIDL文件键袱,并在其中聲明他為Parcelable類型燎窘,因為在上面的接口定義中,我們使用了Book類蹄咖,所以我們必須定義一個aidl文件去聲明他
// Book.aidl
package com.example.learnretrofit.LearnMoreProcess;
parcelable Book;
不知道大家注意到接口定義的方法的參數(shù)聲明前有個in褐健,這個參數(shù)是什么意思呢?AIDL中除了基本數(shù)據(jù)類型之外澜汤,其他類型的參數(shù)都必須標上方向蚜迅,in , out 俊抵,或者inout谁不,in表示輸入性參數(shù),out表示輸出型參數(shù)徽诲,inout表示輸入輸出型參數(shù)刹帕,因為這個參數(shù)代表著底層的操作,所以在標注的時候不能隨便標
最后要注意的是AIDL接口中只支持方法谎替,不支持靜態(tài)變量偷溺,還有,服務(wù)端和客戶端的包結(jié)構(gòu)必須一致钱贯,因為客戶端需要反序列話服務(wù)器中和AIDL接口相關(guān)的所有類挫掏,如果包路徑不同就會無法反序列化成功
-
那么上面的接口定義好了,我們就來看看我們的服務(wù)端怎么實現(xiàn)
注意秩命,在編寫好aidl文件之后記得build-make projectpublic class BookManagerService extends Service {
private static final String TAG = "BookManagerService";
private CopyOnWriteArrayList<Apple> mAppleList = new CopyOnWriteArrayList<>();
private Binder mBinder = new IAppleManager.Stub() {
@Override
public List<Apple> getAppleList() throws RemoteException {
return mAppleList;
}@Override public void addApple(Apple apple) throws RemoteException { mAppleList.add(apple); } }; @Override public void onCreate() { super.onCreate(); mAppleList.add(new Apple(10,"紅富士")); mAppleList.add(new Apple(10,"早熟蘋果")); } @Nullable @Override public IBinder onBind(Intent intent) { return mBinder; } }
這里的IAppleManager.Stub是系統(tǒng)給我們生成的東西尉共,他實際上是一個Binder類,然后我們先在onCreate方法里面添加兩個數(shù)據(jù)弃锐,注意到我們這里使用了CopyOnWriteArrayList袄友,因為他在原理上是支持并發(fā)讀寫的,* * 而AIDL支持的是抽象的List這個接口拿愧,雖然服務(wù)端返回的是CopyOnWriteArrayList杠河,但是在Binder中會按照List的規(guī)范去訪問數(shù)據(jù)并最終形成一個ArrayList傳遞給客戶端,所以我們在這里使用CopyOnWriteArrayList是完全合理的,類似的還有ConcurrentHashMap券敌,然后在注冊文件中注冊這個Service
接著來看看客戶端的實現(xiàn)-
客戶端的話我們就需要綁定服務(wù)唾戚,然后將服務(wù)端返回的Binder對象轉(zhuǎn)換成AIDL接口,然后就可以通過這個接口去調(diào)服務(wù)端的遠程方法了待诅,看看客戶端的代碼
void bindMyAppleService(){ ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { IAppleManager appleManager = IAppleManager.Stub.asInterface(service); try { List<Apple> list = appleManager.getAppleList(); Log.d(TAG, "onServiceConnected: query apple list ,list type is " + list.getClass().getCanonicalName()); for(Apple apple : list){ Log.d(TAG, "onServiceConnected: apple.color = " + apple.color + ",apple.size = " + apple.size); } } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) {} }; bindService(new Intent(this,AppleManagerService.class),mConnection,Context.BIND_AUTO_CREATE); }
在綁定成功之后直接跨進程獲取數(shù)據(jù)叹坦,注意這里是為了方便才這么寫,一般還是寫在新線程中比較好卑雁,因為這個服務(wù)端響應(yīng)時間是不定的募书,萬一響應(yīng)時間過長就會出現(xiàn)ANR
如果你對AIDL還不太清楚,可能看這些有點模糊测蹲,煩請移駕androidIPC機制探索先把這個看看
-
在啟動程序后打印出來的log信息為
onServiceConnected: query apple list ,list type is java.util.ArrayList onServiceConnected: apple.color = 紅富士,apple.size = 10 onServiceConnected: apple.color = 早熟蘋果,apple.size = 10
可見客戶端收到的確實是Arraylist類型莹捡,證實了我們上面的說法,同時下面的信息也證明了我們的跨進程操作成功
-
我們再試一下他的addApple方法扣甲,修改onServiceConnection方法
public void onServiceConnected(ComponentName name, IBinder service) { IAppleManager appleManager = IAppleManager.Stub.asInterface(service); try { List<Apple> list = appleManager.getAppleList(); Log.d(TAG, "onServiceConnected: query apple list ,list type is " + list.getClass().getCanonicalName()); for(Apple apple : list){ Log.d(TAG, "onServiceConnected: apple.color = " + apple.color + ",apple.size = " + apple.size); } Apple apple = new Apple(10,"綠色的蘋果"); appleManager.addApple(apple); list = appleManager.getAppleList(); Log.d(TAG, "onServiceConnected: 重新接收"); for(Apple apple1 : list){ Log.d(TAG, "onServiceConnected: apple.color = " + apple1.color + ",apple.size = " + apple1.size); } } catch (RemoteException e) { e.printStackTrace(); } }
log信息
onServiceConnected: query apple list ,list type is java.util.ArrayL
onServiceConnected: apple.color = 紅富士,apple.size = 10
onServiceConnected: apple.color = 早熟蘋果,apple.size = 10
onServiceConnected: 重新接收
onServiceConnected: apple.color = 紅富士,apple.size = 10
onServiceConnected: apple.color = 早熟蘋果,apple.size = 10
onServiceConnected: apple.color = 綠色的蘋果,apple.size = 10
沒毛病篮赢,我們接下來看看AIDL更多的用法
AIDL深入探索
我們看到,前面我們的跨進程通信都是一問一答式琉挖,就是客戶端是主導(dǎo)启泣,服務(wù)器是被主導(dǎo),那么會不會有一種需求示辈,我們不想去實時的查詢了寥茫,因為這樣太耗費時間,每次還不一定都有信息更新矾麻,所以纱耻,能不能有一種操作,客戶端去問服務(wù)器:當有信息更新的時候你能不能直接給我說呢射富?
以上需求是一種典型的觀察者模式膝迎,服務(wù)器在有數(shù)據(jù)更新的時候通知每一位想知道這個消息的客戶端,可是怎么用AIDL實現(xiàn)呢胰耗?
-
首先限次,我們需要提供一個AIDL接口,每個用戶都需要實現(xiàn)這個接口并對服務(wù)器申請數(shù)據(jù)更新的通知功能柴灯,當然用戶也可以隨時取消這種功能卖漫,這里我們創(chuàng)建一個
// InewDataListener.aidl package com.example.learnretrofit.LearnMoreProcess; import com.example.learnretrofit.LearnMoreProcess.Apple; interface InewDataListener { void DataUpdata(in Apple apple); }
當然不止增加這個接口,我們還需要在原來接口做改動
// IBookManager.aidl
package com.example.learnretrofit.LearnMoreProcess;
import com.example.learnretrofit.LearnMoreProcess.Apple;
import com.example.learnretrofit.LearnMoreProcess.InewDataListener;
interface IAppleManager {
List<Apple> getAppleList();
void addApple(in Apple apple);
void registerListener(InewDataListener listener);
void unRegisterListener(InewDataListener listener);
}
這兩個方法很容易理解
然后我們build -> make project
-
然后服務(wù)端的Binder對象就會提示我們有兩個接口方法沒實現(xiàn)赠群,手動實現(xiàn)羊始,然后看看此時的服務(wù)端代碼有哪些改動吧
private AtomicBoolean mAtomicBoolean = new AtomicBoolean(false); private CopyOnWriteArrayList<InewDataListener> mListeners = new CopyOnWriteArrayList<>(); private Binder mBinder = new IAppleManager.Stub() { @Override public List<Apple> getAppleList() throws RemoteException { return mAppleList; } @Override public void addApple(Apple apple) throws RemoteException { mAppleList.add(apple); } @Override public void registerListener(InewDataListener listener) throws RemoteException { if(!mListeners.contains(listener)){ mListeners.add(listener); } } @Override public void unRegisterListener(InewDataListener listener) throws RemoteException { if(mListeners.contains(listener)){ mListeners.remove(listener); } } }; public void onCreate() { super.onCreate(); mAppleList.add(new Apple(10,"紅富士")); mAppleList.add(new Apple(10,"早熟蘋果")); new Thread(new ServiceWorker()).start(); } private class ServiceWorker implements Runnable{ @Override public void run() { while (!mAtomicBoolean.get()){ try{ Thread.sleep(5000); int appleId = mAppleList.size() + 1; Apple apple = new Apple(appleId,"蘋果 " + appleId + " 號"); addNewApple(apple); } catch (InterruptedException e) { e.printStackTrace(); } } } } private void addNewApple(Apple apple) { mAppleList.add(apple); for(int i = 0;i < mListeners.size() ; i ++) { InewDataListener listener = mListeners.get(i); try { listener.DataUpdata(apple); } catch (RemoteException e) { e.printStackTrace(); } } }
-
分析起來不難理解,這里就不再說了查描,我們再看看客戶端的改動
private InewDataListener mInewDataListener = new InewDataListener.Stub() { @Override public void DataUpdata(Apple apple) throws RemoteException { mHandler.obtainMessage(10,apple).sendToTarget(); } }; void bindMyAppleService(){ ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { IAppleManager appleManager = IAppleManager.Stub.asInterface(service); try { List<Apple> list = appleManager.getAppleList(); Log.d(TAG, "onServiceConnected: query apple list ,list type is " + list.getClass().getCanonicalName()); for(Apple apple : list){ Log.d(TAG, "onServiceConnected: apple.color = " + apple.color + ",apple.size = " + apple.size); } Apple apple = new Apple(10,"綠色的蘋果"); appleManager.addApple(apple); list = appleManager.getAppleList(); Log.d(TAG, "onServiceConnected: 重新接收"); for(Apple apple1 : list){ Log.d(TAG, "onServiceConnected: apple.color = " + apple1.color + ",apple.size = " + apple1.size); } appleManager.registerListener(mInewDataListener); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) {} }; bindService(new Intent(this,AppleManagerService.class),mConnection,Context.BIND_AUTO_CREATE); } @SuppressLint("HandlerLeak") private Handler mHandler = new Handler(){ @Override public void handleMessage(Message msg) { switch (msg.what) { case 10: Log.d(TAG, "handleMessage: receive The new Data " + msg.obj); } super.handleMessage(msg); } };
定義了接口突委,聲明了當被回調(diào)時候的動作柏卤,接著在onServiceConnection中注冊接口
這里要注意的是接口回調(diào)是在Binder線程池中執(zhí)行的,這一點可以通過我的上篇博客看到匀油,這里不再多說缘缚,因此我們要進行UI操作的話,還是盡量使用Handler將他弄到主線程使用
-
這里運行程序看到log信息為
onServiceConnected: query apple list ,list type is java.util.ArrayList onServiceConnected: apple.color = 紅富士,apple.size = 10 onServiceConnected: apple.color = 早熟蘋果,apple.size = 10 onServiceConnected: 重新接收 onServiceConnected: apple.color = 紅富士,apple.size = 10 onServiceConnected: apple.color = 早熟蘋果,apple.size = 10 onServiceConnected: apple.color = 綠色的蘋果,apple.size = 10 handleMessage: receive The new Data 蘋果 4 號 handleMessage: receive The new Data 蘋果 5 號 handleMessage: receive The new Data 蘋果 6 號
AIDL解決跨進程取消注冊問題
-
Android系統(tǒng)為我們提供了一個專門用于刪除跨進程listener的接口–RemoteCallbackList敌蚜,他是一個泛型桥滨,支持管理任意的AIDL接口,這點從他的聲明就可以看出來弛车,因為所有的AIDL接口都繼承自IInterface接口齐媒,這點如果是因為我們寫的aidl接口都會由系統(tǒng)為我們自行生成一個java文件,而標準格式就是都會繼承自IInterface接口纷跛,下面是這個RemoteCallbackList接口的聲明
public class RemoteCallbackList<E extends IInterface>
它的工作原理很簡單喻括,在他的內(nèi)部有一個Map結(jié)構(gòu)專門用來保存所有的AIDL回調(diào),這個Map的key是IBinder類型忽舟,value是Callback類型
ArrayMap<IBinder, Callback> mCallbacks = new ArrayMap<IBinder, Callback>();
其中Callback中封裝了真正的遠程Listener双妨,當客戶端注冊listener的時候淮阐,他會把這個listener信息存入mCallback中叮阅,其中key和value分別通過下面的方式獲得
到這里的時候有沒有點明悟了,還記得我們負責(zé)注冊的過程是誰來完成的嗎泣特?是服務(wù)端給我們返回的一個Binder對象浩姥,而我們的跨進程通信都是基于這個對象的,而一般一個組件都是只有一個Binder的状您,所以勒叠?我們每個客戶端與服務(wù)器之間的底層Binder是一一對應(yīng)的,這點是不會變的膏孟,所以就有了上面的寫法眯分,我們存binder就行了,到時候你想取消注冊柒桑,那就看看你的底層是哪個binder
RemoteCallbackList這個接口不光可以解決這個問題弊决,他還有一個很有用的功能,那就是當客戶端進程終止時魁淳,他會自動移除客戶端所注冊的listener
另外飘诗,RemoteCallbackList內(nèi)部自動實現(xiàn)了線程同步的功能,所以我們用它的時候不需要做額外的線程同步工作
說了這么多界逛,我們來看看他到底怎么用吧
服務(wù)端的改變
-
先用RemoteCallbackList創(chuàng)建新的集合代替原來的集合
private RemoteCallbackList<InewDataListener> mCallbackList = new RemoteCallbackList<>(); // private CopyOnWriteArrayList<InewDataListener> mListeners = new CopyOnWriteArrayList<>();
-
修改注冊和取消注冊兩個接口的實現(xiàn)
public void registerListener(InewDataListener listener) throws RemoteException { // if(!mListeners.contains(listener)){ // mListeners.add(listener); // } mCallbackList.register(listener); } @Override public void unRegisterListener(InewDataListener listener) throws RemoteException { // if(mListeners.contains(listener)){ // mListeners.remove(listener); // } mCallbackList.unregister(listener); }
-
接下來修改通知的方法
private void addNewApple(Apple apple) { mAppleList.add(apple); // for(int i = 0;i < mListeners.size() ; i ++) { // InewDataListener listener = mListeners.get(i); // try { // listener.DataUpdata(apple); // } catch (RemoteException e) { // e.printStackTrace(); // } // } final int N = mCallbackList.beginBroadcast(); for (int i = 0;i < N;i++){ InewDataListener listener = mCallbackList.getBroadcastItem(i); if(listener != null){ try{ listener.DataUpdata(apple); } catch (RemoteException e) { e.printStackTrace(); } } } mCallbackList.finishBroadcast(); }
是不是非常簡單呢昆稿,我們來看一下具體效果吧,我在客戶端進程的activity中添加一個取消注冊的按鈕
-
運行之后的log
unRegisterListener: 取消之前的數(shù)量是 1 unRegisterListener: 取消之后的數(shù)量是 0
-
注意:使用RemoteCallbackList的時候息拜,我們無法像操作List一樣去操作它溉潭,他并不是一個List净响,而且,遍歷RemoteCallbackList的時候喳瓣,必須要將代碼寫在CallbackList.beginBroadcast()和CallbackList.finishBroadcast()這兩個方法之間别惦,無論是遍歷,還是哪怕只是獲取個元素個數(shù)
final int N = mCallbackList.beginBroadcast(); for (int i = 0;i < N;i++){ InewDataListener listener = mCallbackList.getBroadcastItem(i); if(listener != null){ try{ listener.DataUpdata(apple); } catch (RemoteException e) { e.printStackTrace(); } } } mCallbackList.finishBroadcast();
到這里關(guān)于AIDL的基本東西就說完了夫椭,這里說幾點注意
- 我們必須清楚什么方法執(zhí)行在主線程掸掸,什么方法執(zhí)行在Binder線程池,一些耗時的或者不可知的操作盡可能放在多線程中解決
- 涉及到UI操作的蹭秋,如果當前線程非主線程扰付,要使用Handler切換到主線程再進行操作
還有,binder是有可能意外死亡的仁讨,為了我們程序的健壯性羽莺,我們有必要給Binder設(shè)置死亡監(jiān)聽
AIDL拓展
- 如果我們的服務(wù)想要更健全的話,我們有必要給他加上權(quán)限驗證功能洞豁,因為在默認情況下盐固,我們的遠程服務(wù)是任何人都能連接的,這是我們不愿意看到的丈挟,所以我們必須給服務(wù)加入權(quán)限驗證功能
怎么加這個功能呢刁卜?我們想一下,服務(wù)最早接觸客戶端的方法是哪個方法曙咽? - 當然是onBind方法了蛔趴,我們在綁定服務(wù)的時候,會使用onBind方法返回的對象來拿到我們操作服務(wù)的句柄,那么我們的權(quán)限驗證就可以在這里做
- 不過在做之前我們首先應(yīng)該定義自己的權(quán)限
- 這里簡單說一下自定義權(quán)限
自定義權(quán)限簡單說明
-
在AndroidManifest.xml中,像這個樣子
<permission android:description="string resource" android:icon="drawable resource" android:label="string resource" android:name="string" android:permissionGroup="string" android:protectionLevel=["normal" | "dangerous" |"signature" | "signatureOrSystem"] />
-
這里對各個屬性做個說明
2.png -
protectionLevel權(quán)限等級的說明
3.png -
然后我們在這里在我們的AndroidManifest.xml聲明權(quán)限
<permission android:name="com.example.learnretrofit.LearnMoreProcess.permission.ACCESS_APPLE_SERVICE" android:protectionLevel="normal"/>
-
然后在我們的onBind方法添加權(quán)限驗證
public IBinder onBind(Intent intent) { int check = checkCallingOrSelfPermission("com.example.learnretrofit.LearnMoreProcess.permission.ACCESS_APPLE_SERVICE"); if(check == PackageManager.PERMISSION_DENIED){ return null; } return mBinder; }
-
如果我們自己的應(yīng)用想要綁定我們的服務(wù)冰更,只需要在他的AndroidManifest.xml中聲明權(quán)限即可
<uses-permission android:name="com.example.learnretrofit.LearnMoreProcess.permission.ACCESS_APPLE_SERVICE"/>
當然利朵,權(quán)限驗證不僅僅局限于AIDL,他可以用在所有你想用的地方
第二種權(quán)限驗證的方法
我們可以在服務(wù)端的Binder的onTransact方法中進行權(quán)限驗證,如果驗證失敗,就直接返回false,這樣服務(wù)端就不會去執(zhí)行客戶端想要的邏輯 羔挡,也達到了權(quán)限驗證的效果
我們甚至還可以采用UID和PID來做驗證,通過getCallingUID和getCallingPid來拿到客戶端所屬進程的UID和Pid派撕,這樣我們就可以驗證包名婉弹,等具體的操作大家具體去嘗試吧
下面我們繼續(xù)學(xué)習(xí)另外的IPC方式