Android的IPC机制(上)
文章目录
1.1 IPC概念简介
- IPC(Inter-Process Communication),进程间通信或者跨进程通信。
- 线程:CPU调度的最小单元,是一种有限的系统资源。
- 进程:一个执行单元,比如一个程序或者一个应用。
- 一个进程可以包含一个(主线程)或多个线程,Android中主线程也叫UI线程,只能在UI线程中操作界面元素。耗时任务在主线程可能会产生ANR(Application Not Responding)错误。
1.2 Android中的多进程模式
- Android中进程间通讯方式:Binder、Socket。
- 应用可以通过多进程获取多份内存空间。
1.2.1 开启多进程模式
Android使用多进程的方式:在Menifest中为四大组件指定android:process属性。我们无法给一个线程或者一个实体类指定其运行时所在的进程。还存在一种非常规多进程方法:通过JNI在native层fork一个新进程。
举例:
<activity android:name=".MainActivity" android:launchMode="standard"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".SecondActivity" android:launchMode="standard" android:process=":remote"/> <activity android:name=".ThirdActivity" android:launchMode="standard" android:process="com.virtual.learn101101.remote"/>
- MainActivity未指定process属性,将运行在默认进程中,进程名是包名。
- SecondActivity指定process属性:
android:process=":remote"
,其中冒号的含义是在当前的进程名前加上当前的包名。并且以冒号开头的进程属于当前应用的私有进程,其他应用的组件不可以和它运行在一个进程中。- ThirdActivity指定process属性:
android:process="com.virtual.learn101101.remote"
,完整的命名方式。Android系统会为每个应用分配一个唯一的UID,具有相同UID的应用才能共享数据。两个应用运行在同一个进程中的条件:有相同的ShareUID和相同的签名。拥有相同的ShareUID和相同的签名的两个应用,如果不运行在同一个进程:它们可以互相访问对方的私有数据,比如:data目录、组件信息等;如果运行在同一个进程:除上述私有数据外还可以共享内存数据。
1.2.2 多进程模式的运行机制
多进程模式可能会出现一个问题:设置一个全局的Int类型的变量初始值为1,在MainActivity中修改其为2,但是之后在SecondActivity中读取该变量时读取到的值为1。
产生上述问题的原因是Android为每一个应用分配了一个独立的虚拟机(每个进程都分配一个独立的虚拟机),不同的虚拟机在内存分配上有各自不同的虚拟空间,这会导致在对同一个类的对象会产生多份副本,不同的虚拟机中保留各自的副本。因此,运行在不同进程的四大组件,只要它们之间通过内存共享数据时会失败,因而我们需要通过一些中间层来共享数据。
一般情况下多进程造成的问题:
- 1.静态成员和单例模式完全失效。
- **2.线程同步机制完全失效。**不是一块内存,不同进程锁的不是同一个对象。
- 3.SharedPreference可靠性降低。SP底层是通过读/写XML文件实现,并发会出现问题。
- **4.Application多次创建。**运行在同一进程的组件是属于同一虚拟机和同一Application的。
一个应用间的多进程相当于不同应用采用SharedUID模式。
补充:Android 获取进程名的工具类MyUtil.java
public class MyUtil { public static String getProcessName(Context context, int pid) { ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); List<ActivityManager.RunningAppProcessInfo> runningAppProcessInfos = activityManager.getRunningAppProcesses(); if (runningAppProcessInfos == null) { return null; } for (ActivityManager.RunningAppProcessInfo runningAppProcessInfo : runningAppProcessInfos) { if (runningAppProcessInfo.pid == pid) { return runningAppProcessInfo.processName; } } return null; } }
//使用方法 MyUtil.getProcessName(getApplicationContext(), Process.myPid());
1.3 IPC基础
- 理解Android的IPC机制,需要理解Serializable接口、Parcelable接口和Binder。Serializable接口和Parcelable接口用以完成对象序列化。我们通过Intent和Binder传输的数据对象需要实现序列化接口;同样对象持久化到存储设备或通过网络传输也需要经过序列化。
1.3.1 Serializable接口
Serializable接口是Java提供的序列化接口,是一个空接口,为对象提供标准的序列化和反序列化操作。使用Serializable接口实现序列化:我们在类中声明serialVersionUID标识后便可自动完成默认的序列化过程(非必须声明):
static final long serialVersionUID = 123456789L;
- serialVersionUID作用:只有序列化后的数据中的serialVersionUID和当前类中的serialVersionUID一致才能正常反序列化。
- 正常我们应手动指定serialVersionUID值,比如1L;也可以使用IDE根据当前类结构去生成hash值。
- 如果我们不指定serialVersionUID值,反序列化时当前类有所改变(添加或删除某些成员变量),系统会重新计算当前类的hash并赋给serialVersionUID,这会导致反序列化失败。
- 修改类名、修改成员变量的类型也会使反序列化失败。
序列化和反序列化:
//序列化 Person person = new Person("Tom",20); ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("example.txt")); out.writeObject(person); out.close(); //反序列化 ObjectInputStream in = new ObjectInputStream(new FileInputStream("example.txt")); Person person = (Person)in.readObject(); in.close();
- 静态成员变量属于类,不属于对象,不会参与序列化过程。
- 可以使用transient关键字标记不参与序列化过程的成员变量。
- 可以重写系统默认的序列化和反序列化的过程。
1.3.2 Parcelable接口
Parcelable接口是Android提供的序列化接口。
使用方法举例:
public class Person implements Parcelable { public String pName; public int pAge; public boolean pState; public Info pInfo; public Person(String pName, int pAge, boolean pState, Info pInfo) { this.pName = pName; this.pAge = pAge; this.pState = pState; this.pInfo = pInfo; } private Person(Parcel in) { pName = in.readString(); pAge = in.readInt(); pState = in.readByte() != 0; pInfo = in.readParcelable(Thread.currentThread().getContextClassLoader()); } public static final Creator<Person> CREATOR = new Creator<Person>() { @Override public Person createFromParcel(Parcel in) { return new Person(in); } @Override public Person[] newArray(int size) { return new Person[size]; } }; @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(pName); dest.writeInt(pAge); dest.writeByte((byte) (pState ? 1 : 0)); dest.writeParcelable(pInfo, 0); } }
序列化功能writeToParcel方法实现;反序列化功能CREATOR实现。
方法 功能 标记位 writeToParcel(Parcel dest, int flags) 将当前对象写入序列化结构中,其中flags标识有两种值: 0或者1。为1时标识当前对象需要作为返回值返回,不能立即释放资源。几平所有情况都为0。 PARCELABLE_ WRITE_RETURN_ VALUE describeContents 返回当前对象的内容描述。如果含有文件描述符,返回1,否则返回0。几平所有情况都为0。 CONTENTS FILE_ DESCRIPTOR 例子中的pInfo是另一个可序列化对象,反序列化过程需要传递当前线程的上下文类的加载器:Thread.currentThread().getContextClassLoader()。
Android系统中有许多类实现了Parcelabel接口,它们是可以直接序列化的,比如:Intent、Bundle、Bitmap等。,当Map和List里面的元素都是可序列化时,Map和List也可以序列化。
1.3.3 Serializable接口和Parcelable接口对比
- Serializable接口是Java中的序列化接口,使用简单但开销大,序列化和反序列化过程需要大量的IO操作。
- Parcelable接口是Android中序列化方式,更适合在Android平台上使用,虽然使用稍复杂,但是效率高。
- 在将对象序列化到存储设备或通过网络传输的时候,通过Parcelable接口实现会复杂(但可以),推荐Serializable接口实现。
1.3.4 Binder
多个角度看Binder:
- 直观上,Binder是Android中的一个类,实现了IBinder接口。
- IPC角度,Binder是Android中的一种跨进程通信方式,可以理解为一种虚拟物理设备,它的设备驱动是/dev/binder(在linux中没有)。
- Android Framework角度,Binder是ServiceManager连接各种Manager(ActivityManager、WindowsManager,等等)和相应ManagerService的桥梁。
- Android应用层角度,Binder是客户端和服务端进行通信的媒介。服务端会返回一个包含了服务端业务调用的Binder对象,通过这个Binder对象,客户端就可以获取服务端提供的服务或者数据,这里的服务包含普通服务和基于AIDL的服务。
Android开发中,Binder主要用在Service中,包括AIDL和Messenger。
Binder的工作机制:
通过AIDL文件实现Binder的示例:
我们继续使用上面实现Parcelable接口的Person实体,我们要在客户端提供两个方法:1.查看人员列表;2.添加新的成员。
1.创建Person.aidl文件
// Person.aidl package com.virtual.aidltest; //Person类在AIDL中的声明 parcelable Person;
2.创建IPersonManager.aidl。
// IPersonManager.aidl package com.virtual.aidltest; /** * Person.java实体类要自己引入,注意不是Person.aidl */ import com.virtual.aidltest.Person; //接口形式声明,包含两个方法 interface IPersonManager { List<Person> getPersonList(); void addPerson(in Person person); }
3.创建成功后的目录结构:
4.在客户端使用该Binder:
final List<Person> mPersonList = new ArrayList<>(); //系统生成的IPersonManager.java中的内部类Stub就是Binder类。 private final IPersonManager.Stub mBinder = new IPersonManager.Stub() { @Override public List<Person> getPersonList() throws RemoteException { synchronized (mPersonList) { return mPersonList; } } @Override public void addPerson(Person person) throws RemoteException { synchronized (mPersonList) { if (!mPersonList.contains(person)) { mPersonList.add(person); } } } };
服务端将mBinder传给客户端使用即可。
不通过AIDL文件实现Binder的示例:(参考AIDL生成的java文件)
使用AIDL文件实现Binder是系统提供的简便方法。可以参考生成的文件不使用AIDL文件直接实现Binder。
1.首先我们声明一个AIDL性质的接口:IPersonManager。
/** * 声明一个AIDL性质的接口:继承IInterface接口即可。 */ public interface IPersonManager extends IInterface { /** * DESCRIPTOR:Binder的唯一标识,一般用当前Binder的类名表示。 * TRANSACTION_XXXX:使用整形id标识方法,id标识用于在transact过程中区分客户端请求的是哪一个方法。 */ String DESCRIPTOR = "com.virtual.learn101101.IPersonManager"; int TRANSACTION_getPersonList = IBinder.FIRST_CALL_TRANSACTION + 0; int TRANSACTION_addPerson = IBinder.FIRST_CALL_TRANSACTION + 1; /** * 抽象两个方法: * 1.获取人员列表。 * 2.一个是往人员列表中添加新成员。 */ List<Person> getPersonList() throws RemoteException; void addPerson(Person person) throws RemoteException; }
2.实现Binder类PersonManagerImpl(相当于系统生成的IPersonManager.java中的Stub内部类),和Binder类中的代理类Proxy:
public class PersonManagerImpl extends Binder implements IPersonManager { private final List<Person> mPersonList; public PersonManagerImpl(List<Person> mPersonList, List<Person> personList) { /** * attachInterface方法是将IInterface的实现与Binder相连。 * 这里是将PersonManagerImpl与Binder关联。 */ this.attachInterface(this, DESCRIPTOR); this.mPersonList = mPersonList; } /** * asInterface方法用于将服务端的Binder对象转换成客户端所需的AIDL接口类型的对象。 * 本例子中所需的AIDL接口类型的对象为IPersonManager。 * 转换是区分进程的: * 1.客户端和服务端位于同一进程:返回服务端的IPersonManager对象本身。 * 2.客户端和服务端位于不同进程:返回经Proxy封装后的IPersonManager对象。 */ public static IPersonManager asInterface(IBinder obj) { //Binder不存在,返回null if ((obj == null)) { return null; } /** * 请求本地接口。 * 之前调用过attachInterface(this, DESCRIPTOR)将IPersonManager与Binder关联; * 通过queryLocalInterface(DESCRIPTOR)尝试将IPersonManager取回。 */ IInterface iin = obj.queryLocalInterface(DESCRIPTOR); /** * 如果iin不为null且类型为IPersonManager;说明客户端和服务端位于同一进程,直接返回。 */ if (((null != iin) && (iin instanceof IPersonManager))) { return ((IPersonManager) iin); } /** * 客户端和服务端位于不同进程,通过代理返回。 */ return new PersonManagerImpl.Proxy(obj); } /** * 返回当前Binder对象。 */ @Override public IBinder asBinder() { return this; } /** * 服务端onTransact */ @Override protected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException { switch (code) { case INTERFACE_TRANSACTION: { reply.writeString(DESCRIPTOR); return true; } case TRANSACTION_getPersonList: { data.enforceInterface(DESCRIPTOR); List<Person> result = this.getPersonList(); reply.writeNoException(); reply.writeTypedList(result); return true; } case TRANSACTION_addPerson: { data.enforceInterface(DESCRIPTOR); Person arg0; if ((0 != data.readInt())) { arg0 = Person.CREATOR.createFromParcel(data); } else { arg0 = null; } this.addPerson(arg0); reply.writeNoException(); return true; } default: } return super.onTransact(code, data, reply, flags); } /** * 运行在客户端的代理 */ private static class Proxy implements IPersonManager { private IBinder mRemote; public Proxy(IBinder remote) { mRemote = remote; } @Override public IBinder asBinder() { return mRemote; } /** * 注意内部类这里要实现获取Binder的唯一标识的方法。 * 外部类继承Binder,构造器调用attachInterface方法会自带getInterfaceDescriptor。 * 内部类未继承Binder。 */ String getInterfaceDescriptor() { return DESCRIPTOR; } /** * 运行在客户端的两个方法: * 创建输入型的Parcel对象data、reply;方法参数写入data(如果有) * 接着调用transact方法发起RPC(远程过程调用)请求,挂起当前线程; * 然后服务端onTransact方法调用; * RPC返回后,当前线程继续,并从reply中取出RPC过程的返回结果,再返回该结果。 */ @Override public List<Person> getPersonList() throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); List<Person> result; try { data.writeInterfaceToken(DESCRIPTOR); mRemote.transact(TRANSACTION_getPersonList, data, reply, 0); reply.readException(); result = reply.createTypedArrayList(Person.CREATOR); } finally { reply.recycle(); data.recycle(); } return result; } @Override public void addPerson(Person person) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); try { data.writeInterfaceToken(DESCRIPTOR); if (null != person) { data.writeInt(1); person.writeToParcel(data, 0); } else { data.writeInt(0); } mRemote.transact(TRANSACTION_addPerson, data, reply, 0); reply.readException(); } finally { reply.recycle(); data.recycle(); } } } /** * 服务端两种方法的实现 */ @Override public List<Person> getPersonList() throws RemoteException { synchronized (mPersonList) { return mPersonList; } } @Override public void addPerson(Person person) throws RemoteException { synchronized (mPersonList) { if (!mPersonList.contains(person)) { mPersonList.add(person); } } } }
linkToDeath和unlinkToDeath:
Binder运行在服务端,如果服务端进程异常终止,客户端到服务端的Binder连接断裂,会导致远程调用失败。如果不知道Binder连接断裂,挂起的客户端会受到影响。Binder提供了linkToDeath和unlinkToDeath这两个方法解决这个问题。通过linkToDeath可以设置一个死亡代理,当Binder连接断裂时会收到通知,接着可以重发连接请求恢复连接。
声明DeathRecipient对象:(当binder死亡时,系统会回调binderDied方法)
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() { @Override public void binderDied() { if (null == mPersonManager) { return; } mPersonManager.asBinder().unlinkToDeath(mDeathRecipient, 0); mPersonManager = null; // TODO 重新绑定Service } };
在客户端绑定远程服务成功后,给binder设置死亡代理。
IPersonManager mService=IPersonManager.Stub.asInterface(mBinder); mBinder.linkToDeath(mDeathRecipient,0);
通过Binder的方法isBinderAlive方法也可以判断Binder是否死亡。