Android Binder進(jìn)程間通信工具AIDL使用示例深入分析
前言
眾所周知,Android進(jìn)程間通信采用的是Binder機(jī)制。Binder是Android系統(tǒng) 獨(dú)有的進(jìn)程間通信方式,它是采用mmp函數(shù)將進(jìn)程的用戶空間與內(nèi)核空間的一塊內(nèi)存區(qū)域進(jìn)行映射,免去了一次數(shù)據(jù)拷貝,相比Linux上的傳統(tǒng)IPC具有高效、安全的優(yōu)點(diǎn)。本文結(jié)合AIDL與bindService函數(shù),在Android體系的應(yīng)用層和Framework層,對(duì)Binder通信進(jìn)行深入剖析,以加深對(duì)Binder的了解。
AIDL
AIDL是Android接口描述語言,它也是一個(gè)工具,能幫助我們自動(dòng)生成進(jìn)程間通信的代碼,省去了很多工作。既然它是一個(gè)工具,其實(shí)也不是必需的。筆者結(jié)合AIDL生成的代碼進(jìn)行剖析,分析它生成的代碼是如何進(jìn)程IPC通信的。
AIDL示例
服務(wù)端
創(chuàng)建PersonController.aidl文件:
在src目錄包名com.devnn.libservice上右鍵點(diǎn)擊創(chuàng)建AIDL文件并全名為PersonController,就會(huì)在aidl目錄下創(chuàng)建同名的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修飾表示數(shù)據(jù)可以發(fā)送到服務(wù)端進(jìn)程,而且服務(wù)端進(jìn)程對(duì)數(shù)據(jù)的修改,也會(huì)同步到客戶端進(jìn)程。還有另外兩個(gè)方式in和out,表示單向的傳輸。當(dāng)使用in時(shí),服務(wù)端對(duì)person對(duì)象的修改,客戶端是無法感知的。當(dāng)使用out時(shí),客戶端發(fā)送過去的字段是空的,返回的數(shù)據(jù)是服務(wù)端的。
創(chuàng)建Person.aidl文件:
// Person.aidl package com.devnn.libservice; // Declare any non-default types here with import statements parcelable Person;
創(chuàng)建Person.java文件,Person類需要實(shí)現(xiàn)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文件中函數(shù)參數(shù)類型是inout時(shí)需要。
*/
fun readFromParcel(parcel: Parcel) {
name = parcel.readString()
age = parcel.readInt()
}
/**
* 默認(rèn)即可
*/
override fun describeContents(): Int {
return 0
}
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)
}
}
}創(chuàng)建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")
/**
* 為了證明是在新進(jìn)程中運(yùn)行,將進(jìn)程名打印出來
* 在android 33設(shè)備上運(yùn)行的。
*/
if (Build.VERSION.SDK_INT >= 28) {
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("張三", 20))
list.add(Person("李四", 21))
return list
}
override fun addPerson(person: Person) {
Log.d("MyService", "addPerson")
Log.d("MyService", "name=${person.name},age=${person.age}")
}
}
}為了讓MyService在獨(dú)立進(jìn)程運(yùn)行,在Manifest聲明時(shí)需要注明是新進(jìn)程:
<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” 表示是獨(dú)立進(jìn)程,用冒號(hào)開頭就示是子進(jìn)程或叫私有進(jìn)程,不用冒號(hào)開頭表示是獨(dú)立進(jìn)程。注意進(jìn)程名中間不能用冒號(hào),比如這種就不行:com.devnn.xxx:MyService。
以上代碼是在單獨(dú)的libservice module中編寫的,結(jié)構(gòu)如下:

客戶端
在appmodule中創(chuàng)建客戶端代碼,命名為ClientActivity,只有三個(gè)按鈕,id分別btn1、btn2、btn3,功能分別對(duì)應(yīng)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.btn1.setOnClickListener {
val intent = Intent().apply {
component =
ComponentName(this@ClientActivity.packageName, "com.devnn.libservice.MyService")
}
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)
}
//getPersons
binding.btn2.setOnClickListener {
personController?.let {
val list = it.persons;
list?.map {
Log.i("ClientActivity", "person:name=${it.name},age=${it.age}")
}
}
}
//addPerson
binding.btn3.setOnClickListener {
personController?.let {
val person = Person("王五", 22)
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")
}
}
}運(yùn)行界面如下:

