Android多进程、序列化、Binder、AIDL

多进程

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对象后就可以远程调用方法了

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值