多进程
android中使用多进进程的方法只有一个,在manifest文件里指定android:process属性(还有一种是在native层fork子进程)
进程名以“:”开头有两层含义
1.指在当前的进程名称前附加上当前应用的包名
2.“:”开头标示当前应用私有进程,其他应用的组件不可与和它跑在同一个进程
全局进程:进程名不以“:”开头的进程属于,和私有进程的区别就是,其他应用可以通过ShareUID的方式和它跑在同一个进程(ShareUID相同+签名相同)
android:sharedUserId="com.xxx.xxx"
如果应用之间要互相调用,只能是Uid相同才行
ShareUID:我们知道android系统会为每个应用分配一个唯一的UID,且具有相同UID的才能共享数据,相互调用。使得共享数据有了一定的安全性, 在同一个进程的时候不光能共享data目录,组件信息,还可以共享内存数据
PID是进程id的意思,一个pid可以对应多个UID
Android序列化
1.Serializable: 不声明serialVersionUID的话是可以的,但是将会对反序列化产生影响
原因:原则上序列化后数据中的serialVersionUID只有和当前类的serialVersionUID相同才能正常的被反序列化
如果我们没有手动指定serialVersionUID的值,反序列化的时候,如果当前类有所改变(增减成员变量),系统就会
重新计算当前类的hash值并把它赋值给serialVersionUID,这个时候当前类的serialVersionUID就和序列化类中的serialVersionUID不一致,反序列化失败,导致程序挂掉
当我们手动指定serialVersionUID就能在很大程度上避免反序列化的失败;但是就算我们制定了serialVerisonUID,但是因为我们修改了类名,或者变量类型,也是会失败的
静态成员变量属于类,不属于对象,不会参与到序列化当中
其次用transient关键字标记的成员变量也不会参与序列化过程
另外系统的默认序列化过程可以通过重写writeObject(ObjectOutputStream out) readObject(ObjectInputStream in)改变
2.Parcelable:只要实现了这个接口,就可以实现序列化,并可以通过Intent Binder传递
序列化功能由wirteToParcel()完成,实现是由Parcel的一系列write方法完成
反序列功能由CREATOR来完成。内部表明了如何创建序列化对象和数组,并最终通过Parcel的一系列read方法完成
Android系统已经为我们提供了很多实现了Parcelable的类,比如Intent,Bundle,Bitmap等
取舍:
Parcelable使用起来稍微麻烦,但是效率高,主要用在内存序列化上
Serializable是java中的序列化接口,使用简单但是开销大,序列化过程和反序列化过程都需要大量IO操作适合序列化本地文件,网络传输。
Android常见IPC方式
为什么需要IPC?全局静态变量在两个进程中会不会共享?
因为android系统为每个进程都分配了独立的虚拟机。不同的虚拟机在内存分配上有不同的地址空间,这就导致了在不同虚拟机中访问同一个类的对象会产生多个副本。静态变量也不会共享
1、使用Bundle: 在Intent中传递Bundle数据
由于Bundle实现了Parcelable接口,所以可以方便的在不同进程间传递
我们所传输的数据必须能够被序列化,bundle不支持的类型我们无法通过它在进程间通信
2、使用文件共享:两个进程通过读写同一个文件来交换数据
写文件:
User user = new User(1, "hello world", false);
File dir = new File(MyConstants.CHAPTER_2_PATH);
if (!dir.exists()) {
dir.mkdirs();
}
File cachedFile = new File(MyConstants.CACHE_FILE_PATH);
ObjectOutputStream objectOutputStream = null;
try {
objectOutputStream = new ObjectOutputStream(
new FileOutputStream(cachedFile));
objectOutputStream.writeObject(user);
Log.d(TAG, "persist user:" + user);
} catch (IOException e) {
e.printStackTrace();
}
读文件:
User user = null;
File cachedFile = new File(MyConstants.CACHE_FILE_PATH);
if (cachedFile.exists()) {
ObjectInputStream objectInputStream = null;
try {
objectInputStream = new ObjectInputStream(
new FileInputStream(cachedFile));
user = (User) objectInputStream.readObject();
Log.d(TAG, "recover user:" + user);
} catch (IOException e) {
e.printStackTrace();
}
缺点就是并发性做的好,适用于对数据同步要求不高的情况,并且需要妥善处理并发读写问题
注意:SharedPreference虽然也属于文件的一种,但是系统对它的读写却有一定的缓存策略,即内存中会有 一份SharedPreference文件的缓存,当面对高并发,或者对进程情况下就变的不可靠,因此在进程间通信中不建议使用SharedPerference
3、使用Messenger
在不同进程中传递Message对象,底层也是aidl实现的
劣势:
1、如果有大量的消息同时发送到服务端,服务端也只能一个个处理,不适合并发场景
2、Messenger主要是为了传递消息,很多时候我们需要的是跨进程调用服务端的方法,不适用于这种场景
使用步骤:
1.服务端进程:
创建一个继承自Service的类来处理客户端的连接请求;
同时创建一个Handler的子类并通过它来创建Messager对象;
在Handler子类的handleMessage()中(Message中可以装Bundle类型的数据)发送消息;
然后在Service的OnBind方法中返回这个Messager对象底层的Binder(mMessenger.getBinder())
2.客户端进程
bindService,通过SeviceConnection做参数;
利用服务端返回(OnServiceConnected())的IBinder对象创建一个Messenger
通过这个Messenger就可以向服务端发送消息,消息类型为Message。
如果需要服务端能回应客户端:
我们还需要在客户端创建一个Hander子类并创建一个新的Messenger;
并把这个Messenger对象通过Message的replyTo参数传递给服务端,服务端通过这个replayTo参数就可以回调客户端,
发消息的过程是在onServiceConnected()中做的,接收消息是在Handler子类的HandleMessage()中做的
4、使用ContentProvider
底层实现也是Binder实现的。
需要注意的是query,update,insert,delete四大方法内部需要做好线程同步
5、使用Socket
分为两种:流式套接字,用户数据报套接字,分别对应于网络的传输控制协议的TCP和UDP
TCP协议:面向连接的协议,提供稳定的双方通信功能,需要经过三次握手才能完成,本身提供超时重传机制,有很高的稳定性
UDP协议:是无连接,提供不稳定的单项通信功能,在性能上,UDP有更好的效率,缺点是不能保证数据一定能正常传输
5、使用AIDL
AIDL支持的数据类型
1.基本类型(int long char boolean )
2.String和CharSequence
3.ArrayList,并且每个元素都必须被AIDL支持
4.HashMap,并且每个元素都必须被AIDL支持,包括key和value
5.所有实现了Parcelable的对象
6.aidl接口本身
其中自定义的Parcelable对象和aidl对象,必须要显示的import进来,不管它们是否和aidl文件在同一个包下面
如果使用了自定义的Parcelable对象,还需要创建一个和它同名的aidl文件,并在adil文件内将其声明为Parcelable类型,aidl文件只有这一行代码 ( parcelable Book;)
和传统接口不同的是,aidl中只支持方法,不支持声明静态常量
AIDL中除了基本类型,其他类型的参数都需要标注上方向,in(输入型)out(输出型) inout(输入输出型)
客户端需要反序列化服务中和aidl虽有相关的类,如果类的完整路径不同,就无法反序列化成功,程序无法正常运行
使用步骤:
Service:
1.创建AIDL文件,并声明该服务给客户端提供的接口,并编译
2.创建Service 并 在Service中实现AIDL中定义的接口
3.在AndroidManifest.xml中注册服务
Client:
1.拷贝服务端的AIDL文件到src/main目录下
2.使用Stub.asInterface接口获取服务器的Binder,根据需要调用服务提供的方法 在ServiceConnection中回调回来的方法里实现
3.通过 Intent 指定服务端的服务名称和所在包,绑定远程Service
问:在使用aidl实现观察者模式的时候,在客户端调用unRegisterListener(listener)的时候,服务端能不能找到我们注册的那个listener?
不能,Binder会把客户端传递过来的对象重新转化并生成一个新的对象,对象的传递都是反序列化的过程。
解决:使用RemoteCallbackList<IBinder,CallBack>,原理就是内部专门搞了个map维护aidl的回调,key是IBinder类型,value就是Callback类型,CallBack中封装了真正的远程Listener
可以看出,虽然跨进程传输的客户的同一个对象会在服务端生成不同的对象,但是这些新生对象有一个共同特点,就是底层的Binder对象是同一个
RemoteCallbackList还提供了两个功能,当客户端进程终止后,会自动移除客户端注册进来的listener;注册和反注册的时候里面实现了线程同步
问:如果服务端的进程意外死亡,我们怎么重连服务?
1,给Binder设置Deathrecipient监听,binderDied方法会在客户端的Binder线程池中被回调,就是在binderDied方法中不能访问UI
2,在onServiceDisconnected中重连服务,onServiceDisconnect会在UI线程中被回调,可以访问UI
在AIDL中使用权限验证功能,我们的服务应该做到不是任何人想连就能连的
1,在onBind方法中进行权限验证,验证不通过就返回null。 permission验证的方法: checkCallingOrSelfPermission
2,在服务端的onTransact方法中进行权限验证,如果验证失败就返回false
Binder
1.从android应用层来说,Binder是客户端是和服务进行通讯的媒介,当bindService的时候,服务端就会返回一个包含服务端业务调用的Binder对象。
2.从android Framework层来说,Binder是ServiceManager连接各种Manager(ActvivtyManager,WindowManager)和相依ManagerService的桥梁
3. 从IPC角度来说,Binder是android中夸进程通讯方式
通过AIDL生成的java文件分析Binder工作机制:
我们的创建的aidl接口,build生成后的java文件,继承自android 的IInterface接口(接口中只有一个方法 IBinder asBinder() ),包含了两个内部类:Stub 和 Proxy
1.生成的java文件接口中声明了我们在aidl文件中声明的方法
2.Stub类进程自Binder类,实现外面的接口方法,在asInterface方法里面判断,如果服务端和客户端在不同进程的时候,会走夸进程的的transact过程
3.Proxy也是个内部类,跨进程的transact就是由这个代理类实现的
代码分析:
Stub# asInterface方法:在客户端onServiceConnected()回调中,我们会调用到asInterface方法 :IXXXXX
service= IXXXXX.Stub.asInterface()
Stub#onTransact方法,当客户端发起夸进程请求时执行:
此方法运行在服务端,在Stub类中的onTransact方法,是父类Binder类的方法,这个方法运行在服务端中的Binder线程池中。
当客户端发起跨进程请求时,远程请求会通过系统底层封装后交给此方法处理
服务端通过code来判断客户端请求的目标方法是什么,接着从data中取出目标方法的参数,然后执行目标方法,当方法执行完后就把返回值写入reply中,返回false,客户端的请求就会失败
Proxy#func():
此方法运行在客户端,当客户端远程调用此方法时,内部实现先创建两个Parcel对象的参数_data和 _reply,把参数信息写入_data中
调用transact方法发起远程过程调用(PRC过程)同时当前线程挂起,
等到服务端的onTansact方法执行完后,当先线程继续执行,_reply取出服务端返回的结果,最后返回_reply的值给到客户端
注意:
客户端调用服务端的的方法,被调用的方法运行在服务端的Binder线程池中,同时客户端线程被挂起,如果服务端的方法耗时的操作,就会导致客户端长时间的阻塞在这里,很次我们避免在客户端的UI线程中调用服务端方法
特别是在OnServiceConnected和OnServiceDisconnected中,不要在里面直接调用服务端的耗时方法。由于服务端的方法运行在服务端的Binder线程池中,所以服务端本身可以进行大量耗时操作,我们尽量避免在里面做异步任务。
同理服务端调用Lisenter的方法时候,也是运行在客户端的Binder线程池中,我们不能在服务端的非UI线程调用客户端耗时方法
Binder连接池:
可能我们一个应用对外提供10个Service,总不能创建10个Service来实现吧
解决:
每个业务块创建自己的aidl接口并实现这个接口,互补耦合,向服务端提供自己的唯一标识和对应的Binder对象
对于服务端只需要提供一个Service,并提供一个queryBinder接口,这个接口根据业务模块的特征来返回相应的Binder对象给各个业务模块,不同的业务模块拿到自己所需要的Binder对象后就可以远程调用方法了