开启多进程
进程名以":"开头的讲程属于当前应用的私有进程,其他应用的组件不可以和它跑在同一个进程中,而进程名不以开头的进程属于全局进程, 其他应用通过ShareUID方式可以和它跑在同一个进程中。
我们知道Andrid系统会为每个应用分配一个唯一的UID,具有相同UID的应用才能共享数据。这里要说明的是,两个应用通过ShareUID跑在同一个进程中是有要求的,需要这两个应用有相同的ShareUID并且签名相同才可以。在这种情况下,它们可以互相访问对方的私有数据,比如data目录、组件信息等,不管它们是否跑在同一个进程中。 当然如果它们跑在同一个进程中,那么除了能共享data目录、组件信息,还可以共享内存数据,或者说它们看起来就像是一个应用 的两个部分。
多进程造成问题
- 静态成员变量和单例对象完全失效
- 线程同步失效
- sp可靠性下降
- Application多次创建
IPC方式
- 使用Bundle
- 共享文件(sp不可靠,系统对sp的读写做了缓存策略)
- Binder
- ContentProvider,天生支持跨进程
- 网络通信实现数据传递,socket
- Messenger,底层由AIDL实现,一次处理一个请求(Handler)
Messenger
Messenger可以翻译为信使,顾名思义,通过它可以在不同进程中传递Mesge对象,
在Message中放入我们需要传递的数据,就可以轻松地实现数据的进程间传递了
-
服务端进程
首先,我们需要在服务端创建一个Service来处理客户端的连接请求,同时创建一个Handler并通过它来创建个Messenger对象,然后在Service的onBind中返回这个Messenger对象底层的Binder即可。
-
客户端进程
客户端进程中,首先要绑定服务端的Service, 绑定成功后用服务端返回的IBinder 对
象创建一个Messenger,通过这个Messenger 就可以向服务端发送消息了,发消息类型为
Message 对象。如果需要服务端能够回应客户端,就和服务端一*样,我们还需要创建 一个
Handler并创建一个新的Messenger, 并把这个Messenger对象通过Message的replyTo参数
传递给服务端,服务端通过这个replyTo参数就可以回应客户端。
Binder
直观来说,Binder 是Android中的一个类, 它实现了IBinder 接口。从IPC 角度来说,
Binder是Android中的一种跨进 程通信方式,Binder 还可以理解为一种虚拟的物理设备,它的设备驱动是/dev/binder,该通信方式在Linux中没有;从Android Framework角度来说Binder是ServiceManager连接各种Manager (ActivityManager、WindowManager,等等)相应ManagerService的桥梁;
从Android 应用层来说,Binder 是客户端和服务端进行通化的媒介,当bindService 的时候,服务端会返回一一个包含了服务端业务调用的Binder对象通过这个Binder对象,客户端就可以获取服务端提供的服务或者数据,这里的服务包括通服务和基于AIDL的服务。
Android开发中,Binder主要用在Service中,包括AIDL和Messenger,其中普通Serv中的Binder不涉及进程间通信,所以较为简单,无法触及Binder的核心,而Messenget底层其实是AIDL。
AIDL支持的数据类型
- 基本数据类型
- String和CharSequence
- List,只支持ArrayList,里面每个元素都必须能够被AIDL支持
- Map,只支持HashMap,里面每个元素都必须能够被AIDL支持
- Parcelable,所有实现了Parcelable接口的对象
- AIDL,所有的aidl接口本身也可以在adil文件中使用
aidl注意
-
aidl文件中使用了自定义的Parcelable对象和aidl对象必须显式import进来,同一个包路径下也要导入
-
aidl文件中使用了自定义的Parcelable对象,那必须创建一个和自定义的Parcelable对象同名的aidl文件,并且在使用到这个对象的aidl文件中声名它为Parcelable类型
parcelable User;
-
aidl文件中每个实现Parcelable接口的类都需要声名parcelable,之外除了基本数据类型,其他类型参数必须标上方向:in out inout
- in:输入型参数
- out:输出型参数
- inout:输入输出参数
interface IBookManager{ void addBook(in Book book); }
-
aidl接口只支持方法,不支持声明静态常量
-
aidl的包结构在服务端和客户端要保持一直.因为客户端需要反序列化服务端中和aidl接口相关的所有类,类的完整路径不一样反序列化失败
-
aidl方法是在服务端的Binder线程池中执行,CopyOnWriteArrayList自动线程同步,还有ConcurrentHashMap
-
RemoteCallbackList,自动线程同步,进程终止时自动移除注册的listener;beginBroadcast()和finishBroadcast()必须配对使用
-
服务端aidl接口方法全部运行在Binder线程池中,接口方法可以做耗时操作,如果调用接口的客户端是在UI线程中,那么不要再接口方法中做耗时操作否则就会导致ANR;如果明确知道必须做耗时操作,那么在调用接口的客户端中需要开启线程来调用耗时的aidl接口方法
-
客户端中的aidl接口运行在客户端的线程池中,不能做刷新UI;
-
服务端在UI线程中(比如在onBind方法中获取客户端的相关信息)调用客户端耗时的aidl接口,会导致服务端ANR
-
客户端的onServiceConnected()和onServiceDisconnected()运行在UI线程
Binder死亡、Binder连接断裂
- 两种方法收到Binder死亡通知
- 设置DeathRecipient监听–>binderDied()
- ServiceConnection–>onServiceDisconnected()方法
- 二者区别
- onServiceDisconnected()在客户端的UI线程中被回调,binderDied()在客户端的Binder线程池中被回调,binderDied()不能访问UI
- Binder提供两个配对使用方法:linkToDeath()和unlinkToDeath(),通过linkToDeath方法设置一个死亡代理,Binder死亡时收到通知,此时可以重新发起请求重新连接
private IBinder.DeathRecipient recipient = new IBinder.DeathRecipient(){
@override
public void binderDied(){
if(iBookManager == null){
return;
}
iBookManager.asBinder().unlinkToDeath(recipient,0);
iBookManager = null;
//这里重新绑定远程Service
}
};
其次,在客户端绑定远程服务成功之后,给Binder设置死亡代理
@Override
public void onServiceConnected(ComponentName name, IBinder binder) {
iBookManager = IBookManager.Stub.asInterface(binder);
binder.linkToDeath(recipient,0);
}
aidl权限验证
- 在onBind()中通过自定义权限验证,没有权限直接返回null
- 通过UID和PID验证,使用getCallingUid()和getCallingPid()可以拿到客户端所属应用的UID和PID
- 通过UID和PID这两个参数拿到包名进行包名验证
ContentProvider
//authority与表名关联,根据传入的URI取出来外界想要操作哪张表
//content://com.icbc.im.provider/user
//content://com.icbc.im.provider/book
UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(authority, path, code);
uriMatcher.addURI(authority, path, code);
- onCreate()主线程
- insert query update delete Binder线程池中
- 数据发生改变通知外界
getContext().getContentResolver().notifyChange(uri, null);
外界注册数据改变监听
getContext().getContentResolver().registerContentObserver(uri,true,new ContentObserver(null) {
@Override
public void onChange(boolean selfChange, Uri uri) {
super.onChange(selfChange, uri);
}
});
//解除注册
getContext().getContentResolver().unregisterContentObserver()