简介
AIDL(Android Interface definition language)是Android中IPC(Inter-Process Communication)方式中的一种,在 Android 中,一个进程通常无法访问另一个进程的内存。因此,为进行通信,进程需将其对象分解成可供操作系统理解的原语,并将其编组为可供您操作的对象。编写执行该编组操作的代码较为繁琐,Android提供了AIDL来方便开发者进行进程间通信。
简单使用
- 准备两个项目
这边是Player(服务端)创建服务提供功能给Browser(客户端)使用
- 服务端:新建aidl文件(注意包名路径)
aidl文件定义了服务端提供了哪些功能给客户端
建好的aidl文件长这样
package com.dean.player;
interface IPlayer {
//aidl传输数据支持类型有:八大基本类型、String、CharSequence、List、Map、实现了Parcelable接口的自定义类型
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
}
- 服务端:点击build下面的Make Project
点击后gradle会根据aidl文件自动生成IPlayer.java 类,这里面有个内部Stub内部类
- 服务端:编写可绑定服务,binder对象需要实现自动生成的IPlayer里面的内部类Stub
package com.dean.player
import android.app.Service
import android.content.Intent
import android.os.IBinder
import android.os.Process
import android.util.Log
import android.widget.Toast
class PlayerService : Service() {
val LOG_TAG = PlayerService::class.java.simpleName
override fun onBind(intent: Intent?): IBinder? {
//将binder提供出去
return binder
}
//继承自Stub,实现里面所有方法来处理客户端请求
private val binder = object : IPlayer.Stub() {
override fun basicTypes(
anInt: Int,
aLong: Long,
aBoolean: Boolean,
aFloat: Float,
aDouble: Double,
aString: String?
) {
Log.d(LOG_TAG, "Pid ${Process.myPid()} receive browser client Data $anInt, $aLong, $aBoolean, $aFloat, $aDouble, $aString")
}
}
}
- 服务端:编写服务别忘了在AndroidManifest中添加
<service android:name=".PlayerService">
<intent-filter>
<action android:name="android.intent.action.PlayerService" />
</intent-filter>
</service>
自此服务端就完成了,下面来写客户端
- 客户端:将服务端的aidl拷贝过来(包括路径),也要点击build的make project,这时就表示可以使用服务端提供的功能了
- 客户端:和绑定服务一样正常使用服务。
package com.dean.browser
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.os.Process
import android.util.Log
import android.view.View
import androidx.databinding.DataBindingUtil
import com.dean.browser.databinding.ActivityMainBinding
import com.dean.player.IPlayer
class MainActivity : AppCompatActivity() {
val LOG_TAG = MainActivity::class.java.simpleName
lateinit var mBinding : ActivityMainBinding
var mIPlayer: IPlayer? = null
val mIPlayerCon = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
mIPlayer = IPlayer.Stub.asInterface(service)
}
override fun onServiceDisconnected(name: ComponentName?) {
mIPlayer = null
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)
mBinding.handleEvent = HandleEvent()
val intent = Intent("android.intent.action.PlayerService")
intent.`package` = "com.dean.player" //服务端项目包名
bindService(intent, mIPlayerCon, Context.BIND_AUTO_CREATE)
}
inner class HandleEvent{
/**
* 向播放器发送播放指令
*/
fun sendPlayerCommand(view: View?){
Log.d(LOG_TAG, "Pid ${Process.myPid()} Browser start send command")
mIPlayer?.basicTypes(1, 2L, true, 3.0F, 5.0, "aa")
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<layout>
<data>
<variable
name="handleEvent"
type="com.dean.browser.MainActivity.HandleEvent" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="发送播放指令"
android:onClick="@{handleEvent::sendPlayerCommand}"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
- 运行:需要先打开服务端项目,再打开客户端,点击按钮
客户端日志:
MainActivity: Pid 1367 Browser start send command
服务端日志:
Pid 830 receive browser client Data 1, 2, true, 3.0, 5.0, aa
传输自定义类
- 服务端:自定义类,并且实现Parcelable接口
package com.dean.player
import android.os.Parcel
import android.os.Parcelable
class Command(key: String, params: HashMap<String, String>) : Parcelable {
var key = key
var params = params
//String类是由最上层的BootStrap ClassLoader来加载的,为了安全上层是无法获取这个ClassLoader的,这边的classloader直接传了HashMap的,因为双亲委托最终还是到BootStrap ClassLoader来找String类。
constructor(parcel: Parcel) : this(parcel.readString() ?: "", parcel.readHashMap(HashMap::class.java.classLoader) as HashMap<String, String>)
companion object CREATOR : Parcelable.Creator<Command> {
/**
* 根据传来parcel构建出Command实例
*/
override fun createFromParcel(parcel: Parcel): Command {
return Command(parcel)
}
override fun newArray(size: Int): Array<Command?> {
return arrayOfNulls(size)
}
}
/**
* dest用于写入参数
* flags有两种值: 1 当前对象需要作为返回值返回,不可立即释放该对象
* 0 其余情况,一般传0
*/
override fun writeToParcel(dest: Parcel?, flags: Int) {
//parcel操作共享内存,在两个进程间传输数据,这边的write顺序需要和上面的read顺序一样
dest?.writeString(key)
dest?.writeMap(params as Map<*, *>?)
}
/**
* 描述当前 Parcelable 实例的对象类型
* 如果对象中有文件描述符,返回Parcelable.CONTENTS_FILE_DESCRIPTOR
* 其他情况会返回一个位掩码,正常返回0即可
*/
override fun describeContents(): Int {
return 0
}
}
- 服务端:为自定义类新建一个aidl:Command.aidl,这边和之前的aidl文件放在一起了
package com.dean.player;
parcelable Command;
- 服务端:在之前的对外暴露的接口增加自定义类相关功能
package com.dean.player;
//需要手动引入自定义类
import com.mh.kotlins.Command;
interface IPlayer {
long getPlayerPosition(int playerID);
void sendCommand(in Command command);
}
- 客户端:将相同的Command类和aidl文件拷贝过来(注意包名),make project,然后就可以使用服务端提供的功能了
inner class HandleEvent{
/**
* 向播放器发送播放指令
*/
fun sendPlayerCommand(view: View?){
Log.d(LOG_TAG, "Pid ${Process.myPid()} Browser start send command")
val params = HashMap<String, String>()
params["a"] = "a"
params["b"] = "b"
params["c"] = "c"
params["d"] = "d"
val command = Command("open", params)
mIPlayer?.sendCommand(command)
}
/**
* 获取当前播放位置
*/
fun getPlayerPosition(view: View?){
Log.d(LOG_TAG, "Pid ${Process.myPid()} Browser start get player position")
Log.d(LOG_TAG, "player ${playerID} current position is ${mIPlayer?.getPlayerPosition(playerID)}")
playerID = playerID.inc()
}
}
- 运行结果
服务端日志打印:
D/PlayerService: 1 player return position 1000
D/PlayerService: 2 player return position 2000
D/PlayerService: 3 player return position 3000
D/PlayerService: 4 player return position 4000
D/PlayerService: 5 player return position 5000
D/PlayerService: 6 player return position 6000
D/PlayerService: handle command key is open
D/PlayerService: command params key is a value is a
D/PlayerService: command params key is b value is b
D/PlayerService: command params key is c value is c
D/PlayerService: command params key is d value is d
注:
- 如果是在一个项目中的并且是单线程(不需要高并发)的可以用Messenger,Messenger相对简单
- aidl支持RPC,如果使用了aidl就要处理好多线程,默认情况下RPC 调用是同步调用,所以不能在Activity 的主线程调用该服务端功能,否则一旦该功能是耗时的就会引起ANR
- AIDL文件可以分为两类。一类用来声明实现了Parcelable接口的数据类型,还有一类是用来定义接口方法,告诉客户端提供了哪些功能
- 定向Tag:分为in、out、inout三种,其中 in 表示数据只能由客户端流向服务端, out 表示数据只能由服务端流向客户端,而 inout 则表示数据可在服务端与客户端之间双向流通。基本数据类型、String、CharSequence或者其他AIDL文件定义的方法接口的定向TAG默认并且必须是in,所以之前都没加,其他类型在使用时必须加上,不然没法传值,比如:
interface Player {
//这边用in,是客户端传给服务端的参数
void addParams(in Map data);
}