IPC : 多进程 Binder-AIDL 使用指南、包含案例

相关文章指引

Android多个进程同时写同一个文件,会怎么样?
Android:多进程的开启方式、注意点以及如何解决。

Binder

简介

简单来说,BinderAndroid.os包下的一个类,它继承了IBinder接口。从IPC的角度来说BinderAndroid特有的一种跨进程通信的方式。在FrameWork中,BinderServiceManager连接各种Manager(ActivityManagerWindowManager等等)的桥梁。

在多进程应用开发且使用Binder进行通讯,Binder则是客户端与服务端的通讯媒介。当bindService时,服务端会返回包含服务端业务调用的Binder对象,通过这个Binder对象,客户端就可以调用服务端所提供的一系列服务,完成进程间通讯。

AIDL

上面简单了解了一下Binder,那么AIDL是什么呢?

AIDL又为Android接口定义语言。一个通用的模板,用于快速实现跨进程通讯,通过AIDL可以快速实现Binder进行跨进程通讯。

我们会定义.aidl文件,build时,Android SDK 工具会根据 .aidl 文件生成一个 IBinder 接口,并将其保存在项目的 gen/ 目录中。 服务必须适当地实现 IBinder 接口。 然后客户端应用程序可以绑定到服务并从 IBinder 调用方法来执行 IPC

所以下面构建一个AIDL的使用示例,首先熟悉一下使用AIDL进行跨进程通讯。

AIDL使用

须知

实现AIDL需要以下几个步骤

  1. 创建.aidl文件

    该文件定义了带有方法签名的编程接口。

  2. 实现接口

    Android SDK 工具会根据您的.aidl文件生成接口。 此接口有一个名为 Stub 的内部抽象类,它继承Binder 并实现 AIDL接口中的方法。 您必须继承Stub 类并实现方法。

  3. 为客户端暴露接口

    实现Service 并重写onBind() 以返回 Stub 类的实现。

默认情况下,AIDL支持以下数据类型

  1. Java 编程语言中的所有基本类型(如 intlongcharboolean 等)

  2. 原始类型数组,例如 int[]

  3. 自定义的Parcelable对象以及AIDL接口本身

  4. StringCharSequence

  5. ListMap

    对于所有元素必须是此列表中受支持的数据类型之一,或者是您声明的其他 AIDL 生成的接口或 parcelables 之一。另外对于接收者list始终是ArrayListMap始终是HashMap

注意

  1. aidl方法输入的参数,所有的非基本类型均需要指向一个数据的流向,inout或者inout
  2. 基本类型、StringIBinderAIDL接口本身默认即为in,不能是其他的。
  3. 可以为 Null 的参数和返回类型必须使用 @nullable 进行注释。

IN OUT INOUT

关于inoutinout这几个tag的使用效果,这里列一个表格出来。

定义进程1调用进程2提供的传入Book对象方法,而tag分别是inoutinout。具体的调用效果如下方表格所示:

addBook(in Book book)
addBook(out Book book)
addBook(inout Book book)
TAG方法效果
inaddBook(in Book book)进程2将收到进程1 add 进来的Book对象。
outaddBook(out Book book)进程2将收到进程1调用的add方法,但是传递过来的Book对象是空对象,里面没有值。但是进程2对传递过来的对象做任何修改,将同步给进程1的Book对象。
inoutaddBook(inout Book book)进程2将收到进程1 add 进来的Book对象,同时进程2对传递过来的对象做任何修改,将同步给进程1的Book对象。

案例一

