第三方移动支付(银联/菜信等)使用微信/支付宝支付过程中,出现支付宝或者微信支付界面。 从一个APP到另一个APP调用的过程,就需要今天的主角AIDL来实现。

AIDL使用

AIDL(Android 接口定义语言),可以使用它定义一个app作为Client端与另一个app作为Server端通信(IPC)的编程接口,在 Android 中,进程之间无法共享内存(用户空间),不同进程之间的通信一般使用 AIDL 来处理。

aidl用通俗的话阐述,就是定义了一个跨进程的接口,使得客户端app调用,服务端app实现此接口。

aidl使用流程

  • 创建aidl接口
  • 创建客户端app,用于调用aidl接口
  • 创建服务端app,用于实现aidl接口

本文举一个比较常见的例子,仅供参考(Android的系统升级ota包校验的数据更新)。

目前有一个需求,跨进程的数据来更新UI

需要一个app可以直接展示画面,动态接收数据后来更新UI

需要另一个app可以拿到原始更新数据,并且发送动态数据

创建aidl接口

定义一个aidl接口

 1// IDataUpdate.aidl
 2package com.example.aidl;
 3/**
 4 * 这是一个aidl定义接口
 5 * 这个aidl接口主要用来发送数据
 6 */
 7interface IDataUpdate {
 8    //是否开启数据发送,0是不准备发送,1是准备发送
 9    int StartUp();
10    void DialogUpdate(int progress);
11}

默认的aidl是和java目录为同级目录,但是也可以认为修改aidl目录,笔者修改在java下面,/java/com.example.aidl

如果像笔者一样更换路径需要注意的点

1.app/build.gradle中修改sourceSets,如下所示

1sourceSets {
2    main {
3        aidl.srcDirs = ['src/main/']
4    }
5}

2.aidl的client端和aidl的server端需要保证相同的路径

aidl服务端和客户端路径 aidl服务端和客户端路径

