Binder进程间通信实践

Demo地址:

https://github.com/qinyafeiii/BinderDemo

一.Binder在项目中的简单使用

第一步: 新建AIDL文件。选中对应 module 鼠标右键 new -> AIDL-> AIDL File 新建AIDL文件;新建对应的数据bean & 对应的AIDL文件(项目中数据bean为:AppInfo)。项目结构如下
image

注意点:

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文件,文件路径为
image
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 的线程池中,所以 服务端 方法不管是否耗时都应该采用同步的方式去实现, 因为它已经运行在一个线程中了,除非非要开线程;

四.参考文献

https://www.jianshu.com/p/69e5782dd3c3

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,843评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,538评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,187评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,264评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,289评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,231评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,116评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,945评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,367评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,581评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,754评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,458评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,068评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,692评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,842评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,797评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,654评论 2 354

推荐阅读更多精彩内容