Android IPC机制
Android
中使用多进程的方法只有一种给四大组件在AndroidMenifest
中指定android:process
属性, 除此之外没有别的方法, 也就是说我们无法给一个线程或者一个实体类指定其运行所在的进程我们可以通过设置两个App的
ShareUserID
和签名都相同的方式, 使两个App不管是否跑在同一个进程中都可以共享Data文件夹下的数据, 当他们跑在同一个进程中时他们还可以共享内存
ShareUID示例android:process
属性分为android:process:":remote"
和android:process:"com.shishuo.example.remote"
两种, 以:开头的属于私有进程, 其他应用组件不可以和它跑在同一个进程中, 不以:开头的属于全局进程, 应用可以通过指定shareUserID
来和它跑在同一个进程中.-
android里每一个进程都是运行在一个单独的虚拟机里, 因此进程间是不能共享内存的, 因此如果使用多进程会导致以下问题:
- 静态成员和单例模式完全失效
- 线程同步锁完全失效
-
SharePrefrence
可靠性降低 -
Application
会多次创建, 有多少个进程就会创建几次
-
多进程分为两种
- 一个应用因为某些原因自身需要采用多线程模式来实现。
- 当前应用需要向其他应用获取数据
-
Serializable
是Java
所提供的一个序列化接口,它是一个空接口,为对象提供标准的序列化和反序列化操作。使用Serializable
来实现序列化相当简单,只需要在类的声明中指定一个类似下面的标识即可自动实现默认的序列化过程。private static final long serialVersionUID = 8711368828010083044L
通过
Serializable
方来实现对象的序列化,如下代码://序列化过程 User user = new User(0, "jake", true); ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("cache.txt")); out.writeObject(user); out.close(); //反序列化过程 ObjectInputStream in = new ObjectInputStream(new FileInputStream("cache.txt")); User newUser = (User)in.readObject(); in.close();
原则上序列化后的数据中的
serialVersionUID
只有和当前类的serialVersionUID
相同时才能够正常的被反序列化。serialVersionUID
的详细工作机制是这样的:序列化的时候系统会把当前类的serialVersionUID
写入序列化的文件中(也可能是其他的中介),当反序列化的时候系统会去检测文件中的serialVersionUID
,它是否和当前类的serialVersionUID
一致,如果一致就说明序列化的类的版本和当前类的版本是相同的,这个时候可以成功反序列化,否则就说明当前类和序列化的类相比发生了某些变换.给
serialVersionUID
制定为1L或者采用Eclipse根据当前类结构去生成的hash值,这两者并没有本质区别。- 静态成员变量属于类不属于对象,所以不会参与序列化过程
- 其次用
transient
关键字标记的成员变量不参与序列化过程
-
Parcelable
Parcelable
也是一个接口,只要实现这个接口,一个类的对象就可以实现序列化并可以通过Intent
和Binder
传递.
Parcelable
通过parcel
将序列化之后的数据写入到一个共享内存中,其他进程通过Parcel
可以从这块共享内存中读出字节流,并反序列化成对象. -
Serializable和Parcelable优劣:
- 当我们序列化后的数据只在内存操作时, 首选
Parcelable
. 例如Activity
间传递数据. - 当我们序列化后的数据需要持久化的保存到存储设备或者进行网络传输时优先选用
Serializable
.
是因为
Serializable
序列化时需要大量的调用反射而且还会产生很多临时变量, 会导致它的性能比Parcelable
慢10倍.因此情况1我们优先选用Parcelable
Parcelable
在序列化时和Serializable
一样也会把数据转化成字节流, 但是它向流中写入描述信息时只写入了一个类名, 而Serializable
还会写入serialVersionUID
等信息来作为反序列化的验证条件, 因此在2中我们选择性能更低的Serializabl
, 因为它的可靠性高. - 当我们序列化后的数据只在内存操作时, 首选
Binder
Binder详解1
Binder详解2
我对Binder的理解-
各种IPC方式对比
-
使用Bundle
Bundle
实现了Parcelable
接口,Activity
、Service
和Receiver
都支持在Intent
中传递Bundle
数据, 传递的数据必须可以被序列化比如基本类型
实现了Parcelable接口的对象
实现了Serializable接口的对象
, 以及一些Android
支持的特殊对象
具体可以去Bundle
中查看- 优点: 简单易用
- 缺点: 只能传输Bundle支持的数据类型
- 适用场景: 四大组件之间的进程通信
-
使用共享文件
- 共享文件的方式适合在对数据同步性要求不高的进程之间通信, 并且要妥善的处理并发读/写的问题.
-
SharePrefrence
虽然也是以文件的形式储存的, 但是由于系统对它在内存中做了缓存, 因此并不能把它作为共享文件来进行进程间通信.
- 优点: 简单易用
- 缺点: 不适合高并发的场景, 并且无法做到进程间的及时通信
- 适用场景: 无并发访问情形, 交换简单数据, 实时性不高的场景
-
使用Messenger
-
Messenger
是一种轻量级的IPC方式, 它的底层也是通过AIDL
实现的.Messenger
只能串行的处理请求, 即服务端只能一个一个处理, 不存在并发的情况. - 在
Messenger
中传递数据必须将数据放入Message
中,Message
中可以使用的载体只有what
,arg1
,arg2
,Bundle
和replyTo
,Message
中的另一个字段Object
在2.2以前不支持跨进程传输对象, 2.2以后也只支持系统提供的实现了Parcelable
接口的对象才可以. 因此在使用Message
时尽量不要将数据放到Object
中.
- 优点: 支持一对多串行通信, 支持实时通信
- 缺点: 不能很好的处理高并发场景, 只能一个一个执行, 数据需要通过
Message
传输, 因此只能传输Bundle
支持的数据类型, 不支持RPC(客户端调用服务器端的方法) - 适用场景: 低并发的一对多通信, 只需要跨进程传输数据的简单请求
-
-
使用ContentProvider
-
ContentProvider
以表格来存储数据, 并且可以包含多个表. -
ContentProvider
对底层的数据存储方式没有要求, 可以是SQLite
, 可以是文件, 甚至可以是内存中的一个对象. - 要观察
ContentProvider
中的一个数据变化情况, 可以通过ContentResolver
中的registerContentObserver
方法来注册观察者.
- 优点: 在数据源访问方面功能强大, 支持一对多并发共享数据, 可以通过
call
方法. - 缺点: 可以理解为受约束的
AIDL
, 主要提供数据源的CRUD
操作. - 适用场景: 一对多进程间的数据共享
-
-
使用Socket
套接字,分为流式套接字和用户数据报套接字两种,分别对应于网络的传输控制层中TCP
和UDP
协议。-
TCP
协议是面向连接的协议,提供稳定的双向通信功能,TCP
连接的建立需要经过"三次握手"才能完成,为了提供稳定的数据传输功能,其本身提供了超时重传功能,因此具有很高的稳定性. -
UDP
是无连接的,提供不稳定的单向通信功能,当然UDP
也可以实现双向通信功能,在性能上,UDP
具有更好的效率,其缺点是不保证数据能够正确传输,尤其是在网络拥塞的情况下。
- 优点: 可以实现一对多并发实时通信, 可以通过网络传输字节流
- 缺点: 实现细节有些麻烦, 不支持直接的
RPC
- 适用场景: 网络数据交换
-
-
使用AIDL
大致流程: 首先建立一个Service
和AIDL
接口, 然后在Service
中实现这个AIDL
接口并在Service
的onBind
方法返回这个实现了AIDL
接口的Binder
对象, 然后客户端的ServiceConnection
中接受这个Binder
对象的代理BinderProxy
, 之后客户端就可以通过这个BinderProxy
来调取Service
中的方法了.-
AIDL
支持的数据类型:基本数据类型
、String和CharSequence
、ArrayList
、HashMap
、Parcelable
以及AIDL
. - 某些类即使和
AIDL
文件在同一个包中也要显式import
进来. -
AIDL
中除了基本数据类,其他类型的参数都要标上方向:in
、out
'或者inout
. -
AIDL
接口支持方法, 不支持静态变量. - 为了方便
AIDL
的开发,建议把所有和AIDL
相关的类和文件全部放入同一个包中,这样做的好处是,当客户端是另一个应用的时候,可以直接把整个包复制到客户端工程中. -
RemoteCallbackList
是系统专门提供的用于删除跨进程Listener
的接口。RemoteCallbackList
是一个泛型,支持管理任意的AIDL
接口,因为所有的AIDL
接口都继承自IInterface
接口. - 不管是
客户端
还是服务端
在调用另一方的方法时, 方法都会运行在Binder
线程池中, 其自身线程都会处于挂起状态, 特别是客户端, 如果这个客户端线程是UI线程的话, 就会导致客户端ANR. 因此, 如果我们明确知道调用的远程方法是耗时的时候, 我们就要避免在主线程中访问远程方法.
ServiceConnection
的onServiceConnected
和onServiceDisconnected
方法都运行在UI线程中, 因此也要避免在这两个方法中访问远程方法. - 为了避免客户端一直等待远程方法执行完成,我们就可以将
AIDL
接口声明为oneway
,声明为oneway
的AIDL
接口中的所有方法在调用时都不会阻塞,具体来说,调用了远程方法后,不用等着远程方法执行完毕,会立即返回继续执行后面的代码,所以正因为此特性,oneway
接口下面的方法都必须是返回void
类型,不能返回其他类型的数据。大部分情况下,我们一般将客户端的回调接口AIDL
定义为oneway
的,这样远程服务调用回调接口中的方法时不会阻塞远程服务后面代码的执行。 - AIDL的权限验证方法:
-
在
onBind
方法中进行验证用户绑定验证,例子:使用Permission
验证.
步骤1: 在AndroidManifest中声明所需的权限,例如:<permission android:name="com.ryg.chapter_2.permission.AXXESS_BOOK_SERVICE" android:protectionLevel="Normal"/>
步骤2:在OnBind方法中进行权限验证。例如:
public IBinder onBind(Intent intent){ int check=checkCallingOrSelfpermission("com.ryg.chapter_2.permission.AXXESS_BOOK_SERVICE"); if(check == PackageManager.PERMISSION_DEBIND){ return null; } return mBinder; }
-
在服务端的
onTransact
方法中进行权限认证。例子:使用Permission
验证。(也可以使用Uid
和Pid
来做认证)public boolean onTransact(int Code. Parcel data, Parcel reply, int flag) throws RemoteException{ int check=checkCallingOrSelfpermission("com.ryg.chapter_2.permission.AXXESS_BOOK_SERVICE"); if(check == PackageManager.PERMISSION_DEBIND){ return false; } String packageName = null; String[] packages = getPackageManager().getPackagesForUid(getCallingUid()); if(packages != null && package.length > 0){ packageName = packages[0]; } if(!packageName.startWith("com.ryg")){ return false; } return super.onTransact(code,data,reply,flags); }
-
-
-
-
Binder连接池
- 当项目规模很大的时候, 每一个
AIDL
创建一个Service
的方法是不对的, 因为Service
是系统资源, 太多的Service
会使应用看起来很繁重, 所以我们最好将所有的AIDL
放到一个Service
中去管理. -
整体的工作机制是: 每个业务创建自己的
AIDL
接口并实现它, 这个时候业务模块间必须是解耦的, 然后每个AIDL
向服务端提供它的唯一标识和实现后的Binder
对象, 服务端提供一个queryBinder
接口, 这个接口可以根据不同模块的唯一标识返回给我们相应的BinderProxy
对象, 然后我们通过拿到的BinderProxy
就可以进行远程方法调用了. -
Binder
连接池主要作用就是将每个业务模块的Binder
放到一个Service
中去统一管理, 从而避免了重复创建Service
的过程.
- 当项目规模很大的时候, 每一个