学更好的别人,
做更好的自己。
——《微卡智享》
本文长度为3130字,预计阅读7分钟
前言
多进程其实在大的APP中越来越多,像微信里面就是,消息接收是单独的进程服务,所以AIDL的跨进程通讯少不了是需要掌握的技能,本篇就是实现一个AIDL跨进程通讯的简单事例,做为一个入门的了解。
AIDL简介
微卡智享
AIDL全名Android Interface Definition Language,目的是为了实现进程间通信,由于不同的进程有着不同的内存区域,并且它们只能访问自己的那一块内存区域,所以我们必须将要传输的数据转化为能够在内存之间流通的形式,通过AIDL进行跨进程通信的时候,选择的序列化方式是实现 Parcelable 接口。
AIDL默认支持的数据类型包括:
AIDL中还有定向的Tag,包括了in、out、inout。其中 in 表示数据只能由客户端流向服务端, out 表示数据只能由服务端流向客户端,而 inout 则表示数据可在服务端与客户端之间双向流通。
代码实现
微卡智享
01
创建AIDL服务
在Android Studio中新建一个应用后,我们先创建一个AIDL的Service,File--New--New Module
02
创建数据类实现Parcelable接口
前面简介中提到过,AIDL数据类通讯需要实现Parcelable接口,为了省去接口实现的代码,Kotlin中通过kotlin-parcelize即可实现了。
在build.gradle的plugins中加入id("kotlin-parcelize")
创建TestData数据类
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中加入一个修改项后才能正常显示。
android {
buildFeatures {
aidl = true
}
}
加入上面代码后,即可正常创建了,所以在app和aidlservice两个module中都加入了这一项。
// 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方法。
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,并且服务启动时要改为前台服务,所以前台服务的权限也要加入
<?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服务端这样就完成了。
01
加入AIDL和数据类
因为客户端和服务端是两个不同的进程,所以客户端也要像服务端一样创建AIDL文件夹,复制对应的 aidl 文件和自定义的数据类,请保证包名保持一致,然后编译一下。
02
客户端布局
主页面一个Recyclerview,两个按钮,一个为获取列表,一个获取第一条数据,Adapter中布局就是显示数据信息。
03
绑定服务
绑定服务最主要的就是创建ServiceConnection,通过ServiceConnecttion返回Aidl的接口数据,再通过Aidl的接口调用里面的接口方法来实现数据对交互。
这块单独放在一个类中,方便后续别的页面调用接口,所以单独摘了出来,放在了AidlProcessUtil类中。
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,实现调用,核心的方法如下:
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请求,否则无法实现
<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入门(三)——数据的分包发送和接收(源码已更新)