Android aidl双app进程通信详细讲解

一、场景

        公司项目里用了很多的独立进程的服务与其他进程之间存在了很多跨进程的通信。之前有很长一段时间没有实际去做跨进程通信 AIDL了,查阅了一些资料和文章看了些 Demo 把温习的心路历程介绍一下。

工具

Android studio

小米手机Android11(api 30)

模拟器手机Android8 (api 26)

为什么要准备两个手机,因为这里Android高版本的aidl使用有点坑的地方,需要做兼容处理,后面会讲到原因。

二、创建aidl的服务端  AIDLService(单独进程)

先创建aidl的服务端,因为正常情况下一个APP就是一个进程

新建一个服务,目前这个服务还是空类,因为需要等待aidl的创建好和aidl编译生成的新类,我们才能进一步去完善这个service。

package com.kang.aidlservice

import android.app.Service
import android.content.Intent
import android.os.IBinder

class KtvService:Service() {
    override fun onBind(p0: Intent?): IBinder? {//这里参数变量显示p0是因为没下载对应sdk源码
        return null
    }
}

在清单文件中注册

<service
    android:name=".KtvService"
    android:enabled="true"
    android:exported="true" >
            
    <intent-filter>
         <!--添加了一个唯一的action,供客户端隐式启动service-->
         <action android:name="com.kang.aidlservice.KtvService"/>
    </intent-filter>

</service>

三、在服务端创建aidl文件

1、在main右键--->New--->Directory--->选择aidl

2、新建aidl文件:

右键刚刚新建的aidl目录---》New--->AIDL--->AIDL File--->输入新建的aidl文件名 

默认的aidl文件内容 

我们修改后的aidl文件内容

 之后就make project或者Rebuild project一下,等待编译。

 编译成功之后,会在app目录下的build生成一个IKtvController.java的文件。

我们看看这个文件里面生成的内容,我们需要用到那个继承了Binder的类,因为Binder这是IPC的原理,进程之间通信就是用到的Binder机制。

 3、使用上面生成的内容,完善刚刚创建的KtvService.kt类

package com.kang.aidlservice

import android.app.Service
import android.content.Intent
import android.os.IBinder
import android.util.Log

class KtvService : Service() {

    companion object {
        private const val TAG = "binkang"
    }

    override fun onCreate() {
        super.onCreate()
        Log.i(TAG, "onCreate: ")
    }

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

    override fun onBind(p0: Intent?): IBinder? {
        return KtvBinder()
    }


    //内部类KtvBinder,实现了aidl文件生成的Stub类
    inner class KtvBinder : IKtvController.Stub() {
        override fun setPause(pause: String?) {
            Log.i(TAG, "setPause: $pause")
        }

        override fun setPlay(play: String?) {
            Log.i(TAG, "setPlay: $play")
        }
    }

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

    override fun onDestroy() {
        super.onDestroy()
        Log.i(TAG, "onDestroy: ")
    }

    override fun onRebind(intent: Intent?) {
        super.onRebind(intent)
        Log.i(TAG, "onRebind: ")
    }
}

 四、创建客户端

1、我这里新建了一个项目,作为另一个APP客户端。当然也可以在服务端的那个项目新建一个APP module也是一样的。

 2、将刚刚服务端的main下的aidl文件整个目录都复制过来,放到客户端的main目录下

注意这里的包名也是和服务端的一致。

 之后也是一样,make project或者Rebuild project,也会在app--->build目录下生成一个IKtvController.java文件。

 3、接下来就是编写客户端的mainActivity的代码,去绑定服务端的KtvService。

package com.kang.aidlclient

import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.os.IBinder
import android.util.Log
import android.widget.Button
import com.kang.aidlservice.IKtvController

class MainActivity : AppCompatActivity() {

    var iKtvController: IKtvController? = null

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

        bindKtvService()

        findViewById<Button>(R.id.pause).setOnClickListener {
            iKtvController?.setPause("sorry~, pause")
        }

        findViewById<Button>(R.id.play).setOnClickListener {
            iKtvController?.setPlay("hi~, play")
        }
    }

    private fun bindKtvService() {
        //通过action隐式去绑定service
        val intent = Intent()
        intent.action = "com.kang.aidlservice.KtvService"//服务端的清单文件中的action
        intent.setPackage("com.kang.aidlservice")
        bindService(intent, object : ServiceConnection {
            override fun onServiceConnected(name: ComponentName?, service: IBinder?){
                //这个回调就代表客户端和服务端建立了链接,之后便可以通信
                Log.i("binkang", "onServiceConnected: ")
                //获取Stub(Binder)实例,此处不能使用new的方式创建
                iKtvController = IKtvController.Stub.asInterface(service)
            }

            override fun onServiceDisconnected(name: ComponentName?) {
                //这个断开链接的回调,主动调用service的unBindService()不会回调到这个方法
                //service所在的宿主由于异常终止或者其他原因终止,导致service与访问者间断才会
                //回调这个方法
                Log.i("binkang", "onServiceDisconnected: ")
            }

        }, Context.BIND_AUTO_CREATE)
    }
}

