目录介绍
- 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.1.0 AIDL所支持的数据类型有哪些?
- 1.1.1 提供给客户端连接的service什么时候运行?
- 1.1.2 Stub类是干什么用的呢?
- 1.1.3 如何解决远程调用失败的问题?
2.Aidl相关属性介绍
2.1 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 服务端操作步骤
// 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);
}
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 + '\'' +
'}';
}
}
/**
* <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;
}
};
}
<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 测试
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对象。拿到接口对象之后,我们就能够调用相应方法进行自己的处理
参考文章
关于我的博客