客戶端與服務(wù)端進(jìn)程的aidl文件要保持一致,可以將服務(wù)端的aidl拷貝到客戶端。如果客戶端在單獨(dú)module,并且依賴了service所在的module,也可以不用拷貝aidl。
運(yùn)行日志
運(yùn)行后,點(diǎn)擊bindService按鈕輸出日志如下:

在Logcat查看進(jìn)程列表也出現(xiàn)了2個(gè):

點(diǎn)擊getPersons按鈕,輸出日志如下:

可以看到,客戶端進(jìn)程可以正常獲取服務(wù)端進(jìn)程的數(shù)據(jù)。
點(diǎn)擊addPerson按鈕,輸出日志如下:

可以看到,服務(wù)端進(jìn)程可以正常接收客戶端進(jìn)程的發(fā)來的數(shù)據(jù)。
以上是AIDL使用的一個(gè)例子,下面分析AIDI通信的過程。
AIDL通信過程分析
項(xiàng)目build后就會(huì)自動(dòng)在build目錄下生成對(duì)應(yīng)的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.
*/
@Override public java.util.List<com.devnn.libservice.Person> getPersons() throws android.os.RemoteException
{
return null;
}
@Override public void addPerson(com.devnn.libservice.Person person) throws android.os.RemoteException
{
}
@Override
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);
}
@Override public android.os.IBinder asBinder()
{
return this;
}
@Override 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 _arg0;
if ((0!=data.readInt())) {
_arg0 = com.devnn.libservice.Person.CREATOR.createFromParcel(data);
}
else {
_arg0 = null;
}
this.addPerson(_arg0);
reply.writeNoException();
if ((_arg0!=null)) {
reply.writeInt(1);
_arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
}
else {
reply.writeInt(0);
}
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;
}
@Override 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.
*/
@Override 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, 0);
if (!_status && getDefaultImpl() != null) {
return getDefaultImpl().getPersons();
}
_reply.readException();
_result = _reply.createTypedArrayList(com.devnn.libservice.Person.CREATOR);
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override 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(1);
person.writeToParcel(_data, 0);
}
else {
_data.writeInt(0);
}
boolean _status = mRemote.transact(Stub.TRANSACTION_addPerson, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
getDefaultImpl().addPerson(person);
return;
}
_reply.readException();
if ((0!=_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 + 0);
static final int TRANSACTION_addPerson = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
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;
}仔細(xì)觀察會(huì)發(fā)現(xiàn),這個(gè)接口有個(gè)Stub靜態(tài)抽象內(nèi)部類,里面有一個(gè)asInterface方法、onTransact方法,Proxy靜態(tài)內(nèi)部類是我們要關(guān)注的。
客戶端進(jìn)程調(diào)用 PersonController.Stub.asInterface(service)這個(gè)代碼實(shí)際上就返回了Proxy這個(gè)代理對(duì)象,當(dāng)調(diào)用getPersons和addPerson方法時(shí),也相當(dāng)于調(diào)用Proxy代理類里的對(duì)應(yīng)的方法。
拿addPerson方法舉例,這個(gè)方法實(shí)際上把JavaBean對(duì)象轉(zhuǎn)成了Pacel類型的對(duì)象,然后調(diào)用IBinder的transact方法開始進(jìn)程間通信。
addPerson方法進(jìn)程間通信調(diào)用的方法如下:
mRemote.transact(Stub.TRANSACTION_getPersons, _data, _reply, 0);
mRemote就是通過binderService獲取到的服務(wù)端進(jìn)程的IBinder對(duì)象。
第一個(gè)參數(shù)Stub.TRANSACTION_getPersons表示要調(diào)用的方法ID。
第二個(gè)參數(shù)_data就是要發(fā)送給服務(wù)端的數(shù)據(jù)
第三個(gè)參數(shù)_reply就是服務(wù)端返給客戶端的數(shù)據(jù)。
第四個(gè)參數(shù)0表示是同步的,需要等待服務(wù)端返回?cái)?shù)據(jù),1表示異步不需要等待服務(wù)端返回?cái)?shù)據(jù)。
整體來說Proxy這個(gè)類就是給客戶端使用的代碼。
Stub這個(gè)類是給服務(wù)端使用的代碼。
注意:誰主動(dòng)發(fā)起通信誰就是客戶端,接收通信是服務(wù)端。有時(shí)候一個(gè)進(jìn)程同時(shí)充當(dāng)客戶端和服務(wù)端,比如app與ams的通信,app既充當(dāng)客戶端也充當(dāng)服務(wù)端。
Stub類的onTransact方法就是服務(wù)端被調(diào)用時(shí)的回調(diào)函數(shù)。
boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)
第一個(gè)參數(shù)code表示調(diào)用的方法ID
第二個(gè)參數(shù)data表示調(diào)用方發(fā)來的數(shù)據(jù)
第三個(gè)參數(shù)reply表示服務(wù)端需要回傳的數(shù)據(jù)
第四個(gè)參數(shù)flags表示同步還是異步。
注意這個(gè)onTransact方法是在binder服務(wù)端的binder線程池被調(diào)用的,也就是在子線程調(diào)用的。所以上面的MyService里的getPersons和addPersons方法是在子線程里運(yùn)行的,如果需要與主線程通信還得使用Handler。
經(jīng)過以上分析,可以發(fā)現(xiàn)進(jìn)程間通信實(shí)際上是拿到對(duì)方的IBinder引用后,通過調(diào)用IBinder的transact方法發(fā)送和接收Parcel類型數(shù)據(jù)進(jìn)行通信。AIDL實(shí)際上是簡化了Binder調(diào)用的過程,幫助我們自動(dòng)生成了通信代碼。我們也可以根據(jù)需要,自己編寫相關(guān)代碼通信。
那么客戶端調(diào)用bindServcie方法是如何拿到服務(wù)端進(jìn)程的IBinder對(duì)象呢?這部分涉及到android framewok層,這里把它大致流程分析一下。
bindService流程分析
調(diào)用bindService方法時(shí),實(shí)際是調(diào)用ContextWrapper的bindService方法,Activity是繼承于ContextWrapper。下面基于Android 10的源碼,用流程圖表示這個(gè)調(diào)用鏈。