AIDL简单通讯案例。

  1. 创建.aidl文件。IRemoteService

    package com.howie.multiple_process;
    
    // Declare any non-default types here with import statements
    
    interface IRemoteService {
       /**
        * 获取服务进程id
        */
        int getPid();
    }
    
  2. 实现IRemoteService.Stub接口,实现接口方法。

    object RemoteServiceImpl : IRemoteService.Stub() {
    	
    	/**
         * 获取服务端进程ID
         */
        override fun getPid(): Int = Process.myPid()
    }
    
  3. 通过ServiceClient暴露IRemoteService

    1. 定义一个RemoteService,在onBind方法中返回RemoteServiceImpl

      class RemoteService : Service() {
      
          override fun onCreate() {
              super.onCreate()
          }
      
          override fun onBind(intent: Intent?): IBinder = RemoteServiceImpl
      
      }
      
    2. AndroidManifest文件中注册RemoteService

      <service android:name=".aidl_service.RemoteService"/>
      
    3. 构建客户端,我们这里使用一个Activity回调bindService去绑定该服务。另外需要构建ServiceConnection onServiceConnected通过YouService.Stub.asInterface(service)获取远程服务,onServiceDisconnected中会在连接意外断开之后进行回调。

      	var iRemoteService: IRemoteService? = null
      
          private val mConnection = object : ServiceConnection {
              override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
                  //获取远程服务
                  iRemoteService = IRemoteService.Stub.asInterface(service)
              }
      
              override fun onServiceDisconnected(name: ComponentName?) {
                  unBind()
              }
      
              override fun onBindingDied(name: ComponentName?) {
                  super.onBindingDied(name)
                  unBind()
              }
          }
          
          private fun unBind() {
              iRemoteService = null
              unbindService(mConnection)
          }
          
          private fun bindRemoteService() {
              //绑定远程服务,传入mConnection
              bindService(Intent(this, RemoteService::class.java), mConnection, Context.BIND_AUTO_CREATE)
          }
          
          private fun doEvent() {
              // TODO: 获取远程进程PID
              binding.btObtainRemotePid.setOnClickListener {
                  ToastUtil.toastShort("Remote pid is ${iRemoteService?.pid ?: ""}.")
              }
          }
          
          override fun onCreate(savedInstanceState: Bundle?) {
              super.onCreate(savedInstanceState)
              binding = ActivityClientBinding.inflate(layoutInflater)
              setContentView(binding.root)
              binding.btDescribe.text = this::class.java.simpleName
      
              // TODO: bind 远程aidl服务
              bindRemoteService()
              doEvent()
          }
      

      注意RemoteService在主进程,客户端时在另一个新的进程中的。案例二也一样。

案例二

案例一描述了一个简单的案例,关于如何使用AIDL。那么对于日常开发来说肯定是不够的,下面我们写一个复杂一点的案例,利用AIDL传递对象以及回调接口。

  1. 创建aidl文件,因为包含对象、回调通知,故我们需要创建三个aidl文件,分别如下。

    Book对象

    // Book.aidl
    package com.howie.multiple_process.bean;
    
    parcelable Book;
    
    package com.howie.multiple_process.bean
    
    import android.os.Parcel
    import android.os.Parcelable
    
    class Book
    @JvmOverloads
    constructor(var bookId: Int = 0, var bookName: String? = "", var bookDescribe: String? = "") : Parcelable {
    
        constructor(parcel: Parcel) : this() {
            bookId = parcel.readInt()
            bookName = parcel.readString()
            bookDescribe = parcel.readString()
        }
    
        override fun writeToParcel(parcel: Parcel, flags: Int) {
            parcel.writeInt(bookId)
            parcel.writeString(bookName)
            parcel.writeString(bookDescribe)
        }
    
        override fun describeContents(): Int {
            return 0
        }
    
        override fun toString(): String {
            return "Book(bookId=$bookId, bookName=$bookName, bookDescribe=$bookDescribe)"
        }
    
        companion object CREATOR : Parcelable.Creator<Book> {
            override fun createFromParcel(parcel: Parcel): Book {
                return Book(parcel)
            }
    
            override fun newArray(size: Int): Array<Book?> {
                return arrayOfNulls(size)
            }
        }
    
    }
    

    callback接口回调对象

    // IBookSizeChangeListener.aidl
    package com.howie.multiple_process.listener;
    import com.howie.multiple_process.bean.Book;
    // Declare any non-default types here with import statements
    
    interface IBookSizeChangeListener {
        void bookSizeChange(in List<Book> books);
    }
    

    BookService对象提供的方法

    // IBookManagerService.aidl
    package com.howie.multiple_process;
    
    import com.howie.multiple_process.bean.Book;
    import com.howie.multiple_process.listener.IBookSizeChangeListener;
    
    interface IBookManagerService {
        //获取book列表
        List<Book> getBookList();
        //增加书籍
        void addBook(in Book book);
    
        //注册与反注册监听
        void registerListener(IBookSizeChangeListener listener);
        void unRegisterListener(IBookSizeChangeListener listener);
    }
    
  2. 实现IBookManagerService.Stub接口,实现接口方法。

    /**
     * 函数在binder线程池中执行,注意并发问题
     */
    object BookManagerServiceImpl : IBookManagerService.Stub() {
    
        private const val TAG = "BookManagerServiceImpl"
    
        /**
         * 专门用于AIDL服务端保存回调接口,内部使用binder作为key。保证客户端传入同一个对象,在服务端可以顺利的找到该对象。
         */
        private val callbacks: RemoteCallbackList<IBookSizeChangeListener> = RemoteCallbackList()
    
        private val books = ArrayList<Book>()
    
        override fun getBookList(): MutableList<Book> {
            //mock time consuming
            Thread.sleep(3000)
            return books
        }
    
        override fun addBook(book: Book?) {
            //在binder线程池进行操作,故需要进行同步处理
            synchronized(books) {
                books.add(book ?: Book())
            }
            //回调callback
            synchronized(callbacks) {
                try {
                    val num = callbacks.beginBroadcast()
                    if (AppUtil.isDebug) {
                        Log.d(TAG, "callback num is $num")
                    }
                    for (i in 0 until num) {
                        callbacks.getBroadcastItem(i)?.bookSizeChange(books)
                    }
                } finally {
                    callbacks.finishBroadcast()
                }
            }
    
        }
    
        override fun registerListener(listener: IBookSizeChangeListener?) {
            //RemoteCallbackList  内部保证了线程安全,故不需要加锁
            if (listener != null) {
                callbacks.register(listener)
            }
        }
        override fun unRegisterListener(listener: IBookSizeChangeListener?) {
            //RemoteCallbackList  内部保证了线程安全,故不需要加锁
            if (listener != null) {
                callbacks.unregister(listener)
            }
        }
    
    }
    
  3. 客户端需要bind,和第一个案例一样,代码就不列出来了,主要是需要声明一个IBookSizeChangeListener注册到服务端,用于在服务端Book变化之后,基于监听。

        private val callback = object : IBookSizeChangeListener.Stub() {
            override fun bookSizeChange(books: MutableList<Book>?) {
                //binder 线程池中执行 需要调度到主线程
                Log.d(TAG, "callback book size is ${books?.size}")
                safeLaunch {
                    binding.tvDisplayBook.text = books?.joinToString("\t\n")
                }
            }
        }
    

    注册和反注册函数

    iBookManagerService?.registerListener(callback)
    iBookManagerService?.unRegisterListener(callback)
    override fun onDestroy() {
        super.onDestroy()
        iBookManagerService?.unRegisterListener(callback)
        unBindBookManagerService()
    }
    

