本文部分内容参照《Android开发艺术探索》
IPC是什么?
IPC全称为Inter-Process Communication,译为“跨进程通信”,在这里要着重提一下,进程(Process)和线程(Thread)是完全不一样的概念,两者不能混淆。在操作系统的概念里,进程是CPU的调度单元,而线程是CPU的最小调度单位,一个进程包含一个或一组线程,两者为包含关系,具体两者的区别还请查阅相关资料。
一:增加App能使用的内存大小,缓解OOM问题
Android为了保证整个系统的稳定性,确保在一个应用崩溃时不会影响到其他正在运行的应用,Android会为每一个应用都会分配一个虚拟机(DVM),更加确切的说,Android其实为每一个进程都分配了一个虚拟机,当应用所在的进程崩溃时不会影响到其他的进程。
为了保证Android内存不被某一个应用进程所在的虚拟机大量占用,导致其他应用没有可用的内存资源,Android系统会对进程能够分配的内存大小进行限制,这个数值每个手机厂商的标准都有所不同。
你可以通过这样获取你的可用内存大小
ActivityManager activityManager = (ActivityManager) this.getSystemService(Activity.ACTIVITY_SERVICE);
ActivityManager.MemoryInfo info = new ActivityManager.MemoryInfo();
activityManager.getMemoryInfo(info);
System.out.println(info.availMem);
你也可以通过在清单中添加属性,调整可以申请到的内存,但仍不能超出已规定的限制
android:largeHeap="true"
当某个进程所占用的内存超出了这个内存限制之后,就会因为申请不到足够的内存而出现OOM错误。但如果将一个应用分到几个进程上运行以后,就相当于增加了应用可使用的内存大小,避免出现OOM错误。
二:应用保活策略
为了保证Android系统能够平稳的运行,保证有足够的剩余内存分配,Android系统必须及时杀死无用进程或者优先级较低的进程,来省出稀缺的内存空间。这就像Java的垃圾收集一样,只不过Java是回收垃圾,Android是杀死进程。
而某些应用则为了自身的需求需要保证一直运行,不能简单的被杀死,那就需要一定的保活策略。其中的几种常用策略就需要用到多进程,如双进程守护策略、关联服务进程策略等,想要深入了解进程保活相关知识,可以阅读此文章关于 Android 进程保活,你所需要知道的一切
Android想要开启多进程非常简单,只需要在清单里,为需要运行在另外进程上的四大组件指定属性
android:process=":name"
也可以这样设置
android:process="com.aze.demo.name"
虽然开启多进程简单,但是使用起来却并不是那么简单。
他会导致以下问题:
假设有两个Activity,其中一个Activity运行在另一个进程上,因为又开启了一个进程,所以系统分别为两个进程开启了一个虚拟机,那么你完全可以把这两个进程看成不同的两个应用,因为它们运行在不同的内存块上,尽管它们是同一个应用的两个部分。
而这两个Activity调用的Java类虽然看起来是相同的,但是其实两个Activity调用的只是同一个类的副本,数据是不能以正常手段传输的。但要想整个应用平稳高效的运行,每个进程不可能只单干,而不相互交流。要想实现这个交流就要用到IPC机制。
Bundle可以翻译为“捆,束”,它可以绑定“一捆”Parcelable数据,通过Intent在四大组件之间传输,但是它是通过什么原理实现跨进程传输的呢?
Parcelable是Android基于自身系统的特性设计的对象序列化接口,能够将对象序列化为二进制字节流,存储到系统的共享内存区,需要这个对象的进程从共享内存区将其取出后,经过反序列化,将二进制字节流转化为对象。
以下图示显示了这一过程:
一个进程作为Server端,另一个进程作为Client端,两者预先协商好一个端口,通过TCP或UDP方式互相传送各种数据流,通过网络的途径实现跨进程通信。
Messenger、ContentProvider、AIDL这几种主要的IPC方式,底层都是通过Binder实现的,那么Binder是什么呢?
我们首先来看Google提供的Android系统的平台架构图:
可以看出Binder在Android平台架构的Linux驱动层,Binder是一个没有对应的硬件设备,专门为IPC设计的底层驱动,它的位置为:
/dev/binder
Binder是一个非常深入的话题,想要更加了解它,可以去看Gityuan的Binder概述,本文只从使用的角度上去介绍Binder。
我们以Android接口定义语言(AIDL)来分析Binder:
AIDL可以稍加配置由IDE自动生成,本文不再介绍AIDL的生成方式。
前面提到了Binder是基于C/S架构的,那么把服务端与客户端分开介绍。
服务端:
package com.aze.demo;
public interface IAddInterface extends android.os.IInterface {
public static abstract class Stub extends android.os.Binder implements com.aze.demo.IAddInterface {
//DESCRIPTOR 是区分Binder的唯一标识
private static final java.lang.String DESCRIPTOR = "com.aze.demo.IAddInterface";
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* 将IBinder对象转换为IAddInterface接口
* 如果服务端与客户端不在同一进程则使用代理
*/
public static com.aze.demo.IAddInterface asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.aze.demo.IAddInterface))) {
return ((com.aze.demo.IAddInterface) iin);
}
return new com.aze.demo.demo.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 {
java.lang.String descriptor = DESCRIPTOR;
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(descriptor);
return true;
}
case TRANSACTION_add: {
data.enforceInterface(descriptor);
int _arg0;
_arg0 = data.readInt();
int _arg1;
_arg1 = data.readInt();
int _result = this.add(_arg0, _arg1);
reply.writeNoException();
reply.writeInt(_result);
return true;
}
default: {
return super.onTransact(code, data, reply, flags);
}
}
}
private static class Proxy implements com.aze.demo.IAddInterface {
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote) {
mRemote = remote;
}
/**
* 返回当前的Binder对象
*/
@Override
public android.os.IBinder asBinder() {
return mRemote;
}
public java.lang.String getInterfaceDescriptor() {
return DESCRIPTOR;
}
@Override
public int add(int a, int b) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
int _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeInt(a);
_data.writeInt(b);
mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
_reply.readException();
_result = _reply.readInt();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
static final int TRANSACTION_add = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
}
public int add(int a, int b) throws android.os.RemoteException;
}
onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)方法特别说一下,第一个参数可以分辨出客户端是调用的哪一个方法,具体可以参见
static final int TRANSACTION_add = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
第二个参数是需要向调用方法中传递的参数,执行完毕后如果有返回值,就写入第三个参数reply中。
我们实现接口以后,我们还需要向客户端暴露这个接口,这里扩展Service并实现onBinder使其返回Binder对象。
public class IAddService extends Service {
public final IAddInterface.Stub binder=new IAddInterface.Stub() {
@Override
public int add(int a, int b) throws RemoteException {
return a+b;
}
};
@Override
public IBinder onBind(Intent intent) {
return binder;
}
}
客户端:
当客户端(如 Activity)调用 bindService() 以连接此服务时,客户端的 onServiceConnected() 回调会接收服务的 onBind() 方法返回的Binder的对象实例。
注意:如果服务端设置了检查权限的需求,客户端首先要有对AIDL的访问权限,一般需要在配置清单里添加相关权限。
public class BinderActivity extends AppCompatActivity {
IAddInterface iAddInterface;
private ServiceConnection serviceConnection=new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
iAddInterface=IAddInterface.Stub.asInterface(service);
try {
iAddInterface.add(1,2); //调用远程方法
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent=new Intent(this,IAddService.class);
bindService(intent,serviceConnection, Context.BIND_AUTO_CREATE);
}
}
整个过程为:
Client进程绑定服务,获取返回的Binder对象,传入想要调用的方法与需要的参数,Binder将这些数据传输给Service进程,Service进程验证Client权限后,通过onTransact方法验证传入的数据是否合法,验证无误后调用相应方法,将任务提交给线程池,任务完成后将结果返回给Binder,让Binder转交给Client。至此,Client与Service进程通过Binder完成了IPC。
两个进程共同访问同一个物理硬盘上的文件,把这个文件作为信息传输的“中转站”,可以通过序列化对象到该文件,另一个进程对文件的数据反序列化获得相应对象。