Android的IPC机制

本章的部分代码可参见https://github.com/OYYMING/ArtOfAndroidDev/tree/master/app/src/main/java/com/example/artofandroiddev/chapter2

前言

这一章是关于android中的IPC机制的。IPC(Inter-Process Communication)是进程间通讯的意思。进程之间无法像多线程一样,直接共享同一个变量,一个线程对变量的修改立刻就会对另一个线程造成影响。由于每个进程都处于不同的虚拟机,拥有不同的application实例,内存空间也完全不同,无法直接共享同一个变量,所以就需要IPC来进行不同进程间的数据交互和共享。

1.1 如何开启多进程模式

一个应用开启多进程有一种方法,两种写法,而不同的写法会造成不同的结果。
方法是改变Manifest文件,在四大组件的声明中添加android:process="XXXX"
两种写法是指上面的XXXX部分不同:
第一种是 :newProcess(在进程名前面加冒号),最终的进程名是 package名:newProcess
第二种是直接写全进程名,如com.example.hh.newProcess。最终的进程名就是这个了。
那么有什么差别呢?
第一种方式表明该进程是当前应用的私有进程,其他应用的组件不可以和他跑在同一个进程,而第二种方式启动的进程,其他应用的进程可以通过shareUID和他跑在同一个进程中,至于怎么跑,见问题1

2 IPC中的数据

之前说过,进程之间无法直接共享变量,那么言外之意如何间接共享变量呢?在同一块内存中我们可以直接传递对象引用给对方,这显然在多进程中并不适用。既然不能直接传递引用,那就只能直接传递对象过去了,但对象并不是可以随随便便跨进程传递的,他们必须满足一个前提条件——可以序列化。通过序列化,将对象转化成字节序列,便可以在进程间,甚至网络间进行传递,只要在抵达目的地后反序列化就可以恢复之前的数据,只不过这是一个新建的对象。

2.1 两种序列化的方式

序列化有两种方式,实现Serializable接口或者Parcelable接口,下面分别来说说。

2.1.1 实现Serializable接口

Serializable是目前最通用的序列化方式,无论是网络间通信还是将对象持久化保存到本地,都是采取这个接口,但是由于包含较多的IO操作,所以开销相对较大。而这个方法也很简单,需要序列化的类只需要实现这个接口,然后在类中加上serialVersionUID标注对象的版本号就可以了。接下来在需要序列化的时候,就可以用ObjectOutputStream和ObjectInputStream进行对象的序列化和反序列化。

2.1.2 实现Parcelable接口

相比Serializable,这个方法稍显麻烦,但却是专为Android序列化而开发的接口,所以效率较高。我们在android中传输数据时应该尽量使用这个接口。实现Parcelable需要实现几个方法:
(1)describeContents:描述对象内容,通常返回0,如果对象含有FileDescriptor则返回1。
(2)writeToParcel:按照顺序将对象的成员变量依次写入parcel中
(3)拥有一个public static final CREATOR的成员变量,在其内部重写createFromParcel方法(通过parcel创建成员)和newArray方法(创建对象数组)

3 IPC的方式

毫无疑问,IPC的桥梁是IBinder,虽然可以自己实现IBinder,但是android已经为我们提供了几种现成的方法:

3.1 AIDL

AIDL无疑是最基本却又最强大的方法,基于Binder,进程之间可以进行RPC(远程过程调用)和实时数据交互,而且实现方法也并不太复杂,可谓是IPC的首选。那么具体的实现方法见下文:

3.1.1 服务端实现

(1) 首先我们需要一个AIDL文件,在其中声明想要在进程间通信的接口,不过接口的参数的数据类型是有要求的:java的八种基本数据类型,String,Bundle,Parcelable。但要注意如果想要使用自定义的Parcelable对象的话,需要先在AIDL文件中声明这个类,然后再在接口所在的AIDL文件中import一下才可以使用。
(2) 然后我们需要编译一下,android会根据AIDL文件生成实现接口的抽象类Stub和Stub类的代理类Proxy(Proxy中会在业务逻辑外加一层序列化和反序列化的过程)。
(3) 接下来我们就可以写Service了。在Service中写一个Stub的子类,并重写接口中的定义好的交互方法,然后在onBind方法中返回这个子类。这样一来服务端的代码就完成了。

