一、IPC含义
IPC Inter-Process Communication.含义为进程间通信或者跨进程通信,是指两个进程之间进行数据交换的过程。
二、Android中的多进程模式
在Android中使用多进程只有一种方法:给四大组件(Activity,Service,Receiver,ContentProvider)在AndroidMenifest中制定android:process属性
android:process设定:remote和全称的差别“
- :方式会在名字前附上包名,当前应用私有进程
- 全称指定可以通过ShareUID的方式跟它跑在同一个进程中
使用多进程会有下面几个方面的问题:
- 静态成员和单例模式完全失效
- 线程同步机制完全失效
- SharePreference的可靠性下降(SharePreference不支持两个进程同时去执行写操作)
- Application会多次创建
三、IPC的基础概念
- 当通过Intent和和Binder传输数据时,需要用到Serializable和Parcelable把数据序列化
- Binder
- Binder实现了IBinder接口
- 从IPC角度来说Binder是跨进程通信的一种方式
- 从framework角度来说,Binder是ServiceManager连接各种Manager(ActivityManager、WindowManger等)和相应ManagerService的桥梁
- 从Android应用层来说,Binder是客户端和服务器端进行通信的媒介
- Binder主要用在Service中,包括AIDL和Messenger(底层其实是AIDL)
四、Android中的IPC方式
跨进程通信方式有很多:Intent传递Bundle数据,共享文件,Binder,Intent传递Bundle数据,ContentProvider,Socket.
下面一一来看下每个方式。
1. Intent附加extras传递信息
三大组件(Activity,Service,Receiver)都支持Intent传递Bundle数据(Bundle有实现Parcelable接口)
Intent 方式较为简单,暂不包含在示例中
2. 通过共享文件的方式共享数据
- file文件
- android基于Linux对文件并发读写没有限制
- 适用于对数据同步要求不高的进程之间的通信
- 需要处理文件的并发读写问题,特别是并发写(尽量避免或多线程同步来限制多个进程写操作)
- SharedPreferences(不建议用在跨进程间通讯)
- 目录位于data/data/package name/shared_prefs目录下
- 本质上也是文件
- 系统对它的读写有一定的缓存策略,即内存中会有一份缓存,多进模式下读写不可靠
file文件在并发读写时会有问题,而SharedPreferences多进模式下读写不可靠,暂不包含在示例中
3. Messenger - 轻量级的IPC方案
Messenger:信使,可以在不同进程中传递Message对象。是一种轻量级的IPC方案。底层实现也是AIDL,不过系统做了封装。
- Messenger的底层其实就是AIDL,它对AIDL做了封装,可以更简洁的进行进程间通信
- 以串行方式,一个一个处理客户端发来的消息。不会有线程同步的问题
- 不适合于有大量并发请求的场景
实现Messenger的步骤:
1)服务器端进程
- 创建Service来处理客户端的连接请求
- 创建Handler并通过它创建一个Messenger对象
- Service的onBind中返回Messenger对底层的Binder
Service放在独立的process运行
<service
android:name=".messenger.MessengerService"
android:enabled="true"
android:exported="true"
android:process=":remote">
<intent-filter>
<action android:name="com.vv.ipc.messenger.MessengerService.launch" />
</intent-filter>
</service>
核心Code:
public class MessengerService extends Service {
private static final String TAG = MessengerService.class.getSimpleName();
// messenger used to return mMessenger.getBinder when bind Service
private final Messenger mMessenger = new Messenger(new MessengerHandler());
// handler to deal with msg from client
private static class MessengerHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case Const.MSG_FROM_CLIENT:
dealWithClientMsg(msg);
break;
default:
super.handleMessage(msg);
}
}
}
public MessengerService() {
}
private static void dealWithClientMsg(Message msg) {
Log.d(TAG, "get msg :" + msg.getData().getString(Const.KEY_FROM_CLIENT));
SystemClock.sleep(2000);
Messenger client = msg.replyTo;
Message reply = Message.obtain(null, Const.MSG_FROM_SERVICE);
Bundle bundle = new Bundle();
bundle.putString(Const.KEY_FROM_SERVICE, "service reply at " + Const.DATA_FORMAT.format(new Date()));
reply.setData(bundle);
try {
client.send(reply);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
}
2)客户端进程
- bindService
- bind成功后拿到IBinder对象,创建一个Messenger
- 通过Messenger就可以像服务器发送消息,消息类型为Message对象。
- 如果服务器端要回应客户端,就和服务器端一样,传给服务器端一个Messenger
- 创一个Handler并创建一个新的Messenger
- 把Messenger通过Message的replayTo参数传给服务端。
- 服务端通过replyTo参数(Messenger)回应客户端。
核心Code:
public class MessengerActivity extends AppCompatActivity {
private static final String TAG = MessengerActivity.class.getSimpleName();
private static final String SERVICE_URI = "com.vv.ipc.messenger.MessengerService.launch";
private TextView mTVShow;
// service and reply messenger
private Messenger mService;
private Messenger mGetReplyMessenger = new Messenger(new MessengerHandler());
// handler to update ui
private class MessengerHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case Const.MSG_FROM_SERVICE:
mTVShow.append(msg.getData().getString(Const.KEY_FROM_SERVICE) + "\r\n");
break;
default:
super.handleMessage(msg);
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_messenger);
initView();
// bind service : after 5.0 must follow
// https://developer.android.com/google/play/billing/billing_integrate.html#billing-requests
Intent intent = new Intent(SERVICE_URI);
intent.setPackage(getPackageName());
bindService(intent, mServiceConn, Context.BIND_AUTO_CREATE);
}
private void initView() {
mTVShow = (TextView) findViewById(R.id.show);
mTVShow.setMovementMethod(ScrollingMovementMethod.getInstance());
}
private ServiceConnection mServiceConn = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
mService = new Messenger(service);
Log.d(TAG, "bind service");
}
public void onServiceDisconnected(ComponentName className) {
}
};
public void sendMsgToService(View v) {
if(mService == null){
Log.w(TAG, "mService == null");
return;
}
Message msg = Message.obtain(null, Const.MSG_FROM_CLIENT);
Bundle data = new Bundle();
data.putString(Const.KEY_FROM_CLIENT, "hello, I' client." + Const.DATA_FORMAT.format(new Date()));
msg.setData(data);
msg.replyTo = mGetReplyMessenger;
try {
mService.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onDestroy() {
super.onDestroy();
if (mService != null) {
unbindService(mServiceConn);
}
}
}
Messenger是轻量级的进程间通信,以串行方式处理客户端发来的消息。不适合于有大量并发请求的场景。
主要为了传递消息,如果要跨进程调用服务器端的方法,可以使用AIDL来实现跨进程的方法调用。
4. AIDL - 最常用的进程间通讯方式
Messenger是串行的方式处理消息,如果大量的消息同时发送的情景就不太合适了。但是我们可以使用AIDL来实现跨进程的方法调用。
AIDL是最常用的进程间通讯方式,是日常开发中涉及进程间通信是的首选。
AIDL进行进程间通讯的流程
1)服务器端进程
- 创建一个Service用来监听客户端的连接请求
- 创建AIDL文件,将透给客户端的接口在AIDL中声明
- Service中实现这个AIDL接口
服务器端功能说明:
- 获取书单
- 添加书
- 注册新书通知
- 服务器端的方法本身就运行在服务器端的Binder线程池中,所以服务器端方法本身就可以执行大量耗时操作。
- 客户端可以在非UI线程中调用
- 切记不要在服务器方法中开线程去进行异步任务,除非你明确知道自己在干什么。
- 例如 getBookList 接口
- 远程服务器端需要调用客户端的listener中的方法时,被调用的方法运行在客户端的Binder线程池中。
- 确保调用是在服务器端非UI线程中
- 可以在服务器端中调用客户端的耗时方法。负责服务器无法响应
- 例如: 服务器端的 listener.onNewBookArrived
- Linsener接口的unregister需要用到RemoteCallbackList
- 通过Binder传递到服务器端后,会产生两个全新的对象。对象是不能跨进程传输的,对象的跨进程传输本质上都是反序列化的过程。
- RemoteCallbackList是系统专门提供的用于删除跨进程listener的接口。
-
RemoteCallbackList无法像List一样操作。遍历需要beginBroadcast和finishBroadcast配对使用。哪怕是仅获取元素个数。
final int N = mListenerList.beginBroadcast(); mListenerList.finishBroadcast(); Log.d(TAG, "registerListener, current size:" + N);
- 为了程序的健壮性,服务器进程意外停止需要重新连接服务。Binder是可能意外死亡的。
-
方案一:设定DeathRecipient. 它运行在Client的Binder线程池,不能访问UI
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() { @Override public void binderDied() { Log.d(TAG, "Binder died, tid = " + Thread.currentThread().getName()); if (mRemoteBookManager == null) { return; } mRemoteBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0); mRemoteBookManager = null; // release resource or rebind server here } };
-
方案二:onServiceDisconnected中重新连接远程服务器。 运行在UI线程,可以访问UI
public void onServiceDisconnected(ComponentName className) { mRemoteBookManager = null; Log.d(TAG, "onServiceDisconnected, tid = " + Thread.currentThread().getName()); }
-
- AIDL中使用权限验证
- 默认情况下远程Service是任何人都可以连接。必须给服务器加入权限验证功能。
-
方案一:onBind中进行验证。验证方式有很多,例如使用Permission验证方式。Messenger通讯也可以使用此方案
<permission android:name="com.vv.ipc.permission.ACCESS_BOOK_SERVICE" android:protectionLevel="normal" /> <uses-permission android:name="com.vv.ipc.permission.ACCESS_BOOK_SERVICE" /> @Override public IBinder onBind(Intent intent) { int check = checkCallingOrSelfPermission(Const.PERMISSION_ACCESS_BOOK_SERVICE); Log.d(TAG, "onBind check = " + check); if (check == PackageManager.PERMISSION_DENIED) { Log.d(TAG, "onBind permission deny"); return null; } return mBinder; }
- 方案二:onTransact中进行验证。验证方式可以使用上面的Permission,也可以拿到Client端的Uid,Pid,用package name等来验证
核心Code
2)客户端
- 绑定Service
- bind成功后将Binder对象转成AIDL接口类型
- 调用AIDL接口的方法
客户端功能说明:
- 获取书单
- 添加书
- 注册新书通知
核心Code: BookManagerActivity.java
3)AIDL接口创建 和 AIDL接口的实现(远程服务器端Service的实现)
- 支援的数据类型:java基本类型,String,List(ArrayList),Map(HashMap),Parcelable,AIDL接口
- 自定义Parcelable对象和AIDL对象必须要显式import
- 自定义Parcelable对象,必须新建一个和它同名的AIDL文件,并在其中声明Parcelable类型。这两个文件要在同一个package下面
- AIDL除了基本数据类型,其他类型的参数必须标上方向:in,out, inout
- AIDL接口只支持方法,不支持声明静态变量
功能逻辑:
- Service 提供获取书单,管理书单接口。
- Service 提供接口来监听和解除书单变化的通知
知识点:
- CopyOnWriteArrayList 支持并发读写
- AIDL支持的是抽象的List不支持CopyOnWriteArrayList,但是因为List只是一个接口。虽然服务器端返回的是CopyOnWriteArrayList,但是Binder会按照List规范去访问数据并最终形成一个新的ArrayList传递给客户端
- 和此类似的还有ConcurrentHashMap
- RemoteCallbackList
管理AIDL接口 - 跨进程通信客户端的同一个对象在服务器端生成不同的对象,但是新生成的对象底层的Binder对象是同一个
- RemoteCallbackList内部自动实现了线程同步的功能,不需要额外的线程同步功能
- 客户端进程终止后,自动移除客户端注册的listener
核心Code
// Book.aidl
package com.vv.ipc.book;
parcelable Book;
// IBookManager.aidl
package com.vv.ipc;
import com.vv.ipc.book.Book;
import com.vv.ipc.INewBookListener;
interface IBookManager {
// get all book list
List<Book> getBookList();
// add one book
void addBook(in Book book);
// get book list size
int getBookSize();
// register and unregister new book listener
void registerListener(INewBookListener listener);
void unregisterListener(INewBookListener listener);
}
// INewBookListener.aidl
package com.vv.ipc;
import com.vv.ipc.book.Book;
interface INewBookListener {
void onNewBookArrived(in Book newBook);
}
5. ContentProvider
ContentProvider天生支持跨进程数据传递,是Android中提供的专门用于不同应用间进行数据共享的方式。
和Messenger一样,底层实现同样也是Binder.但是使用过程比AIDL简单许多,系统有做封装。
6. Socket
Socket网络通讯也可以实现数据传递
五、Binder连接池
大量的业务模块都需要使用AIDL来进行进程通讯的时候,不可能每个业务模块创建一个Service。
我们要减少Service的数量,将所有的AIDL放在同一个Service中去管理。
Binder连接池的工作机制:
- 每个业务模块创建自己的AIDL接口并实现。
- 每个业务模块之间不能有耦合
- 单独实现细节后,向服务器提供自己的唯一标识和其对应的Binder对象
- 服务器端只需要一个Service,服务器提供一个queryBinder接口,依据业务模块的特征来返回相应的Binder对象
- 不同业务模块拿到所需Binder后,可以进行远程方法调用。
Binder连接池的主要作用就是将每个业务模块的Binder请求统一转发到远程Service中执行,从而避免重复创建Service的过程。
核心Code
六、选择合适的IPC方式
七、Binder源码
Android进程间的通讯没有沿用Linux的原有的通讯模式,而是采用新的通讯模式Binder.
Binder的优势: