这里记录一下使用Android Studio实现AIDL通信的操作步骤
说明一下:这一篇仅仅记录了初步使用步骤,传输的数据是基本类型。
一·服务端
1.首先创建一个Application,在此Application中创建一个Service,姑且叫RemoteService吧
在Manifest文件中给Service添加一个action,这是为了方便调用方找到这个服务,这两个箭头所指向的内容后面会用到
2.定义一个私有方法,返回值为一个String。
public class RemoteService extends Service {
public RemoteService() {
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
private String getTime() {
return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss sss").format(new Date());
}
@Override
public void onDestroy() {
super.onDestroy();
}
}
3.创建.aidl文件,project视图下,选中src-->main,new-->AIDL-->AIDL File,新建一个AIDL文件,取名IRemoteServiceAidl
4.build一下工程,可以看到编译器帮助我们生成了一个对应的AIDL的java文件
4.大体看一下文件内容,Stub是一个抽象类,继承了Binder类,实现了IRemoteServiceAidl接口,也就是说,在服务中,需要继承Stub类,然后实现IRemoteServiceAidl接口方法,这样调用端就可以访问service的方法了
4.service里面添加Binder成员变量,并在onBind回调方法中返回实例对象,此时的Binder类似于中介,可通过它调用service的公有或者私有方法。
public class RemoteService extends Service {
private RemoteServiceBinder binder;
public RemoteService() {
}
@Override
public IBinder onBind(Intent intent) {
return binder;
}
@Override
public void onCreate() {
super.onCreate();
binder = new RemoteServiceBinder();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
private String getTime() {
return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss sss").format(new Date());
}
@Override
public void onDestroy() {
System.out.println("remote service has been destory");
super.onDestroy();
}
/**
* 实现aidl文件中暴露的接口方法
*/
private class RemoteServiceBinder extends IRemoteServiceAidl.Stub {
@Override
public String callGetTime() throws RemoteException {
//调用service中的私有方法
return getTime();
}
}
}
至此,AIDL服务端完成。
二·客户端
1.同样,新建一个application,复制服务端的aidl文件夹到相同层级的目录,然后build一下项目
2.定义RemoteServiceBinder接口对象,并在serviceConnection回调中获取Binder的实例,代码如下
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private Button btnRemoteService;
//定义接口对象
private IRemoteServiceAidl binder;
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//绑定服务时回调,然后通过返回回来的Binder实例化成员binder
binder = IRemoteServiceAidl.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btnRemoteService = findViewById(R.id.btn_remote_service);
btnRemoteService.setOnClickListener(this);
Intent intent2 = new Intent();
intent2.setAction("com.aoto.remote.service");
intent2.setPackage("com.aoto.remoteservice");
bindService(intent2,serviceConnection,BIND_AUTO_CREATE);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_remote_service:
try {
//此时按钮点击事件就可以调用远程接口的方法了
System.out.println("localService: "+binder.callGetTime());
} catch (RemoteException e) {
e.printStackTrace();
}
break;
default:
break;
}
}
}
此时客户端完成
关于非基本数据类型的数据传输会在后续博客中继续,文章中有误的地方欢迎您提供宝贵意见们也可以留言讨论,谢谢。
注意事项说一下吧(主要是我自己在写的时候遇到的坑):
1.bindService()绑定到远程服务后报错:
Service Intent must be explicit
这是由于安全机制的问题,不能使用隐式Intent启动服务,必须明确指定到具体的service组件
解决方案:
①.使用 Intent.setPackage("**.**.remoteservice");设置服务所在的应用的包名。就是在第一张图中的箭头所指的内容。
②.通过PackageManager获取符合action的具体的Intent和ComponentName,将隐式Intent转换为显示Intent,方法如下:
/**
*get an explicit intent in order to start or bind service
* @param context the context that need to start or bind a service component
* @param implicitIntent the intent that not so explicit,such as an intent created from an non-parameter constructor
* or with an extra action but no idea which package it belongs to
* @return Intent that can start or bind an service component directly
*/
public static Intent getExplicitIntent(Context context, Intent implicitIntent) {
// Retrieve all services that can match the given intent
PackageManager pm = context.getPackageManager();
List<ResolveInfo> resolveInfo = pm.queryIntentServices(implicitIntent, 0);
// Make sure only one match was found
if (resolveInfo == null || resolveInfo.size() != 1) {
return null;
}
// Get component info and create ComponentName
ResolveInfo serviceInfo = resolveInfo.get(0);
String packageName = serviceInfo.serviceInfo.packageName;
String className = serviceInfo.serviceInfo.name;
ComponentName component = new ComponentName(packageName, className);
// Create a new intent. Use the old one for extras and such reuse
Intent explicitIntent = new Intent(implicitIntent);
// Set the component to be explicit
explicitIntent.setComponent(component);
return explicitIntent;
}
2.调用Binder对象的方法时报空指针异常 ( 不走onServiceConnected()回调方法 )
在出发按钮点击事件或其他时间调用binder对象的接口方法时,binder为空,经调试发现没有回调onServiceConnected(),查阅了一番文档没有找到令人信服的解释,有的网友说在执行bindService()后,并没有立即执行onServiceConnected()回调并返回binder对象,所以执行时报错,官方文档还没有提到时间差的问题,所以,仅在这里记录一下解决方案。
将一下代码放在onCreate方法中执行,而不是事件回调方法中执行即可解决
Intent intent2 = new Intent();
intent2.setAction("com.aoto.remote.service");
intent2.setPackage("com.aoto.remoteservice");
bindService(intent2,serviceConnection,BIND_AUTO_CREATE);
3.AIDL支持的基本数据类型不包括short
无论以何种形式,返回值、或者参数类型,都会编译报错,之所以不支持short类型,是因为Parcel没有办法对short进行序列化,也就没办法通过aidl将short类型在客户端与服务端进行传递
Process 'command 'E:\android\sdk\build-tools\28.0.3\aidl.exe'' finished with non-zero exit value 1
4.服务端和客户端的.aidl文件里的接口方法个数和顺序要保持一致
之所以发现这个问题是因为服务端的aidl接口方法有的时候会过时废弃,这个时候可能会考虑删除客户端的aidl接口方法,well,don't do that.因为客户端的方法的个数和顺序必须完全一致,经测试发现,如果两个接口方法调换了位置,除了这两个方法之外,其他的方法工作正常,若是客户端删除了一个过时弃用的接口方法,那么从下一个方法到最后一个方法都无法正常调用了。什么原因还没有研究出来。。。但是这个坑我已经踩过了,在此谨记用于提醒我自己。
5.aidl接口不支持方法重载
也就是说方法名必须不同。即使参数个数个类型不同,方法名相同也无法使用