IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android aidl双app进程通信详细讲解 -> 正文阅读

[移动开发]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?) {
                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(),我也确实遇到了。

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-05-09 12:51:06  更:2022-05-09 12:53:01 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 1:33:52-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码