注意点:

下面来详细说一说上述代码的注意事项。

  1. 客户端向服务端传递对象的时候,本质上是序列化和反序列化的过程,所以并不是同一个对象(甚至你可以想到都两个进程了肯定不是同一个对象了)。所以即使客户端注册和反注册是同一个对象,在服务端也会生成不同的对象。那么如何保证反注册的时候,移除真正需要移除的对象呢?首先可以做一个测试,同一个对象,多次由客户端传递给服务端,肯一下对象、hashcode以及binder对象。

    register
     D/BookManagerServiceImpl: Client IBookSizeChangeListener hash code is 86046646
      D/BookManagerServiceImpl: Client IBookSizeChangeListener binder is com.howie.multiple_process.view.aidlClient.AbstractClientActivity$event$callback$1@520f7b6
     D/BookManagerServiceImpl: Service IBookSizeChangeListener hash code is 69562410
     D/BookManagerServiceImpl: Service IBookSizeChangeListener binder is android.os.BinderProxy@91b10ff
    
    unregister
     D/BookManagerServiceImpl: Client IBookSizeChangeListener hash code is 86046646
     D/BookManagerServiceImpl: Client IBookSizeChangeListener binder is com.howie.multiple_process.view.aidlClient.AbstractClientActivity$event$callback$1@520f7b6
     D/BookManagerServiceImpl: Service IBookSizeChangeListener hash code is 110738971
     D/BookManagerServiceImpl: Service IBookSizeChangeListener binder is android.os.BinderProxy@91b10ff
    

    看得出每次到达服务端之后,对应的hashcode都是变化的,但是负责当前对象传递的Binder对象时唯一的91b10ff是唯一的,所以我们可以利用Binder作为key来进行反注册。

    上述代码也是这么做的,用了官方的集合RemoteCallbackList,专门用来储存客户端传递过来的CallBack对象。其内部用于查找的key即为Binder对象。

     ArrayMap<IBinder, Callback> mCallbacks = new ArrayMap<IBinder, Callback>();
    
  2. 服务端的方法均在Binder线程池中执行,所以需要进行同步处理

  3. 通过callback回调回去的结在客户端的Binder线程池中执行,如需要进行UI操作则需要切换到UI线程。上述代码中safeLaunch是对lifecycleScope.launch做了简单的封装,对异常进行了处理。

    val coroutineExceptionHandler = CoroutineExceptionHandler { _, throwable ->
        // TODO: 异常处理
        throwable.printStackTrace()
    }
    
    fun LifecycleOwner.safeLaunch(
        context: CoroutineContext = EmptyCoroutineContext,
        start: CoroutineStart = CoroutineStart.DEFAULT,
        block: suspend CoroutineScope.() -> Unit
    ): Job {
        return lifecycleScope.launch(context + coroutineExceptionHandler, start, block)
    }
    

本章代码
下一期,浅析Binder原理。

创作不易,如有帮助一键三连咯🙆‍♀️。欢迎技术探讨噢!

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

pumpkin的玄学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值