日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > Android >内容正文

Android

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

發(fā)布時間:2023/12/1 Android 44 豆豆
生活随笔 收集整理的這篇文章主要介紹了 android ipc 多个客户端,Android IPC之AIDL进阶篇 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

前言

在Android IPC之AIDL中我介紹了如何使用AIDL進行多進程通信,不過由于當時個人水平有限,僅僅介紹了最基礎(chǔ)的部分,所以本篇博客主要是在Android IPC之AIDL的基礎(chǔ)上深入介紹下AIDL的進階的幾點理解以及用法。

AIDL接口中的in out inout的含義

在Android IPC之AIDL中稍微提了下,在客戶端與服務端進行復雜數(shù)據(jù)傳遞的時候,需要使用這三個修飾符,表示數(shù)據(jù)的流向,并沒有具體說明,這里通過我的測試給出一個結(jié)果,使用in修飾,客戶端的對象可以"傳遞給"服務端,但是服務端不能修改客戶端的對象,使用out修飾,客戶端的對象不可以"傳遞給"服務端,但是服務端可以修改客戶端的對象,inout則是雙向的,服務端可以拿到客戶端的數(shù)據(jù)也可以修改客戶端的數(shù)據(jù)。"傳遞"之所以有引號,表示客戶端和服務端的對象不是同一個,而是序列化/反序列化的而已。

上面的傳遞的意思是數(shù)據(jù)類型以及值都能傳遞過去,舉例來說,一個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,客戶端變?yōu)閇name=a,age=11]

使用inout修飾符,則服務端可以拿到正確的數(shù)據(jù),對數(shù)據(jù)的修改也會同步到客戶端

oneway的用法

AIDL定義的方法是阻塞的,所以我們需要很注意不要在UI線程中調(diào)用耗時很長的AIDL方法,不然會導致ANR,如果不想客戶端被阻塞可以選擇開啟一個線程去執(zhí)行或者使用oneway修飾被調(diào)用的方法或者接口。這樣當客戶端調(diào)用的時候就不會被阻塞了。

//IRemote.aidl

interface IRemote{

oneway void testOneWay();

}

如果我們多次調(diào)用被oneway修飾的方法,都不會被阻塞,而且遠程方法是順序執(zhí)行的,比如上面的testOneWay()方法被調(diào)用兩次,只有當?shù)谝淮螆?zhí)行完畢,第二次才會繼續(xù)執(zhí)行,但是對于客戶端來說是感知不到的。

服務端感知客戶端是否崩潰

當我們綁定一個遠程服務的時候,如果遠程服務崩潰了,我們可以通過ServiceConnection的onServiceDisconnected感知到,可是如果客戶端崩潰了,服務端怎么感知呢?

對于這種情景,我們可以讓客戶端傳遞一個Binder對象給服務端,然后服務端使用IBinder.linkToDeath監(jiān)聽,當客戶端終止的時候,Binder對象也會被殺死,然后服務端就可以收到客戶端死亡消息了。

binder.linkToDeath(new DeathRecipient() {

@Override

public void binderDied() {

Log.i("IPC", "client died");

}

}, 0);

具體實現(xiàn)如下。

首先定義個AIDL接口

//IRemote.aidl

interface IRemote{

void testClientError(IBinder binder);

}

服務端實現(xiàn)

@Override

public void testClientError(IBinder binder) throws RemoteException {

binder.linkToDeath(new DeathRecipient() {

@Override

public void binderDied() {

Log.i("IPC", "client died");

}

}, 0);

}

客戶端調(diào)用,最后一句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對象可以感知到對應的進程是否死亡,那么我們也可以換種方式在客戶端獲取服務端的狀態(tài),上面的例子中,我們是主動new了一個Binder對象發(fā)送給服務端,那么怎么獲取到服務端的Binder對象呢?答案就在ServiceConnection的onServiceConnected中,第二個參數(shù)就可以用來監(jiān)聽服務端是否死亡。

