摘要:實際開發中案例操作通信業務需求多進程通信應用服務端某客戶端調試工具。當服務端和客戶端位于同一個進程時,方法調用不會走跨進程的過程,當兩者處于不同晉城市,方法調用走過程,這個邏輯由的內部代理類完成。
目錄介紹
1.問題答疑
2.Aidl相關屬性介紹
2.1 AIDL所支持的數據類型
2.2 服務端和客戶端
2.3 AIDL的基本概念
3.實際開發中案例操作
3.1 aidl通信業務需求
3.2 操作步驟偽代碼
3.3 服務端操作步驟
3.4 客戶端操作步驟
3.5 測試
4.可能出現的問題
4.1 客戶端在子線程中發起通信訪問問題
4.2 什么情況下會導致遠程調用失敗
4.3 設置aidl的權限,需要通過權限才能調用
5.部分源碼解析
5.1 服務端aidl編譯生成的java文件
5.2 客戶端綁定服務端service原理
關于aidl應用案例https://github.com/yangchong2...
關于所有的博客筆記均已開源,是markdown格式,鏈接地址:https://github.com/yangchong2...
1.問題答疑1.1.0 AIDL所支持的數據類型有哪些?
1.1.1 提供給客戶端連接的service什么時候運行?
1.1.2 Stub類是干什么用的呢?
1.1.3 如何解決遠程調用失敗的問題?
2.Aidl相關屬性介紹 2.1 AIDL所支持的數據類型
在AIDL中,并非支持所有數據類型,他支持的數據類型如下所示:
基本數據類型(int、long、char、boolean、double、float、byte、short)
String和CharSequence
List:只支持ArrayList,并且里面的每個元素必須被AIDL支持
Map: 只支持HashMap, 同樣的,里面的元素都必須被AIDL支持,包括key和value
Parcelable:所有實現了Parcelable接口的對象
AIDL: 所有的AIDL接口本身也可以在AIDL 文件中使用
2.2 服務端和客戶端
2.2.1 服務端
注意:服務端就是你要連接的進程。服務端給客戶端一個Service,在這個Service中監聽客戶端的連接請求,然后創建一個AIDL接口文件,里面是將要實現的方法,注意這個方法是暴露給客戶端的的。在Service中實現這個AIDL接口即可
2.2.2 客戶端
客戶端首先需要綁定服務端的Service,綁定成功后,將服務端返回的Binder對象轉換成AIDL接口所屬的類型,最后調用AIDL的方法就可以了。
2.3 AIDL的基本概念AIDL:Android Interface Definition Language,即Android接口定義語言;用于讓某個Service與多個應用程序組件之間進行跨進程通信,從而可以實現多個應用程序共享同一個Service的功能。
3.實際開發中案例操作 3.1 aidl通信業務需求aidl多進程通信應用——服務端:某app;客戶端:app調試工具。注意:aidl多進程通信是指兩個獨立app之間的通信……
打開app調試工具,可以通過綁定服務端某app的service,獲取到公司app的信息,比如渠道,版本號,簽名,打包時間,token等屬性
通過app調試工具,可以通過aidl接口中的方法設置屬性,設置成功后,查看某app是否設置屬性成功
3.2 操作步驟偽代碼
3.2.1 服務端
步驟1:新建定義AIDL文件,并聲明該服務需要向客戶端提供的接口
補充,如果aidl中有對象,則需要創建對象,并且實現Parcelable
步驟2:在Service子類中實現AIDL中定義的接口方法,并定義生命周期的方法(onCreat、onBind()、blabla)
步驟3:在AndroidMainfest.xml中注冊服務 & 聲明為遠程服務
3.2.2 客戶端
步驟1:拷貝服務端的AIDL文件到目錄下
步驟2:使用Stub.asInterface接口獲取服務器的Binder,根據需要調用服務提供的接口方法
步驟3:通過Intent指定服務端的服務名稱和所在包,綁定遠程Service
3.3 服務端操作步驟
3.3.1 創建一個aidl文件【注意:在main路徑下創建】
可以看到里面有一個AppInfo,注意這個類需要自己創建,并且手動導包進來。否則編譯時找不到……
// ICheckAppInfoManager.aidl package cn.ycbjie.ycaudioplayer; import cn.ycbjie.ycaudioplayer.AppInfo; // Declare any non-default types here with import statements interface ICheckAppInfoManager { //獲取app信息,比如token,版本號,簽名,渠道等信息 ListgetAppInfo(String sign); boolean setToken(String sign,String token); boolean setChannel(String sign,String channel); boolean setAppAuthorName(String sign,String name); }
3.3.2 創建一個AppInfo類,實現Parcelable接口
這個類就是需要用的實體類,因為是跨進程,所以實現了Parcelable接口,這個是Android官方提供的,它里面主要是靠Parcel來傳遞數據,Parcel內部包裝了可序列化的數據,能夠在Binder中自由傳輸數據。
注意:如果用到了自定義Parcelable對象,就需要創建一個同名的AIDL文件,包名要和實體類包名一致。我之前這個地方沒加,導致出現錯誤!
如圖所示:
import android.os.Parcel; import android.os.Parcelable; public class AppInfo implements Parcelable { private String key; private String value; public AppInfo(String key, String value) { this.key = key; this.value = value; } public String getKey() { return key; } public void setKey(String key) { this.key = key; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(this.key); dest.writeString(this.value); } public AppInfo() { } protected AppInfo(Parcel in) { this.key = in.readString(); this.value = in.readString(); } public static final CreatorCREATOR = new Creator () { @Override public AppInfo createFromParcel(Parcel source) { return new AppInfo(source); } @Override public AppInfo[] newArray(int size) { return new AppInfo[size]; } }; @Override public String toString() { return "AppInfo{" + "key="" + key + """ + ", value="" + value + """ + "}"; } }
3.3.3 在Service子類中實現AIDL中定義的接口方法,并定義生命周期的方法(onCreat、onBind()等)
重寫的onBinde()方法中返回Binder對象,這個Binder對象指向IAdvertManager.Stub(),這個Stub類并非我們自己創建的,而是AIDL自動生成的。系統會為每個AIDL接口在build/source/aidl下生成一個文件夾,它的名稱跟你命名的AIDL文件夾一樣,里面的類也一樣。
創建binder對象,在這個getAppInfo方法中,可以設置app基本信息,方便后期多進程通信測試
/** ** @author yangchong * blog : * time : 2018/05/30 * desc : 用于aidl多進程通信服務service * revise: **/ public class AppInfoService extends Service { @Nullable @Override public IBinder onBind(Intent intent) { LogUtils.i("AppInfoService--IBinder:"); return binder; } @Override public void onCreate() { super.onCreate(); LogUtils.i("AppInfoService--onCreate:"); } @Override public void onDestroy() { super.onDestroy(); LogUtils.i("AppInfoService--onDestroy:"); } /** * 1.核心,Stub里面的方法運行的binder池中。 * 2.Stub類并非我們自己創建的,而是AIDL自動生成的。 * 系統會為每個AIDL接口在build/generated/source/aidl下生成一個文件夾,它的名稱跟你命名的AIDL文件夾一樣 * 3.Stub類,是一個內部類,他本質上是一個Binder類。當服務端和客戶端位于同一個進程時,方法調用不會走跨進程的transact過程, * 當兩者處于不同晉城市,方法調用走transact過程,這個邏輯由Stub的內部代理類Proxy完成。 */ private final IBinder binder = new ICheckAppInfoManager.Stub() { @Override public ListgetAppInfo(String sign) throws RemoteException { List list=new ArrayList<>(); String aidlCheckAppInfoSign = AppToolUtils.getAidlCheckAppInfoSign(); LogUtils.e("AppInfoService--AppInfoService",aidlCheckAppInfoSign+"-------------"+sign); if(!aidlCheckAppInfoSign.equals(sign)){ return list; } list.add(new AppInfo("app版本號(versionName)", BuildConfig.VERSION_NAME)); list.add(new AppInfo("app版本名稱(versionCode)", BuildConfig.VERSION_CODE+"")); list.add(new AppInfo("打包時間", BuildConfig.BUILD_TIME)); list.add(new AppInfo("app包名", getPackageName())); list.add(new AppInfo("app作者", SPUtils.getInstance(Constant.SP_NAME).getString("name","楊充"))); list.add(new AppInfo("app渠道", SPUtils.getInstance(Constant.SP_NAME).getString("channel"))); list.add(new AppInfo("token", SPUtils.getInstance(Constant.SP_NAME).getString("token"))); list.add(new AppInfo("App簽名", AppToolUtils.getSingInfo(getApplicationContext(), getPackageName(), AppToolUtils.SHA1))); return list; } @Override public boolean setToken(String sign, String token) throws RemoteException { if(!AppToolUtils.getAidlCheckAppInfoSign().equals(sign)){ return false; } SPUtils.getInstance(Constant.SP_NAME).put("token",token); LogUtils.i("AppInfoService--setToken:"+ token); return true; } @Override public boolean setChannel(String sign, String channel) throws RemoteException { if(!AppToolUtils.getAidlCheckAppInfoSign().equals(sign)){ return false; } SPUtils.getInstance(Constant.SP_NAME).put("channel",channel); LogUtils.i("AppInfoService--setChannel:"+ channel); return true; } @Override public boolean setAppAuthorName(String sign, String name) throws RemoteException { if(!AppToolUtils.getAidlCheckAppInfoSign().equals(sign)){ return false; } SPUtils.getInstance(Constant.SP_NAME).put("name",name); LogUtils.i("AppInfoService--setAppAuthorName:"+ name); return true; } }; }
3.3.4 在AndroidMainfest.xml中注冊服務 & 聲明為遠程服務
在清單文件注冊即可,需要設置action。這個在客戶端中綁定服務service需要用到!
3.4 客戶端操作步驟
3.4.1 拷貝服務端的AIDL文件到目錄下
注意:復制時不要改動任何東西!
如圖所示:
3.4.2 通過Intent指定服務端的服務名稱和所在包,綁定遠程Service
通過Intent指定服務端的服務名稱和所在包,進行Service綁定;
創建ServiceConnection對象
/** * 跨進程綁定服務 */ private void attemptToBindService() { Intent intent = new Intent(); //通過Intent指定服務端的服務名稱和所在包,與遠程Service進行綁定 //參數與服務器端的action要一致,即"服務器包名.aidl接口文件名" intent.setAction("cn.ycbjie.ycaudioplayer.service.aidl.AppInfoService"); //Android5.0后無法只通過隱式Intent綁定遠程Service //需要通過setPackage()方法指定包名 intent.setPackage(packName); //綁定服務,傳入intent和ServiceConnection對象 bindService(intent, connection, Context.BIND_AUTO_CREATE); } /** * 創建ServiceConnection的匿名類 */ private ServiceConnection connection = new ServiceConnection() { //重寫onServiceConnected()方法和onServiceDisconnected()方法 // 在Activity與Service建立關聯和解除關聯的時候調用 @Override public void onServiceDisconnected(ComponentName name) { Log.e(getLocalClassName(), "無法綁定aidlServer的AIDLService服務"); mBound = false; } //在Activity與Service建立關聯時調用 @Override public void onServiceConnected(ComponentName name, IBinder service) { Log.e(getLocalClassName(), "完成綁定aidlServer的AIDLService服務"); //使用IAppInfoManager.Stub.asInterface()方法獲取服務器端返回的IBinder對象 //將IBinder對象傳換成了mAIDL_Service接口對象 messageCenter = ICheckAppInfoManager.Stub.asInterface(service); mBound = true; if (messageCenter != null) { try { //鏈接成功 Toast.makeText(MainActivity.this,"鏈接成功",Toast.LENGTH_SHORT).show(); } catch (Exception e) { e.printStackTrace(); } } } };
3.4.3 使用Stub.asInterface接口獲取服務器的Binder,根據需要調用服務提供的接口方法
通過步驟3.4.2完成了跨進程綁定服務,接下來通過調用方法獲取到數據。這里可以調用getAppInfo方法獲取到服務端[app]的數據
private void getAppInfo() { //如果與服務端的連接處于未連接狀態,則嘗試連接 if (!mBound) { attemptToBindService(); Toast.makeText(this, "當前與服務端處于未連接狀態,正在嘗試重連,請稍后再試", Toast.LENGTH_SHORT).show(); return; } if (messageCenter == null) { return; } try { List3.5 測試info = messageCenter.getAppInfo(Utils.getSign(packName)); if(info==null || (info.size()==0)){ Toast.makeText(this, "無法獲取數據,可能是簽名錯誤!", Toast.LENGTH_SHORT).show(); }else { mRecyclerView.setLayoutManager(new LinearLayoutManager(this)); FirstAdapter adapter = new FirstAdapter(info, this); mRecyclerView.setAdapter(adapter); adapter.setOnItemClickListener(new FirstAdapter.OnItemClickListener() { @Override public void onItemClick(View view, int position) { } }); } } catch (RemoteException e) { e.printStackTrace(); } }
最后看看通過測試工具[客戶端]跨進程獲取服務端app信息截圖
具體可以通過實際案例操作:后來發現跨進程通信原來挺好玩的……項目地址:https://github.com/yangchong2...
如圖所示:
4.可能出現的問題 4.1 客戶端在子線程中發起通信訪問問題當客戶端發起遠程請求時,客戶端會掛起,一直等到服務端處理完并返回數據,所以遠程通信是很耗時的,所以不能在子線程發起訪問。由于服務端的Binder方法運行在Binder線程池中,所以應采取同步的方式去實現,因為它已經運行在一個線程中呢。
4.2 什么情況下會導致遠程調用失敗Binder是會意外死亡的。如果服務端的進程由于某種原因異常終止,會導致遠程調用失敗,如果我們不知道Binder連接已經斷裂, 那么客戶端就會受到影響。不用擔心,Android貼心的為我們提供了連個配對的方法linkToDeath和unlinkToDeath,通過linkToDeath我們可以給Binder設置一個死亡代理,當Binder死亡時,我們就會收到通知。
// 在創建ServiceConnection的匿名類中的onServiceConnected方法中 // 設置死亡代理 messageCenter.asBinder().linkToDeath(deathRecipient, 0); /** * 給binder設置死亡代理 */ private IBinder.DeathRecipient deathRecipient = new IBinder.DeathRecipient() { @Override public void binderDied() { if(messageCenter == null){ return; } messageCenter.asBinder().unlinkToDeath(deathRecipient, 0); messageCenter = null; //這里重新綁定服務 attemptToBindService(); } };4.3 設置aidl的權限,需要通過權限才能調用
5.部分源碼解析 5.1 服務端aidl編譯生成的java文件//在AppInfoService服務中驗證權限 @Nullable @Override public IBinder onBind(Intent intent) { LogUtils.i("AppInfoService--IBinder:"); int check = checkCallingOrSelfPermission("aidl.AppInfoService"); if(check == PackageManager.PERMISSION_DENIED){ return null; } return binder; }
5.1.1 首先找到aidl編譯生成的Java文件
5.1.2 分析生成的java文件
這個ICheckAppInfoManager.java就是系統為我們生成的相應java文件,簡單說下這個類。它聲明了三個方法getAppInfo,setToken和setChannel,分明就是我們AIDL接口中的三個方法。同時他聲明了3個id用來標識這幾個方法,id用于標識在transact過程中客戶端請求的到底是哪個方法。接著就是我們的Stub,可以看到它是一個內部類,他本質上是一個Binder類。當服務端和客戶端位于同一個進程時,方法調用不會走跨進程的transact過程,當兩者處于不同晉城市,方法調用走transact過程,這個邏輯由Stub的內部代理類Proxy完成。
這個Stub對象之所以里面有我們AIDL的接口,正是因為官方替我們做好了,我們只要在這里具體實現就好了。
5.2 客戶端綁定服務端service原理客戶端也非常簡單,首先我們連接到服務端Service,在連接成功時,也就是onServiceConnected方法里,通過asInterface(service)方法可以將服務端的Binder對象轉換成客戶端所需的AIDL的接口的對象。這種轉換是區分進程的,如果是同一進程,那么此方法返回的就是Stub本身,否則返回的就是系統Stub.proxy對象。拿到接口對象之后,我們就能夠調用相應方法進行自己的處理
參考文章Android 進階7:進程通信之 AIDL 的使用:https://blog.csdn.net/u011240...
Android中AIDL的使用詳解:https://www.jianshu.com/p/d1f...
Android Aidl的使用:https://blog.csdn.net/menglon...
安卓中AIDL的使用:https://blog.csdn.net/qq_3200...
關于其他內容介紹 01.關于博客匯總鏈接1.技術博客匯總
2.開源項目匯總
3.生活博客匯總
4.喜馬拉雅音頻匯總
5.其他匯總
02.關于我的博客我的個人站點:www.yczbj.org,www.ycbjie.cn
github:https://github.com/yangchong211
知乎:https://www.zhihu.com/people/...
簡書:http://www.jianshu.com/u/b7b2...
csdn:http://my.csdn.net/m0_37700275
喜馬拉雅聽書:http://www.ximalaya.com/zhubo...
開源中國:https://my.oschina.net/zbj161...
泡在網上的日子:http://www.jcodecraeer.com/me...
郵箱:yangchong211@163.com
阿里云博客:https://yq.aliyun.com/users/a... 239.headeruserinfo.3.dT4bcV
segmentfault頭條:https://segmentfault.com/u/xi...
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/77281.html
摘要:前言進程間通信簡稱就是指進程與進程之間進行通信一般來說一個只有一個進程但是可能會有多個線程所以我們用得比較多的是多線程通信比如但是在一些特殊的情況下我們會需要多個進程或者是我們在遠程服務調用時就需要跨進程通信了設置多進程設置多進程的步驟很 前言: 進程間通信(Inter-Process Communication),簡稱IPC,就是指進程與進程之間進行通信.一般來說,一個app只有一個...
摘要:中為何新增來作為主要的方式運行機制是怎樣的機制有什么優勢運行機制是怎樣的基于通信模式,除了端和端,還有兩角色一起合作完成進程間通信功能。 目錄介紹 2.0.0.1 什么是Binder?為什么要使用Binder?Binder中是如何進行線程管理的?總結binder講的是什么? 2.0.0.2 Android中進程和線程的關系?什么是IPC?為何需要進行IPC?多進程通信可能會出現什么問...
閱讀 2043·2021-11-11 16:54
閱讀 2121·2019-08-30 15:55
閱讀 3621·2019-08-30 15:54
閱讀 398·2019-08-30 15:44
閱讀 2239·2019-08-30 10:58
閱讀 431·2019-08-26 10:30
閱讀 3055·2019-08-23 14:46
閱讀 3204·2019-08-23 13:46