目錄介紹
- 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應用案例
關于連結
- 1.1.0 AIDL所支援的資料類型有哪些?
- 1.1.1 提供給用戶端連接配接的service什麼時候運作?
- 1.1.2 Stub類是幹什麼用的呢?
- 1.1.3 如何解決遠端調用失敗的問題?
- 在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.1 服務端
- 注意:服務端就是你要連接配接的程序。服務端給用戶端一個Service,在這個Service中監聽用戶端的連接配接請求,然後建立一個AIDL接口檔案,裡面是将要實作的方法,注意這個方法是暴露給用戶端的的。在Service中實作這個AIDL接口即可
- 2.2.2 用戶端
- 用戶端首先需要綁定服務端的Service,綁定成功後,将服務端傳回的Binder對象轉換成AIDL接口所屬的類型,最後調用AIDL的方法就可以了。
- AIDL:Android Interface Definition Language,即Android接口定義語言;用于讓某個Service與多個應用程式元件之間進行跨程序通信,進而可以實作多個應用程式共享同一個Service的功能。
- aidl多程序通信應用——服務端:某app;用戶端:app調試工具。注意:aidl多程序通信是指兩個獨立app之間的通信……
- 打開app調試工具,可以通過綁定服務端某app的service,擷取到公司app的資訊,比如管道,版本号,簽名,打包時間,token等屬性
- 通過app調試工具,可以通過aidl接口中的方法設定屬性,設定成功後,檢視某app是否設定屬性成功
- 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.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.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();
}
}
- 最後看看通過測試工具[用戶端]跨程序擷取服務端app資訊截圖
- 具體可以通過實際案例操作:後來發現跨程序通信原來挺好玩的……項目位址:
-
- 當用戶端發起遠端請求時,用戶端會挂起,一直等到服務端處理完并傳回資料,是以遠端通信是很耗時的,是以不能在子線程發起通路。由于服務端的Binder方法運作在Binder線程池中,是以應采取同步的方式去實作,因為它已經運作在一個線程中呢。
- 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();
}
};
<!--給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.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的接口,正是因為官方替我們做好了,我們隻要在這裡具體實作就好了。
- 用戶端也非常簡單,首先我們連接配接到服務端Service,在連接配接成功時,也就是onServiceConnected方法裡,通過asInterface(service)方法可以将服務端的Binder對象轉換成用戶端所需的AIDL的接口的對象。這種轉換是區分程序的,如果是同一程序,那麼此方法傳回的就是Stub本身,否則傳回的就是系統Stub.proxy對象。拿到接口對象之後,我們就能夠調用相應方法進行自己的處理
參考文章
關于我的部落格