进程间通信即IPC。首先我们要理解什么是进程?什么是线程?线程是cpu的调度的最小资源,同时是一种有限的资源。进程则是一个执行单元,在Android中一般指一个应用程序(也有多进程的程序)。一个进程中可以有多个线程,是包含关系。在多进程的应用程序中,就出现多进程通信的方式。对于Android来说多进程通信一般有Binder、messager、socket、文件、ContentProvider等方式。
多进程开启方式
Android中的四大组件(Activity、BoardcastReceiver、Service、ContentProvider)都可以使用多进程。Android中的多进程实现只有通过在AndroidMenfiest中指定android:process属性。来指定进程。还有一个中特殊方法即在jni中通过native方法fork一个进程。下面示例开启多进程
以上开启多进程中可以看到我们为ARemoteActivity和BRemoteActivity分别指定了单独的进程。进程名称分别为":remote"
和"com.demo.launchmode.remote"
分别开启两个activity在ddms中看到如下三个进程:
三个进程名不相同。需要注意的是
":remote"
和
"com.demo.launchmode.remote"
的区别。
其中以:
开头的进程为当前应用的私有进程。其他应用不可以和他在同一进程中。而不以:
开头,指定完整进程名的为全局进程其他应用可以通过shareUid泡在相同的进程中去。
多进程的特点
开启多进程很简单,但是在使用中会发现多进程中间的数据不共享等问题。Android会为每个进程分配一个独立的虚拟机。不同的虚拟机分配不同的内存和地址空间。这会导致多进程中访问同一个类会产生多个副本。
一、优点
- 分配不同的内存,使得同一个应用可以使用的内存增大,
- 可以执行一些耗时操作而不影响主进程。
二、缺点:一般来说多进程会造成:
- 单例模式和静态成员变量完全失效。
原因: - sharedpreferences可靠性降低。
- 线程同步机制失效。
- application会创建多次。
多进程通信方式
intent
文件
ContentProvider
messager
messager切记是串行的
aidl
因为通常直接使用aidl进行通信,而且ContentProvider、Messenger的底层都是通过binder实现的。我们下面详细介绍一下Binder:
Binder基础
一、数据传递格式要求
AIDL支持的数据格式有以下几种:
- 基本数据类型(int、double、float、boolean、long、char等)
- String和CharSequence
- Parcelable型数据
- list只支持ArrayList,Map只支持HashMap,其中包含的数据类型是以上种类型
- AIDL接口类型
使用Parcelable和AIDL类型要导包,另外AIDL中使用Parcelable数据类型必须生成相应的aidl文件。
// Book.aidl
package com.demo.remote.beans;
parcelable Book ;
二、定向Tag
在使用AIDL中使用数据,除基本数据和AIDL之外,都要为输入参数添加定向tag。定向Tag包含in、out、inout三种。它们区别如下:
- in 表示输入型参数。即
void addBook(in Book book);
说明当客户端调用的addBook(book)时,传入的参数book,如果service服务端对book对象的属性改变是不会改变客户点的book对象的属性. - out 输出型参数。即
void addBook(out Book book);
当客户端调用addBook(book)时,传入的参数到达服务端是,是一个新的空对象,当服务端修改了该对象时,客户端本地的book对象也会随之改变。 - inout 输入输出型参数。及in和out两种方式的集合体。即
void addBook(inout Book book);
说明当客户端调用的addBook(book)时,传入的参数book对象,会完整到达服务端,当服务端修改传入的book对象时,客户端的book对象也会随之改变。
总结:aidl的定向tag只能作用与aidl方法中的输入参数,返回参数不能使用定向tag。区别就是输入的参数能否完整到达服务端,并且服务端对入参的修改,是否会影响到客户端。具体如下表:
参数名称 | 参数能否完整到达服务端 | 服务端修改是否印象客户端 |
---|---|---|
in | 是 | 否 |
out | 否 | 是 |
inout | 是 | 是 |
三、RemoteCallbackList
当我们需要监听service端某个数据发生变化,我们一般需要在service端注册监听完成。当我们不需要时,取消监听注册。然而实际运用过程中发现不是这样的,注册监听可以成功,但是取消注册时,会因为无法找到该listener而解注册失败。因为当我们在跨进程调用的时候服务端接收到的已经是一个新生成的对象。用原有的对象去解除注册肯定找不到对象而失败。这个时候我们要用RemoteCallbackList ,定义声明如下:
class RemoteCallbackList
它适用于管理任何aidl接口。以下是RemoteCallbackList核心源码分析
public boolean register(E callback, Object cookie) {
synchronized (mCallbacks) {
if (mKilled) {
return false;
}
// Flag unusual case that could be caused by a leak. b/36778087
logExcessiveCallbacks();
//获取注册监听本身的binder 对象
IBinder binder = callback.asBinder();
try {
Callback cb = new Callback(callback, cookie);
binder.linkToDeath(cb, 0);
//将回调本身的binder对象作为key值,回调作为value存入map集合中,缓存起来。
mCallbacks.put(binder, cb);
return true;
} catch (RemoteException e) {
return false;
}
}
}
public boolean unregister(E callback) {
synchronized (mCallbacks) {
Callback cb = mCallbacks.remove(callback.asBinder());
if (cb != null) {
cb.mCallback.asBinder().unlinkToDeath(cb, 0);
return true;
}
return false;
}
}
在RemoteCallbackList的注册方法中,会将回调listener的binder对象作为key存入map集合中。我们知道在跨进程中binder是我们跨进程的基础。当我们移除注册的时候,根据回调本身的binder对象找出新生成的回调对象,将至移除,完成解注册过程。
四、binder在多进程中的区别
如上调用示例所示,通过binder进行通信,区分service进程是否在单独的进程中,如果在单独的进程中话,binder对象是同一个数据对象,如果在多进程中那么两者是不同进程那两者则是不同的对象。
同进程binder对比
不同进程binderhashcode
源码分析
/**
* Cast an IBinder object into an com.demo.remote.IBookManager interface,
* generating a proxy if needed.
*/
public static com.demo.remote.IBookManager asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.demo.remote.IBookManager))) {
return ((com.demo.remote.IBookManager) iin);
}
return new com.demo.remote.IBookManager.Stub.Proxy(obj);
}
上层是调用IBookManager.Stub.asInterface(service)
获取binder对象,通过查看源码发现其中如果是同进程则直接返回,如果不是则通过生成一个代理对象返回。
调用示例
//客户端
public class AidlTestActivity extends Activity {
private int count = 0;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_base_test_remote);
}
private void bindTestService() {
Intent intent = new Intent(this, AIDLTestService.class);
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
}
private IBookManager iBookManager;
ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
iBookManager = IBookManager.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
public void addBook(View view) {
if (iBookManager == null) {
bindTestService();
Log.e("AIDLTest","waite for service bind success");
return;
}
try {
count++;
iBookManager.addBook(new Book("activity" + count, count));
} catch (RemoteException e) {
e.printStackTrace();
}
}
public void showBookList(View view) {
if (iBookManager == null) {
bindTestService();
Log.e("AIDLTest","waite for service bind success");
return;
}
try {
List bookList = iBookManager.getBookList();
if (bookList==null){
return;
}
for (Book book : bookList) {
Log.e("AIDLTest","book: "+book);
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
service端
public class AIDLTestService extends Service {
private List mBookList = new ArrayList<>();
Binder iBookManager = new IBookManager.Stub() {
@Override
public void addBook(Book book) throws RemoteException {
if (mBookList == null) {
mBookList = new ArrayList<>();
}
mBookList.add(book);
}
@Override
public List getBookList() throws RemoteException {
return mBookList == null ? new ArrayList() : mBookList;
}
};
@Override
public void onCreate() {
super.onCreate();
mBookList.add(new Book("binder", 1));
mBookList.add(new Book("binder", 2));
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return iBookManager;
}
}
aidl文件
// IBookManager.aidl
package com.demo.remote;
import com.demo.remote.beans.Book;
// Declare any non-default types here with import statements
interface IBookManager {
void addBook(in Book book);
List getBookList();
}
调用方法执行线程
客户端调用服务端的方法是运行在Service的Binder线程池中,同时客户端线程会被挂起,要注意如果服务端的检查比较耗时,会导致客户端长时间挂起,要注意导致主线程ANR。反之,服务端回调客户端的方法则执行在客户端的binder线程池中。因为调用方法。结论:无论任何甲端调用对方的方法,乙端会在自己进程的binder线程池中运行,甲会挂起,要注意防止出现ANR,并且需要因为每次调用都在自己的线程中,所以如果涉及数据问题要考虑数据同步问题!!!
Binder健壮性
Binder 因为可能是会意外死亡的,所以为了健壮性我们需要当binder死亡时,重连服务。有两种方式:
- DeathRecipient
IBinder.DeathRecipient deathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
//重连服务器,在binder线程池中执行
bindTestService();
}
};
ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
try {
service.linkToDeath(deathRecipient,0);
} catch (RemoteException e) {
e.printStackTrace();
}
iBookManager = IBookManager.Stub.asInterface(service);
Log.e("binder_hash", "onServiceConnected binder : " + iBookManager);
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
- onServiceDisconnected()方法重连执行在UI线程中