4、运行,先运行启动服务端AIDLService,再运行启动客户端AIDLClient

这里可以看到有两个进程了。

 5、看运行的结果:

到这一步,在Android11手机上是没有任何日志输出的。运行在Android8(API26)的手机上是有日志输出的。高版本兼容(看这篇Android12兼容问题第七点,才发现是版本的问题,折腾了半天时间:分享一下适配 Android 12 遇到的坑

 

 因此我们需要在客户端的清单文件中加上queries这个属性

package是服务端进程包名。

 这下在我的Android11小米手机上就可以看日志了:这里我少打了一个onBind()方法的日志

我后面加了这个日志打印,会在onCreate后打印:com.kang.aidlservice I/binkang: onBind: 

 点击客户端的两个按钮pause和play。

 至此,客户端向服务端发送数据就通了

五、服务端向客户端发送数据通信

1、我们修改一下服务端的aidl目录下的内容 

新增一个aidl文件IControllerStatusListener.aidl(回调监听的作用),内容如下

 修改刚刚的IKtvController.aidl,内容如下:多提供了一个方法,用于设置监听回调

 修改完,接着就是make project一下,这个时候KtvService会报错:因为我们新增了一个接口方法

    inner class KtvBinder : IKtvController.Stub() {
        override fun setPause(pause: String?) {
            Log.i(TAG, "setPause: $pause")
        }

        override fun setPlay(play: String?) {
            Log.i(TAG, "setPlay: $play")
        }

        override fun setOnControllerStatusListener(i: IControllerStatusListener?) {
            Log.i(TAG, "setOnControllerStatusListener: ")

        }
    }

 修改完服务端之后,将修改之后的aidl目录复制,覆盖客户端的aidl目录(可以先删除,再复制进去,以免覆盖发生错误),同样make project就行。

2、修改一下客户端的工作,把监听设置上即可代码如下,注意回来后操作UI需要自己切线程

修改之后的代码:

package com.kang.aidlclient

import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.os.Bundle
import android.os.Handler
import android.os.IBinder
import android.os.Looper
import android.util.Log
import android.widget.Button
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.kang.aidlservice.IControllerStatusListener
import com.kang.aidlservice.IKtvController


class MainActivity : AppCompatActivity() {

    var iKtvController: IKtvController? = null

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

        bindKtvService()

        findViewById<Button>(R.id.pause).setOnClickListener {
            iKtvController?.setPause("sorry~, pause")
        }

        findViewById<Button>(R.id.play).setOnClickListener {
            iKtvController?.setPlay("hi~, play")
        }
    }

    private fun bindKtvService() {
        //通过action隐式去绑定service
        val intent = Intent()
        intent.action = "com.kang.aidlservice.KtvService"//服务端的清单文件中的action
        intent.setPackage("com.kang.aidlservice")
        bindService(intent, object : ServiceConnection {
            override fun onServiceConnected(name: ComponentName?, service: IBinder?){

                Log.i("binkang", "onServiceConnected: ")
                //获取Stub(Binder)实例,此处不能使用new的方式创建
                iKtvController = IKtvController.Stub.asInterface(service)

                iKtvController?.setOnControllerStatusListener(object :
                    IControllerStatusListener.Stub() {

                    override fun onPauseSucess() {
                        Log.i("binkang", "onPauseSucess: ")
                        Handler(Looper.getMainLooper()).post {
                            Toast.makeText(
                                this@MainActivity,
                                "onPauseSuccess",
                                Toast.LENGTH_SHORT
                            ).show()
                        }
                    }

                    override fun onPauseFailed(errorCode: Int) {
                        Log.i("binkang", "onPauseFailed: $errorCode")
                        Handler(Looper.getMainLooper()).post {
                            Toast.makeText(
                                this@MainActivity,
                                "onPauseFailed $errorCode",
                                Toast.LENGTH_SHORT
                            ).show()
                        }
                    }

                    override fun onPlaySuccess() {
                        Log.i("binkang", "onPlaySuccess: ")
                        Handler(Looper.getMainLooper()).post {
                            Toast.makeText(
                                this@MainActivity,
                                "onPlaySuccess",
                                Toast.LENGTH_SHORT
                            ).show()
                        }
                    }

                    override fun onPlayFailed(errorCode: Int) {
                        Log.i("binkang", "onPlayFailed: $errorCode")
                        Handler(Looper.getMainLooper()).post {
                            Toast.makeText(
                                this@MainActivity,
                                "onPlayFailed $errorCode",
                                Toast.LENGTH_SHORT
                            ).show()
                        }
                    }
                })
            }

            override fun onServiceDisconnected(name: ComponentName?) {
                Log.i("binkang", "onServiceDisconnected: ")
            }

        }, Context.BIND_AUTO_CREATE)
    }
}