服務端調(diào)用客戶端的方法

假設(shè)我們有這樣一個需求,一個客戶連接服務端的時候,服務端需要通知其他所有的客戶端,如果在同一個進程中,我們可以使用回調(diào)的方式,在多進程條件下,我們也可以使用回調(diào),不過客戶端需要實現(xiàn)AIDL接口才行。

具體實現(xiàn)如下

首先定義一個AIDL接口,當連接的時候,服務端調(diào)用所有客戶端的接口,顯示一個Toast

//IClientCallBack.aidl

interface IClientCallBack{

void showToastInClient(String msg);

}

然后添加注冊/解注冊方法

//IRemote.aidl

interface IRemote{

void register(IClientCallBack callback);

void unregister(IClientCallBack callback);

}

客戶端實現(xiàn)AIDL接口

private IClientCallBack mCallBack = new IClientCallBack.Stub() {

@Override

public void showToastInClient(String msg) throws RemoteException {

Toast.makeText(MainActivity.this, msg, Toast.LENGTH_SHORT).show();

}

};

然后接下來就是服務端保存所有的回調(diào)接口到一個List中,剩下的就和普通調(diào)用一樣了。

可是重點來了,如果客戶端注冊了回調(diào),但是沒有解除注冊就意外終止了,那么服務端是無法感知到的,這樣無疑浪費了資源,而且導致程序不穩(wěn)定,所以我們需要在客戶端意外終止的時候移除監(jiān)聽,由于使用的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);

......

}

程序員都是喜歡偷懶的,能簡單就簡單,上面還要我們自己去實現(xiàn)binderDied方法,無疑是降低了開發(fā)效率,好歹谷歌給我們提供了RemoteCallbackList用來為我們保存回調(diào)列表,它會在客戶端異常終止的時候自動移出,免去了我們的人工操作,流程如下。

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 {

//保存回調(diào)到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();

}

}

//結(jié)束通知客戶端

mList.finishBroadcast();

}

@Override

public void unregister(IClientCallBack callback) throws RemoteException {

//將回調(diào)從RemoteCallbackList移除

mList.unregister(callback);

}

};

}

RemoteCallbackList的內(nèi)部實現(xiàn)與我們上面提到的一樣,使用的IBinder.linkToDeath方法。有興趣的可以查看下其源碼。

給服務端加入權(quán)限認證

有時候,我們并不想讓我們的遠程服務隨便的被人綁定,這是就需要使用給我們的服務加入權(quán)限認證才行。一般來說,有兩種方法。

首先我們在AndroidManifest.xml文件中聲明的權(quán)限,如下

然后我們在Service的onBind方法中使用checkCallingOrSelfPermission方法驗證客戶端是否聲明了權(quán)限,如果沒有聲明,則返回null,那么客戶端的onServiceConnected方法將不會被回調(diào),客戶端將綁定失敗。這個方法對于普通的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文件中聲明這個權(quán)限才行。

對于AIDL來說,我們可以在實現(xiàn)AIDL接口的Stub中,覆寫onTransact,在onTransact方法中進行權(quán)限驗證,如下。

private IRemote.Stub mStud = new IRemote.Stub() {

/**

* 這里可以進行權(quán)限驗證,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 調(diào)用本服務提供的方法

// 此方法并不能阻止綁定,但是能讓非法調(diào)用者無法使用本服務提供的方法

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方法中,我們不僅可以驗證是否聲明了權(quán)限,我們還可以獲取到客戶端的包名,對包名等信息進行限制,但是此方法會回調(diào)客戶端的onServiceConnected方法,但是客戶端無法調(diào)用服務端提供的方法。

參考資料:《Android開發(fā)藝術(shù)探索》第二章、

總結(jié)

以上是生活随笔為你收集整理的android ipc 多个客户端,Android IPC之AIDL进阶篇的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。