整體來說,客戶端進(jìn)程需要與服務(wù)端進(jìn)程通信,先要獲取服務(wù)端的binder對(duì)象,這中間需要經(jīng)過AMS(Activity Manager Service)服務(wù)作為中介。先向AMS發(fā)起請(qǐng)求(bindService,攜帶ServiceConnection對(duì)象),AMS再跟服務(wù)端進(jìn)程通信,服務(wù)端進(jìn)程把binder給到AMS,AMS再通過ServiceConnection的onServiceConnected回調(diào)把binder發(fā)送給客戶端進(jìn)程??蛻舳双@取到binder就可以調(diào)用transact方法發(fā)送數(shù)據(jù)。
OK,關(guān)于AIDL和Binder進(jìn)程間通信就介紹到這了。
到此這篇關(guān)于Android Binder進(jìn)程間通信工具AIDL使用示例深入分析的文章就介紹到這了,更多相關(guān)Android Binder AIDL內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解Android .9.png “點(diǎn)九”圖片的使用
這篇文章主要為大家詳細(xì)介紹了Android .9.png “點(diǎn)九”圖片的使用方法,感興趣的小伙伴們可以參考一下2016-09-09
Android實(shí)現(xiàn)瘋狂連連看游戲之狀態(tài)數(shù)據(jù)模型(三)
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)瘋狂連連看游戲之狀態(tài)數(shù)據(jù)模型,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-03-03
Android實(shí)現(xiàn)斷點(diǎn)多線程下載
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)斷點(diǎn)多線程下載,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-12-12
android中寫一個(gè)內(nèi)部類來選擇文件夾中指定的圖片類型實(shí)例說明
選擇文件夾中指定的圖片類型,本類是用來選擇文件夾中是.jpg類型的圖片具體實(shí)現(xiàn)如下,感興趣的朋友可以參考下哈2013-06-06
簡單仿寫Android控件SlidingMenu的實(shí)例代碼
下面小編就為大家分享一篇簡單仿寫Android控件SlidingMenu的實(shí)例代碼,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-01-01
Android基于Sqlite實(shí)現(xiàn)注冊(cè)和登錄功能
這篇文章主要為大家詳細(xì)介紹了Android基于Sqlite實(shí)現(xiàn)注冊(cè)和登錄功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-04-04
Android應(yīng)用 坐標(biāo)系詳細(xì)介紹
這篇文章主要介紹了 Android 坐標(biāo)系的相關(guān)資料,本文對(duì)Android 坐標(biāo)系的知識(shí)做了詳細(xì)解讀,需要的朋友可以參考下2016-11-11

