android ipc 多个客户端,Android IPC之AIDL进阶篇
前言
在Android IPC之AIDL中我介紹了如何使用AIDL進行多進程通信,不過由于當時個人水平有限,僅僅介紹了最基礎的部分,所以本篇博客主要是在Android IPC之AIDL的基礎上深入介紹下AIDL的進階的幾點理解以及用法。
AIDL接口中的in out inout的含義
在Android IPC之AIDL中稍微提了下,在客戶端與服務端進行復雜數據傳遞的時候,需要使用這三個修飾符,表示數據的流向,并沒有具體說明,這里通過我的測試給出一個結果,使用in修飾,客戶端的對象可以"傳遞給"服務端,但是服務端不能修改客戶端的對象,使用out修飾,客戶端的對象不可以"傳遞給"服務端,但是服務端可以修改客戶端的對象,inout則是雙向的,服務端可以拿到客戶端的數據也可以修改客戶端的數據。"傳遞"之所以有引號,表示客戶端和服務端的對象不是同一個,而是序列化/反序列化的而已。
上面的傳遞的意思是數據類型以及值都能傳遞過去,舉例來說,一個Person,初始化為name=a,age=10,
使用in修飾,客戶端為[name=a,age=10]服務端拿到的是[name=a,age=10],但是服務端修改age=11,客戶端還是[name=a,age=10]
使用out修飾,客戶端為[name=a,age=10]服務端拿到的是[name=null,age=0](String的默認類型為空,int的默認類型為0),服務端修改age=11,客戶端變為[name=a,age=11]
使用inout修飾符,則服務端可以拿到正確的數據,對數據的修改也會同步到客戶端
oneway的用法
AIDL定義的方法是阻塞的,所以我們需要很注意不要在UI線程中調用耗時很長的AIDL方法,不然會導致ANR,如果不想客戶端被阻塞可以選擇開啟一個線程去執行或者使用oneway修飾被調用的方法或者接口。這樣當客戶端調用的時候就不會被阻塞了。
//IRemote.aidl
interface IRemote{
oneway void testOneWay();
}
如果我們多次調用被oneway修飾的方法,都不會被阻塞,而且遠程方法是順序執行的,比如上面的testOneWay()方法被調用兩次,只有當第一次執行完畢,第二次才會繼續執行,但是對于客戶端來說是感知不到的。
服務端感知客戶端是否崩潰
當我們綁定一個遠程服務的時候,如果遠程服務崩潰了,我們可以通過ServiceConnection的onServiceDisconnected感知到,可是如果客戶端崩潰了,服務端怎么感知呢?
對于這種情景,我們可以讓客戶端傳遞一個Binder對象給服務端,然后服務端使用IBinder.linkToDeath監聽,當客戶端終止的時候,Binder對象也會被殺死,然后服務端就可以收到客戶端死亡消息了。
binder.linkToDeath(new DeathRecipient() {
@Override
public void binderDied() {
Log.i("IPC", "client died");
}
}, 0);
具體實現如下。
首先定義個AIDL接口
//IRemote.aidl
interface IRemote{
void testClientError(IBinder binder);
}
服務端實現
@Override
public void testClientError(IBinder binder) throws RemoteException {
binder.linkToDeath(new DeathRecipient() {
@Override
public void binderDied() {
Log.i("IPC", "client died");
}
}, 0);
}
客戶端調用,最后一句int i = 0/0;是為了測試客戶端異常退出
private IBinder mToken = new Binder();
public void testError(View v) {
if (remoteInterface == null) {
return;
}
try {
remoteInterface.testClientError(mToken);
} catch (RemoteException e) {
e.printStackTrace();
}
int i = 0 / 0;
}
擴展:既然通過Binder對象可以感知到對應的進程是否死亡,那么我們也可以換種方式在客戶端獲取服務端的狀態,上面的例子中,我們是主動new了一個Binder對象發送給服務端,那么怎么獲取到服務端的Binder對象呢?答案就在ServiceConnection的onServiceConnected中,第二個參數就可以用來監聽服務端是否死亡。
服務端調用客戶端的方法
假設我們有這樣一個需求,一個客戶連接服務端的時候,服務端需要通知其他所有的客戶端,如果在同一個進程中,我們可以使用回調的方式,在多進程條件下,我們也可以使用回調,不過客戶端需要實現AIDL接口才行。
具體實現如下
首先定義一個AIDL接口,當連接的時候,服務端調用所有客戶端的接口,顯示一個Toast
//IClientCallBack.aidl
interface IClientCallBack{
void showToastInClient(String msg);
}
然后添加注冊/解注冊方法
//IRemote.aidl
interface IRemote{
void register(IClientCallBack callback);
void unregister(IClientCallBack callback);
}
客戶端實現AIDL接口
private IClientCallBack mCallBack = new IClientCallBack.Stub() {
@Override
public void showToastInClient(String msg) throws RemoteException {
Toast.makeText(MainActivity.this, msg, Toast.LENGTH_SHORT).show();
}
};
然后接下來就是服務端保存所有的回調接口到一個List中,剩下的就和普通調用一樣了。
可是重點來了,如果客戶端注冊了回調,但是沒有解除注冊就意外終止了,那么服務端是無法感知到的,這樣無疑浪費了資源,而且導致程序不穩定,所以我們需要在客戶端意外終止的時候移除監聽,由于使用的AIDL接口,所以這時我們就可以使用上面的IBinder.linkToDeath方法了。類似如下形式。
@Override
public void register(IClientCallBack callback) throws RemoteException {
callback.asBinder().linkToDeath(new DeathRecipient() {
@Override
public void binderDied() {
// TODO Auto-generated method stub
}
}, 0);
......
}
程序員都是喜歡偷懶的,能簡單就簡單,上面還要我們自己去實現binderDied方法,無疑是降低了開發效率,好歹谷歌給我們提供了RemoteCallbackList用來為我們保存回調列表,它會在客戶端異常終止的時候自動移出,免去了我們的人工操作,流程如下。
public class RemoteService extends Service {
//定義RemoteCallbackList對象,保存類型為IClientCallBack
private RemoteCallbackList mList = new RemoteCallbackList();
@Override
public void onDestroy() {
//當RemoteService銷毀的時候,清空RemoteCallbackList列表
mList.kill();
super.onDestroy();
}
private IRemote.Stub mStud = new Stub() {
@Override
public void register(IClientCallBack callback) throws RemoteException {
//保存回調到RemoteCallbackList中
mList.register(callback);
//開始通知全部客戶端
int i = mList.beginBroadcast();
Log.i("IPC", "size = " + (i - 1));
//遍歷客戶端
while (i > 0) {
i--;
try {
mList.getBroadcastItem(i).showToastInClient("i am from Server");
} catch (RemoteException e) {
e.printStackTrace();
}
}
//結束通知客戶端
mList.finishBroadcast();
}
@Override
public void unregister(IClientCallBack callback) throws RemoteException {
//將回調從RemoteCallbackList移除
mList.unregister(callback);
}
};
}
RemoteCallbackList的內部實現與我們上面提到的一樣,使用的IBinder.linkToDeath方法。有興趣的可以查看下其源碼。
給服務端加入權限認證
有時候,我們并不想讓我們的遠程服務隨便的被人綁定,這是就需要使用給我們的服務加入權限認證才行。一般來說,有兩種方法。
首先我們在AndroidManifest.xml文件中聲明的權限,如下
然后我們在Service的onBind方法中使用checkCallingOrSelfPermission方法驗證客戶端是否聲明了權限,如果沒有聲明,則返回null,那么客戶端的onServiceConnected方法將不會被回調,客戶端將綁定失敗。這個方法對于普通的Service同樣適用。
public class MyService extends Service {
private static final String TAG = "MyService";
public MyService() {
}
@Override
public IBinder onBind(Intent intent) {
int permission = checkCallingOrSelfPermission("com.remote.service.PRI");
if (permission == PackageManager.PERMISSION_DENIED) {
Log.i(TAG, "onBind: Error");
return null;
}
Log.i(TAG, "onBind: Success");
return mBinder;
}
}
如果客戶端想綁定我們的服務,那么需要在AndroidManifest.xml文件中聲明這個權限才行。
對于AIDL來說,我們可以在實現AIDL接口的Stub中,覆寫onTransact,在onTransact方法中進行權限驗證,如下。
private IRemote.Stub mStud = new IRemote.Stub() {
/**
* 這里可以進行權限驗證,true,允許綁定,false,不允許綁定
*/
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)
throws RemoteException {
int check = checkCallingOrSelfPermission("com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE");
Log.d("IPC", "onbind check=" + check);
// getCallingPid();
// getCallingUid();
// 只允許包名以org.ipc.demo開頭的app 調用本服務提供的方法
// 此方法并不能阻止綁定,但是能讓非法調用者無法使用本服務提供的方法
String[] packagesForUid = getPackageManager().getPackagesForUid(getCallingUid());
if (packagesForUid != null && packagesForUid.length > 0) {
if (packagesForUid[0].startsWith("org.ipc.demo")) {
return super.onTransact(code, data, reply, flags);
}
}
return false;
};
};
在onTransact方法中,我們不僅可以驗證是否聲明了權限,我們還可以獲取到客戶端的包名,對包名等信息進行限制,但是此方法會回調客戶端的onServiceConnected方法,但是客戶端無法調用服務端提供的方法。
參考資料:《Android開發藝術探索》第二章、
總結
以上是生活随笔為你收集整理的android ipc 多个客户端,Android IPC之AIDL进阶篇的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: android服务下载,android服
- 下一篇: 《楚乔传》宇文玥神秘身世揭秘 母亲为什么