背景
这两天开始学习IPC的内容,从AIDL开始
AIDL是安卓接口定义语言的简称,用于进程间通信。现在记录一下使用步骤
步骤
1、建立Person类,实现Parcelable接口
AIDL默认支持的数据类型有:八种基本数据类型、String、List
如果要使用AIDL传递类对象,就必须让类实现Parcelable接口,并且要定义一个方法,名曰readFromParcel
完整的Person代码如下
public class Person implements Parcelable{ private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } public Person() { // 无参构造方法必须写上,原因后面说 } .. get/set + toString() public static final Creator<Person> CREATOR = new Creator<Person>() { @Override public Person createFromParcel(Parcel in) { return new Person(in); } @Override public Person[] newArray(int size) { return new Person[size]; } }; @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(name); dest.writeInt(age); } protected Person(Parcel in) { name = in.readString(); // read和write的顺序最好一致 age = in.readInt(); } public Person readFromParcel(Parcel parcel) { name = parcel.readString(); age = parcel.readInt(); return this; } }
2、在Person类的同一个包下,建立Person.aidl,名字一定要和Person类名一致
这个是为了给aidl引入Person类
// Person.aidl package com.example.songzeceng.studyofipc; // 引入Person序列化类 // IPerson.aidl包名要和Person.java包名一样 parcelable Person;
3、也是在这个包下,建立负责IPC的aidl文件
先贴代码
// IPersonManagerInterface.aidl package com.example.songzeceng.studyofipc; import com.example.songzeceng.studyofipc.Person; // 即便是在同一包下,也要手动导入Person类 // Declare any non-default types here with import statements interface IPersonManagerInterface { List<Person> getPeople(); void addPerson(in Person person); Person updatePerson(out Person person); Person updatePerson2(inout Person person); }
aidl方法传入自定义类对象,in、out、inout必须写(aidl默认支持的类型不用写,默认且只能是in),否则报错。
关于参数前的in、out和inout,跨进程时,in参数会把参数的内容传给aidl,但其改动不会同步到调用进程;out参数不会把参数的属性传给aidl(aidl获取的参数对象属性为空),但其改动会同步到调用进程;inout参数则是in和out的综合。不跨进程时,三者则是摆设,详情请参见第6步的实际操作
4、编译,生成和aidl同名的java文件(我这儿是IPersonManagerInterface.java)
文件内容一会儿再说
5、编写Service,处理信息
代码如下
public class PeopleService extends Service { private static final String TAG = "PeopleService"; private LinkedList<Person> peopleList = new LinkedList<>(); private Random random = new Random(); private final Stub peopleManager = new Stub() { // IPersonManagerInterface.Stub @Override public List<Person> getPeople() throws RemoteException { return peopleList; } @Override public void addPerson(Person person) throws RemoteException { boolean isNull = person == null; // 参数为in logger("in person is null--" + isNull); person.setAge(person.getAge() + 1); peopleList.add(person); } @Override public Person updatePerson(Person person) throws RemoteException { boolean isNull = person == null; // 参数为out logger("out person is null--" + isNull); if (isNull) { person = new Person(); } else { logger(person.toString()); } person.setAge(random.nextInt() % 40); person.setName("jason"); return person; } @Override public Person updatePerson2(Person person) throws RemoteException { boolean isNull = person == null; // 参数为inout logger("inout person is null--" + isNull); if (isNull) { person = new Person(); } else { logger(person.toString()); } person.setAge(random.nextInt() % 40); person.setName("mike"); return person; } }; private void logger(String msg) { Log.i(TAG, msg); } @Override public void onCreate() { Person p = new Person("szc", 21); peopleList.add(p); } @Override public IBinder onBind(Intent intent) { logger("有连接请求"); logger(intent.toString()); return peopleManager; // 返回Stub,因为Stub实现了IBinder接口 } }
在清单文件中注册这个Service的时候
要加上exported,以便别的进程获取service;再写上process(进程名),一般就写:remote,这样进程名就是调用方包名:remote
清单文件中注册信息如下
<service android:name=".PeopleService" android:exported="true" android:process=":remote"> <intent-filter> <action android:name="com.example.songzeceng"></action> <category android:name="android.intent.category.DEFAULT"/> </intent-filter> </service>
6、MainActivity中使用
6.1、同一个进程下
同一个进程,就是在Service所在项目里的MainActivity里进行绑定,Activity的代码如下
public class MainActivity extends Activity { public static final String TAG = "MainActivity"; private static boolean isConnected = false; private Person p = new Person("Dustin", 27); private IPersonManagerInterface.Stub peopleManager = null; private ServiceConnection connection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { if (peopleManager == null) { peopleManager = (IPersonManagerInterface.Stub) IPersonManagerInterface.Stub.asInterface(service); // 此处的service,就是Service的onBind()方法返回的Stub,必须经过这个方法才能还原成Stub类对象 } isConnected = true; if (peopleManager != null) { try { LinkedList<Person> people = (LinkedList<Person>) peopleManager.getPeople(); logger(people.toString()); logger("================="); logger("before add"); logger(p.toString()); logger("================="); peopleManager.addPerson(p); // 验证in logger(p.toString()); logger("================="); peopleManager.updatePerson(p); // 验证out logger(p.toString()); logger("================="); peopleManager.updatePerson2(p); // 验证in/out logger(p.toString()); logger("================="); } catch (RemoteException e) { e.printStackTrace(); } } } @Override public void onServiceDisconnected(ComponentName name) { logger(name + "已经断开连接"); isConnected = false; } }; private void logger(String info) { Log.i(TAG, info); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } @Override protected void onStop() { super.onStop(); tryDisconnectService(); } @Override protected void onStart() { super.onStart(); tryConnectService(); } private void tryConnectService() { logger("try to connect service"); if (!isConnected) { Intent intent = new Intent(this, PeopleService.class); intent.setAction("com.example.songzeceng"); bindService(intent, connection, BIND_AUTO_CREATE); } } private void tryDisconnectService() { logger("try to disconnect service"); if (isConnected) { unbindService(connection); isConnected = false; } } }
运行结果如下
为了简洁,我把传入in参数的方法称为in方法,传入out参数的方法称为out方法,传入inout参数的方法称为inout方法
我的Service里的日志都是改变参数前打的,只是看看传入的对象及参数是不是null
in方法:如果像网上说的,in方法传入的对象发生的改变,不会同步到外界,但事实却是外界的实参发生了变化
out方法:如果像网上说的,out方法传入的对象属性为空,但在同进程下,参数并不为空
所以我才觉得,统一进程内,in、out、inout似乎是摆设
6.2、不同进程下
为了验证aidl的跨进程,我新建了一个项目Client,把上面的项目做为Client的模块依赖,具体操作参见文章安卓开发学习之为项目添加模块依赖
MainActivity的代码和刚才的几乎一样,如下
public class MainActivity extends Activity { public static final String TAG = "MainActivity"; private IPersonManagerInterface personManager = null; private boolean isConnected = false; private Person p = new Person("Dustin", 27); private ServiceConnection connection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { logger(name.toString()); // BinderProxy isConnected = true; if (service == null) { logger("service is null"); return; } try { logger(service.getClass().getCanonicalName()); if (personManager == null) { personManager = IPersonManagerInterface.Stub.asInterface(service); // Stub.proxy logger(personManager.getClass().getCanonicalName()); } if (personManager != null) { logger("before add"); logger(p.toString()); logger("================="); personManager.addPerson(p); logger(p.toString()); logger("================="); ArrayList<Person> people = (ArrayList<Person>) personManager.getPeople(); // proxy返回的是ArrayList logger(people.toString()); logger("================="); personManager.updatePerson(p); // out和inout似乎并不起作用 logger(p.toString()); logger("================="); personManager.updatePerson2(p); logger(p.toString()); logger("================="); } } catch (Exception e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { isConnected = false; } }; private void logger(String s) { Log.i(TAG, s); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } @Override protected void onStart() { super.onStart(); if (!isConnected) { Intent intent = new Intent(this, PeopleService.class); intent.setAction("com.example.songzeceng"); bindService(intent, connection, BIND_AUTO_CREATE); } } @Override protected void onStop() { super.onStop(); if (isConnected) { unbindService(connection); isConnected = false; } } }
运行后Client进程的日志截图
可见,out和inout的参数对象,果然和Service同步发生了变化
再看service进程的日志
out参数的属性确实是空
IPersonManagerInterface#java文件阅读
这个java文件是我们编写IPersonManagerInterface.aidl后,系统编译后生成的,路径是aidl所在工程下的build\generated\source\aidl\debug\包名目录下,如图所示
打开之,格式化(mac的快捷键是option+command+l),这个IPersonManagerInterface继承了IInterface,明显是用来IPC的,而IInterface里方法asBinder()有内部类分别实现,而我们在aidl里定义的四个方法,就放在这个接口里
public interface IPersonManagerInterface extends android.os.IInterface { /** * Local-side IPC implementation stub class. */ public static abstract class Stub extends android.os.Binder implements com.example.songzeceng.studyofipc.IPersonManagerInterface { ... private static class Proxy implements com.example.songzeceng.studyofipc.IPersonManagerInterface { ... } ... } public java.util.List<com.example.songzeceng.studyofipc.Person> getPeople() throws android.os.RemoteException; public void addPerson(com.example.songzeceng.studyofipc.Person person) throws android.os.RemoteException; public com.example.songzeceng.studyofipc.Person updatePerson(com.example.songzeceng.studyofipc.Person person) throws android.os.RemoteException; public com.example.songzeceng.studyofipc.Person updatePerson2(com.example.songzeceng.studyofipc.Person person) throws android.os.RemoteException; }
可以看到,这个接口有一个静态内部类,Stub。它是抽象类,继承于Binder,实现了外面的接口IPersonManagerInterface
而Stub也有一个静态内部类,叫做Proxy,但只是实现了外层的接口
下面我花开两朵,各表一枝
Stub
Stub是工作在aidl进程的(我们的Service实例化了一个Stub,所以我们的aidl进程就是Service进程)
源码如下
public static abstract class Stub extends android.os.Binder implements com.example.songzeceng.studyofipc.IPersonManagerInterface { private static final java.lang.String DESCRIPTOR = "com.example.songzeceng.studyofipc.IPersonManagerInterface"; // 描述符 /** * Construct the stub at attach it to the interface. */ public Stub() { // 构造方法 this.attachInterface(this, DESCRIPTOR); } /** * Cast an IBinder object into an com.example.songzeceng.studyofipc.IPersonManagerInterface interface, * generating a proxy if needed. */ // 我们在onServiceConnected()方法中,就是用这个asInterface()方法,来把Service的onBind()返回的IBinder对象转化为Stub对象或Proxy对象 public static com.example.songzeceng.studyofipc.IPersonManagerInterface asInterface(android.os.IBinder obj) { if ((obj == null)) { return null; } android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); // 寻找本地Stub接口,也就是当前进程的Stub接口 // 如果找到了,就表示和aidl是在一个进程里 // 否则,就不是一个进程里,比如Client if (((iin != null) && (iin instanceof com.example.songzeceng.studyofipc.IPersonManagerInterface))) { // 在一个进程里,返回的是Service类里的Stub对象 return ((com.example.songzeceng.studyofipc.IPersonManagerInterface) iin); } // 不在一个进程里,就返回一个Proxy,所以Client获取的是proxy // 传入的参数,就是Service的onBind()方法返回值 return new com.example.songzeceng.studyofipc.IPersonManagerInterface.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 { .. // 此方法仅用于ipc,如果是一个进程里用不到 } private static class Proxy implements com.example.songzeceng.studyofipc.IPersonManagerInterface { ... } static final int TRANSACTION_getPeople = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); static final int TRANSACTION_addPerson = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1); static final int TRANSACTION_updatePerson = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2); static final int TRANSACTION_updatePerson2 = (android.os.IBinder.FIRST_CALL_TRANSACTION + 3); }
可以得出的一个结论就是:如果调用方和aidl在一个进程里,asInterface()获取到的就是Service.onBind()方法的返回结果,调用的就是Service里的Stub的方法,再加上同一进程用的是同一块内存空间,所以才导致in、out、inout没了实际作用
Proxy
Proxy是IPC时,工作在调用方进程的,通过IBinder.transact()方法调用aidl进程的Stub.onTransact()方法
代码如下
private static class Proxy implements com.example.songzeceng.studyofipc.IPersonManagerInterface { private android.os.IBinder mRemote; Proxy(android.os.IBinder remote) { // 构造方法由Stub.asInterface()调用,传过来是Service.onBind()的返回值 mRemote = remote; } @Override public android.os.IBinder asBinder() { return mRemote; } public java.lang.String getInterfaceDescriptor() { return DESCRIPTOR; } @Override public java.util.List<com.example.songzeceng.studyofipc.Person> getPeople() throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); // 传入参数 android.os.Parcel _reply = android.os.Parcel.obtain(); // 返回结果 java.util.List<com.example.songzeceng.studyofipc.Person> _result; try { _data.writeInterfaceToken(DESCRIPTOR); mRemote.transact(Stub.TRANSACTION_getPeople, _data, _reply, 0); // 调用Stub.onTransact() _reply.readException(); _result = _reply.createTypedArrayList(com.example.songzeceng.studyofipc.Person.CREATOR); // 写入结果 } finally { _reply.recycle(); _data.recycle(); } return _result; // 返回结果 } @Override public void addPerson(com.example.songzeceng.studyofipc.Person person) throws android.os.RemoteException { // in方法 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); // 把传入参数person写入data,做为Stub.onTransact()的参数 } else { _data.writeInt(0); } mRemote.transact(Stub.TRANSACTION_addPerson, _data, _reply, 0); // reply是空,调用Stub.onTransact() _reply.readException(); } finally { _reply.recycle(); _data.recycle(); } // 没有返回结果 } @Override // out方法 public com.example.songzeceng.studyofipc.Person updatePerson(com.example.songzeceng.studyofipc.Person person) throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); com.example.songzeceng.studyofipc.Person _result; try { _data.writeInterfaceToken(DESCRIPTOR); // 并没有写入参数person mRemote.transact(Stub.TRANSACTION_updatePerson, _data, _reply, 0); // 调用Stub.onTransact(),但这时data和reply都是空的 _reply.readException(); if ((0 != _reply.readInt())) { _result = com.example.songzeceng.studyofipc.Person.CREATOR.createFromParcel(_reply); // 根据reply读取结果 } else { _result = null; } if ((0 != _reply.readInt())) { person.readFromParcel(_reply); // 根据reply读取person,调用Person.readFromParcel()方法。由于是引用传递,所以会同步到调用方进程 } } finally { _reply.recycle(); _data.recycle(); } return _result; } @Override public com.example.songzeceng.studyofipc.Person updatePerson2(com.example.songzeceng.studyofipc.Person person) throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); com.example.songzeceng.studyofipc.Person _result; try { _data.writeInterfaceToken(DESCRIPTOR); if ((person != null)) { _data.writeInt(1); person.writeToParcel(_data, 0); // 写入person参数 } else { _data.writeInt(0); } mRemote.transact(Stub.TRANSACTION_updatePerson2, _data, _reply, 0); // 调用Stub.onTransact()方法 _reply.readException(); if ((0 != _reply.readInt())) { _result = com.example.songzeceng.studyofipc.Person.CREATOR.createFromParcel(_reply); // 读取结果 } else { _result = null; } if ((0 != _reply.readInt())) { person.readFromParcel(_reply); // 读取结果给参数person } } finally { _reply.recycle(); _data.recycle(); } return _result; } }
清楚说明了in、out、inout的工作机制,而且可以看到,关键是调用了Stub.onTransact()方法,代码如下
@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException { // 这个方法用于IPC,也就是不在一个进程里的情况(Client) // 此时this指针指向的也是Service.onBind()的返回值 // data里面是Client向Service传过来的参数 // reply则是Service向Client返回的结果 switch (code) { case INTERFACE_TRANSACTION: { reply.writeString(DESCRIPTOR); return true; } case TRANSACTION_getPeople: { // 处理列表 data.enforceInterface(DESCRIPTOR); java.util.List<com.example.songzeceng.studyofipc.Person> _result = this.getPeople(); reply.writeNoException(); reply.writeTypedList(_result); return true; } case TRANSACTION_addPerson: { // in方法 data.enforceInterface(DESCRIPTOR); com.example.songzeceng.studyofipc.Person _arg0; // 读取参数 if ((0 != data.readInt())) { // in方法走这里,调用了Person.CREATOR的createFromParcel()方法 _arg0 = com.example.songzeceng.studyofipc.Person.CREATOR.createFromParcel(data); } else { _arg0 = null; } this.addPerson(_arg0); // 没有往reply里写东西 reply.writeNoException(); return true; } case TRANSACTION_updatePerson: { // out方法 data.enforceInterface(DESCRIPTOR); com.example.songzeceng.studyofipc.Person _arg0; _arg0 = new com.example.songzeceng.studyofipc.Person(); // 参数是直接用无参构造方法new出来的,所以IPC时out方法里参数属性是空 com.example.songzeceng.studyofipc.Person _result = this.updatePerson(_arg0); // 获取返回值 reply.writeNoException(); // 写入结果reply if ((_result != null)) { reply.writeInt(1); _result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE); } else { reply.writeInt(0); } if ((_arg0 != null)) { // 似乎还要同步arg0,但arg0的生命周期只在这个方法之内 reply.writeInt(1); _arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE); } else { reply.writeInt(0); } return true; } case TRANSACTION_updatePerson2: { // inout方法 data.enforceInterface(DESCRIPTOR); com.example.songzeceng.studyofipc.Person _arg0; if ((0 != data.readInt())) { // 读取参数 _arg0 = com.example.songzeceng.studyofipc.Person.CREATOR.createFromParcel(data); } else { _arg0 = null; } com.example.songzeceng.studyofipc.Person _result = this.updatePerson2(_arg0); reply.writeNoException(); if ((_result != null)) { // 写入结果 reply.writeInt(1); _result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE); } else { reply.writeInt(0); } if ((_arg0 != null)) { reply.writeInt(1); _arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE); } else { reply.writeInt(0); } return true; } } return super.onTransact(code, data, reply, flags); }
注释写得很清楚,无需赘言
结语
AIDL是Android跨进程通信的重要手段,应该掌握一下