3、服务端代码,我们来回调暂停和播放。各自模拟一个 1 秒的耗时操作,代码如下

package com.kang.aidlservice

import android.app.Service
import android.content.Intent
import android.os.IBinder
import android.os.RemoteException
import android.util.Log

class KtvService : Service() {

    var listener: IControllerStatusListener? = null

    companion object {
        private const val TAG = "binkang"
    }

    override fun onCreate() {
        super.onCreate()
        Log.i(TAG, "onCreate: ")
    }

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

    override fun onBind(p0: Intent?): IBinder? {
        Log.i(TAG, "onBind: ")
        return KtvBinder()
    }

    inner class KtvBinder : IKtvController.Stub() {
        override fun setPause(pause: String?) {
            Log.i(TAG, "setPause: $pause")

            //模拟播放耗时 1000 毫秒
            if (listener != null) {
                Thread {
                    try {
                        Thread.sleep(1000)
                        if (System.currentTimeMillis() % 2 == 0L) {
                            listener!!.onPauseSucess()
                        } else {
                            listener!!.onPauseFailed(1002)
                        }
                    } catch (e: InterruptedException) {
                        e.printStackTrace()
                    } catch (remoteException: RemoteException) {
                        remoteException.printStackTrace()
                    }
                }.start()
            }
        }

        override fun setPlay(play: String?) {
            Log.i(TAG, "setPlay: $play")

            //模拟播放耗时 1000 毫秒
            if (listener != null) {
                Thread {
                    try {
                        Thread.sleep(1000)
                        if (System.currentTimeMillis() % 2 == 0L) {
                            listener!!.onPlaySuccess()
                        } else {
                            listener!!.onPlayFailed(1002)
                        }
                    } catch (e: InterruptedException) {
                        e.printStackTrace()
                    } catch (remoteException: RemoteException) {
                        remoteException.printStackTrace()
                    }
                }.start()
            }
        }

        override fun setOnControllerStatusListener(i: IControllerStatusListener?) {
            Log.i(TAG, "setOnControllerStatusListener: ")
            listener = i
        }
    }

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

    override fun onDestroy() {
        super.onDestroy()
        Log.i(TAG, "onDestroy: ")
    }

    override fun onRebind(intent: Intent?) {
        super.onRebind(intent)
        Log.i(TAG, "onRebind: ")
    }
}

4、修改之后运行看效果:

 5、点击按钮pause和play看日志:

 

 另外客户端也有toast,这里就不截图了。至此,就完成了进程之间的双向通信。

六、总结

 Android使用aidl实现进程之间双向通信,就是借助Binder机制的。上面实现的过程中,唯一卡住的地方就是那个高版本兼容的问题。

另外要注意一点:在 aidl 方法中如果想要操作 UI 需要自己处理线程切换到主线程,否则会报错:

Can't toast on a thread that has not called Looper.prepare(),我也确实遇到了。

  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
Android AIDLAndroid Interface Definition Language)是一种Android中用于实现跨进程通信的机制。它允许一个应用程序的进程与另一个应用程序的进程进行交互。 在AIDL中,双向通信可以通过以下步骤实现: 1. 创建AIDL接口:首先,在服务提供方的应用程序中创建一个AIDL接口类,该接口定义了需要在两个应用程序之间进行通信的方法。 2. 实现AIDL接口:在服务提供方的应用程序中实现AIDL接口。这些方法将用于处理由客户端发起的请求。 3. 绑定Service:在客户端应用程序中,通过绑定Service与提供方建立连接。这可以通过Intent和bindService()方法完成。 4. 获取Service接口:一旦客户端与服务提供方建立了连接,客户端将获得Service的接口。这个接口将通过onServiceConnected()方法返回给客户端。 5. 调用方法:客户端可以使用接口对象来调用服务提供方的方法,从而向服务提供方发送请求。 6. 返回结果:服务提供方在接收到请求后,根据请求的类型进行相应的处理,并将处理结果返回给客户端。 通过以上步骤,双向通信将会在两个应用程序之间建立起来。服务提供方可以处理客户端的请求,并将结果返回给客户端。而客户端可以调用服务提供方的方法,并获取所需的数据。 需要注意的是,为了保证AIDL双向通信的正常运行,需要在AndroidManifest.xml文件中声明相应的权限和服务。 总结来说,使用Android AIDL可以实现双向通信。服务提供方与客户端通过AIDL接口进行交互,客户端发送请求给服务提供方,并获取处理结果。这种机制可以方便地实现不同应用程序之间的数据交换和通信。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值