Aidl程式間通訊詳細介紹
目錄介紹
- 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/yangchong211/YCAudioPlayer
- 關於所有的部落格筆記均已開源,是markdown格式,連結地址:https://github.com/yangchong211/YCBlogs
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,版本號,簽名,渠道等資訊
List<AppInfo> getAppInfo(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 Creator<AppInfo> CREATOR = new Creator<AppInfo>() {
@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基本資訊,方便後期多程式通訊測試
/**
* <pre>
* @author yangchong
* blog :
* time : 2018/05/30
* desc : 用於aidl多程式通訊服務service
* revise:
* </pre>
*/
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 List<AppInfo> getAppInfo(String sign) throws RemoteException {
List<AppInfo> 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需要用到!
<service android:name=".service.AppInfoService"
android:process=":remote"
android:exported="true">
<intent-filter>
<action android:name="cn.ycbjie.ycaudioplayer.service.aidl.AppInfoService"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</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 { List<AppInfo> 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(); } }
3.5 測試
-
最後看看通過測試工具[客戶端]跨程式獲取服務端app資訊截圖
- 具體可以通過實際案例操作:後來發現跨程式通訊原來挺好玩的……專案地址:https://github.com/yangchong211/YCAudioPlayer
- 如圖所示:
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的許可權,需要通過許可權才能呼叫
<!--給aidl多程式通訊,服務加入許可權驗證功能-->
<permission android:name="aidl.AppInfoService"
android:protectionLevel="normal"/>
//在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.部分原始碼解析
5.1 服務端aidl編譯生成的java檔案
- 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/u011240877/article/details/72765136
- Android中AIDL的使用詳解:https://www.jianshu.com/p/d1fac6ccee98
- Android Aidl的使用:https://blog.csdn.net/menglong0329/article/details/75127547
- 安卓中AIDL的使用:https://blog.csdn.net/qq_32006371/article/details/71255764
關於其他內容介紹
01.關於部落格彙總連結
02.關於我的部落格
- 我的個人站點:www.yczbj.org,www.ycbjie.cn
- github:https://github.com/yangchong211
- 知乎:https://www.zhihu.com/people/yang-chong-69-24/pins/posts
- 簡書:http://www.jianshu.com/u/b7b2c6ed9284
- csdn:http://my.csdn.net/m0_37700275
- 喜馬拉雅聽書:http://www.ximalaya.com/zhubo/71989305/
- 開源中國:https://my.oschina.net/zbj1618/blog
- 泡在網上的日子:http://www.jcodecraeer.com/member/content_list.php?channelid=1
- 郵箱:yangchong211@163.com
- 阿里雲部落格:https://yq.aliyun.com/users/article?spm=5176.100- 239.headeruserinfo.3.dT4bcV
- segmentfault頭條:https://segmentfault.com/u/xiangjianyu/articles
相關文章
- Android 程式間通訊 AIDL詳解AndroidAI
- 程式間的五種通訊方式介紹-詳解
- android AIDL程式間通訊(只介紹了簡單資料型別)AndroidAI資料型別
- Android程式間通訊之AIDLAndroidAI
- Android程式間通訊,AIDL工作原理AndroidAI
- 使用AIDL實現程式間的通訊AI
- 程式間的五種通訊方式介紹
- 關於無線通訊的核心技術詳細介紹
- Android探索之AIDL實現程式間通訊AndroidAI
- 程式間通訊簡介
- javascript this詳細介紹JavaScript
- JDBC 詳細介紹JDBC
- Kafka詳細介紹Kafka
- Git詳細介紹Git
- 恆訊科技詳細介紹:新加坡資料中心
- Android IPC程式間通訊之AIDL和Messenger的使用AndroidC程式AIMessenger
- 從AIDL開始談Android程式間Binder通訊機制AIAndroid
- Android Studio 建立aidl檔案,用於程式間通訊AndroidAI
- Android AIDL SERVICE 雙向通訊 詳解AndroidAI
- Android程式間通訊詳解Android
- Go Channel 詳細介紹Go
- Nacos 介面詳細介紹
- MQ詳細命令介紹MQ
- Recovery命令詳細介紹
- Vmstat 命令詳細介紹
- 4-AIII–Service跨程式通訊:aidlAI
- Android 程式通訊機制之 AIDLAndroidAI
- 從AIDL看Android跨程式通訊AIAndroid
- 使用AIDL實現程式間的通訊之複雜型別傳遞AI型別
- Android跨程式通訊之非AIDL(二)AndroidAI
- 恆訊科技詳細介紹:立陶宛伺服器機房伺服器
- 恆訊科技詳細介紹:丹麥伺服器機房伺服器
- 恆訊科技詳細介紹:希臘伺服器機房伺服器
- RabbitMQ訊息佇列(一): Detailed Introduction 詳細介紹MQ佇列AI
- Flutter系列(一)——詳細介紹Flutter
- Nginx服務詳細介紹Nginx
- python字典詳細介紹Python
- Spring bean詳細介紹SpringBean