code小生,一个专注 Android 领域的技术平台 作者:UsherChen 声明:本文已获 UsherChen
投稿发表,转发等请联系原作者授权
Android Study - 关于AIDL的使用学习 关于Android中多进程的了解
使用AIDL进行多进程通信
自定义代码模拟AIDL的工作流程
其他说明
关于Android中多进程的了解 进程:一般指一个执行单元,在平台上指一个程序或一个应用;
线程:CPU调度的最小单位,也是一种资源,一个应用可包含多个线程;
Android多进程通信通常指两种现象:
一个应用的组件
、、、可在清单文件 AndroidManifest.xml 中指定 android:process 属性名称,一个应用内多个线程通信的现象 多个应用对应多个进程间通信的现象
以上两种现象本质上没有区别,本质上一个进程对应了一块内存区域,彼此之间需要通过一些特殊的方式来进行通信,不可直接通信。
使用AIDL进行多进程通信 上文说道,多进程彼此之间不可直接通信,需借助一些特殊的方式,AIDL是Android系统提供的一种多进程通信的方式。
AIDL:Android Interface Deifinition Laguage 安卓接口定义语言,本质就是一个接口文件,但是是用 .aild 后缀结尾的;
使用AIDL进行进程间通信牵扯到几个类:Service、Binder、IBinder、Intent、Parce 这里先不管这几个类是什么东西,下面会说明;
然后还牵扯到两个概念:服务端与客户端;
从业务层面理解以上元素的作用就是,客户端(含有进程)调用服务端(含有进程)中的方法业务,然后客户端会给一些数据到服务端,服务端再返回一些数据给客户端,如果不需要数据的话就不给或者不返回即可。
看起来似乎有一些抽象, 以下图例模拟一下进程间通信的原理:
两个应用(进程)之间的通信是通过Binder作为桥梁来完成的,Binder是Android提供的一种用于进程通信的方式,这里把它简单理解为一个类即可。
这里模拟两个app来演示AIDL的工作原理,服务端的app提供了三个方法:求和、求最大值、求最小值三个方法;客户端的app需要调用服务端里的这三个方法,并且获取到这三个方法的返回值。
服务端app 我们需要创建一个组件来保持服务常驻后台,并提供业务方法。首先需要创建一个AIDL文件,用来定义需要提供的业务方法,这里在AS中右键单击app module,选择创建一个AIDL文命名为ICalculateAIDL.aidl
// ICalculateAIDL.aidl package com.usherchen.demo.server;interface ICalculateAIDL { // 计算x与y的和 int add (int x, int y) ; // 对比x与y的最大值 int max (int x, int y) ; // 对比x与y的最小值 int min (int x, int y) ; }
系统生成的那个方法直接删掉即可,由于AIDL文件本质是一个接口文件,那么在这里定义三个抽象方法用来在其他代码中实现具体业务。需要注意的是,AIDL文件并不会在AS中出现代码提示,所有的代码需要手动敲上去,代码量比较少。
然后点击Build - Make Project,会发现,在app的build - generated - aidl_source_output_dir - debug 这个路径(路径可能会更长)下有一个 ICalculateAIDL.java 文件。有意思的事情来了,原来点了Make Project以后AS自动帮我们实现了ICalculateAIDL.aidl中的接口,可以看到 ICalculateAIDL.java 这个文件里的代码超级复杂,我们先不管这个文件,继续去编写我们其他的代码。
为了使应用置于后台仍旧可以继续工作,这里我们写一个ICalculateService继承Service用于提供服务:
public class ICalculateService extends Service { private static final String TAG = "usherchen_server" ; /** * 这是一个实现了AIDL接口方法的IBinder实例 */ private ICalculateAIDL.Stub mIBinder = new ICalculateAIDL.Stub() { /** * 实现了x与y求和的逻辑 */ @Override public int add (int x, int y) throws RemoteException { return x + y; } /** * 实现了x与y求最大值的逻辑 */ @Override public int max (int x, int y) throws RemoteException { if (x > y) { return x; } return y; } /** * 实现了x与y求最小值的逻辑 */ @Override public int min (int x, int y) throws RemoteException { if (x < y) { return x; } return y; } }; @Override public void onCreate () { Log.i(TAG, "onCreate: " ); super .onCreate(); } @Override public int onStartCommand (Intent intent, int flags, int startId) { Log.i(TAG, "onStartCommand: " ); return super .onStartCommand(intent, flags, startId); } /** * @param intent intent * @return 这里返回了上面实现了AIDL内部方法的实例 */ @Override public IBinder onBind (Intent intent) { Log.i(TAG, "onBind: " ); return mIBinder; } @Override public void onRebind (Intent intent) { Log.i(TAG, "onRebind: " ); super .onRebind(intent); } @Override public boolean onUnbind (Intent intent) { Log.i(TAG, "onUnbind: " ); return super .onUnbind(intent); } @Override public void onDestroy () { Log.i(TAG, "onDestroy: " ); super .onDestroy(); } }
代码不是很长,首先这里写了一个ICalculateAIDL.Stub的实例并实现了内部的方法。看这三个方法就知道这里是实现AIDL文件中抽象方法的业务的地方。然后我们点击Stub类,可以发现,这个类的类名如下所示:
public static abstract class Stub extends android .os .Binder implements com .usherchen .demo .server .ICalculateAIDL
这里可以看到Stub这个类继承了Binder,而Binder点进去可以看到:
public class Binder implements IBinder
因此Stub也是IBinder的字类,所以可以在Service的onBind方法中直接返回,onBind这个方法返回了一个IBinder的实例,是在启动这个Service的组件中用到的。这里不再解释怎么使用Service。接下来去清单文件中注册ICalculateService,如下所示:
<!--这里注册自定义binder的service用于提供服务--> <service android:name =".service.ICalculateService" tools:ignore ="ExportedService" > <intent-filter > <action android:name ="com.usherchen.demo.aidl.custom" /> <category android:name ="android.intent.category.DEFAULT" /> </intent-filter > </service >
这里一目了然,对CustomICalculateService进行,因为我们需要在另一个app(另一个进程)中启动这个服务,因此这里需要采用隐式启动的方式对其进行属性声明。以上编码完成后,直接在设备跑起来,然后将这个app置于后台即可,接下来进行客户端的代码编写。
客户端app 对于客户端而言,我们需要调用服务端里面的方法,如果凭空什么都没有的话是肯定无法调用另一个app里面的方法的。所以我们需要把服务端的AIDL文件拷贝出来一份,即ICalculateAIDL.aidl这个文件,包含其所在路径,一起拷贝到客户端app的 src - main 文件夹下,然后点击Build - Make Project ,等待完成后,同样在客户端app的build - generated - aidl_source_output_dir - debug(路径可能更长)路径下看到有一个 ICalculateAIDL.java 文件,你会发现这个文件的内容与服务端的 ICalculateAIDL.java 文件中的代码一模一样,不用管他,我们继续写其他的代码。
这里我们写一个页面出来,有两个输入框,三个按钮,页面代码与样式如下:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android ="http://schemas./apk/res/android" xmlns:tools ="http://schemas./tools" android:layout_width ="match_parent" android:layout_height ="match_parent" android:orientation ="vertical" > <EditText android:id ="@+id/et_number_x" android:layout_width ="match_parent" android:layout_height ="wrap_content" android:layout_margin ="10dp" android:background ="@android:drawable/edit_text" android:gravity ="center" android:inputType ="number" tools:ignore ="UnusedAttribute" /> <EditText android:id ="@+id/et_number_y" android:layout_width ="match_parent" android:layout_height ="wrap_content" android:layout_margin ="10dp" android:background ="@android:drawable/edit_text" android:gravity ="center" android:inputType ="number" tools:ignore ="UnusedAttribute" /> <Button android:id ="@+id/btn_add" android:layout_width ="match_parent" android:layout_height ="wrap_content" android:layout_margin ="10dp" android:text ="add" /> <Button android:id ="@+id/btn_max" android:layout_width ="match_parent" android:layout_height ="wrap_content" android:layout_margin ="10dp" android:text ="max" /> <Button android:id ="@+id/btn_min" android:layout_width ="match_parent" android:layout_height ="wrap_content" android:layout_margin ="10dp" android:text ="min" /> </LinearLayout >
然后在Activity里面去编写逻辑:
public class MainActivity extends AppCompatActivity implements View .OnClickListener { private static final String TAG = "usherchen_client" ; private EditText mEtNumberX; private EditText mEtNumberY; private Button mBtnAdd; private Button mBtnMax; private Button mBtnMin; //////////////////////////////////////////////////////////////////////////////// private ICalculateAIDL mICalculateAIDLBinder; private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected (ComponentName name, IBinder service) { mICalculateAIDLBinder = ICalculateAIDL.Stub.asInterface(service); Log.i(TAG, "onServiceConnected: mICalculateAIDLBinder " + mICalculateAIDLBinder.hashCode()); } @Override public void onServiceDisconnected (ComponentName name) { mICalculateAIDLBinder = null ; Log.i(TAG, "onServiceDisconnected: mICalculateAIDLBinder null" ); } }; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_home); mEtNumberX = findViewById(R.id.et_number_x); mEtNumberY = findViewById(R.id.et_number_y); mBtnAdd = findViewById(R.id.btn_add); mBtnMax = findViewById(R.id.btn_max); mBtnMin = findViewById(R.id.btn_min); mBtnAdd.setOnClickListener(this ); mBtnMax.setOnClickListener(this ); mBtnMin.setOnClickListener(this ); intentService(); } private void intentService () { Intent i = new Intent(); i.setPackage("com.usherchen.demo.server" ); i.setAction("com.usherchen.demo.aidl.custom" ); bindService( i, mConnection, BIND_AUTO_CREATE ); } @Override public void onClick (View v) { try { int id = v.getId(); int x = Integer.parseInt(mEtNumberX.getText().toString()); int y = Integer.parseInt(mEtNumberY.getText().toString()); if (id == mBtnAdd.getId()) { Log.i(TAG, "onClick: add" + mICalculateAIDLBinder.add(x, y)); } else if (id == mBtnMax.getId()) { Log.i(TAG, "onClick: max" + mICalculateAIDLBinder.max(x, y)); } else if (id == mBtnMin.getId()) { Log.i(TAG, "onClick: min" + mICalculateAIDLBinder.min(x, y)); } } catch (Exception e) { e.printStackTrace(); } } }
这里的代码也很简单,首先创建一个 ICalculateAIDL 类的实例,然后创建一个ServiceConnection实例去实现内部类的方法,这是使用 bindService 方法去绑定一个服务的前提。当 ServiceConnection 绑定成功的时候,会接收到一个 IBinder 实例,这个实例内部实现了 ICalculateAIDL 的抽象方法,也就是add、max、min这三个方法。
在输入框中输入数字1和2,点击三个按钮可以看到打印的日志结果是3、2、1,说明我们确实是成功的调用了另一个进程中的方法业务,并且获取到了返回值。那么这一过程是怎么实现的,这里需要看一下 ICalculateAIDL.java 这个文件。
总结分析 这个文件的内容很多,我们选择性的来看两个地方,第一个地方是 Stub 字类中的 onTransact 方法:
@Override public boolean onTransact (int code, Parcel data, Parcel reply, int flags) throws RemoteException { String descriptor = DESCRIPTOR; switch (code) { case INTERFACE_TRANSACTION: { reply.writeString(descriptor); return true ; } case TRANSACTION_add: { data.enforceInterface(descriptor); int _arg0; _arg0 = data.readInt(); int _arg1; _arg1 = data.readInt(); int _result = this .add(_arg0, _arg1); reply.writeNoException(); reply.writeInt(_result); return true ; } case TRANSACTION_max: { data.enforceInterface(descriptor); int _arg0; _arg0 = data.readInt(); int _arg1; _arg1 = data.readInt(); int _result = this .max(_arg0, _arg1); reply.writeNoException(); reply.writeInt(_result); return true ; } case TRANSACTION_min: { data.enforceInterface(descriptor); int _arg0; _arg0 = data.readInt(); int _arg1; _arg1 = data.readInt(); int _result = this .min(_arg0, _arg1); reply.writeNoException(); reply.writeInt(_result); return true ; } default : { return super .onTransact(code, data, reply, flags); } } }
这个方法看名字翻译成中文叫做传输,那么它里面肯定作了一些传输性的工作,我们来解释一下这个方法。首先它有四个入参:
同时还有一个 DESCRIPTOR 常量,对于这个常量,我个人对其理解是,用于服务端和客户端对接的一个标识,后面我们自定义代码去实现以上功能的时候会说到。
然后我们选择一个 case TRANSACTION_add 去看一下里面的代码:
data.enforceInterface(descriptor); int _arg0; _arg0 = data.readInt(); int _arg1; _arg1 = data.readInt(); int _result = this .add(_arg0, _arg1); reply.writeNoException(); reply.writeInt(_result);
这里一行行来看,第一行:data.enforceInterface(descriptor); 这里的作用即将 DESCRIPTOR 传入,表示一个认证的动作。
然后下面是定义了两个 int 值 _arg0 _arg1传入了 add 方法里,并返回了一个 _result,然后 reply 执行了两行代码,reply.writeNoException(); 表示这里没有异常发生,然后将 _result 写入,返回出去。
所以,当客户端调用 mICalculateAIDLBinder.add(x, y) 这行代码的时候,本质上是服务端的 ICalculateAIDL.java 的 Stub 的 onTransact 方法执行,然后将返回值递给了客户端。
从以上分析可以看出,数据的传输是集中在了 Stub 类里面的 onTransact 方法里,那么我们是不是也可以自己来写一个Binder来实现进程间的通信呢?
自定义代码模拟AIDL的工作流程 AIDL文件实现服务端app与客户端app通信的根本是Binder,那么我们模拟系统帮我们生成的 ICalculateAIDL.java 文件,自己去写代码模拟一下这个过程。
服务端app 自定义一个新的服务命名为CustomICalculateService,编写代码如下:
public class CustomICalculateService extends Service { private static final String TAG = "usherchen_server" ; @Override public void onCreate () { super .onCreate(); } @Override public IBinder onBind (Intent intent) { Log.i(TAG, "onBind: " ); return mCustomBinder; } @Override public int onStartCommand (Intent intent, int flags, int startId) { return super .onStartCommand(intent, flags, startId); } @Override public void onDestroy () { super .onDestroy(); } @Override public boolean onUnbind (Intent intent) { return super .onUnbind(intent); } @Override public void onRebind (Intent intent) { super .onRebind(intent); } //////////////////////////////////////////////////////////////////////////////// private static final String DESCRIPTOR = "com.usherchen.demo.descriptor" ; /** * 自定义一个binder实例用于提供服务 */ private Binder mCustomBinder = new Binder() { @Override protected boolean onTransact (int code, Parcel data, Parcel reply, int flags) throws RemoteException { switch (code) { case 1 : data.enforceInterface(DESCRIPTOR); int x_1 = data.readInt(); int y_1 = data.readInt(); int s_1 = x_1 + y_1; reply.writeNoException(); reply.writeInt(s_1); return true ; case 2 : data.enforceInterface(DESCRIPTOR); int x_2 = data.readInt(); int y_2 = data.readInt(); int s_2; if (x_2 > y_2) { s_2 = x_2; } else { s_2 = y_2; } reply.writeNoException(); reply.writeInt(s_2); return true ; case 3 : data.enforceInterface(DESCRIPTOR); int x_3 = data.readInt(); int y_3 = data.readInt(); int s_3; if (x_3 < y_3) { s_3 = x_3; } else { s_3 = y_3; } reply.writeNoException(); reply.writeInt(s_3); return true ; default : return super .onTransact(code, data, reply, flags); } } }; }
这里我没有用AIDL去让系统帮我生成java文件,而是直接写了一个Binder示例实现了onTransact方法,然后我们自己定义好 DESCRIPTOR 常量,code定义1 - add、2 - max、3 - min,然后在清单文件中注册
<!--这里注册自定义binder的service用于提供服务--> <service android:name =".service.CustomICalculateService" tools:ignore ="ExportedService" > <intent-filter > <action android:name ="com.usherchen.demo.aidl.custom" /> <category android:name ="android.intent.category.DEFAULT" /> </intent-filter > </service >
服务端的代码编写完毕。
客户端app 这里页面就沿用之前的页面,然后需要对Activity里面的代码进行重新编写,如下所示:
public class MainActivity extends AppCompatActivity implements View .OnClickListener { private static final String TAG = "usherchen_client" ; private EditText mEtNumberX; private EditText mEtNumberY; private Button mBtnAdd; private Button mBtnMax; private Button mBtnMin; //////////////////////////////////////////////////////////////////////////////// private IBinder mIBinder; private ServiceConnection mCustomBinderConnection = new ServiceConnection() { @Override public void onServiceConnected (ComponentName name, IBinder service) { mIBinder = service; } @Override public void onServiceDisconnected (ComponentName name) { mIBinder = null ; } }; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_home); mEtNumberX = findViewById(R.id.et_number_x); mEtNumberY = findViewById(R.id.et_number_y); mBtnAdd = findViewById(R.id.btn_add); mBtnMax = findViewById(R.id.btn_max); mBtnMin = findViewById(R.id.btn_min); mBtnAdd.setOnClickListener(this ); mBtnMax.setOnClickListener(this ); mBtnMin.setOnClickListener(this ); initCustomService(); } private void initCustomService () { Intent i = new Intent(); i.setPackage("com.usherchen.demo.server" ); i.setAction("com.usherchen.demo.aidl.custom" ); bindService( i, mCustomBinderConnection, BIND_AUTO_CREATE ); } @Override public void onClick (View v) { try { int id = v.getId(); int x = Integer.parseInt(mEtNumberX.getText().toString()); int y = Integer.parseInt(mEtNumberY.getText().toString()); if (id == mBtnAdd.getId()) { add(x, y); } else if (id == mBtnMax.getId()) { max(x, y); } else if (id == mBtnMin.getId()) { min(x, y); } } catch (Exception e) { e.printStackTrace(); } } private void showToast (String s) { Toast.makeText(this , s, Toast.LENGTH_SHORT).show(); } private static final String DESCRIPTOR = "com.usherchen.demo.descriptor" ; private void add (int x, int y) { if (mIBinder == null ) { return ; } try { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(DESCRIPTOR); data.writeInt(x); data.writeInt(y); mIBinder.transact(1 , data, reply, 0 ); reply.readException(); int s = reply.readInt(); showToast(String.valueOf(s)); } catch (RemoteException e) { e.printStackTrace(); } } private void max (int x, int y) { if (mIBinder == null ) { return ; } try { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(DESCRIPTOR); data.writeInt(x); data.writeInt(y); mIBinder.transact(2 , data, reply, 0 ); reply.readException(); int s = reply.readInt(); showToast(String.valueOf(s)); } catch (RemoteException e) { e.printStackTrace(); } } private void min (int x, int y) { if (mIBinder == null ) { return ; } try { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(DESCRIPTOR); data.writeInt(x); data.writeInt(y); mIBinder.transact(3 , data, reply, 0 ); reply.readException(); int s = reply.readInt(); showToast(String.valueOf(s)); } catch (RemoteException e) { e.printStackTrace(); } } }
代码稍微有点多,但相比之前的AIDL案例,并没有改动太多。首先创建一个IBinder实例,然后在ServiceConnection里面去获取对象。这个时候,我们去调用add方法,我们来看一下这里面的代码:
private void add (int x, int y) { if (mIBinder == null ) { return ; } try { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(DESCRIPTOR); data.writeInt(x); data.writeInt(y); mIBinder.transact(1 , data, reply, 0 ); reply.readException(); int s = reply.readInt(); showToast(String.valueOf(s)); } catch (RemoteException e) { e.printStackTrace(); } }
首先我们获取到两个序列化对象(关于序列化不懂自行百度),一个data用于将客户端的数据传输出去,一个reply用于获取服务端返回的数据,然后这一行代码 data.writeInterfaceToken(DESCRIPTOR); 对应了AIDL案例中的 data.enforceInterface(DESCRIPTOR); 前文已经说明,这是一种认证行为,不再做过多解释。然后按照应有的顺序执行,不可随意更改代码顺序,否则会造成异常。这里需要说明 mIBinder.transact(1, data, reply, 0); 这行代码执行后,实际上就是调用了服务端的 onTransact 方法,那么我们接下来就可以获取到服务端执行业务后的结果了。
其他说明 另外AIDL需要注意的有以下几点: