目录
- 前言
- AIDL
- AIDL示例
- 客户端
- 运行日志
- AIDL通信过程分析
- bindService流程分析
前言
众所周知,Android进程间通信采用的是Binder机制。Binder是Android系统 独有的进程间通信方式,它是采用mmp函数将进程的用户空间与内核空间的一块内存区域进行映射,免去了一次数据拷贝,相比Linux上的传统IPC具有高效、安全的优点。本文结合AIDL与bindService函数,在Android体系的应用层和Framework层,对Binder通信进行深入剖析,以加深对Binder的了解。
AIDL
AIDL是Android接口描述语言,它也是一个工具,能帮助我们自动生成进程间通信的代码,省去了很多工作。既然它是一个工具,其实也不是必需的。笔者结合AIDL生成的代码进行剖析,分析它生成的代码是如何进程IPC通信的。
AIDL示例
服务端
创建PersonController.aidl文件:
在src目录包名com.devnn.libservice上右键点击创建AIDL文件并全名为PersonController,就会在aidl目录下创建同名的aidl文件。
// PersonController.aidl | |
package com.devnn.libservice; | |
import com.devnn.libservice.Person; | |
// Declare any non-default types here with import statements | |
interface PersonController { | |
/** | |
* Demonstrates some basic types that you can use as parameters | |
* and return values in AIDL. | |
*/ | |
List<Person> getPersons(); | |
void addPerson(inout Person person); | |
} |
形参上的inout修饰表示数据可以发送到服务端进程,而且服务端进程对数据的修改,也会同步到客户端进程。还有另外两个方式in和out,表示单向的传输。当使用in时,服务端对person对象的修改,客户端是无法感知的。当使用out时,客户端发送过去的字段是空的,返回的数据是服务端的。
创建Person.aidl文件:
// Person.aidl | |
package com.devnn.libservice; | |
// Declare any non-default types here with import statements | |
parcelable Person; |
创建Person.java文件,Person类需要实现Parcelable接口:
package com.devnn.libservice | |
import android.os.Parcel | |
import android.os.Parcelable | |
class Person(var name: String?, var age: Int) : Parcelable { | |
constructor(parcel: Parcel) : this(parcel.readString(), parcel.readInt()) { | |
} | |
/** | |
* 字段写入顺序和读取顺序要保持一致 | |
*/ | |
override fun writeToParcel(parcel: Parcel, flags: Int) { | |
parcel.writeString(name) | |
parcel.writeInt(age) | |
} | |
/** | |
* readFromParcel不是必需的,在aidl文件中函数参数类型是inout时需要。 | |
*/ | |
fun readFromParcel(parcel: Parcel) { | |
name = parcel.readString() | |
age = parcel.readInt() | |
} | |
/** | |
* 默认即可 | |
*/ | |
override fun describeContents(): Int { | |
return | |
} | |
companion object CREATOR : Parcelable.Creator<Person> { | |
override fun createFromParcel(parcel: Parcel): Person { | |
return Person(parcel) | |
} | |
override fun newArray(size: Int): Array<Person?> { | |
return arrayOfNulls(size) | |
} | |
} | |
} |
创建MyService类继承Service类:
package com.devnn.libservice | |
import android.app.Application | |
import android.app.Service | |
import android.content.Intent | |
import android.os.Binder | |
import android.os.IBinder | |
import android.os.Build | |
import android.util.Log | |
import java.util.ArrayList | |
class MyService : Service() { | |
override fun onCreate() { | |
super.onCreate() | |
Log.d("MyService", "onCreate") | |
/** | |
* 为了证明是在新进程中运行,将进程名打印出来 | |
* 在android设备上运行的。 | |
*/ | |
if (Build.VERSION.SDK_INT >=) { | |
val processName = Application.getProcessName() | |
Log.d("MyService", "processName=$processName") | |
} | |
} | |
override fun onBind(intent: Intent): IBinder? { | |
Log.d("MyService", "onBind") | |
return binder | |
} | |
private val binder: Binder = object : PersonController.Stub() { | |
override fun getPersons(): List<Person> { | |
Log.d("MyService", "getPersons") | |
val list: MutableList<Person> = ArrayList() | |
list.add(Person("张三",)) | |
list.add(Person("李四",)) | |
return list | |
} | |
override fun addPerson(person: Person) { | |
Log.d("MyService", "addPerson") | |
Log.d("MyService", "name=${person.name},age=${person.age}") | |
} | |
} | |
} |
为了让MyService在独立进程运行,在Manifest声明时需要注明是新进程:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" | |
package="com.devnn.libservice"> | |
<application> | |
<service | |
android:name=".MyService" | |
android:process="com.devnn.libservice.MyService"></service> | |
</application> | |
</manifest> |
android:process=“com.devnn.libservice.MyService” 表示是独立进程,用冒号开头就示是子进程或叫私有进程,不用冒号开头表示是独立进程。注意进程名中间不能用冒号,比如这种就不行:com.devnn.xxx:MyService。
以上代码是在单独的libservice module中编写的,结构如下:
客户端
在appmodule中创建客户端代码,命名为ClientActivity,只有三个按钮,id分别btn1、btn2、btn3,功能分别对应bindService、getPersons、addPersons,代码如下:
package com.devnn.demo | |
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.util.Log | |
import androidx.appcompat.app.AppCompatActivity | |
import com.devnn.libservice.Person | |
import com.devnn.libservice.PersonController | |
import com.devnn.demo.databinding.ActivityClientBinding | |
class ClientActivity : AppCompatActivity() { | |
private val binding by lazy { | |
ActivityClientBinding.inflate(this.layoutInflater) | |
} | |
private lateinit var personController: PersonController | |
override fun onCreate(savedInstanceState: Bundle?) { | |
super.onCreate(savedInstanceState) | |
setContentView(binding.root) | |
//bindService | |
binding.btn.setOnClickListener { | |
val intent = Intent().apply { | |
component = | |
ComponentName(this@ClientActivity.packageName, "com.devnn.libservice.MyService") | |
} | |
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE) | |
} | |
//getPersons | |
binding.btn.setOnClickListener { | |
personController?.let { | |
val list = it.persons; | |
list?.map { | |
Log.i("ClientActivity", "person:name=${it.name},age=${it.age}") | |
} | |
} | |
} | |
//addPerson | |
binding.btn.setOnClickListener { | |
personController?.let { | |
val person = Person("王五",) | |
it.addPerson(person) | |
} | |
} | |
} | |
private val serviceConnection = object : ServiceConnection { | |
override fun onServiceConnected(name: ComponentName?, service: IBinder?) { | |
Log.i("ClientActivity", "onServiceConnected") | |
personController = PersonController.Stub.asInterface(service) | |
} | |
override fun onServiceDisconnected(name: ComponentName?) { | |
Log.i("ClientActivity", "onServiceDisconnected") | |
} | |
} | |
} |
运行界面如下:
客户端与服务端进程的aidl文件要保持一致,可以将服务端的aidl拷贝到客户端。如果客户端在单独module,并且依赖了service所在的module,也可以不用拷贝aidl。
运行日志
运行后,点击bindService按钮输出日志如下:
在Logcat查看进程列表也出现了2个:
点击getPersons按钮,输出日志如下:
可以看到,客户端进程可以正常获取服务端进程的数据。
点击addPerson按钮,输出日志如下:
可以看到,服务端进程可以正常接收客户端进程的发来的数据。
以上是AIDL使用的一个例子,下面分析AIDI通信的过程。
AIDL通信过程分析
项目build后就会自动在build目录下生成对应的Java代码:
PersonController.java代码如下:
/* | |
* This file is auto-generated. DO NOT MODIFY. | |
*/ | |
package com.devnn.libservice; | |
// Declare any non-default types here with import statements | |
public interface PersonController extends android.os.IInterface | |
{ | |
/** Default implementation for PersonController. */ | |
public static class Default implements com.devnn.libservice.PersonController | |
{ | |
/** | |
* Demonstrates some basic types that you can use as parameters | |
* and return values in AIDL. | |
*/ | |
public java.util.List<com.devnn.libservice.Person> getPersons() throws android.os.RemoteException | |
{ | |
return null; | |
} | |
public void addPerson(com.devnn.libservice.Person person) throws android.os.RemoteException | |
{ | |
} | |
public android.os.IBinder asBinder() { | |
return null; | |
} | |
} | |
/** Local-side IPC implementation stub class. */ | |
public static abstract class Stub extends android.os.Binder implements com.devnn.libservice.PersonController | |
{ | |
private static final java.lang.String DESCRIPTOR = "com.devnn.libservice.PersonController"; | |
/** Construct the stub at attach it to the interface. */ | |
public Stub() | |
{ | |
this.attachInterface(this, DESCRIPTOR); | |
} | |
/** | |
* Cast an IBinder object into an com.devnn.libservice.PersonController interface, | |
* generating a proxy if needed. | |
*/ | |
public static com.devnn.libservice.PersonController asInterface(android.os.IBinder obj) | |
{ | |
if ((obj==null)) { | |
return null; | |
} | |
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); | |
if (((iin!=null)&&(iin instanceof com.devnn.libservice.PersonController))) { | |
return ((com.devnn.libservice.PersonController)iin); | |
} | |
return new com.devnn.libservice.PersonController.Stub.Proxy(obj); | |
} | |
public android.os.IBinder asBinder() | |
{ | |
return this; | |
} | |
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException | |
{ | |
java.lang.String descriptor = DESCRIPTOR; | |
switch (code) | |
{ | |
case INTERFACE_TRANSACTION: | |
{ | |
reply.writeString(descriptor); | |
return true; | |
} | |
case TRANSACTION_getPersons: | |
{ | |
data.enforceInterface(descriptor); | |
java.util.List<com.devnn.libservice.Person> _result = this.getPersons(); | |
reply.writeNoException(); | |
reply.writeTypedList(_result); | |
return true; | |
} | |
case TRANSACTION_addPerson: | |
{ | |
data.enforceInterface(descriptor); | |
com.devnn.libservice.Person _arg; | |
if ((!=data.readInt())) { | |
_arg = com.devnn.libservice.Person.CREATOR.createFromParcel(data); | |
} | |
else { | |
_arg = null; | |
} | |
this.addPerson(_arg); | |
reply.writeNoException(); | |
if ((_arg!=null)) { | |
reply.writeInt(); | |
_arg.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE); | |
} | |
else { | |
reply.writeInt(); | |
} | |
return true; | |
} | |
default: | |
{ | |
return super.onTransact(code, data, reply, flags); | |
} | |
} | |
} | |
private static class Proxy implements com.devnn.libservice.PersonController | |
{ | |
private android.os.IBinder mRemote; | |
Proxy(android.os.IBinder remote) | |
{ | |
mRemote = remote; | |
} | |
public android.os.IBinder asBinder() | |
{ | |
return mRemote; | |
} | |
public java.lang.String getInterfaceDescriptor() | |
{ | |
return DESCRIPTOR; | |
} | |
/** | |
* Demonstrates some basic types that you can use as parameters | |
* and return values in AIDL. | |
*/ | |
public java.util.List<com.devnn.libservice.Person> getPersons() throws android.os.RemoteException | |
{ | |
android.os.Parcel _data = android.os.Parcel.obtain(); | |
android.os.Parcel _reply = android.os.Parcel.obtain(); | |
java.util.List<com.devnn.libservice.Person> _result; | |
try { | |
_data.writeInterfaceToken(DESCRIPTOR); | |
boolean _status = mRemote.transact(Stub.TRANSACTION_getPersons, _data, _reply,); | |
if (!_status && getDefaultImpl() != null) { | |
return getDefaultImpl().getPersons(); | |
} | |
_reply.readException(); | |
_result = _reply.createTypedArrayList(com.devnn.libservice.Person.CREATOR); | |
} | |
finally { | |
_reply.recycle(); | |
_data.recycle(); | |
} | |
return _result; | |
} | |
public void addPerson(com.devnn.libservice.Person person) throws android.os.RemoteException | |
{ | |
android.os.Parcel _data = android.os.Parcel.obtain(); | |
android.os.Parcel _reply = android.os.Parcel.obtain(); | |
try { | |
_data.writeInterfaceToken(DESCRIPTOR); | |
if ((person!=null)) { | |
_data.writeInt(); | |
person.writeToParcel(_data,); | |
} | |
else { | |
_data.writeInt(); | |
} | |
boolean _status = mRemote.transact(Stub.TRANSACTION_addPerson, _data, _reply,); | |
if (!_status && getDefaultImpl() != null) { | |
getDefaultImpl().addPerson(person); | |
return; | |
} | |
_reply.readException(); | |
if ((!=_reply.readInt())) { | |
person.readFromParcel(_reply); | |
} | |
} | |
finally { | |
_reply.recycle(); | |
_data.recycle(); | |
} | |
} | |
public static com.devnn.libservice.PersonController sDefaultImpl; | |
} | |
static final int TRANSACTION_getPersons = (android.os.IBinder.FIRST_CALL_TRANSACTION +); | |
static final int TRANSACTION_addPerson = (android.os.IBinder.FIRST_CALL_TRANSACTION +); | |
public static boolean setDefaultImpl(com.devnn.libservice.PersonController impl) { | |
// Only one user of this interface can use this function | |
// at a time. This is a heuristic to detect if two different | |
// users in the same process use this function. | |
if (Stub.Proxy.sDefaultImpl != null) { | |
throw new IllegalStateException("setDefaultImpl() called twice"); | |
} | |
if (impl != null) { | |
Stub.Proxy.sDefaultImpl = impl; | |
return true; | |
} | |
return false; | |
} | |
public static com.devnn.libservice.PersonController getDefaultImpl() { | |
return Stub.Proxy.sDefaultImpl; | |
} | |
} | |
/** | |
* Demonstrates some basic types that you can use as parameters | |
* and return values in AIDL. | |
*/ | |
public java.util.List<com.devnn.libservice.Person> getPersons() throws android.os.RemoteException; | |
public void addPerson(com.devnn.libservice.Person person) throws android.os.RemoteException; | |
} |
仔细观察会发现,这个接口有个Stub静态抽象内部类,里面有一个asInterface方法、onTransact方法,Proxy静态内部类是我们要关注的。
客户端进程调用 PersonController.Stub.asInterface(service)这个代码实际上就返回了Proxy这个代理对象,当调用getPersons和addPerson方法时,也相当于调用Proxy代理类里的对应的方法。
拿addPerson方法举例,这个方法实际上把JavaBean对象转成了Pacel类型的对象,然后调用IBinder的transact方法开始进程间通信。
addPerson方法进程间通信调用的方法如下:
mRemote.transact(Stub.TRANSACTION_getPersons, _data, _reply, 0);
mRemote就是通过binderService获取到的服务端进程的IBinder对象。
第一个参数Stub.TRANSACTION_getPersons表示要调用的方法ID。
第二个参数_data就是要发送给服务端的数据
第三个参数_reply就是服务端返给客户端的数据。
第四个参数0表示是同步的,需要等待服务端返回数据,1表示异步不需要等待服务端返回数据。
整体来说Proxy这个类就是给客户端使用的代码。
Stub这个类是给服务端使用的代码。
注意:谁主动发起通信谁就是客户端,接收通信是服务端。有时候一个进程同时充当客户端和服务端,比如app与ams的通信,app既充当客户端也充当服务端。
Stub类的onTransact方法就是服务端被调用时的回调函数。
boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)
第一个参数code表示调用的方法ID
第二个参数data表示调用方发来的数据
第三个参数reply表示服务端需要回传的数据
第四个参数flags表示同步还是异步。
注意这个onTransact方法是在binder服务端的binder线程池被调用的,也就是在子线程调用的。所以上面的MyService里的getPersons和addPersons方法是在子线程里运行的,如果需要与主线程通信还得使用Handler。
经过以上分析,可以发现进程间通信实际上是拿到对方的IBinder引用后,通过调用IBinder的transact方法发送和接收Parcel类型数据进行通信。AIDL实际上是简化了Binder调用的过程,帮助我们自动生成了通信代码。我们也可以根据需要,自己编写相关代码通信。
那么客户端调用bindServcie方法是如何拿到服务端进程的IBinder对象呢?这部分涉及到android framewok层,这里把它大致流程分析一下。
bindService流程分析
调用bindService方法时,实际是调用ContextWrapper的bindService方法,Activity是继承于ContextWrapper。下面基于Android 10的源码,用流程图表示这个调用链。
整体来说,客户端进程需要与服务端进程通信,先要获取服务端的binder对象,这中间需要经过AMS(Activity Manager Service)服务作为中介。先向AMS发起请求(bindService,携带ServiceConnection对象),AMS再跟服务端进程通信,服务端进程把binder给到AMS,AMS再通过ServiceConnection的onServiceConnected回调把binder发送给客户端进程。客户端获取到binder就可以调用transact方法发送数据。
OK,关于AIDL和Binder进程间通信就介绍到这了。