什么是IPC:
inter-process Communication;翻译过来就是进程间通信
首先要理解什么是进程:
进程是pc端或者移动端上的一个程序或者应用。一个进程可以包含多个线程,这是一个包含和被包含的关系。
什么时候需要使用多进程:
举个简单的例子,我们平时经常用的获取联系人,获取手机里的照片,这都是进程间的通信,只是这个通信的两个进程是我们的应用和系统的应用。再比如说,音乐播放器的后台播放,当你把显示播放信息的Activity全部杀死之后,还是能够继续播放,这时候后台实际上是还有另外一个进程在播放音乐,两个进程的生命是互补干扰的。这样就可以实现后台播放了。
还有一点就是当我们的应用需要实现的功能很复杂,需要大内存的时候,android系统为每个进程分配的内存是有限的,这时候就需要使用多进程来获得更多的内存使用
为什么会存在进程间通信?
线程间通信实现起来比进程间通信简单得多,最简单的使用一个handler就可以实现,为什么google不把handler机制使用在进程间通信上呢。这个问题我们需要从android的运行机制上分析了。android为每一个进程分配了一个独立的JVM,不同的JVM有不同的内存地址,这样会导致不同的进程访问同一个类的对象会产生新的副本,既然他们不能通过简单的内存共享来传递数据,那么必然涉及到进程间通信。
进程间通信的方式:
很多人第一答案就是AIDL,显然,AIDL是进程间通信的一种方式。但是android系统为我们提供了各种进程间通信的方式来适应不同的需要。
下面就来总结一下IPC实现的方式:
1.bundle,通过intent传递一些信息就可以实现进程间通信----这种方式适用于四大组件间的通信
2.文件共享
3.contentprovider,比如获取联系人
4.AIDL、messenger
5.socket,socket大多用于网络间的通信,功能强大,但是实现起来较复杂,需要注意的细节也很多。
说到进程间通信就必须说到非常重要的一个知识点,那就是序列化。java提供的序列化方式是serializable,android中提供的序列化方式是parcelable。这里就不进行分析了,只是需要说一下两者之间的区别。parcelable的效率是远高于serializable的,而且serializable的开销非常大,所以android上才开发了parcelable序列化方式。
还有一个就是binder,但是binder这个东西不是一时半会能讲清楚的,我只是也是一知半解。以后彻底透彻了之后再写吧
下面就说重点:AIDL的使用。
传递简单数据就不写出来了,直接写传递对象(我们自定义的model类)
服务端写法:
第一步新建aidl文件夹和新建一个包,名称一定要和java代码下放要传递的对象的类的包名一致
第二步:序列化要传递的对象类
第三步:在第一步建好的包下面新建aidl接口。注意:这里是两个aidl文件;一个aidl文件和要传递的对象类名一致,还有一个定义我们传递的接口
建好之后工程目录如下:
User.aidl文件里面只需要写一句话,下面是User.aidl的内容
parcelable User;
然后是传递接口aidl,里面内容如下:
package com.ciotea.server.model;
import com.ciotea.server.model.User;
// Declare any non-default types here with import statements
interface TransformUser {
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,double aDouble, String aString);
boolean addUser(in User user);
List<User> getUser(in String name);
}
注意:其中import com.ciotea.server.model.User;是需要我们手写上去的,AS不会自动导包
addUser方法传递的参数是User,前面需要加上in,否则编译会报错。这里的in可以设置为in,out,intout;具体含义如下
in:参数由客户端设置,或者理解成客户端传入参数值。
out:参数由服务端设置,或者理解成由服务端返回值。
inout:客户端输入端都可以设置,或者理解成可以双向通信。
序列化部分就不贴出来了。自行度娘吧
接下来需要我们make project一下。AS会自动在我们的generated / source / aidl / debug / 目录下面自动生成一段Java代码
目录结构如下:
如果AS已经自动生成了这些代码。说明前面的aidl文件定义是成功的。
客户端
接下来要做的就是把aidl文件和需要传递的对象的类拷贝到客户端中,就不一一解释了,贴上结构:
只有一个需要注意的:包名要和服务端代码的包名一致,同样的make project一下。也会生成和服务器端一样的代码
接下来我们来看看AS帮我们自动生成的这个类
/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: D:\\AidlDemo\\client\\src\\main\\aidl\\com\\ciotea\\server\\model\\TransformUser.aidl
*/
package com.ciotea.server.model;
// Declare any non-default types here with import statements
public interface TransformUser extends android.os.IInterface {
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements com.ciotea.server.model.TransformUser {
private static final java.lang.String DESCRIPTOR = "com.ciotea.server.model.TransformUser";
/**
* Construct the stub at attach it to the interface.
*/
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.ciotea.server.model.TransformUser interface,
* generating a proxy if needed.
*/
public static com.ciotea.server.model.TransformUser asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.ciotea.server.model.TransformUser))) {
return ((com.ciotea.server.model.TransformUser) iin);
}
return new com.ciotea.server.model.TransformUser.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_basicTypes: {
data.enforceInterface(DESCRIPTOR);
int _arg0;
_arg0 = data.readInt();
long _arg1;
_arg1 = data.readLong();
boolean _arg2;
_arg2 = (0 != data.readInt());
float _arg3;
_arg3 = data.readFloat();
double _arg4;
_arg4 = data.readDouble();
java.lang.String _arg5;
_arg5 = data.readString();
this.basicTypes(_arg0, _arg1, _arg2, _arg3, _arg4, _arg5);
reply.writeNoException();
return true;
}
case TRANSACTION_addUser: {
data.enforceInterface(DESCRIPTOR);
User _arg0;
if ((0 != data.readInt())) {
_arg0 = User.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
boolean _result = this.addUser(_arg0);
reply.writeNoException();
reply.writeInt(((_result) ? (1) : (0)));
return true;
}
case TRANSACTION_getUser: {
data.enforceInterface(DESCRIPTOR);
java.lang.String _arg0;
_arg0 = data.readString();
java.util.List<User> _result = this.getUser(_arg0);
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.ciotea.server.model.TransformUser {
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 void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeInt(anInt);
_data.writeLong(aLong);
_data.writeInt(((aBoolean) ? (1) : (0)));
_data.writeFloat(aFloat);
_data.writeDouble(aDouble);
_data.writeString(aString);
mRemote.transact(Stub.TRANSACTION_basicTypes, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
@Override
public boolean addUser(User user) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
boolean _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((user != null)) {
_data.writeInt(1);
user.writeToParcel(_data, 0);
} else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_addUser, _data, _reply, 0);
_reply.readException();
_result = (0 != _reply.readInt());
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override
public java.util.List<User> getUser(java.lang.String name) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<User> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeString(name);
mRemote.transact(Stub.TRANSACTION_getUser, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(User.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
static final int TRANSACTION_basicTypes = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addUser = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
static final int TRANSACTION_getUser = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
}
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException;
public boolean addUser(User user) throws android.os.RemoteException;
public java.util.List<User> getUser(java.lang.String name) throws android.os.RemoteException;
}
貌似一看,好多代码,看得脑袋都大,但是我们把Stub这个内部类收起来之后再来看:
一下就变得清晰了:一个内部类Stub,三个方法:basicTypes();addUser();getUser();
是不是有点熟悉:没错,这就是我们刚才在aidl中定义的三个方法;
我们再来一步一步看这个Stub内部类,
public static abstract class Stub extends android.os.Binder implements com.ciotea.server.TransformUser
首先,它继承自Binder类,所以它是binder子类。
它里面还有一个内部类Proxy,Proxy作为一个代理类,当位于不同的进程时就会调用proxy来执行请求
再来解释stub里面的方法:
DESCRIPTOR:binder的标示,一般用当前binder的包名+类名
asInterface(android.os.IBinder obj):传入的是一个binder对象,这个方法的作用是将服务端的Binder对象转换成客户端所需的AIDL接口类型的对象,但是它里面是做了判断的,如果是同一个进程,那么返回的就是stub对象本身,如果是不同的进程就返回Stub的代理proxy对象
asBinder():返回当前binder对象
public booleanonTransact(intcode, android.os.Parcel data, android.os.Parcel reply,intflags):
这个方法运行在服务端中,当客户端发起请求的时候,服务端会调用这个方法去做处理,
ontransact方法的第一个参数是客户端传递过来的需要调用的方法的code,服务端根据这个code判断客户端需要调用的方法
ontransact方法第二个参数是客户端传递过来的参数。
ontransact方法第三个参数是服务端回复给客户端的结果,如果是有返回值的方法,会先调用本地的方法进行查询。如果有结果,就会向reply里面序列化写入结果_result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
接下来就是服务端service的编写
这里我声明了一个list集合,模仿数据库操作
service里面主要做一件事就可以了,创建一个Stub对象,在onbind方法里面返回给客户端就好,客户端拿到这个stub对象就可以进行访问了
别忘了服务要在mainfest里面注册,还需要声明action才能被其他进程访问。
<service android:name="com.ciotea.MyService" android:enabled="true" android:exported="true"> <intent-filter> <action android:name="cn.bgxt.Service.CUSTOM_TYPE_SERVICE"/> </intent-filter> </service>
代码如下:
public class MyService extends Service { static List<User> users = new ArrayList<>(); static { users.add(new User("xiong",23,"北京大学")); users.add(new User("zhou",23,"人名大学")); users.add(new User("xiong",23,"中国民航大学")); users.add(new User("xiong",23,"哈佛大学")); } private Binder stub = new TransformUser.Stub() { @Override public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException { } @Override public boolean addUser(User user) throws RemoteException { boolean b = users.add(user); return b; } @Override public List<User> getUser(String name) throws RemoteException { List<User> reply = new ArrayList<>(); for (User user : users) { if (user.name.equals(name)){ reply.add(user); } } return reply; } }; public MyService() { } @Override public IBinder onBind(Intent intent) { Log.i("MyService","onBind"); return stub; } }
再后面就是客户端绑定服务和调用服务的方法就好了
首先需要创建一个ServiceConnection对象,通过onServiceConnected 方法里面的binder对象通过asInterface就能拿到
服务端我们声明的aidl接口的对象,就能调用服务端的方法进行操作了
private ServiceConnection connection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName componentName, IBinder iBinder) { transformUser = TransformUser.Stub.asInterface(iBinder); } @Override public void onServiceDisconnected(ComponentName componentName) { } };
//绑定服务 Intent intent = new Intent(); intent.setAction("cn.bgxt.Service.CUSTOM_TYPE_SERVICE"); boolean b = bindService(intent, connection, BIND_AUTO_CREATE); Log.i("====",b+"");
接下来只需要调用aidl中声明的方法:
查询结果和添加结果@Override public void onClick(View view) { switch (view.getId()){ case R.id.add_user: try { boolean b = transformUser.addUser(new User("周鱼儿", 23, "中山大学")); Log.i("server adduser",b+""); } catch (RemoteException e) { e.printStackTrace(); } break; case R.id.select_user: try { List<User> users = transformUser.getUser("xiong"); for (User ser : users) { Log.i("client getuser",ser.name+ser.age+ser.shcool); } } catch (RemoteException e) { e.printStackTrace(); } break; } }
到这里一个aidl过程就完成了。其实aidl并不难,只是要细心,很多地方一个小问题就导致自动生成代码不通过。
这里就把需要注意的地方和问题总结一下:
1.包名一定要一致,aidl的包名和要传递的对象的包名要一致,客户端和服务端的aidl文件包名要一致,客户端要传递对象的包名和服务端要传递对象的包名要一致
2.当不是基本类型的传递时,需要添加一个新的aidl类,类名和传递对象一致,里面只写一句话: pareclable +传递对象的类名
3.refactor AIDL包名之后,aidl文件里面不会自动更新包名,需要收到更改。
4.aidl文件的不会自动import,需要手动import