3.1.2 客户端实现

客户端需要绑定Service,并在ServiceConnection的onServiceConnected回调中将返回的IBinder对象,用Stub的静态方法asInterface()进行转化。之所以需要这步转化,是为了当是跨进程通信时返回Stub的代理类(正如上面所说,Proxy中在业务逻辑之外有序列化和反序列化的步骤,而这正是因为跨进程通信中只能传递序列化对象),用代理类来进行RPC。

3.1.3 监听器

有时如果需要监听Service中事件的发生,并回调Client的方法,我们必须在Service中注册监听器,但是跨进程注册的监听器却不能像单进程那样去管理,否则会无法成功解除Service端注册的监听器,最终造成Client的内存泄漏。而android也提供了解决方法,就是用RemoteCallbackList来管理Listener,RemoteCallbackList内部是一个Map,他将Listener底层的Binder作为Key,Listener作为Value,以确保解除Listener时,能够通过Binder找到对应Listener进行解除。同时记得要在离开Activity时解除这个监听器,不然又要内存泄漏了。

3.2 文件共享

文件共享无疑是非常简单的一种方式,直接将要共享的对象用流写入文件,然后在另一个进程中读取出来就可以了。但是显然用文件共享对象的方法有几个问题:
(1) 并发读写可能读出的内容不是最新,并发写则可能文件混乱(这里有个问题见问题2
(2) 不支持即时消息传递
(3) 不支持RPC
另外文中提到用SharedPreferences同步数据可能因为缓存的原因会变得不可靠,我也写了一个测试,发现确实存在一个进程改完数据但是另一个进程还是读取旧数据的现象。

3.3 Messenger

这是基于AIDL封装的方法。两个进程间各有一个Messenger以进行通信,而由于Messenger是基于Handler构造的,所以可以直接对UI进行修改。但也正因为是基于Handler,所以Service端一次只能处理一个请求,不能很好的处理高并发。另外虽然可以进行实时通信,但是RPC不支持,而数据也因为是通过Message传递,所以需要被Bundle支持。

3.4 ContentProvider

ContentProvider也是基于Binder的实现,这就意味着增删改查的几种方法是运行在新进程的Binder线程池中的,只不过onCreate还是运行在主线程的。用ContentProvider进行IPC的实现有这么两方面:

3.4.1 服务器端

这里的服务器端是指ContentProvider所在的进程。首先要有一个数据源(数据库之类的)。然后再有一个类继承ContentProvider,在其中重写onCreate和增删改查几个方法。由于一个ContentProvider可能会处理多种请求,所以有时需要UriMatcherContentUris这两个工具类来匹配Uri或读取Uri中的ID部分,然后再针对不同的Uri进行数据读取或处理。还有就是可能需要设置访问权限,不能让自己随随便便就被人访问是不是。

3.4.2 客户端

这里的客户端是指访问ContentProvider的进程。首先当然是写一个要访问ContentProvider的Activity了(当然Service和BroadcastReceiver之类的也可以)。接下来就是访问ContentProvider了。考虑到跨进程通信可能很耗时,应该在子线程中进行增删改查操作。读取(Query)操作一般都是用LoaderManager创建Loader进行异步访问,然后在LoaderCallback的onLoadFinish中刷新读取到的数据。最后就是不要忘了权限噢。

3.5 Socket

Socket不光可以用来进行网络间的数据传递,也可以在Android中用来做进程间的数据共享。由于是用字节流来进行数据共享,所以即使是图片或者文件都没有问题。具体请参考作者的实例。

4 Binder连接池

当需要考虑为多个AIDL接口创建Service时,用Binder连接池将他们统一管理无疑是最佳选择。如果按照之前的写法来实现的话:每个AIDL接口对应一个Service,每个Service都需要绑定一下,每次绑定都需要写ServiceConnection,每个ServiceConnection需要实现两个回调,或许还需要各自写一个DeathRecipient之后还要记得解除这一堆ServiceConnection的绑定,是不是想想就很恐怖。但是如果用了连接池,你只需要写好每个AIDL对应的Service,然后绑定一个Service(BinderPoolService)就可以了,需要某个具体的Service就通过这个连接池Service去创建。而这无疑在需要多个AIDL进行作业的情况下会带来效率上的极大提升。


问题:

1.p39,通过shareUID让两个应用跑在同一个进程?
根据google,应该只要两个应用的android:shareUID和android:process一样,再加上签名证书一样就可以了。我也试了一下,确实两个应用最终跑在了同一个进程,不过却在运行的时候报了java.lang.ClassCastException: android.support.v7.widget.ActionBarOverlayLayout cannot be cast to android.support.v7.widget.DecorContentParent。暂时还没有什么眉目,不过确实是在process设成一致之后发生的。


2.p64 中间一段,作者提到用线程同步来限制多个线程的写操作。但是一方面这里谈的是多进程,而且进程的并发读写恐怕也没有办法用线程同步。感觉如果涉及到跨进程对同一个文件的读写还是用ContentProvider来统一比较好吧。
而且之前在github上正好看到有人这么做,大家可以看看:
https://github.com/grandcentrix/tray


3.p85,最上一段,写着跨进程传输,在服务器端新生成的对象们的底层Binder都是同一个,为什么?
在网上搜了一下,发现这个问题牵扯很多Binder运行机制的问题,篇幅有限且能力有限,就先不写了,这个坑以后再填。


4.p90,关于在onBind中的通过checkCallingOrSelfPermission的方法来验证权限的方法,始终无法通过验证?
经过查阅资料和文档,发现几点问题:
首先,先说两个方法:checkCallingOrSelfPermissioncheckCallingPermission
checkCallingPermission验证通过的前提有两个:首先需要是IPC并且方法的调用者进程拥有指定权限。也就是说,Service端自己调用自己的方法,是无法通过验证的,就算Service自己拥有权限。
checkCallingOrSelfPermission的话,从名字就可以看出来,除了IPC,即使调用者是Service本身,只要拥有权限就可以通过。
看到上面两个方法,想必大家也发现了,而Google对方法的注释也说明了这一点:checkCallingPermission是对checkCallingOrSelfPermission潜在的权限泄漏的保护。即Client进程即使不拥有某些权限,但是通过调用Service端的方法可以拥有这项权限。所以Google也在checkCallingOrSelfPermission上标注了Use with care!
而在作者给的例子中,onBind方法显然是Service端自己的回调方法,由于调用者就是自己,所以这个check方法检验的也只可能是Service本身是否有权限,并没有检查客户端的权限。
所以,个人感觉这里应该用checkCallingPermission来替换,并且不能放在onBind做检查,应该放在IPC时Service端被调用的方法中。


5.声明权限时protectionLevel的含义
在manifest文件里声明一个权限的时候有两项必选:name和protectionLevel。name就不说了。protectionLevel是保护等级,有四个值可选:
(1) Normal
即使赋予这个权限也无法造成什么危害。但是不要求这个权限又有点麻烦。比如给手机换墙纸的权限:SET_WALLPAPER并不会导致敏感信息泄漏,但是任何应用随随便便就可以换手机壁纸又很烦,所以对于换墙纸的请求就要求拥有一个普通权限。这类权限在安装应用的时候会由系统直接赋予权限,而不需要用户同意,所以不会直接显示出来,但是用户还是能在安装前查看这些权限。
(2) Dangerous
拥有这个权限可能对用户造成危害。比如通讯录泄漏,或者私自使用网络连接。这类权限在安装的时候会显示出来告诉用户这个应用需要下列权限,只有用户同意才可会赋予权限。
(3) Signature
这个权限就比较私人了,它要求请求者必须和权限的声明者拥有同样的签名证书。一般只有当你想要在自己开发的两个应用间偷偷地进行数据共享的时候才用这个级别。在安装应用的时候不会告诉用户,而是由系统直接赋予权限。
(4) SignatureOrSystem
这个权限不被Google推荐使用且普通的开发者也几乎用不上。除了签名相同的应用,系统应用也可以申请得到权限。但是通常的使用场景是:
The “signatureOrSystem” permission is used for certain special situations where multiple vendors have applications built into a system image and need to share specific features explicitly because they are being built together.个人理解是多个系统预装应用的供应商彼此共享数据的情况。

嗯,第二章就到这里。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值