android进程间通信 - IPC
定义多进程
Android应用中使用多进程只有一个办法(用NDK的fork来做除外),就是在AndroidManifest.xml中声明组件时,用android:process属性来指定。进程的指定分为3种形式,分别是:
-
当用户不指定process属性,则默认运行在主进程中,主进程名字为包名。
-
android:process = “:remote” ,将运行在
默认包名:remote
进程中,而且是APP的私有进程,不允许其他APP的组件来访问。 -
android:process = [进程名称],将运行在名字为
"进程名称"
的进程中,属于全局进程,其他具有相同shareUID与签名的APP可以跑在这个进程中。
IPC是Inter-Process Communication的缩写,含义为进程间通信或者跨进程通信,是指
两个进程之间进行数据交换的过程。
多进程引发的问题
-
静态成员和单例失效:每个进程保持各自的静态成员和单例,相互独立。
-
线程同步机制失效:每个进程有自己的线程锁。
-
SharedPreferences可靠性下降:不支持并发写,会出现脏数据。
-
Application多次创建:不同进程跑在不同虚拟机,每个虚拟机启动会创建自己的Application,自定义Application时生命周期会混乱。
综上,不同进程拥有各自独立的虚拟机,Application,内存空间,由此引发一系列问题。
进程间通信
1. Bundle/Intent传递数据:
可传递基本类型,String,实现了Serializable或Parcellable接口的数据结构。Serializable是Java的序列化方法,Parcellable是Android的序列化方法,前者代码量少(仅一句),但I/O开销较大,一般用于输出到磁盘或网卡;后者实现代码多,效率高,一般用户内存间序列化和反序列化传输。
2. 文件共享:
对同一个文件先后写读,从而实现传输,Linux机制下,可以对文件并发写,所以要注意同步。顺便一提,Windows下不支持并发读或写。
3. Messenger:
Messenger是基于AIDL实现的,服务端(被动方)提供一个Service来处理客户端(主动方)连接,维护一个Handler来创建Messenger,在onBind时返回Messenger的binder。
双方用Messenger来发送数据,用Handler来处理数据。Messenger处理数据依靠Handler,所以是串行的,也就是说,Handler接到多个message时,就要排队依次处理。
4. AIDL:
AIDL通过定义服务端暴露的接口,以提供给客户端来调用,AIDL使服务器可以并行处理,而Messenger封装了AIDL之后只能串行运行,所以Messenger一般用作消息传递。
通过编写aidl文件来设计想要暴露的接口,编译后会自动生成响应的java文件,服务器将接口的具体实现写在Stub中,用iBinder对象传递给客户端,客户端bindService的时候,用asInterface的形式将iBinder还原成接口,再调用其中的方法。
5. ContentProvider:
系统四大组件之一,底层也是Binder实现,主要用来为其他APP提供数据,可以说天生就是为进程通信而生的。自己实现一个ContentProvider需要实现6个方法,其中onCreate是主线程中回调的,其他方法是运行在Binder之中的。自定义的ContentProvider注册时要提供authorities属性,应用需要访问的时候将属性包装成Uri.parse(“content://authorities”)。还可以设置permission,readPermission,writePermission来设置权限。 ContentProvider有query,delete,insert等方法,看起来貌似是一个数据库管理类,但其实可以用文件,内存数据等等一切来充当数据源,query返回的是一个Cursor,可以自定义继承AbstractCursor的类来实现。
6. Socket:
学过计算机网络的对Socket不陌生,所以不需要详细讲述。只需要注意,Android不允许在主线程中请求网络,而且请求网络必须要注意声明相应的permission。然后,在服务器中定义ServerSocket来监听端口,客户端使用Socket来请求端口,连通后就可以进行通信。
1. Bundle/Intent传递数据:
在Intent和Bundle中和容易完成一个java基本类型数据的传递(比如 int,String,long等等),但是当我们需要完成一个对象的传递的时候就需要对这个对象进行序列化操作,基本的序列化操作又包含了Serializable方式和Parcelable方式。
关于 Serializable和Parcelable 详细知识点请移步Android 你不可不知的对象序列化 Serializable和Parcelable
进程间数据单向传递参考下面(相同进程间数据传递也适用)
在启动组件的同时将数据存入
ArrayList<String> stringList = new ArrayList<>();
stringList.add("a");
stringList.add("b");
stringList.add("c");
Intent intent = new Intent(MainActivity.this, GuestureDelectorActivity.class);
intent.putExtra("intValue", 12);
intent.putExtra("StringValue", "this is String value");
intent.putStringArrayListExtra("StringListValue", stringList);
intent.putExtra("SerializableValue"
, new UserBean("小明", 2, "北京"
, new UserBean.HomeTown("北京北", "1263.15", "1545.24")));
intent.putExtra("ParcelableValue"
, new SystemMsgBean("id_123", "推送消息", System.currentTimeMillis(), 0));
Bundle bundle = new Bundle();
bundle.putInt("intValue", 12);
bundle.putString("StringValue", "this is String value");
bundle.putStringArrayList("StringListValue",stringList);
bundle.putSerializable("SerializableValue",new UserBean("小明", 2, "北京"
, new UserBean.HomeTown("北京北", "1263.15", "1545.24")));
bundle.putParcelable("ParcelableValue"
, new SystemMsgBean("id_123", "推送消息", System.currentTimeMillis(), 0));
intent.putExtras(bundle);
startActivity(intent);
在被启动的组件中将数据取出
Intent intent = getIntent();
int intValue = intent.getIntExtra("intValue", 0);
String stringValue = intent.getStringExtra("StringValue");
ArrayList<String> stringListValues = intent.getStringArrayListExtra("StringListValue");
UserBean userBean = (UserBean) intent.getSerializableExtra("SerializableValue");
SystemMsgBean msgBean = intent.getParcelableExtra("ParcelableValue");
Bundle bundle = intent.getExtras();
int intValue2 = bundle.getInt("intValue", 0);
String stringValue2 = bundle.getString("StringValue");
ArrayList<String> stringListValues2 = bundle.getStringArrayList("StringListValue");
UserBean userBean2 = (UserBean) bundle.getSerializable("SerializableValue");
SystemMsgBean msgBean2 = bundle.getParcelable("ParcelableValue");
2. 文件共享:
3. Messenger:
使用messenger可以实现进程间的双向通信
Messenger可以翻译为信使,顾名思义,通过它可以在不同进程中传递Message对象,在Message中放入我们需要传递的数据,就可以轻松地实现数据的进程间传递了。Messenger是一种轻量级的IPC方案,它的底层实现是AIDL,为什么这么说呢,我们大致看一下Messenger这个类的构造方法就明白了。下面是Messenger的两个构造方法,从构造方法的实现上我们可以明显看出AIDL的痕迹,不管是IMessenger还是Stub.asInterface,这种使用方法都表明它的底层是AIDL。Messenger可以翻译为信使,顾名思义,通过它可以在不同进程中传递Message对象,在Message中放入我们需要传递的数据,就可以轻松地实现数据的进程间传递了。Messenger是一种轻量级的IPC方案,它的底层实现是AIDL,为什么这么说呢,我们大致看一下Messenger这个类的构造方法就明白了。下面是Messenger的两个构造方法,从构造方法的实现上我们可以明显看出AIDL的痕迹,不管是IMessenger还是Stub.asInterface,这种使用方法都表明它的底层是AIDL。
public Messenger(Handler target) {
mTarget = target.getIMessenger();
}
public Messenger(IBinder target) {
mTarget = IMessenger.Stub.asInterface(target);
}
1.服务端进程
首先,我们需要在服务端创建一个Service来处理客户端的连接请求,同时创建一个Handler并通过它来创建一个Messenger对象,然后在Service的onBind中返回这个Messenger
对象底层的Binder即可。
2.客户端进程
客户端进程中,首先要绑定服务端的Service,绑定成功后用服务端返回的IBinder对象创建一个Messenger,通过这个Messenger就可以向服务端发送消息了,发消息类型为Message对象。如果需要服务端能够回应客户端,就和服务端一样,我们还需要创建一个Handler并创建一个新的Messenger,并把这个Messenger对象通过Message的replyTo参数传递给服务端,服务端通过这个replyTo参数就可以回应客户端。这听起来可能还是有点抽象,不过看了下面的两个例子,读者肯定就都明白了。首先,我们来看一个简单点的例子,在这个例子中服务端无法回应客户端。
首先看服务端的代码,这是服务端的典型代码,可以看到MessengerHandler用来处理客户端发送的消息,并从消息中取出客户端发来的文本信息。而mMessenger是一个Messenger对象,它和MessengerHandler相关联,并在onBind方法中返回它里面的Binder对象,可以看出,这里Messenger的作用是将客户端发送的消息传递给MessengerHandler处理。
Messenger的使用方法很简单,它对AIDL做了封装,使得我们可以更简便地进行进程间通信。同时,由于它一次处理一个请求,因此在服务端我们不用考虑线程同步的问题,这是因为服务端中不存在并发执行的情形。实现一个Messenger有如下几个步骤,分为服务端和客户端。
服务端代码
public class ServerService extends Service {
private final Messenger mMessager = new Messenger(new MessengerHandler());
//通过队列消息的形式组成一个简易的服务端
private static class MessengerHandler extends Handler {
@Override
public void handleMessage(Message msg) {
LogUtil.e("ServerService's handleMessage is invoke");
switch (msg.what) {
case ContentValue.MSG_FROM_CLIENT:
Bundle data = msg.getData();
//如果本次通讯是跨进程的数据传递,一定要设置classLoader,否则会因找不到可用的classLoader而报错 Parcel: Class not found when unmarshalling: com.zbc.tinkerdemo.entity.SystemMsgBean
data.setClassLoader(getClass().getClassLoader());
LogUtil.e("----------------MessengerHandler-----------------");
LogUtil.e("log from ServerService:\n the pracelable is :"
+ data.getParcelable("systemMsgBean") + "\n the String msg is : "
+ data.getString("msg") + "\n the int code is : "
+ data.getInt("code"));
Messenger messenger = msg.replyTo;
Message message = Message.obtain(null, ContentValue.MSG_FROM_SERVICE);
Bundle bundle = new Bundle();
bundle.putString("reply", "收到了,别比比,劳资烦");
message.setData(bundle);
try {
messenger.send(message);
} catch (Exception e) {
e.printStackTrace();
}
break;
default:
}
}
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
LogUtil.e("ServerService binded success!");
Bundle bundle = intent.getExtras();
SystemMsgBean systemMsgBean = bundle.getParcelable("systemMsgBean");
LogUtil.e("onBind 接收到的消息为————————\n" + systemMsgBean.toString());
return mMessager.getBinder();
}
}
然后,注册service,让其运行在单独的进程中:
<service
android:name=".service.ServerService"
android:enabled="true"
android:exported="true"
android:process=":remote" />
客户端
public class MainActivity extends AppCompatActivity implements ActivityCompat.OnRequestPermissionsResultCallback {
private Messenger mService;
private Messenger mGetReplyMessenger = new Messenger(new MessageHandle());
public void sendMessage(){
if (mService != null) {
Message message = Message.obtain(null, ContentValue.MSG_FROM_CLIENT);
Bundle data = new Bundle();
data.putString("msg", "hello!this is a client.I there are some message i want say you");
data.putInt("code", 100);
data.putParcelable("systemMsgBean", new SystemMsgBean("a00001"
, "Hello ! 我是通过bundle传递数据"
, System.currentTimeMillis()
, 0
));
message.setData(data);
message.replyTo = mGetReplyMessenger;
try {
mService.send(message);
} catch (Exception e) {
e.printStackTrace();
}
} else {
Intent intent = new Intent(MainActivity.this, ServerService.class);
Bundle bundle = new Bundle();
bundle.putParcelable("systemMsgBean", new SystemMsgBean("a00001"
, "Hello ! 我是通过bundle传递数据"
, System.currentTimeMillis()
, 0
));
intent.putExtras(bundle);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
}
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
LogUtil.e("the ComponentName is :" + name.getClassName());
//通过IBinder获取messenger,底层是AIDL
mService = new Messenger(service);
Message message = Message.obtain(null, ContentValue.MSG_FROM_CLIENT);
Bundle data = new Bundle();
data.putString("msg", "hello!this is a client.");
data.setClassLoader(SystemMsgBean.class.getClassLoader());
data.putParcelable("systemMsgBean", new SystemMsgBean("a00001"
, "俺是Message传递过来的数据"
, System.currentTimeMillis()
, 0
));
message.setData(data);
message.replyTo = mGetReplyMessenger;
try {
mService.send(message);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
@Override
public void onBindingDied(ComponentName name) {
}
};
@Override
protected void onDestroy() {
unbindService(mConnection);
super.onDestroy();
}
private class MessageHandle extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case ContentValue.MSG_FROM_SERVICE:
LogUtil.e(msg.getData().getString("reply"));
break;
default:
}
}
}
}
- 第一次运行触发绑定得到如下日志输出
第一次绑定后,我们可以在客户端随时通过 Messenger 的 mService.send(message);
方法,完成一次请求将一个新的message数据发送服务端,并等待服务端的数据响应。
- 绑定完成后,重新调用发送message方法得到如下的日志
通过上面的例子可以看出,在Messenger中进行数据传递必须将数据放入Message中,而Messenger和Message都实现了Parcelable接口,因此可以跨进程传输。简单来说,
Message中所支持的数据类型就是Messenger所支持的传输类型。实际上,通过Messenger来传输Message,Message中能使用的载体只有what、arg1、arg2、Bundle以及replyTo。Message中的另一个字段object在同一个进程中是很实用的,但是在进程间通信的时候,在Android 2.2以前object字段不支持跨进程传输,即便是2.2以后,也仅仅是系统提供的实现了Parcelable接口的对象才能通过它来传输。这就意味着我们自定义的Parcelable对象是无法通过object字段来传输的,读者可以试一下。非系统的Parcelable对象的确无法通过object字段来传输,这也导致了object字段的实用性大大降低,所幸我们还有Bundle,Bundle中可以支持大量的数据类型。
请注意:
1.如果使用message完成了跨进程的通讯,请不要在message的what和Bundle以外的任何位置存放任何数据,否则将会报错 Can't marshal non-Parcelable objects across processes.
消息类型为Message对象,跨进程发送要使用Bundle传送,否则可能会崩溃:Can't marshal non-Parcelable objects across processes.
比如使用 message的 `message.obj = "this is an Object";`直接报错 `Can't marshal non-Parcelable objects across processes.`
2.跨进程的通讯请在服务端的 Handler 中为message中的Bundle重新制定classLoader,否则新的进程将无法获得对应的classLoader来加载class文件,最终我们将无法从message中获取数据
4. AIDL:
更新中
5. ContentProvider:
更新中
6. Socket:
更新中