前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android Aidl跨进程通讯的简单使用

Android Aidl跨进程通讯的简单使用

作者头像
Vaccae
发布2023-08-22 08:11:38
7920
发布2023-08-22 08:11:38
举报
文章被收录于专栏:微卡智享

学更好的别人,

做更好的自己。

——《微卡智享》

本文长度为3130,预计阅读7分钟

前言

多进程其实在大的APP中越来越多,像微信里面就是,消息接收是单独的进程服务,所以AIDL的跨进程通讯少不了是需要掌握的技能,本篇就是实现一个AIDL跨进程通讯的简单事例,做为一个入门的了解。

AIDL简介

微卡智享

AIDL全名Android Interface Definition Language,目的是为了实现进程间通信,由于不同的进程有着不同的内存区域,并且它们只能访问自己的那一块内存区域,所以我们必须将要传输的数据转化为能够在内存之间流通的形式,通过AIDL进行跨进程通信的时候,选择的序列化方式是实现 Parcelable 接口。

AIDL默认支持的数据类型包括:

  • 基本数据类型:(byte,short,int,long,float,double,boolean,char)、String 类型、CharSequence类型。
  • List类型:List中的所有元素必须是AIDL支持的类型之一,或者是一个其他AIDL生成的接口,或者是定义的parcelable(下文关于这个会有详解)。List可以使用泛型。
  • Map类型:Map中的所有元素必须是AIDL支持的类型之一,或者是一个其他AIDL生成的接口,或者是定义的parcelable。Map是不支持泛型的。

AIDL中还有定向的Tag,包括了in、out、inout。其中 in 表示数据只能由客户端流向服务端, out 表示数据只能由服务端流向客户端,而 inout 则表示数据可在服务端与客户端之间双向流通。

代码实现

微卡智享

AIDL服务端

01

创建AIDL服务

在Android Studio中新建一个应用后,我们先创建一个AIDL的Service,File--New--New Module

02

创建数据类实现Parcelable接口

前面简介中提到过,AIDL数据类通讯需要实现Parcelable接口,为了省去接口实现的代码,Kotlin中通过kotlin-parcelize即可实现了。

在build.gradle的plugins中加入id("kotlin-parcelize")

创建TestData数据类

代码语言:javascript
复制
package vac.test.aidlservice

import android.os.Parcelable
import kotlinx.parcelize.Parcelize


@Parcelize
data class TestData(var code: String, var name: String, var price: Float, var qty: Int) : Parcelable

通过引入kotlinx.parcelize.Parcelize,然后加入标注@Parcelize,即可直接实现Parcelable接口,省去了很多代码。

03

创建AIDL文件

首先先创建一个AIDL的目录,在New--Folder--AIDL Folder中直接创建即可。

然后新建一个ITestDataAidlInterface的AIDL文件接口,New--AIDL--AIDL File,这里要注意,默认的AIDL File是灰色的不能创建,需要在build.gradle中加入一个修改项后才能正常显示。

代码语言:javascript
复制
android {
    buildFeatures {
        aidl = true
    }
}

加入上面代码后,即可正常创建了,所以在app和aidlservice两个module中都加入了这一项。

代码语言:javascript
复制
// ITestDataAidlInterface.aidl
package vac.test.aidlservice;

// Declare any non-default types here with import statements

parcelable TestData;

interface ITestDataAidlInterface {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);


    //根据编码返回测试类
    TestData getTestData(String code);

    //返回测试列表
    List<TestData> getTestDatas();

    //修改测试类
    boolean updateTestData(in TestData data);
}

加入了三个实现方法,分别的返回列表数据,返回第一条数据和修改数据三个方法。

在aidl中使用了数据类TestData,所以Aidl文件和数据类的文件必须保证在同一包名下,并不是说放在同一文件夹下,实体类TestData文件在主Code文件夹下(java目录下),包名和aidl文件夹中放置.aidl文件的包名一致。保证这样后再重新Rebuild就不会报错了。

04

创建服务

新建了AidlService服务,在onbind中实现ITestDataAidlInterface方法。

代码语言:javascript
复制
package vac.test.aidlservice

import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.Service
import android.content.Context
import android.content.Intent
import android.os.IBinder
import android.util.Log

class AidlService : Service() {

    val CHANNEL_STRING = "vac.test.aidlservice"
    val CHANNEL_ID = 0x11

    val mTestDatas: MutableList<TestData> = mutableListOf()

    fun initList() {
        for (i in 1..5) {
            val price = ((0..10).random()).toFloat()
            val qty = (10..50).random()
            val item = TestData("0000${i}", "测试数据${i}", price, qty)
            mTestDatas.add(item)
        }
    }

    fun startServiceForeground() {
        val notificationManager =
            getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        val channel = NotificationChannel(
            CHANNEL_STRING, "AidlServer",
            NotificationManager.IMPORTANCE_LOW
        )
        notificationManager.createNotificationChannel(channel)
        val notification = Notification.Builder(applicationContext, CHANNEL_STRING).build()
        startForeground(CHANNEL_ID, notification)
    }

    override fun onCreate() {
        super.onCreate()
        /*Debug版本时调试使用 */
        // Debug.waitForDebugger()
        startServiceForeground()
        //初始化数据
        initList()
    }

