【Android】IPC 之 AIDL、Messenger 、Binder 浅析

前言

本文将演示

  • 使用 Binder 类实现绑定服务和同进程组件之间的通信
  • 使用 Messenger 实现绑定服务和不同进程组件之间的通信
  • 使用 AIDL 实现绑定服务和不同进程组件之间的通信

那么,他们的使用场景分别是什么呢?

  • 如果我们无需跨进程实现绑定服务和同一进程组件之间的通信,则使用 Binder 类即可
  • 如果我们需要跨进程实现绑定服务和其他进程组件之间的通信,且不需要进行多线程处理时,则使用 Messenger
  • 如果我们需要跨进程实现绑定服务和其他进程组件之间的通信,且需要进行多线程处理,则使用 AIDL

再此之前,我们首先需要对 Android 四大组件的 Service 有一定的了解,尤其是绑定服务

我之前也针对 Service 写过一篇文章,感兴趣的同学可以先去观摩学习一下:[【Android】四大组件之 Service

](https://blog.csdn.net/yang553566463/article/details/121291919?spm=1001.2014.3001.5501)

使用 Binder 类

如果我们的服务仅供本应用使用,无需跨进程,则可以实现自有 Binder 类,让客户端通过 Binder 类直接访问 BinderService 中提供的公共方法。例如,将 Activity 与后台播放音乐的服务绑定,以获得音乐服务的控制权。

代码演示:

步骤一:创建 Service 实体类。(别忘了在 AndroidManifest.xml 中注册)

class MyNewService : Service() {
    
    

    var serviceValue = 999

    private val binder = LocalBinder()

    override fun onCreate() {
    
    
        super.onCreate()
        Log.d("testService", "onCreate")
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    
    
        Log.d("testService", "onStartCommand")
        return super.onStartCommand(intent, flags, startId)
    }

    override fun onBind(intent: Intent): IBinder {
    
    
        Log.d("testService", "onBind")
        return binder
    }

    override fun onUnbind(intent: Intent?): Boolean {
    
    
        Log.d("testService", "onUnbind")
        return super.onUnbind(intent)
    }

    override fun onDestroy() {
    
    
        super.onDestroy()
        Log.d("testService", "onDestroy")
    }

    /**
     * 通过内部类可以拿到当前的 service 对象
     * */
    inner class LocalBinder : Binder() {
    
    
        fun getService(): MyNewService = this@MyNewService
    }
}

步骤二:在 AndroidManifest.xml 中注册 MyNewService

<service android:name=".service.MyNewService" />

步骤三:编写 MainActivity 代码。在 ServiceConnection 中,可以通过 IBinder 拿到 MyNewService 中的 LocalBinder 对象,以调用 MyNewService 类的公共方法。

class MainActivity : AppCompatActivity() {
    
    

    private val mMainViewModel: MainViewModel by viewModels()

    private lateinit var mService: MyNewService
    // 服务绑定标记位
    private var mBound: Boolean = false

    /**
     * 定义一个 ServiceConnection 类
     * */
    private val connection = object : ServiceConnection {
    
    
        override fun onServiceConnected(className: ComponentName, ibinder: IBinder) {
    
    
            val binder = ibinder as MyNewService.LocalBinder
            mService = binder.getService()
            mBound = true
            Log.d("testService", "onServiceConnected serviceValue = ${
      
      mService.serviceValue}")
        }

        override fun onServiceDisconnected(arg0: ComponentName) {
    
    
            mBound = false
            Log.d("testService", "onServiceDisconnected..")
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
    
    
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }

    /**
     * 点击绑定服务按钮
     * */
    fun clickToBindService(view: View) {
    
    
        Intent(this, MyNewService::class.java).also {
    
    
            bindService(it, connection, Context.BIND_AUTO_CREATE)
        }
    }

    /**
     * 点击解绑服务按钮
     * */
    fun clickToUnbindService(view: View) {
    
    
        unbindService(connection)
        mBound = false
    }

}

步骤四:代码运行结果

// 代码运行结果
1、先点击绑定服务按钮:
D/testService: onCreate
D/testService: onBind
D/testService: onServiceConnected serviceValue = 999
2、再点击解绑服务按钮:
D/testService: onUnbind
D/testService: onDestroy

使用 Messenger

如果我们需要让当前服务与其他进程通信,则可使用 Messenger 为当前服务提供接口。(借助此方式,无需使用 AIDL 便可执行 IPC )

Messenger 会将所有服务调用加入队列,而纯 AIDL 接口会同时向服务发送多个请求,所以服务必须对多线程进行处理。对于大多数应用,服务无需执行多线程处理,因此使用 Messenger 可让服务一次处理一个调用。

如果我们的服务必须使用多线程,那么应该使用 AIDL 来定义接口。

代码演示:

步骤一:创建 MessengerService 类,继承自 Service。

其中,内部定义了一个 mMessenger 变量,它通过传入一个 Handler 对象来创建,并且来自客户端的消息也将在这个 Handler 的 handleMessage 方法中进行处理。另外,在 onBind 方法中,返回一个 mMessenger.binder 对象。

private const val MSG_SAY_HELLO = 1

class MessengerService : Service() {
    
    

    private lateinit var mMessenger: Messenger

    /**
     * 处理来自客户端的传入消息
     */
    internal class IncomingHandler(
        context: Context,
        private val applicationContext: Context = context.applicationContext,
    ) : Handler(Looper.getMainLooper()) {
    
    
        override fun handleMessage(msg: Message) {
    
    
            when (msg.what) {
    
    
                MSG_SAY_HELLO -> {
    
    
                    Log.d("testService", "Service 收到了来自 Client 的消息")
                }
                else -> super.handleMessage(msg)
            }
        }
    }

    override fun onCreate() {
    
    
        super.onCreate()
        Log.d("testService", "onCreate")
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    
    
        Log.d("testService", "onStartCommand")
        return super.onStartCommand(intent, flags, startId)
    }
    
    override fun onBind(intent: Intent): IBinder? {
    
    
        Log.d("testService", "onBind")
        mMessenger = Messenger(IncomingHandler(this))
        return mMessenger.binder
    }

    override fun onUnbind(intent: Intent?): Boolean {
    
    
        Log.d("testService", "onUnbind")
        return super.onUnbind(intent)
    }

    override fun onDestroy() {
    
    
        super.onDestroy()
        Log.d("testService", "onDestroy")
    }
}

步骤二:在 AndroidManifest.xml 中注册 该 Service。

<service
    android:name=".service.MessengerService"
    android:exported="true">
    <intent-filter>
        <action android:name="ycx.intent.action.messenger_service" />
    </intent-filter>
</service>

至此,服务端的代码编写完毕,下面我们还需要在另一个进程中编写客户端的代码。

步骤三:编写其他进程的 Activity 代码

private const val MSG_SAY_HELLO = 1

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    
    

    private val mMainViewModel: MainViewModel by viewModels()

    // 用于与服务通信的 Messenger
    private var mMessenger: Messenger? = null

    // 服务绑定标记位
    private var mBound: Boolean = false

    private val mConnection = object : ServiceConnection {
    
    
        override fun onServiceConnected(className: ComponentName, iBinder: IBinder) {
    
    
            mMessenger = Messenger(iBinder)
            mBound = true
        }

        override fun onServiceDisconnected(className: ComponentName) {
    
    
            mMessenger = null
            mBound = false
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
    
    
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }

    /**
     * 点击绑定服务按钮(需要跨进程启动 service,因此需要隐式启动)
     */
    fun clickToBindService(view: View) {
    
    
        Intent().also {
    
    
            // 需要启动的 service 的 action
            it.action = "ycx.intent.action.messenger_service"
            // service 所处的应用的包名(非本应用)
            it.setPackage("com.example.demoproject")
            bindService(it, mConnection, Context.BIND_AUTO_CREATE)
        }
    }

    /**
     * 点击发送消息给服务按钮
     */
    fun clickToSayHello(view: View) {
    
    
        if (!mBound) return
        val msg: Message = Message.obtain(null, MSG_SAY_HELLO, 0, 0)
        try {
    
    
            mMessenger?.send(msg)
        } catch (e: RemoteException) {
    
    
            e.printStackTrace()
        }
    }

    /**
     * 点击解绑服务按钮
     */
    fun clickToUnbindService(view: View) {
    
    
        if (mBound) {
    
    
            unbindService(mConnection)
            mBound = false
        }
    }

}

步骤四:代码运行结果

// 代码运行结果
1、先点击绑定服务按钮:
D/testService: onCreate
D/testService: onBind
2、再点击发送消息给服务的按钮:
D/testService: Service 收到了来自 Client 的消息
3、再点击解绑服务按钮:
D/testService: onUnbind
D/testService: onDestroy

使用 AIDL

AIDL(Android Interface Difine Language),Android 接口定义语言。

我们可以利用它定义客户端和服务互相认可的编程接口,以便二者实现跨进程间通信 (IPC)。

创建 .aidl 文件

AIDL 允许我们通过一个或多个方法来声明接口,定义的方法可接收参数和返回值,参数和返回值可以为任意类型,甚至是 AIDL 生成的其他接口。

注意:必须使用 Java 编程语言构建 .aidl 文件。

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

  • 所有基本数据类型(如 intlongcharboolean 等)
  • String
  • CharSequence
  • List:List 中所有元素必须是以上列出的数据类型或者我们声明的由 AIDL 生成的其他接口或 Parcelable 类型。
  • Map:Map 中所有元素必须是以上列出的数据类型或者我们声明的由 AIDL 生成的其他接口或 Parcelable 类型。

注意:即使在与接口相同的包内定义了上方未列出的其他类型,也必须要通过 import 导入这些类型。

我们编写的 .aidl 文件,需要保存至 src/ 目录中,如下所示:

在这里插入图片描述

当我们编写完 aidl 文件后,可以手动对项目进行 Make Project 操作,也可以点击 Run 按钮构建项目,以生成对应的文件,如图所示:

在这里插入图片描述

在构建每个包含 .aidl 文件的应用时,Android SDK 工具会生成基于该 .aidl 文件的 IBinder 接口,并将其保存到项目的 gen/ 目录中。

生成的接口包含一个名为 Stub 的子类(例如,YourInterface.Stub),该子类是其父接口的抽象实现,并且会声明 .aidl 文件中的所有方法。

注意:如果我们在首次发布 AIDL 接口后对其进行更改,则每次更改必须保持向后兼容性(必须保留对原始接口的支持),以免中断其他应用使用我们的服。

方向指示符

传递方向指示符共有三个,分别是:

  • in:表示数据只能由客户端流向服务端,服务端将会收到客户端对象的完整数据,但是客户端对象不会因为服务端对传参的修改而发生变动
  • out:表示数据只能由服务端流向客户端,服务端将会收到客户端对象,该对象不为空,但是它里面的字段为空,但是在服务端对该对象作任何修改之后客户端的传参对象都会同步改动
  • inout:表示数据可以在服务端与客户端之间双向流通,服务端将会接收到客户端传来对象的完整信息,并且客户端将会同步服务端对该对象的任何变动

代码演示:

步骤一:编写 IRemoteService.aidl 类,定义了三个方法

package ycx.aidl;

import ycx.aidl.Book;

interface IRemoteService {
    
    
    void invokeMsg(String msg,int msgType);

    void invokeBook(in Book book);

    void invokeBundle(in Bundle bundle);
}

步骤二:编写 Book.aidl 文件,由于 IRemoteService 文件中的 invokeBook 方法的 Book 类是自定义的,所以需要再声明一个 Book.aidl 文件

package ycx.aidl;

parcelable Book;

IRemoteService.aidlBook.aidl 文件处于目录中的如下位置:

在这里插入图片描述

步骤三:编写 Book.kt 文件,并使其继承 Parcelable 类。注意:我们需要在 main/java 目录下编写 Book.kt 文件,且 Book.kt 和 Book.aidl 的包名路径必须一致

package ycx.aidl

import android.os.Parcel
import android.os.Parcelable
import android.os.Parcelable.Creator

class Book : Parcelable {
    
    
    var name: String?

    constructor(name: String?) {
    
    
        this.name = name
    }

    override fun toString(): String {
    
    
        return "book name:$name"
    }

    override fun describeContents(): Int {
    
    
        return 0
    }

    override fun writeToParcel(dest: Parcel, flags: Int) {
    
    
        dest.writeString(name)
    }

    fun readFromParcel(dest: Parcel) {
    
    
        name = dest.readString()
    }

    private constructor(`in`: Parcel) {
    
    
        name = `in`.readString()
    }

    companion object {
    
    
        @JvmField
        val CREATOR: Creator<Book> = object : Creator<Book> {
    
    
            override fun createFromParcel(source: Parcel): Book {
    
    
                return Book(source)
            }

            override fun newArray(size: Int): Array<Book?> {
    
    
                return arrayOfNulls(size)
            }
        }
    }
}

Book.kt 文件处于目录中的如下位置:

在这里插入图片描述

步骤四:创建 RemoteService 类,并继承自 Service。其通过 IRemoteService.Stub 类来与客户端进行通信

class RemoteService : Service() {
    
    

    private val binder = object : IRemoteService.Stub() {
    
    
        override fun invokeMsg(msg: String?, msgType: Int) {
    
    
            Log.d("testService", "onMsgReceive msg=$msg msgType=$msgType")
        }

        override fun invokeBook(book: Book) {
    
    
            Log.d("testService", "invokeBook book name=${
      
      book.name}")
        }

        override fun invokeBundle(bundle: Bundle) {
    
    
            bundle.classLoader = classLoader // 官网说:必须设置Bundle的类加载器,否则会出现ClassNotFoundException,但实测不设置好像也没问题???
            val book = bundle.getParcelable<Book>("book_key")
            Log.d("testService", "invokeBundle book name=${
      
      book?.name}")
        }

    }

    override fun onCreate() {
    
    
        super.onCreate()
        Log.d("testService", "onCreate")
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    
    
        Log.d("testService", "onStartCommand")
        return super.onStartCommand(intent, flags, startId)
    }

    override fun onBind(intent: Intent): IBinder? {
    
    
        Log.d("testService", "onBind")
        return binder
    }

    override fun onUnbind(intent: Intent?): Boolean {
    
    
        Log.d("testService", "onUnbind")
        return super.onUnbind(intent)
    }

    override fun onDestroy() {
    
    
        super.onDestroy()
        Log.d("testService", "onDestroy")
    }
}

步骤五:在 AndroidManifest.xml 中注册 RemoteService ,并定义其 action

<service
    android:name=".service.RemoteService"
    android:exported="true">
    <intent-filter>
        <action android:name="ycx.intent.action.remote_service" />
    </intent-filter>
</service>

至此,服务端的代码编写完毕,下面我们还需要在另一个进程中编写客户端的代码。

步骤六:将 服务端 的 main/aidl 目录下的 aidl 文件拷贝至客户端,且包名路径一致,如下所示:

在这里插入图片描述

步骤七:编写其他进程的 Activity 代码

class MainActivity : AppCompatActivity() {
    
    

    // 服务绑定标记位
    private var mBound: Boolean = false

    var iRemoteService: IRemoteService? = null

    private val mConnection = object : ServiceConnection {
    
    
        override fun onServiceConnected(className: ComponentName, iBinder: IBinder) {
    
    
            iRemoteService = IRemoteService.Stub.asInterface(iBinder)
            mBound = true
        }

        // 当与服务的连接意外断开时调用
        override fun onServiceDisconnected(className: ComponentName) {
    
    
            Log.d("testService", "Service has unexpectedly disconnected")
            iRemoteService = null
            mBound = false
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
    
    
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }

    /**
     * 点击绑定服务按钮(需要跨进程启动 service,因此需要隐式启动)
     */
    fun clickToBindService(view: View) {
    
    
        Intent().also {
    
    
            // 需要启动的 service 的 action
            it.action = "ycx.intent.action.remote_service"
            // service 所处的应用的包名(非本应用)
            it.setPackage("com.example.demoproject")
            bindService(it, mConnection, Context.BIND_AUTO_CREATE)
        }
    }

    /**
     * 点击发送消息给服务按钮
     */
    fun clickToSayHello(view: View) {
    
    
        if (!mBound) return
        iRemoteService?.invokeMsg("来自客户端的消息", 999)
        iRemoteService?.invokeBook(Book("《西游记》"))
        iRemoteService?.invokeBundle(Bundle().also {
    
    
            it.putParcelable("book_key", Book("《红楼梦》"))
        })
    }

    /**
     * 点击解绑服务按钮
     */
    fun clickToUnbindService(view: View) {
    
    
        if (mBound) {
    
    
            unbindService(mConnection)
            mBound = false
        }
    }
}

步骤八:运行客户端代码

// 代码运行结果
1、先点击绑定服务按钮:
D/testService: onCreate
D/testService: onBind
2、再点击发送消息给服务的按钮:
D/testService: onMsgReceive msg=来自客户端的消息 msgType=999
D/testService: invokeBook book name=《西游记》
D/testService: invokeBundle book name=《红楼梦》
3、再点击解绑服务按钮:
D/testService: onUnbind
D/testService: onDestroy

猜你喜欢

转载自blog.csdn.net/yang553566463/article/details/125191268