笔者使用的as可以在定义完aidl文件之后,通过rebuild会自动生成一个同名的java文件

  1/*
  2 * This file is auto-generated.  DO NOT MODIFY.
  3 */
  4package com.example.aidl;
  5/**
  6 * 这是一个aidl定义接口
  7 * 这个aidl接口主要用来发送数据
  8 */
  9public interface IDataUpdate extends android.os.IInterface
 10{
 11  /** Default implementation for IDataUpdate. */
 12  public static class Default implements com.example.aidl.IDataUpdate
 13  {
 14    //是否开启数据发送,0是不准备发送,1是准备发送
 15
 16    @Override public int StartUp() throws android.os.RemoteException
 17    {
 18      return 0;
 19    }
 20    @Override public void DialogUpdate(int progress) throws android.os.RemoteException
 21    {
 22    }
 23    @Override
 24    public android.os.IBinder asBinder() {
 25      return null;
 26    }
 27  }
 28  /** Local-side IPC implementation stub class. */
 29  public static abstract class Stub extends android.os.Binder implements com.example.aidl.IDataUpdate
 30  {
 31    private static final java.lang.String DESCRIPTOR = "com.example.aidl.IDataUpdate";
 32    /** Construct the stub at attach it to the interface. */
 33    public Stub()
 34    {
 35      this.attachInterface(this, DESCRIPTOR);
 36    }
 37    /**
 38     * Cast an IBinder object into an com.example.aidl.IDataUpdate interface,
 39     * generating a proxy if needed.
 40     */
 41    public static com.example.aidl.IDataUpdate asInterface(android.os.IBinder obj)
 42    {
 43      if ((obj==null)) {
 44        return null;
 45      }
 46      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
 47      if (((iin!=null)&&(iin instanceof com.example.aidl.IDataUpdate))) {
 48        return ((com.example.aidl.IDataUpdate)iin);
 49      }
 50      return new com.example.aidl.IDataUpdate.Stub.Proxy(obj);
 51    }
 52    @Override public android.os.IBinder asBinder()
 53    {
 54      return this;
 55    }
 56    @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
 57    {
 58      java.lang.String descriptor = DESCRIPTOR;
 59      switch (code)
 60      {
 61        case INTERFACE_TRANSACTION:
 62        {
 63          reply.writeString(descriptor);
 64          return true;
 65        }
 66        case TRANSACTION_StartUp:
 67        {
 68          data.enforceInterface(descriptor);
 69          int _result = this.StartUp();
 70          reply.writeNoException();
 71          reply.writeInt(_result);
 72          return true;
 73        }
 74        case TRANSACTION_DialogUpdate:
 75        {
 76          data.enforceInterface(descriptor);
 77          int _arg0;
 78          _arg0 = data.readInt();
 79          this.DialogUpdate(_arg0);
 80          reply.writeNoException();
 81          return true;
 82        }
 83        default:
 84        {
 85          return super.onTransact(code, data, reply, flags);
 86        }
 87      }
 88    }
 89    private static class Proxy implements com.example.aidl.IDataUpdate
 90    {
 91      private android.os.IBinder mRemote;
 92      Proxy(android.os.IBinder remote)
 93      {
 94        mRemote = remote;
 95      }
 96      @Override public android.os.IBinder asBinder()
 97      {
 98        return mRemote;
 99      }
100      public java.lang.String getInterfaceDescriptor()
101      {
102        return DESCRIPTOR;
103      }
104      //是否开启数据发送,0是不准备发送,1是准备发送
105
106      @Override public int StartUp() throws android.os.RemoteException
107      {
108        android.os.Parcel _data = android.os.Parcel.obtain();
109        android.os.Parcel _reply = android.os.Parcel.obtain();
110        int _result;
111        try {
112          _data.writeInterfaceToken(DESCRIPTOR);
113          boolean _status = mRemote.transact(Stub.TRANSACTION_StartUp, _data, _reply, 0);
114          if (!_status && getDefaultImpl() != null) {
115            return getDefaultImpl().StartUp();
116          }
117          _reply.readException();
118          _result = _reply.readInt();
119        }
120        finally {
121          _reply.recycle();
122          _data.recycle();
123        }
124        return _result;
125      }
126      @Override public void DialogUpdate(int progress) throws android.os.RemoteException
127      {
128        android.os.Parcel _data = android.os.Parcel.obtain();
129        android.os.Parcel _reply = android.os.Parcel.obtain();
130        try {
131          _data.writeInterfaceToken(DESCRIPTOR);
132          _data.writeInt(progress);
133          boolean _status = mRemote.transact(Stub.TRANSACTION_DialogUpdate, _data, _reply, 0);
134          if (!_status && getDefaultImpl() != null) {
135            getDefaultImpl().DialogUpdate(progress);
136            return;
137          }
138          _reply.readException();
139        }
140        finally {
141          _reply.recycle();
142          _data.recycle();
143        }
144      }
145      public static com.example.aidl.IDataUpdate sDefaultImpl;
146    }
147    static final int TRANSACTION_StartUp = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
148    static final int TRANSACTION_DialogUpdate = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
149    public static boolean setDefaultImpl(com.example.aidl.IDataUpdate impl) {
150      // Only one user of this interface can use this function
151      // at a time. This is a heuristic to detect if two different
152      // users in the same process use this function.
153      if (Stub.Proxy.sDefaultImpl != null) {
154        throw new IllegalStateException("setDefaultImpl() called twice");
155      }
156      if (impl != null) {
157        Stub.Proxy.sDefaultImpl = impl;
158        return true;
159      }
160      return false;
161    }
162    public static com.example.aidl.IDataUpdate getDefaultImpl() {
163      return Stub.Proxy.sDefaultImpl;
164    }
165  }
166  //是否开启数据发送,0是不准备发送,1是准备发送
167
168  public int StartUp() throws android.os.RemoteException;
169  public void DialogUpdate(int progress) throws android.os.RemoteException;
170}

那么第一步的创建aidl就已经完成了

创建客户端app

跟aidl相关的就是绑定客户端的service

1//主要用于绑定aidlservice,记录服务端的包名和service名
2var aidlIntent: Intent = Intent()
3aidlIntent.setClassName("com.example.myaidlserver", "com.example.myaidlserver.DataUpdateService")
4mContext?.bindService(aidlIntent, mAidlConnect, Context.BIND_AUTO_CREATE)

通过成功连接服务端回调,完成aidl的绑定

 1//这个内部匿名类通过连接服务回调,通知客户端是否可以开始使用aidl
 2private var mAidlConnect: ServiceConnection = object:ServiceConnection{
 3    override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
 4        Log.d(TAG, "->>>mAidlConnect")
 5        mAidlClient = IDataUpdate.Stub.asInterface(service)
 6        //开始使用aidl
 7        Log.d(TAG, "->>>init view")
 8        initView()
 9    }
