0. 前置知识
其实android中追根溯源只有两种进程间通信方式,其他的方式都是通过封装这两种方式而得到的:
Binder与Socket
Android——Binder机制.
Android中Socket通信的简单实现.
首先我们需要知道几点:RPC,IDL,IPC分别是什么。
RPC :
- Remote Procedure Call (远程过程调用) 是一种计算机通讯协议,为我们定义了计算机 C 中的程序如何调用另外一台计算机 S 的程序
- RPC 是 Client/Server 模式,客户端对服务器发出请求,服务器收到请求并且根据客户端提供的参数进行操作,然后结果返回给客户端
- RPC 位于 OSI 模型中的会话层
IDL :
- Interface Description Language (接口定义语言),通过一种中立的方式来描述接口,使得在不同平台上运行的对象和用不同语言编写的程序可以相互通信交流
- RPC 只是一种协议,规定了通信的规则
- 因为客户端与服务端平台的差异性,为了统一处理不同的实现,需要定义一个共同的接口,即就是IDL
IPC :
- Inter-Process Communication (进程间通信)
- Android 基于 Linux,而 Linux 出于安全考虑,不同进程间不能之间操作对方的数据,这叫做“进程隔离”
- 只有允许不同应用的客户端用 IPC 方式调用远程方法,并且想要在服务中处理多线程时,才有必要使用 AIDL
- 如果需要调用远程方法,但不需要处理并发 IPC,就应该通过实现一个 Binder 创建接口
- 如果您想执行 IPC,但只是传递数据,不涉及方法调用,也不需要高并发,就使用 Messenger 来实现接口
- 如果需要处理一对多的进程间数据共享(主要是数据的 CRUD),就使用 ContentProvider
- 如果要实现一对多的并发实时通信,就使用 Socket
1.Intent
Activity,Service,Receiver 都支持在 Intent 中传递 Bundle 数据,而 Bundle实现了 Parcelable 接口,可以在不同的进程间进行传输。
在一个进程中启动了另一个进程的 Activity,Service 和 Receiver ,可以在Bundle 中附加要传递的数据通过 Intent 发送出去。
可以看看,Android——Bundle浅析
2. 文件共享
- Windows 上,一个文件如果被加了排斥锁会导致其他线程无法对其进行访问,包括读和写;而 Android 系统基于 Linux ,使得其并发读取文件没有限制地进行,甚至允许两个线程同时对一个文件进行读写操作,尽管这样可能会出问题。
- 可以在一个进程中序列化一个对象到文件系统中,在另一个进程中反序列化恢复这个对象(注意:并不是同一个对象,只是内容相同)
- SharedPreferences 是个特例,系统对它的读 / 写有一定的缓存策略,即内存中会有一份ShardPreferences 文件的缓存,系统对他的读 / 写就变得不可靠,当面对高并发的读写访问,SharedPreferences 有很多大的几率丢失数据。因此,IPC 不建议采用 SharedPreferences
3. Messenger
Messenger是一种轻量级的 IPC 方案,它的底层实现是 AIDL ,可以在不同进程中传递 Message 对象,它一次只处理一个请求,在服务端不需要考虑线程同步的问题,服务端不存在并发执行的情形。
服务端进程:服务端创建一个 Service 来处理客户端请求,同时通过一个Handler 对象来实例化一个 Messenger 对象,然后在 Service 的 onBind 中返回这个 Messenger 对象底层的 Binder 即可。
public class MessengerService extends Service {
private static final String TAG = MessengerService.class.getSimpleName();
private class MessengerHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case Constants.MSG_FROM_CLIENT:
Log.d(TAG, "receive msg from client: msg = ["+ msg.getData().getString(Constants.MSG_KEY) + "]");
Toast.makeText(MessengerService.this, "receive msg from client: msg = [" + msg.getData().getString(Constants.MSG_KEY) + "]", Toast.LENGTH_SHORT).show();
Messenger client = msg.replyTo;
Message replyMsg = Message.obtain(null, Cons
tants.MSG_FROM_SERVICE);
Bundle bundle = new Bundle();
bundle.putString(Constants.MSG_KEY, "我已经收到你的消息,稍后回复你!");
replyMsg.setData(bundle);
try {
client.send(replyMsg);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
default:
super.handleMessage(msg);
}
}
}
private Messenger mMessenger = new Messenger(new MessengerHandler());
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
}
客户端进程:首先绑定服务端 Service ,绑定成功之后用服务端的 IBinder 对象创建一个 Messenger ,通过这个 Messenger 就可以向服务端发送消息了,消息类型是 Message 。如果需要服务端响应,则需要创建一个 Handler 并通过它来创建一个 Messenger(和服务端一样),并通过 Message 的 replyTo参数传递给服务端。服务端通过 Message 的 replyTo 参数就可以回应客户端了。
public class MainActivity extends AppCompatActivity {
private static final String TAG = MainActivity.class.getSimpleName();
private Messenger mGetReplyMessenger = new Messenger(new MessageHandler());
private Messenger mService;
private class MessageHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case Constants.MSG_FROM_SERVICE:
Log.d(TAG, "received msg form service: msg =[" + msg.getData().getString(Constants.MSG_KEY) + "]");
Toast.makeText(MainActivity.this, "receivedmsg form service: msg = [" + msg.getData().getString(Constants.MSG_KEY) + "]", Toast.LENGTH_SHORT).show();
break;
default:
super.handleMessage(msg);
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void bindService(View v) {
Intent mIntent = new Intent(this, MessengerService.class);
bindService(mIntent, mServiceConnection, Context.BIND_AUTO_CREATE);
}
public void sendMessage(View v) {
Message msg = Message.obtain(null,Constants.MSG_FROM_CLIENT);
Bundle data = new Bundle();
data.putString(Constants.MSG_KEY, "Hello! This is client.");
msg.setData(data);
msg.replyTo = mGetReplyMessenger;
try {
mService.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
protected void onDestroy() {
unbindService(mServiceConnection);
super.onDestroy();
}
private ServiceConnection mServiceConnection = new ServiceConnection() {
/**
* @param name
* @param service
*/
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mService = new Messenger(service);
Message msg = Message.obtain(null,Constants.MSG_FROM_CLIENT);
Bundle data = new Bundle();
data.putString(Constants.MSG_KEY, "Hello! This is client.");
msg.setData(data);
msg.replyTo = mGetReplyMessenger;
try {
mService.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
/**
* @param name
*/
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
}
注意:客户端和服务端是通过拿到对方的 Messenger 来发送 Message 的。只不过客户端通过 bindService onServiceConnected 而服务端通过 message.replyTo 来获得对方的 Messenger 。Messenger 中有一个 Hanlder 以串行的方式处理队列中的消息。不存在并发执行,因此我们不用考虑线程同步的问题。
4. 使用 AIDL(难点)
AIDL是Android中IPC(Inter-Process Communication)方式中的一种,AIDL是Android Interface definition language的缩写,AIDL的作用是让你可以在自己的APP里绑定一个其他APP的service,这样你的APP可以和其他APP交互。
AIDL底层也是通过Binder实现的:Android——Binder机制
Messenger 是以串行的方式处理客户端发来的消息,如果大量消息同时发送到服务端,服务端只能一个一个处理,所以大量并发请求就不适合用 Messenger ,而且Messenger 只适合传递消息,不能跨进程调用服务端的方法。AIDL 可以解决并发和跨进程调用方法的问题,要知道 Messenger 本质上也是 AIDL ,只不过系统做了封装方便上层的调用而已。
AIDL 文件支持的数据类型:
- 基本数据类型;
- String 和 CharSequence
- ArrayList ,里面的元素必须能够被 AIDL 支持;
- HashMap ,里面的元素必须能够被 AIDL 支持;
- Parcelable ,实现 Parcelable 接口的对象; 注意:如果 AIDL 文件中用到了
自定义的 Parcelable 对象,必须新建一个和它同名的 AIDL 文件。 - AIDL ,AIDL 接口本身也可以在 AIDL 文件中使用。
服务端:
服务端创建一个 Service 用来监听客户端的连接请求,然后创建一个 AIDL 文件,
将暴露给客户端的接口在这个 AIDL 文件中声明,最后在 Service 中实现这个 AIDL
接口即可。
客户端:
绑定服务端的 Service ,绑定成功后,将服务端返回的 Binder 对象转成 AIDL 接口所属的类型,然后就可以调用 AIDL 中的方法了。客户端调用远程服务的方法,被调用的方法运行在服务端的 Binder 线程池中,同时客户端的线程会被挂起,如果服务端方法执行比较耗时,就会导致客户端线程长时间阻塞,导致 ANR 。客户端的 onServiceConnected 和 onServiceDisconnected 方法都在 UI 线程中。
具体使用我们看看这篇博文:Android进阶——AIDL详解
接下来让我们看一个实例:
创建两个工程,一个作为服务端,一个作为客户端,客户端绑定服务端service,然后调用方法向服务端获取书籍列表,向服务端添加书籍。
1、服务端:
(1)创建aidl文件Book.aidl
创建后便可以在目录里看到aidl文件。
接下来定义Book类,注意Books类的包名必须与Book.aidl包名一样,但是不可与Book.aidl在同一个目录下。
Book.class的代码如下,其必须继承Parcelable接口:
package com.status