    override fun onBind(intent: Intent): IBinder {

        return object : ITestDataAidlInterface.Stub() {
            override fun basicTypes(
                anInt: Int,
                aLong: Long,
                aBoolean: Boolean,
                aFloat: Float,
                aDouble: Double,
                aString: String?
            ) {
                TODO("Not yet implemented")
            }

            override fun getTestData(code: String?): TestData? {
                return mTestDatas.firstOrNull { t -> t.code == code }
            }

            override fun getTestDatas(): MutableList<TestData> {
                return mTestDatas
            }

            override fun updateTestData(data: TestData?): Boolean {
                data?.let {
                    var item: TestData? =
                        mTestDatas.firstOrNull { t -> t.code == it.code } ?: return false
                    item?.let { t ->
                        t.code = it.code
                        t.name = it.name
                        t.price = it.price
                        t.qty = it.qty
                    }
                    return true
                } ?: return false
            }

        }
    }
}

为了使其他进程可以被调用,所以在AndroidManifest.xml需要加入Action,并且服务启动时要改为前台服务,所以前台服务的权限也要加入

代码语言:javascript
复制
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="vac.test.aidlservice">

    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.AidlRoomDemo">
        <service
            android:name=".AidlService"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="AIDL_SERVICE" />
            </intent-filter>
        </service>
    </application>

</manifest>

一个简单的AIDL服务端这样就完成了。

AIDL客户端

01

加入AIDL和数据类

因为客户端和服务端是两个不同的进程,所以客户端也要像服务端一样创建AIDL文件夹,复制对应的 aidl 文件和自定义的数据类,请保证包名保持一致,然后编译一下。

02

客户端布局

主页面一个Recyclerview,两个按钮,一个为获取列表,一个获取第一条数据,Adapter中布局就是显示数据信息。

03

绑定服务

绑定服务最主要的就是创建ServiceConnection,通过ServiceConnecttion返回Aidl的接口数据,再通过Aidl的接口调用里面的接口方法来实现数据对交互。

这块单独放在一个类中,方便后续别的页面调用接口,所以单独摘了出来,放在了AidlProcessUtil类中。

代码语言:javascript
复制
package vac.test.aidldemo

import android.app.ActivityManager
import android.content.ComponentName
import android.content.Context
import android.content.ServiceConnection
import android.os.IBinder
import android.util.Log
import vac.test.aidlservice.ITestDataAidlInterface

object AidlProcessUtil {

    private var aidlService: ITestDataAidlInterface? = null
    private var mAidlServiceConnection = object : ServiceConnection {
        override fun onServiceConnected(p0: ComponentName?, p1: IBinder?) {
            Log.i("aidlpkg", "onServiceConnected")
            aidlService = ITestDataAidlInterface.Stub.asInterface(p1)
        }

        override fun onServiceDisconnected(p0: ComponentName?) {
            Log.i("aidlpkg", "onServiceDisconnected")
            aidlService = null
        }

    }

    fun getAidlService():ITestDataAidlInterface?{
        return aidlService
    }

    fun getAidlServiceConnection():ServiceConnection{
        return mAidlServiceConnection
    }




    //获取当前进程名
    fun getCurrentProcessName(context:Context):String?{
        val pid = android.os.Process.myPid()
        val am = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager

        val runApps = am.runningAppProcesses
        if(runApps.isEmpty()) return null

        for(procinfo in runApps){
            if(procinfo.pid == pid){
                return procinfo.processName
            }
        }
        return null
    }
}

而MainActivity中的核心主要是绑定Seivice,实现调用,核心的方法如下:

代码语言:javascript
复制
    private fun bindAidlService() {
        val intent = Intent()
        // AIDLService中的包名
        val pkg = "vac.test.aidlservice"
        // AIDLService中定义的action
        val act = "AIDL_SERVICE"
        val cls = "vac.test.aidlservice.AidlService"
        intent.action = act
        intent.setClassName(pkg, cls)

        val aidlserviceconnect = AidlProcessUtil.getAidlServiceConnection()

        val bl = bindService(intent, aidlserviceconnect, BIND_AUTO_CREATE)
        Log.i("aidlpkg", "bindservice ${bl}")
        if (!bl) {
            Snackbar.make(
                binding.recyclerView,
                "AIDL_SERVICEE服务绑定失败,请检查是否安装服务程序",
                Snackbar.LENGTH_INDEFINITE
            )
                .setAction("关闭") {
                    Toast.makeText(this, "点击关闭按钮", Toast.LENGTH_SHORT).show()
                }
                .show()
        }
    }

调用接口方法返回数据

Adapter的实现这里就不再列出来的了,源码会在最后列出地址来。

04

AndroidManifest及build.gradle设置

高版本的Android使用AIDL通信,需要在AndroidManifest中加入queries请求,否则无法实现

代码语言:javascript
复制
    <queries>
        <!--如果想要与其他的应用进行AIDL通信的话,需要在这里注册包名的信息-->
        <!--谷歌的文档说明 https://developer.android.google.cn/guide/topics/manifest/queries-element?hl=en-->
        <package android:name="vac.test.aidlservice" />
    </queries> 

在Build.gradle中也要加入aidl的设置,用到了viewbinding,这两个设置是在一想的,同时引用了basequickadapter。

这样,使用AIDL多进程通讯的Demo就实现了。

实现效果

源码地址

https://github.com/Vaccae/AndroidAIDLDemo.git

点击原文链接可以看到“码云”的源码地址

往期精彩回顾

Android BlueToothBLE入门(三)——数据的分包发送和接收(源码已更新)

Android BlueToothBLE入门(二)——设备的连接和通讯(附Demo源码地址)

Android BlueToothBLE入门(一)——低功耗蓝牙介绍

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-08-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 微卡智享 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • AIDL服务端
  • AIDL客户端
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档