android:process
属性就可以开启多进程模式,除此之外没有其他方法,其实还有另一种非常规的多进程方法,那就是通过JNI在native层去fork一个新的进程:除了在Eclipse的DDMS视图中查看进程信息,还可以用shell来查看,命令为:adb shell ps,默认进程的进程名是包名packageName,进程名以:
开头的进程属于当前应用的私有进程,其他应用的组件不可以和它跑在同一个进程中,而进程名不以:
开头的进程属于全局进程,其他应用通过ShareUID
方法可以和它跑在同一个进程中
SharedPreferences
的可靠性下降:SharedPreferences不支持并发读写,底层是通过写xml;Serializable
接口是Java中为对象提供标准的序列化和反序列化操作的接口,而Parcelable
接口是Android提供的序列化方式的接口。例如:
serialVersionUId
是一串long型数字,主要是用来辅助序列化和反序列化的,原则上序列化后的数据中的serialVersionUId只有和当前类的serialVersionUId相同才能够正常地被反序列化。
transient
的成员变量不参与序列化过程
Parcelable
接口内部包装了可序列化的数据,可以在Binder中自由传输,Parcelable
主要用在内存序列化上,可以直接序列化的有Intent、Bundle、Bitmap以及List和Map等等,下面是一个实现了Parcelable
接口的示例public class Book implements Parcelable {
public int bookId;
public String bookName;
public Book() {
}
public Book(int bookId, String bookName) {
this.bookId = bookId;
this.bookName = bookName;
}
//“内容描述”,如果含有文件描述符返回1,否则返回0,几乎所有情况下都是返回0
public int describeContents() {
return 0;
}
//实现序列化操作,flags标识只有0和1,1表示标识当前对象需要作为返回值返回,不能立即释放资源,几乎所有情况都为0
public void writeToParcel(Parcel out, int flags) {
out.writeInt(bookId);
out.writeString(bookName);
}
//实现反序列化操作
public static final Parcelable.CreatorCREATOR = new Parcelable.Creator () {
//从序列化后的对象中创建原始对象
public Book createFromParcel(Parcel in) {
return new Book(in);
}
public Book[] newArray(int size) {//创建指定长度的原始对象数组
return new Book[size];
}
};
private Book(Parcel in) {
bookId = in.readInt();
bookName = in.readString();
}
}
Binder
是Android中的一个类,它实现了IBinder
接口。从IPC角度看,Binder是Android中一种跨进程通信的方式;Binder还可以理解为虚拟的物理设备,它的设备驱动是/dev/binder
;从Framework层角度看,Binder是ServiceManager连接各种Manager
和相应的ManagerService
的桥梁;从Android应用层来说,Binder是客户端和服务端进行通信的媒介,当bindService
的时候,服务端会返回一个包含了服务端业务调用的Binder对象,通过这个Binder对象,客户端就可以获取服务端提供的服务或者数据,这里的服务包括普通服务和基于AIDL的服务。transact
过程中客户端所请求的到底是哪个方法;接着,它声明了一个内部类Stub
,这个Stub就是一个Binder
类,当客户端和服务端都位于同一个进程时,方法调用不会走跨进程的transact
过程,而当两者位于不同进程时,方法调用需要走transact过程,这个逻辑由Stub内部的代理类Proxy
来完成。
asInterface(android.os.IBinder obj)
:用于将服务端的Binder对象转换成客户端所需的AIDL接口类型的对象,这种转换过程是区分进程的,
如果客户端和服务端是在同一个进程中,那么这个方法返回的是服务端的Stub
对象本身,否则返回的是系统封装的Stub.Proxy
对象。
asBinder
:返回当前Binder对象。
onTransact
:这个方法运行在
服务端中的Binder线程池
中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由此方法来处理。
public Boolean onTransact(int code, Parcelable data, Parcelable reply, int flags)
code
可以知道客户端请求的目标方法,接着从
data
中取出所需的参数,然后执行目标方法,执行完毕之后,将结果写入到
reply
中。如果此方法返回false,说明客户端的请求失败,利用这个特性可以做权限验证(即验证是否有权限调用该服务)。
4.Proxy#[Method]
:代理类中的接口方法,这些方法运行在客户端,当客户端远程调用此方法时,它的内部实现是:首先创建该方法所需要的参数,然后把方法的参数信息写入到_data
中,接着调用transact
方法来发起RPC请求,同时当前线程挂起;然后服务端的onTransact
方法会被调用,直到RPC过程返回后,当前线程继续执行,并从_reply
中取出RPC过程的返回结果,最后返回_reply
中的数据。
5、需要注意两点:首先,当客户端发起远程请求时,由于当前线程会被挂起直至服务器进程返回数据,所以如果一个远程方法很耗时的,那么不能在UI线程中发此远程请求,其次,由于服务器的Binder方法运动在Binder的线程池中,所以Binder方法不管是否耗时都应该采用同步的方式去实现,因为它已经运动在一个线程中了,下面给出Binder的工作机制图:
如果搞清楚了自动生成的接口文件的结构和作用之后,其实是可以不用通过AIDL而直接实现Binder的,主席写的示例代码
下面我们自己来写,首先写服务端的代码:
private Binder mBinder = new IBookManager.Stub() {
@Override
public List getBookList() throws RemoteException {
SystemClock.sleep(5000);
return mBookList;
}
@Override
public void addBook(Book book) throws RemoteException {
mBookList.add(book);
}
}
(6)Binder的两个重要方法linkToDeath
和unlinkToDeath
Binder运行在服务端,如果由于某种原因服务端异常终止了的话会导致客户端的远程调用失败,所以Binder提供了两个配对的方法linkToDeath
和unlinkToDeath
,通过linkToDeath
方法可以给Binder设置一个死亡代理,当Binder死亡的时候客户端就会收到通知,然后就可以重新发起连接请求从而恢复连接了。
如何给Binder设置死亡代理呢?
1.声明一个DeathRecipient
对象,DeathRecipient
是一个接口,其内部只有一个方法bindeDied
,实现这个方法就可以在Binder死亡的时候收到通知了。
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() { |
2.在客户端绑定远程服务成功之后,给binder设置死亡代理
mRemoteBookManager.asBinder().linkToDeath(mDeathRecipient, 0); |
SharedPreferences
是一个特例,虽然它也是文件的一种,但是由于系统对它的读写有一定的缓存策略,即在内存中会有一份SharedPreferences文件的缓存,因此在多进程模式下,系统对它的读写就变得不可靠,当面对高并发读写访问的时候,有很大几率会丢失数据,因此,不建议在进程间通信中使用SharedPreferences,下面使用文件的形式来进行文件共享。
private void persistToFile() {
new Thread(new Runnable() {
@Override
public void run() {
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();
} finally {
MyUtils.close(objectOutputStream);
}
}
}).start();
}
// 从文件恢复
private void recoverFromFile() {
new Thread(new Runnable() {
@Override
public void run() {
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();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
MyUtils.close(objectInputStream);
}
}
}
}).start();
}
(3)
使用Messenger
:把数据放到Messager里面进行传递,
Messenger
是一种轻量级的IPC方案,它的底层实现就是AIDL。Messenger是以串行的方式处理请求的,即服务端只能一个个处理,不存在并发执行的情形,详细的示例见原书,其实Messager的使用方法很简单,它只是对AIDL做了封装,Messager的构造函数。
public Messenger(Handler target) {
mTarget = target.getIMessenger();
}
public Messenger(IBinder target) {
mTarget = IMessenger.Stub.asInterface(target);
}
实现一个Messenger分为如下几个步骤:
String
和CharSequence
、ArrayList
、HashMap
、Parcelable
以及AIDL
;in
、out
或者inout
;in表示输入参数,out表示输出参数,inout表示输入输出参数,RemoteCallbackList
是系统专门提供的用于删除跨进程Listener的接口。RemoteCallbackList是一个泛型,支持管理任意的AIDL接口,因为所有的AIDL接口都继承自IInterface
接口。RemoteCallBackList很有用的功能,当客户端进程终止后,能够自动移除客户端的listenr,而且是线程同步的操作非常方便。专门用来放接口的
MediaStore
就是文件类型的ContentProvider;ContentResolver
的registerContentObserver
方法来注册观察者; (1)当项目规模很大的时候,创建很多个Service是不对的做法,因为service是系统资源,太多的service会使得应用看起来很重,所以最好是将所有的AIDL放在同一个Service中去管理。整个工作机制是:每个业务模块创建自己的AIDL接口并实现此接口,这个时候不同业务模块之间是不能有耦合的,所有实现细节我们要单独开来,然后向服务端提供自己的唯一标识和其对应的Binder对象;对于服务端来说,只需要一个Service,服务端提供一个queryBinder
接口,这个接口能够根据业务模块的特征来返回相应的Binder对象给它们,不同的业务模块拿到所需的Binder对象后就可以进行远程方法调用了。
Binder连接池的主要作用就是将每个业务模块的Binder请求统一转发到远程Service去执行,从而避免了重复创建Service的过程。
(2)作者实现的Binder连接池BinderPool
的实现源码,建议在AIDL开发工作中引入BinderPool机制。
OK,本章结束,谢谢阅读。