Android kotlin语言AIDL使用
AIDL作为Android的一个IPC工具,可以用于进程间通信,进程间通信不仅是不同应用间也可以是同一个应用的不同进程间。Java语言使用AIDL的文档网上有太多,这里不以Java为例,使用Kotlin为例进行讲解。
前言
AIDL英文全写为(Android Interface Definition Language)直译为android接口定义语言。就是用来定义不同进程间通信所用的接口的语言。说白了就是一个帮助我们简化Binder开发的工具,我们按照AIDL的语法进行编写之后进行build操作时SDK中的AIDL.exe会自动为我们在build.generated.source.aidl文件夹中的debug或者release文件夹中生成对应名称的java文件,这个java文件实际使用了Binder机制。此篇文章浅显的讲述Kotlin语言使用AIDL进行进程间通信,至于这个java文件如何编写,我会在下一篇文章中进行讲述。在此感谢**[Android 开发艺术探索]**的作者任玉刚老师对知识的分享。
步骤
- 定义数据实体类
- 创建aidl文件
- make project(build),更改生成的java文件
- 创建服务端服务
- 创建客户端服务
在进行讲述之前我们先看一张最后完成的服务端的整体结构
Person.aidl文件是实体类对应的aidl文件,IMyAidl.aidl文件是生成Java文件的依据文件,Person.kt文件是定义的实体类,MyAidlService.kt文件是服务端服务文件
一、定义数据实体类
Person.kt
package com.example.com.testapplication.bean.kotlin
import android.annotation.SuppressLint
import android.os.Parcelable
import kotlinx.android.parcel.Parcelize
@Parcelize
@SuppressLint("ParcelCreator")
class Person(var name: String) : Parcelable {
override fun toString(): String {
return " [Person name = " + name + " ]"
}
}
随便定义了一个实体类使用注解实现了Parcelable
二、创建aidl文件
在main文件夹下创建aidl文件夹,在其中添加跟项目路径一致的文件夹并在其对应文件夹中创建aidl文件
Person.aidl
// Person.aidl
package com.example.com.testapplication.bean.kotlin;
// Declare any non-default types here with import statements
parcelable Person;
IMyAidl.aidl
// IMyAidl.aidl
package com.example.com.testapplication;
import com.example.com.testapplication.bean.kotlin.Person;
// Declare any non-default types here with import statements
interface IMyAidl {
/**
* 除了基本数据类型,其他类型的参数都需要标上方向类型:in(输入), out(输出), inout(输入输出)
*/
/**
* 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);
void addPerson(in Person person);
List<Person> getPersonList();
}
三、make project(build),更改生成的java文件
在编写完aidl文件之后执行make project或者build之后会提示如下错误
虽然会出现上面的问题,但是需要的Java文件其实已经生成了。
这个错误的产生是因为kotlin的parcelable的实现CREATOR.createFromParcel会将返回类型当成any类型而不是指定类型,主要是因为kotlin的注解序列化机制产生的问题,如果你不是使用的@Parcelize注解进行的序列化那么就不会遇到这个问题,但是会遇到错误: CREATOR可以在Person中访问private,是因为Java与kotlin语言在机制上的冲突引起的。
解决上面的冲突方法也很简单,在本例中就是在生成的Java文件中使用Person.CREATOR.createFromParcel的地方加个强转
(Person) Person.CREATOR.createFromParcel(data)
四、创建服务端服务
在我们改完生成的Java文件后我们需要删除使用的aidl文件并将生成的文件放入对应的文件夹中而不是继续放在build中,因为在我们打包或者运行的时候每次都会检测aidl文件夹并且生成对应的文件,那么步骤三出现的问题就会一直出现。
上面的步骤做完之后我们就可以编写服务端具体逻辑代码了
MyAidlService.kt
package com.example.com.testapplication.service
import android.app.Service
import android.content.Intent
import android.os.IBinder
import com.example.com.testapplication.IMyAidl
import com.example.com.testapplication.bean.kotlin.Person
class MyAidlService : Service() {
val mPersons by lazy { mutableListOf<Person>() }
override fun onBind(intent: Intent?): IBinder = mIBinder
private val mIBinder = object : IMyAidl.Stub() {
override fun addPerson(person: Person?) {
mPersons.add(person!!)
}
override fun getPersonList(): MutableList<Person> {
return mPersons
}
}
}
之后需要在AndroidManifest.xml文件中注册服务。
<service
android:name="com.example.com.testapplication.service.MyAidlService"
android:enabled="true"
android:exported="true"
android:process=":aidl">
<intent-filter>
<action android:name="testapplication.service.MyAidlService" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</service>
至此,服务端的我们就完成了。
五、创建客户端服务
我们假设使用aidl的是为了实现不同应用间的信息传递,那么我们就需要在新创建一个应用,并在这个应用中对应的放入需要的文件。
那么我们都需要什么呢?我们需要同路径下的数据实体类、已经改完的生成Java文件。在此不建议直接导入aidl文件夹,因为还需要重新更改生成的文件还项目增加结构复杂度。
将我们需要的文件都导入之后我们就可以使用bind绑定我们在服务端的服务进行跨进程开发了
另一个程序的MainActivity.kt
package com.aidl.zhang.aidltestapplication
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.os.Bundle
import android.os.IBinder
import android.support.v7.app.AppCompatActivity
import com.example.com.testapplication.IMyAidl
import com.example.com.testapplication.bean.kotlin.Person
import kotlinx.android.synthetic.main.activity_main.*
import java.util.*
class MainActivity : AppCompatActivity() {
private var mAidl: IMyAidl? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initAidlService()
btn_click.setOnClickListener {
mAidl?.addPerson(Person("测试" + Random().nextInt(5)))
val personList = mAidl?.personList
tv_aidl.setText(personList.toString())
}
}
private fun initAidlService() {
val intent = Intent()
intent.action = "testapplication.service.MyAidlService"
intent.`package` = "com.example.com.testapplication"
bindService(intent, MyServiceConnection(), Context.BIND_AUTO_CREATE)
}
inner private class MyServiceConnection : ServiceConnection {
override fun onServiceDisconnected(name: ComponentName?) {
mAidl = null
}
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
mAidl = IMyAidl.Stub.asInterface(service)
}
}
}
至此我们的客户端也开发完了。整体流程走下李,我们知道kotlin语言的aidl开发比java语言的aidl开发多了一个更改生成文件的步骤,其他的都是一样的,而且就代码量来说个人觉得kotlin是真棒。
坑:
- aidl的编写上很容易遇到问题,要注意引入的类路径和类名
- aidl是基于binder内核进行的,也是CS结构的,如果服务端没有启动那么就像网络访问没有启动服务器一样是没有办法进行通信的。