当前位置: 首页 > news >正文

【Android】Android Binder进程间通信AIDL示例与源码分析

前言

众所周知,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 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)
        }
    }
}

创建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 33设备上运行的。
         */
        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
    }

    companion object {
        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在独立进程运行,在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分别btn1btn2btn3,功能分别对应bindServicegetPersonsaddPersons,代码如下:

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")
        }

    }
}

运行界面如下:
在这里插入图片描述
客户端与服务端进程的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.
         */
    @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;
}

仔细观察会发现,这个接口有个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类的onTransact方法就是服务端被调用时的回调函数。
boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)

第一个参数code表示调用的方法ID
第二个参数data表示调用方发来的数据
第三个参数reply表示服务端需要回传的数据
第四个参数flags表示同步还是异步。

经过以上分析,可以发现进程间通信实际上是拿到对方的IBinder引用后,先将Java对象转化成Parcel类型的数据,通过调用IBinder的transact方法发送和接收Parcel类型数据进行通信。AIDL实际上是简化了Binder调用的过程,帮助我们自动生成了通信代码。我们也可以根据需要,自己编写相关代码通信。

那么客户端调用bindServcie方法是如何拿到服务端进程的IBinder对象呢?这部分涉及到android framewok层,需要读者自行查看framework源码,这里把它大致调用过程分析一下。

bindService流程分析

调用bindService方法时,实际是调用ContextWrapper的bindService方法,Activity是继承于ContextWrapper。下面基于Android 29的源码,用流程图表示这个调用链。

Created with Raphaël 2.3.0 activity.bindService ContextWrapper.bindService ContextImpl.bindService ContextImpl.bindServiceCommon(从ServiceManager获取AMS) ActivityManagerService.bindIsolatedService(到了AMS进程中) ActiveServices.bindServiceLocked(AMS进程中) ActiveServices.requestServiceBindingLocked(AMS进程中) ServiceRecord.ProcessRecord.IApplicationThread.scheduleBindService(这里回到了客户端进程的ActivityThread中) 注意:ApplicationThread是IApplicationThread实现类且是ActivityThread内部类 sendMessage(H.BIND_SERVICE, s); 发送handler消息给客户端主线程处理服务绑定 ActivityThread.handleBindService 获取对应service,执行service.onBind回调。ActivityThread源代码: IBinder binder = s.onBind(data.intent); ActivityManager.getService().publishService( data.token, data.intent, binder); 这里将服务端的binder又通过AMS发送了出去,AMS里将这个binder通过客户端的ServiceConnection回调了过去。 ActivityManagerService.publishService ActiveServices.publishServiceLocked 找到客户端的ServiceConnection执行onServiceConnected回调。 ServiceConnection.onServiceConnected:客户端获取到了服务端的IBinder对象,可以通信了

bindService的大致流程就是这样。

可以看到客户端获取服务端的Binder对象,经过了一系列的交互。客户端与AMS的交互,AMS与服务端进程的交互,AMS与客户端进程的交互。

以上复杂的流程只能通过流程图大致介绍,详细细节需要读者自行查阅framework源码。

关于AIDL和Binder进程间通信就介绍到这了。

相关文章:

  • ARM学习(12)基于arm架构的嵌入式操作系统理解
  • pytorch利用hook【钩子】获取torch网络每层结构【附代码】
  • 快速了解Nginx的基本介绍
  • 字符串统计:strlen函数的讲解,及其模拟实现
  • Linux——什么是环境变量?
  • 关于软件定时器的一些讨论
  • 睿智的目标检测60——Pytorch搭建YoloV7目标检测平台
  • Vue教程-监听路由ve-router变化,命名视图,路由嵌套,路由参数,路由高亮,router-link,redirect,创建路由,
  • 知识点杂记
  • 微信小程序入门与实战之rpx响应式单位与flex布局
  • @RequestMapping用法详解
  • 【MATLAB教程案例20】关于优化类算法的改进方向探索及matlab仿真对比分析
  • [ vulhub漏洞复现篇 ] Apache Flink目录遍历(CVE-2020-17519)
  • mysql的聚簇索引和非聚簇索引
  • 【React项目】从0搭建项目,项目准备和基础构建
  • $translatePartialLoader加载失败及解决方式
  • CSS 三角实现
  • css布局,左右固定中间自适应实现
  • Git 使用集
  • go append函数以及写入
  • Javascript基础之Array数组API
  • JSDuck 与 AngularJS 融合技巧
  • Less 日常用法
  • pdf文件如何在线转换为jpg图片
  • Shell编程
  • SpringCloud集成分布式事务LCN (一)
  • Sublime text 3 3103 注册码
  • 浮现式设计
  • 工作中总结前端开发流程--vue项目
  • 聊聊springcloud的EurekaClientAutoConfiguration
  • 一个完整Java Web项目背后的密码
  • 好程序员web前端教程分享CSS不同元素margin的计算 ...
  • ​ArcGIS Pro 如何批量删除字段
  • ​iOS实时查看App运行日志
  • ​LeetCode解法汇总2670. 找出不同元素数目差数组
  • #Ubuntu(修改root信息)
  • $(document).ready(function(){}), $().ready(function(){})和$(function(){})三者区别
  • $emit传递多个参数_PPC和MIPS指令集下二进制代码中函数参数个数的识别方法
  • (3)llvm ir转换过程
  • (ISPRS,2023)深度语义-视觉对齐用于zero-shot遥感图像场景分类
  • (Spark3.2.0)Spark SQL 初探: 使用大数据分析2000万KF数据
  • (动手学习深度学习)第13章 计算机视觉---图像增广与微调
  • (六)软件测试分工
  • (论文阅读31/100)Stacked hourglass networks for human pose estimation
  • (十八)用JAVA编写MP3解码器——迷你播放器
  • (未解决)jmeter报错之“请在微信客户端打开链接”
  • (原創) 如何使用ISO C++讀寫BMP圖檔? (C/C++) (Image Processing)
  • ***详解账号泄露:全球约1亿用户已泄露
  • .bat批处理(二):%0 %1——给批处理脚本传递参数
  • .gitignore文件_Git:.gitignore
  • .NET 4.0网络开发入门之旅-- 我在“网” 中央(下)
  • .net MVC中使用angularJs刷新页面数据列表
  • .net6 webapi log4net完整配置使用流程
  • .NetCore 如何动态路由
  • .NET开源项目介绍及资源推荐:数据持久层 (微软MVP写作)