AIDL1 使用
1000 Words|Read in about 5 Min|本文总阅读量次
第三方移动支付(银联/菜信等)使用微信/支付宝支付过程中,出现支付宝或者微信支付界面。 从一个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端需要保证相同的路径
笔者使用的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更新。
-
首先将服务端app打开,保持展示UI
-
通过adb命令,拉起客户端app进程
1adb shell am start-foreground-service -n com.example.myaidlclient/.BootService
-
查看客户端进程是否启动,启动后可直观观察服务端UI展示
具体效果如下
总结
AIDL是一个在需要比较复杂的跨进程/线程通信场合。比如需要用一个Service去处理各种事务,而又需要跟Service能相互调用的场合。
要注意的是AIDL不是线程安全的,如有这方面需要的话就要自己处理好临界情况。
参考
[1] 官方文档, Android 接口定义语言 (AIDL), 2019.
[2] 躬行之, Android进阶之AIDL的使用详解, 2018.
[3] handsome黄, Service由浅到深——AIDL的使用方式, 2017.