Demo地址:
https://github.com/qinyafeiii/BinderDemo
一.Binder在项目中的简单使用
第一步: 新建AIDL文件。选中对应 module 鼠标右键 new -> AIDL-> AIDL File 新建AIDL文件;新建对应的数据bean & 对应的AIDL文件(项目中数据bean为:AppInfo)。项目结构如下
注意点:
1.进程间传递的数据bean需要新建在 包名下;
2.数据bean需实现序列化,必须实现方法 createFromParcel(parcel: Parcel)。readFromParcel(parcel: Parcel) 按需实现(如果数据类型为 out、inout 则必须实现,否则会报错);
3.AppInfo.aidl 需声明数据bean,如:
package com.car300.binderdemo;
parcelable AppInfo; // 进程间数据传递的bean
4.传递非基本数据类型时,必须声明定向Tag(in、out、inout)
第二步:服务端声明服务(MyBinderService),并实现对应的方法。
备注:MyBinderService 指定进程为 remote进程
class MyBinderService : Service() {
override fun onBind(intent: Intent?): IBinder? {
return MyBinder()
}
inner class MyBinder : IMyAidlInterface.Stub() {
override fun basicTypes(
anInt: Int,
aLong: Long,
aBoolean: Boolean,
aFloat: Float,
aDouble: Double,
aString: String?
) {
}
override fun transmitAppInfo_In(newAppInfo: AppInfo?) {
...
}
...
}
}
第三步:客户端绑定服务,操作相关数据
class MainActivity : AppCompatActivity() {
private var myBinder: IMyAidlInterface? = null
private val myServiceConnection = object : ServiceConnection {
...
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
myBinder = IMyAidlInterface.Stub.asInterface(service) // 获取数据操作实例对象
}
}
...
/**
* 绑定服务
*/
private fun binderService() {
val intent = Intent(this, MyBinderService::class.java)
this.bindService(intent, myServiceConnection, Context.BIND_AUTO_CREATE)
}
/**
* 数据操作
*/
fun transmit_Out(view: View) {
val appInfo = AppInfo(2, "desc 客户端数据2")
myBinder?.transmitAppInfo_Out(appInfo)
}
/**
* 数据操作
*/
fun transmit_InOut(view: View) {
val appInfo = AppInfo(3, "desc 客户端数据3")
myBinder?.transmitInfo_InOut(appInfo)
}
...
}
二.概念解析
我们声明了AIDL文件,然后再进行编译的时候,编译器会生成一些相关的代码。拿示例工程举例,编译器帮我们生成IMyAidlInterface.java文件,文件路径为1.定向Tag:in、out、inout 分别表示什么?
通俗的讲,定向tag可以认为就是数据相较于服务端的流向。
in: 数据只能从客户端传递到服务端,服务端修改了接收到的数据之后,客户端的数据不会更新。我们可以查看下源码,通过源码得知服务端会接收到客户端传递的数据,单在数据处理完成之后并没有同步更新客户端的数据:
@Override
public void transmitAppInfo_In(com.car300.binderdemo.AppInfo newAppInfo) throws android.os.RemoteException {
// 服务端方法的入参数据,会从这里读取
android.os.Parcel _data = android.os.Parcel.obtain();
// 服务端处理后的数据会放到这个对象中
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
....
if ((newAppInfo != null)) {
_data.writeInt(1);
newAppInfo.writeToParcel(_data, 0); // 数据序列化,写入 _data
} else {
_data.writeInt(0);
}
boolean _status =
// 传递数据mRemote.transact(IMyAidlInterface.Stub.TRANSACTION_transmitAppInfo_In, _data, _reply, 0);
....
}
.....
}
out: 数据只能从服务端传递到客户端,无论服务端传递怎样的数据,服务端只会接收到一个只包含默认参数的数据bean,这是因为在传递时会调用对象的无参构造方法新建一个数据bean。IMyAidlInterface.java 源码如下
@Override
public void transmitAppInfo_Out(com.car300.binderdemo.AppInfo newAppInfo) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
....
boolean _status = mRemote.transact(IMyAidlInterface.Stub.TRANSACTION_transmitAppInfo_Out, _data, _reply, 0);
...
if ((0 != _reply.readInt())) {
newAppInfo.readFromParcel(_reply); // 更新客户端数据
}
} finally {
_reply.recycle();
_data.recycle();
}
}
inout: 数据可以在服务端与客户端之间双向流动,服务端可以接收客户端传递的数据,在修改数据之后客户端的数据也会得到更新。IMyAidlInterface.java 源码如下:
@Override
public void transmitInfo_InOut(com.car300.binderdemo.AppInfo newAppInfo) throws android.os.RemoteException {
// 服务端方法的入参数据,会从这里读取
android.os.Parcel _data = android.os.Parcel.obtain();
// 服务端处理后的数据会放到这个对象中
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
.....
if ((newAppInfo != null)) {
_data.writeInt(1);
newAppInfo.writeToParcel(_data, 0);// 数据序列化,写入 _data
} else {
_data.writeInt(0);
}
// 传递数据
boolean _status = mRemote.transact(IMyAidlInterface.Stub.TRANSACTION_transmitInfo_InOut, _data, _reply, 0);
....
if ((0 != _reply.readInt())) {
newAppInfo.readFromParcel(_reply); // 更新客户端数据
}
} finally {
_reply.recycle();
_data.recycle();
}
}
三.一些思考
1.通常一个AIDL文件需要对应一个Service,随着项目功能的扩充,为了区分不同的功能可能需要不同的AIDL文件,如果仍然采用这种一一对应的方式,则就需要很多的Service,这显然有些浪费资源的,要如何解决?
为此可以使用Binder链接池来统一管理这些连接,来减少Service的创建。
2.跨进程可以之间使用回调吗?为什么?Binder 如何实现双向通信?
不能直接使用回调,通信过程中,Binder 会把客户端传递过来的对象重新转化并生成一个新的对象,虽然我们在注册和解注册过程中使用的是同一个客户端,但是通过 Binder 传递到服务端后,却会产生两个全新的对象。可以使用RemoteCallBackList 实现双向通信。
3.如何限制无关应用的访问?
我们可以通过自定义权限的方式,来管理访问
a.自定义权限
<permission
android:name="com.che300.aidl_service" // 可灵活变动
android:protectionLevel="normal"/>
b.在服务端 onBind 方法做权限认证
@Override
fun onBind(intent:Intent) {
val check = checkCallingOrSelfPermission("com.che300.aidl_service");
if (check == PackageManager.PERMISSION_DENIED) {
return null;
}
return mBinder;
}
扩展:也可以通过自定义权限,来限制访问等级!
4.跨进程通信需要注意些什么?
1.当客户端发起远程请求时,由于当前线程会被挂起直至服务端进程返回数据,所以如果一个远程方法是很耗时的,则不能在UI 线程中发起此远程请求,为了避免阻塞UI 线程出现ANR ;
2.由于服务端的Binder 方法运行在 Binder 的线程池中,所以 服务端 方法不管是否耗时都应该采用同步的方式去实现, 因为它已经运行在一个线程中了,除非非要开线程;