IPC含义是进程间通信或者说跨进程通信。
在介绍进程通信之前,我们先理解android中的多进程。我们给指定的四大组件设置androdi:process属性,就开启了多进程模式。一眼看去还是很简单,实际运用时会遇到不少问题。
开启android多进程方法常用的也就是给四大组件设置androdi:process属性,有两种声明方式当然含义也不同。还有通过jni在native层fork一个新的进程(我没用过,暂时不介绍)。下面是开启多进程的代码。
<activity
android:name=".TwoActivity"
android:process=":remote"
android:label="@string/title_activity_two" >
</activity>
<activity
android:name=".ThirdActivity"
android:process="com.unifou.demo.remote"
android:label="@string/title_activity_third" >
</activity>
可以看到两个Activity的process属性分别是“:remote”和“:com.unifou.demo.remote”,两者的区别如下
“:”是一种简写,TwoActivity进程名为com.unifou.demo:remote,包含包名信息。ThirdActivity进程名为com.unifou.demo.remote。
以“:”开头的进程是私有进程,其他的应用组件不能跟他跑在同一个进程。而完整声明的进程(不以“:”开头的进程)则是全局进程,其他的应用可以ShareUID的方式跟他跑在同一个进程。
多进程会造成以下问题
1.单例模式和静态成员都会失效。
2.线程同步机制也会失效。
3.SharedPreferences可靠性下降(不支持两个进程同步读写)。
4.Appliceation会多次创建。
IPC基础概念。
主要包含三方面内容:Parcelable接口、Serializable接口、Binder。Parcelable和Serializeable使用来完成对象序列化和反序列化。
Serializable接口是java提供的序列化接口,是一个空接口,为对象提供标准的序列化和反序列化操作。使用起来简单,不过注意使用时声明一个serialVersionUID,虽然不声明也不会序列化失败,但是如果不声明涉及版本更新,一旦更改了实现了Serializeable接口类中的成员变量数量,则会导致反序列化失败。这个serialVersionUID使用来辅助序列化和反序列化的。工作机制是每次序列化会将serialVersionUID一起写入到文件,反序列化的时候则会去检查serialVersionUID,相同则可以反序列化,不同则不可以。如果声明了serialVersionUID,当我们增加了或者删减了成员变量,仍然可以序列化成功,最大限度的还原数据,当然改变了类名或者改变了成员变量类型,serialVersionUID验证可以通过,但还会失败,类的结构发生改变,老版本数据无法还原出新的对象。
Parcelable接口是android提供的序列化接口写比Serializebale稍微复杂点。写法就不介绍了,将鼠标放在Parcelabel接口上会有提示。二者的区别是Serializable是通过大量I/O操作,比较慢,Parcelable是android独有的序列化,Parcelable主要用于内存,效率高。
Binder是android中的一个类,实现了IBinder接口。从IPC的角度看是android中一种跨进程间通信的方式,从应用层看Binder是服务端跟客户端通信的媒介。主要用于AIDL和Service(Messenger底层也是AIDL)。这里就用AIDL举例,写法如下
aidl文件,服务端客户端都需要。(本例是在两个应用中)接口参数类型只支持八种基本类型、String和CharSequence、实现Parcelable的自定义类型以及list和Map。然后参数如果是输入,前面加in,如果是输出前面加out,底层实现有开销。AIDL接口只支持方法,不支持声明静态长量。
package com.unifou.ClientAIDL;
interface Testaidl{
String getName();
int setID(in int id,out byte[] name);
}
服务端的 service 。service需要声明action,客户端bindService需要action以及包名(这是两个应用bindService的办法),如果service 有增加自定义权限,也需要在客户端增加相应权限。
public class TestService extends Service{
@Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return new testBinder();
class testBinder extends Testaidl.Stub{
@Override
public String getName() throws RemoteException {
// TODO Auto-generated method stub
return null;
}
@Override
public int setID(int id, byte[] name) throws RemoteException {
// TODO Auto-generated method stub
return 0;
}
}
}
客户端bindService类
public class TestClient {
public Testaidl testaidl;
private TestService service;
class TestService implements ServiceConnection{
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// TODO Auto-generated method stub
testaidl = Testaidl.Stub.asInterface(service);
Log.i("unifou", "成功");
}
@Override
public void onServiceDisconnected(ComponentName name) {
// TODO Auto-generated method stub
}
}
public TestClient(Context context){
service = new TestService();
Intent intent = new Intent();
intent.setAction("com.example.server.remote.TestService");
intent.setPackage("com.example.server");
context.bindService(intent, service, Context.BIND_AUTO_CREATE);
}
public void TestonDestroy(Context context){
context.unbindService(service);
}
}
然后我们来分析一下根据Testaidl .aidl系统自动生成的java代码。源码如下:
package com.unifou.ClientAIDL;
public interface Testaidl extends android.os.IInterface{
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.unifou.ClientAIDL.Testaidl{
private static final java.lang.String DESCRIPTOR = "com.unifou.ClientAIDL.Testaidl";
/** Construct the stub at attach it to the interface. */
public Stub(){
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.unifou.ClientAIDL.Testaidl interface,
* generating a proxy if needed.
*/
public static com.unifou.ClientAIDL.Testaidl asInterface(android.os.IBinder obj){
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.unifou.ClientAIDL.Testaidl))) {
return ((com.unifou.ClientAIDL.Testaidl)iin);
}
return new com.unifou.ClientAIDL.Testaidl.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{
switch (code) {
case INTERFACE_TRANSACTION:
{
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_getName:
{
data.enforceInterface(DESCRIPTOR);
java.lang.String _result = this.getName();
reply.writeNoException();
reply.writeString(_result);
return true;
}
case TRANSACTION_setID:
{
data.enforceInterface(DESCRIPTOR);
int _arg0;
_arg0 = data.readInt();
byte[] _arg1;
int _arg1_length = data.readInt();
if ((_arg1_length<0)) {
_arg1 = null;
}else {
_arg1 = new byte[_arg1_length];
}
int _result = this.setID(_arg0, _arg1);
reply.writeNoException();
reply.writeInt(_result);
reply.writeByteArray(_arg1);
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.unifou.ClientAIDL.Testaidl{
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote){
mRemote = remote;
}
@Override
public android.os.IBinder asBinder(){
return mRemote;
}
public java.lang.String getInterfaceDescriptor(){
return DESCRIPTOR;
}
@Override
public java.lang.String getName() throws android.os.RemoteException{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.lang.String _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getName, _data, _reply, 0);
_reply.readException();
_result = _reply.readString();
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override
public int setID(int id, byte[] name) throws android.os.RemoteException{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
int _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeInt(id);
if ((name==null)) {
_data.writeInt(-1);
}else {
_data.writeInt(name.length);
}
mRemote.transact(Stub.TRANSACTION_setID, _data, _reply, 0);
_reply.readException();
_result = _reply.readInt();
_reply.readByteArray(name);
}finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
static final int TRANSACTION_getName = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_setID = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
public java.lang.String getName() throws android.os.RemoteException;
public int setID(int id, byte[] name) throws android.os.RemoteException;
}
将它排版一下,仔细看看,发现Testaidl 类的结构还是比较简单的,它实现了IInterface接口,并自身也是一个接口,所有在Binder传输的接口都需要继承IInterface,它声明了两个方法getName和setID,在内部类中给两个声明了两个ID,用于标识transact调用的哪个方法。DESCRIPTOR是该Binder唯一标识。asInterface该静态方法返回一个Testaidl对象,当服务端跟客户端在同一个进程返回的是Testaidl对象,在不同进程时返回的是Stub.Proxy对象。asBinder返回的是当前Binder对象。onTransact这个方法运行在服务端Binder线程池中,客户端发起跨进程请求时,远程请求会由系统交给该方法执行,通过code可以知道请求的是哪个方法,android.os.Parcel data和android.os.Parcel reply分别是读取目标方法传过来的参数和写入返回值(如果有返回值)以及返回结果。
通过以上的分析,我们可以了解到Binder的工作机制。客户端发起跨进程请求,根据code调用Proxy中的实现,将传入的参数写入_data,然后执行transact,该方法会将发起RPC(远程过程调用)请求,同时将该线程挂起;系统会调用onTransact,同样根据code调用服务端的实现,知道RPC过程返回后,当前线程继续执行,并从_reply取出返回的结果返回。我们要注意的地方是远程方法是耗时的话,不能再UI线程调用。其次,由于服务端Binder运行在Binder线程池中,所以不管是否耗时,我们都应该采取同步的方式实现。