日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

android ipc 多个客户端,Android IPC之AIDL进阶篇

發布時間:2023/12/1 63 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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进阶篇的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。