IPC初探(一)
进程间通信(IPC,InterProcess Communication)是指在不同进程之间传播或交换信息。
IPC的方式通常有管道(包括无名管道和命名管道)、消息队列、信号量、共享存储、Socket、Streams等。
Android中特有的IPC机制主要是Binder,当然也支持Socket。
1. Android中的多进程模式
开启多进程模式
- 在native层fork一个新的进程。不是常用的创建多进程方式,暂时不做讨论。
为四大组件指定
android:process
属性://packageName:com.domain.test <service android:name=".MyService1" /> <service android:name=".MyService2" android:process=":remote" /> <service android:name=".MyService3" android:process="com.domain.test.remote" />
3个Service的进程名分别为:
//packageName:com.domain.test MyService1 com.domain.test MyService2 com.domain.test:remote MyService3 com.domain.test.remote
MyService2
以":"
是简写的命名方式,是在":"
前加上完整的包名,形成最终的进程名,它等同于android:process="com.domain.test:remote"
。MyService3
则是完整的命名方式。
- 两种指定新进程方式的区别:
- 以
":"
方式指定的进程,属于当前应用私有进程,其它应用的组件不可以与它运行在同一个进程中。 - 不以
":"
方式指定的进程,是全局进程,其它应用可以通过ShareUID方式和它运行在同一个进程中。
- 以
多进程模式的问题
系统会为每个进程都分配一个独立的虚拟机,不同的虚拟机在内存分配上有不同的地址空间,所以会导致下列问题:
- Application 多次创建,所以最好不要将数据保存在Application中。
- 静态成员、类的对象将拥有多个副本,因为在每个进程中都会独立分配内存。
- 线程同步将会失效。因为线程压根就是跑在各自独立的进程中。
- SharedPreferences 可靠性下降(SharedPreferences不支持多个进程同时写,会有一定的几率丢失数据)。
Android提供了一些方法来避免这些问题,比如Intent,Messenger,或者Binder,我们也可以利用文件共享来实现进程通信。
2. 传递序列化的对象
使用 Serializable
和 Parcelable
接口。
Serializable
是Java 提供的接口,通过ObjectOutputStream
和ObjectIntputStream
序列化和反序列化对象。Parcelable
是Android提供的接口,使用方法参考示例代码:``` public class Student implements Parcelable { public String name; public int stuId; public Student(int stuId, String name) { this.name = name; this.stuId = stuId; } protected Student(Parcel in) { name = in.readString(); stuId = in.readInt(); } public Student() { } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(name); dest.writeInt(stuId); } @Override public int describeContents() { return 0; } public static final Creator<Student> CREATOR = new Creator<Student>() { @Override public Student createFromParcel(Parcel in) { return new Student(in); } @Override public Student[] newArray(int size) { return new Student[size]; } }; @Override public String toString() { return String.format("[stuId:%s, name:%s]", stuId, name); } } ```
在Android平台,推荐使用
Parcelable
接口, 它的效率更高。
-Serializable
的序列化/反序列化过程,需要大量的 I/O 操作,而且使用了反射机制,在序列化时会创建很多的临时变量,时间和空间开销较大。
-Serializable
是一种标识接口(marker interface),编码更简洁。
-Parcelable
的原理是将对象的属性分别写入parcel对象,这些属性都必须是intent支持的类型,包括基本类型和实现了Parcelable
的对象。
- List和Map也可以序列化,只要它们里面的每个对象都实现了Parcelable
。
3. Binder了解一下
在了解Binder之前,发挥一下想象。所谓IPC,就相当于在两个互相独立、互不接触的空间,搭建一条公路,然后就可以互相传递货物了:
- 为何需要中间的圆圈:前文提到,多进程属于不同的内存空间,你取到到任何常量、变量对象,通通只是本线程内部的值。
- 如果你启动一个Intent,从进程1的组件A启动进程2的组件B,那么你传递的货物,就实现了跨进程的传递,而代价是货物必须实现
Parcelable
接口 ,或干脆就是基本类型。
上面这个朴素的模型实在粗糙简陋,现在来了解一下Binder的工作机制:
可见在Binder机制中,两个进程是C/S结构,其中发起请求的一方是客户端,而另一端提供服务的是服务端。
我准备了一个demo, 简单描述下它的需求和实现。代码后面将会贴上来:
服务端进程拥有数据(List<Student> list
),而客户端请求获取list的内容,并可以进行添加操作。- 数据类是
Student
,它实现了Parcelable
IStudentManager
:
- 声明了两个接口:
addStudent
和getStudentList
, 这正是客户端的需求 - 内部类
IStudentManager.Stub
: 这个Stub类且继承了Binder
。它提供了两个功能,asBinder
来返回Binder
对象,asInterface
则将Binder
对象转化为IStudentManager
接口对象。 - Stub的内部类:
IStudentManager.Stub.Proxy
, 它是IStudentManager
的真正实现,从名字也可以看出,它是Stub的代理。 - 你可以手动写一个这样的接口,但系统提供了AIDL的方式,可以自动生成这个接口(//手动斜眼)。
- 声明了两个接口:
StudentManagerService
:它必须提供一个IStudentManager.Stub
的实现,也就是一个binder
, 然后在onBind
方法中返回这个binder
。客户端:
发送请求:
Intent intent = new Intent(this,StudentManagerService.class); bindService(intent,mConnection, Context.BIND_AUTO_CREATE);
参数
mConnection
:private ServiceConnection mServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { IStudentManager studentManager = IStudentManager.Stub.asInterface(service); try { List<Student> students = studentManager.getStudentList(); Log.d(TAG, "Client Request students:" + students); Student student = new Student(1003,"Jack"); studentManager.addStudent(student); Log.d(TAG, "Client Request students:" + students); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { } };
onServiceConnected
:与远程服务成功连接的回调。显然,我们在成功连接远程服务之后,获取了数据列表,并添加了新的数据。
- 数据类是
上代码:
一些小细节:
AIDL文件在Android Studio和Eclipse 的目录组织上略有不同,生成的java文件目录也不太一样。
- Android Studio 的AIDL文件默认放在:src/main/aidl, aidl目录与java目录同级别
- Android Studio 的AIDL生成的java文件在这里:
build/generated/source/aidl/debug/
- 写完AIDL,或clone项目之后,在Android Studio中 “做”(Make)一下,会自动生成相应的java文件。之后你才能在代码中使用这个相应的接口和类。
请注意为远程服务指定的
android:process
属性客户端绑定了服务,需要在相应的地方解绑服务。
下期预告:我们将继续探索Binder的使用,实现更多的功能,发现更多的坑并填上。