10
11    override fun onServiceDisconnected(name: ComponentName?) {
12        Log.d(TAG, "->>>mAidl disConnect")
13        //结束使用aidl
14        mAidlClient = null
15    }
16}

使用aidl

 1//通过一个简单的调用来使用aidl
 2var ret = mAidlClient?.StartUp()
 3Log.d(TAG, "->>>sleep 2s")
 4Thread.sleep(2000)
 5if (1 == ret)
 6{
 7    for (i in 0  .. 100)
 8    {
 9        Log.d(TAG, "->>>send data update , date is $i")
10        mAidlClient?.DialogUpdate(i)
11        Thread.sleep(200)//控制发送时间间隔200ms一次
12    }
13}else
14{
15    Log.d(TAG, "->>>init failed")
16}

创建服务端app

跟aidl相关的就是继承一个service用于实现aidl的接口

最后在实现onBind方法中返回aidl接口的对象

 1//一个内部匿名类实现aidl的接口
 2private val dataUpdateBinder: IDataUpdate.Stub  = object : IDataUpdate.Stub(){
 3    override fun StartUp(): Int {
 4        Log.d(TAG, "->>>begin to start")
 5        initView()
 6        if (DialogFlag)
 7        {
 8            Log.d(TAG, "->>>initDialog successful")
 9            return 1
10        }
11        return 0
12    }
13
14    override fun DialogUpdate(progress: Int) {
15        if (!DialogFlag) return
16        Log.d(TAG, "->>>data has update to $progress")
17        dataUpdate(progress)
18    }
19}
1//返回aidl对象
2override fun onBind(intent: Intent): IBinder {
3    return dataUpdateBinder
4}

调式

调试过程中可能会出现的问题

service无法被调用

可能出现原因如下:

首先在manifest中确认service是否具备外部可调用,设置android:exported=“true”

其次Android8不允许创建后台服务的情况下,推荐使用startForegroundService,并且还要注意Notification需要一个ChannelID

 1try {
 2            val CHANNEL_ONE_ID = "com.example.myaidlclient"
 3            val CHANNEL_ONE_NAME = "Channel One"
 4            var notificationChannel: NotificationChannel? = null
 5            notificationChannel = NotificationChannel(
 6                CHANNEL_ONE_ID,
 7                CHANNEL_ONE_NAME, NotificationManager.IMPORTANCE_DEFAULT
 8            )
 9            val manager = (getSystemService(NOTIFICATION_SERVICE) as NotificationManager)!!
10            manager!!.createNotificationChannel(notificationChannel)
11            startForeground(1, NotificationCompat.Builder(this, CHANNEL_ONE_ID).build())
12        } catch (e: Exception) {
13            Log.e(TAG, e.message!!)
14        }

最后排查系统是否存在一个用于回收没有动作或者休眠的service的进程

服务端使用了一个Service和一个Activity的形式,数据更新到Service,无法直接通过UI展示,笔者在这里添加了一个内部接口来实现在Activity的SeekBar UI更新。

  1. 首先将服务端app打开,保持展示UI

  2. 通过adb命令,拉起客户端app进程

    1adb shell am start-foreground-service -n com.example.myaidlclient/.BootService
    
  3. 查看客户端进程是否启动,启动后可直观观察服务端UI展示

具体效果如下

aidl跨进程更新UI效果图 aidl跨进程更新UI效果图

总结

AIDL是一个在需要比较复杂的跨进程/线程通信场合。比如需要用一个Service去处理各种事务,而又需要跟Service能相互调用的场合。

要注意的是AIDL不是线程安全的,如有这方面需要的话就要自己处理好临界情况。

本文客户端代码下载

本文服务端代码下载

参考

[1] 官方文档, Android 接口定义语言 (AIDL), 2019.

[2] 躬行之, Android进阶之AIDL的使用详解, 2018.

[3] handsome黄, Service由浅到深——AIDL的使用方式, 2017.

[4] As新晋小白, [Android] AIDL详解, 2020.

[5] 我是午饭, Android中AIDL的使用详解, 2016.