一、IPC简介
(1)IPC是Inter-Process Communication的缩写,含义为进程间通信或者跨进程通信,是指两个进程之间进行数据交换的过程。
(2)ANR是Application Not Responding的缩写,即应用无响应。主线程执行大量的耗时操作容易导致ANR现象发生。
(3)在Android中最有特色的进程间通信方式就是Binder了,通过Binder可以轻松地实现进程间通信。
(4)Android还支持Socket,通过Socket也可以实现任意两个终端或者两个进程之间的通信。
二、Android中的多进程模式
1、在Android中使用多进程只有一种方法:
就是给四大组件(Activity、Service、Receiver、ContentProvider)在AndroidManifest中指定android:process属性。
可以在Eclipse的DDMS视图中查看进程信息,还可以用shell来查看,命令为:adb shell ps 或者 adb shell ps|grep com.ryg.chapter_2。
- <activity
- android:name=".MainActivity"
- android:configChanges="orientation|screenSize"
- android:label="@string/app_name"
- android:launchMode="standard" >
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- </intent-filter>
- </activity>
- <activity
- android:name=".SecondActivity"
- android:configChanges="screenLayout"
- android:label="@string/app_name"
- android:process=":remote" />
- <activity
- android:name=".ThirdActivity"
- android:configChanges="screenLayout"
- android:label="@string/app_name"
- android:process="com.ryg.chapter_2.remote" />
上面的代码中,
(1)MainActivity没有指定process属性,所以它运行在默认的进程中,默认进程的进程名是包名。
(2)SecondActivity会运行在一个单独的进程中,进程名为“com.ryg.chapter_2:remote”,其中com.ryg.chapter_2是包名。在程序中的冒号“:”的含义是指要在当前的进程名前面附加上当前的包名,是一种简写的方法。而且以“:”开头的进程属于当前应用的私有进程,其他应用的组件不可以和它跑在同一个进程中。
(3)ThirdActivity会运行在另一个单独的进程中,进程名为“com.ryg.chapter_2.remote”。这是一种完整的命名方式。属于全局进程,其他应用通过ShareUID方式可以和它跑在同一个进程中。
注意点一:Android系统会为每一个应用分配一个唯一的UID,具有相同UID的应用才能共享数据。要求两个应用具有相同的ShareUID并且签名相同才可以跑在同一个进程中。在这种情况下,它们可以互相访问对方的私有数据,比如data目录、组件信息等,不管它们是否跑在同一个进程中。当然如果它们跑在同一个进程中,那么除了能共享data目录、组件信息,还可以共享内存数据,或者说它们看起来就像是一个应用的两个部分。
2、多进程模式的运行机制
(1)多进程会带来很多意想不到的麻烦,因为Android为每一个应用都分配了一个独立的虚拟机,或者说为每个进程都分配了一个独立的虚拟机,不同的虚拟机在内存分配上有不同的地址空间,这就导致在不同的虚拟机中访问同一个类的对象会产生多份副本。这样很就容易导致数据不同步。
(2)所有运行在不同进程的四大组件,只要它们之间需要通过内存在共享数据,都会共享失败。
(3)主要有以下四方面的问题:
1)静态成员和单例模式完全失效。(由独立虚拟机造成)
2)线程同步机制完全失效。(同上)
3)SharedPreferences的可靠性下降。(存在并发读写的问题)
4)Application会多次创建。(新的进程中又会导致进程所在的Application在新的虚拟机中再次创建)
(4)运行在同一个进程中的组件是属于同一个虚拟机和同一个Application的,同理运行在不同进程的组件是属于两个不同的虚拟机和Application的。
基于上面的这些问题,因为我们需要学习进程间通信机制!!!!!
三、IPC基础概念介绍
当我们需要通过Intent和Binder传输数据时就需要使用Parcelable或者Serializeble。Serializable和Parcelable接口可以完成对象的序列化过程。还有时候我们需要把对象持久化到存储 设备上或者通过网络传输给其他客户端,这个时候也需要Serializable来完成对象的持久化。
1、Serializable接口
(1)Serializable是Java所提供的一个序列化接口,它是一个空接口,为对象提供标准的序列化和反序列化操作。使用Serializable来实现序列化非常简单,只需要在类的声明中指定一个类似下面的标识即可自动实现默认的序列化过程。
- private static final long serialVersionUID = 8723148825838841922L;
- public class User implements Serializable{
- private static final long serialVersionUID = 8723148825838841922L;
-
- public int userId;
- public String userName;
- public boolean isMale;
- }
(2)只需要采用ObjectOutputStream和ObjectInputStream即可轻松实现对象的序列化和反序列化过程:
-
- 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();
注意点一:序列化和反序列化的对象并不是同一个对象!
(3)一般来说,我们应该收到指定serialVersionUID的值,比如1L,也可以让Eclipse根据当前类的结构自动去生成它的hash值。当我们手动指定了它以后,就可以在很大程度上避免反序列化过程的失败。
注意点二:静态成员变量属于类不属于对象,所以不会参与序列化过程。
注意点三:用transient关键字标记的成员变量不参与序列化过程。
2、Parcelable接口
(1)Parcelable也是一个接口,只要实现这个接口,一个类的对象就可以实现序列化并可以通过Intent和Binder传递。
- public class User implements Parcelable {
-
- public int userId;
- public String userName;
- public boolean isMale;
-
- public Book book;
-
- public User(int userId, String userName, boolean isMale) {
- this.userId = userId;
- this.userName = userName;
- this.isMale = isMale;
- }
-
-
-
-
- public int describeContents() {
- return 0;
- }
-
-
-
-
-
-
-
- public void writeToParcel(Parcel out, int flags) {
- out.writeInt(userId);
- out.writeString(userName);
- out.writeInt(isMale? 1:0);
- out.writeParcelable(book, 0);
- }
-
-
-
-
-
- public static final Parcelable.Creator<User> CREATOR = new Parcelable.Creator<User>() {
-
- public User createFromParcel(Parcel in) {
- return new User(in);
- }
-
-
- public User[] newArray(int size) {
- return new User[size];
- }
- };
-
-
-
-
-
- private User(Parcel in) {
- userId = in.readInt();
- userName = in.readString();
- isMale = in.readInt() == 1;
-
-
-
-
- book = in.readParcelable(Thread.currentThread().getContextClassLoader());
- }
- }
(2)系统已经为我们提供了许多实现了Parcelable接口的类,它们都是可以直接序列化的,比如Intent、Bundle、Bitmap等,同时List和Map也可以序列化,前提是它们里面的每个元素都是可序列化的。
3、Parcelable接口和Serializable接口的比较
(1)Serializable用起来简单,但开销很大,序列化和反序列化过程都需要大量的I/O操作。
(2)Parcelable是Android中的序列化方式,更适合在Android平台上使用,用起来比较麻烦,效率很高,首选。主要用在内存序列化上。
四、Binder
1、Binder简介
(1)Binder实现了IBinder接口。
(2)从IPC角度来说,Binder是Android中的一种跨进程通信方式。Binder还可以理解为一种虚拟的物理设备,它的设备驱动是/dev/binder,这种通信方式在Linux中没有。
(3)从Android Framework角度来说,Binder是ServiceManager连接各种Manager(ActivityManager、WindowManager,等等)和相应ManagerService的桥梁。
(4)从Android应用层来说,Binder是客户端和服务端进行通信的媒介,当bindService的时候,服务端会返回一个包含了服务端业务调用的Binder对象,通过这个对象,客户端就可以获取服务端提供的服务或者数据,这里的服务包括普通服务和基于AIDL的服务。
(5)AIDL即Android interface definition Language,即Android接口定义语言。
2、在分析Binder的工作原理之前,我们先补充一下Android设计模式之Proxy模式
(1)Proxy代理模式简介
代理模式是对象的结构模式。代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。
模式的使用场景:就是一个人或者机构代表另一个人或者机构采取行动。在一些情况下,一个客户不想或者不能够直接引用一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
抽象对象角色AbstarctObject:声明了目标对象和代理对象的共同接口,这样一来在任何可以使用目标对象的地方都可以使用代理对象。
目标对象角色RealObject:定义了代理对象所代表的目标对象。
代理对象角色ProxyObject:代理对象内部含有目标对象的引用,从而可以在任何时候操作目标对象;代理对象提供一个与目标对象相同的接口,以便可以在任何时候替代目标对象。代理对象通常在客户端调用传递给目标对象之前或之后,执行某个操作,而不是单纯地将调用传递给目标对象。
(2)Proxy代理模式的简单实现
抽象对象角色:
- public abstract class AbstractObject {
-
- public abstract void operation();
- }
目标对象角色:
- public class RealObject extends AbstractObject {
- @Override
- public void operation() {
-
- System.out.println("一些操作");
- }
- }
代理对象角色:
- public class ProxyObject extends AbstractObject{
- RealObject realObject = new RealObject();
- @Override
- public void operation() {
-
- System.out.println("before");
- realObject.operation();
-
- System.out.println("after");
- }
- }
客户端:
- public class Client {
- public static void main(String[] args) {
- AbstractObject obj = new ProxyObject();
- obj.operation();
- }
- }
(3)代理模式在Binder中的使用
直观来说,Binder是Android中的一个类,它继承了IBinder接口。从IPC角度来说,Binder是Android中的一种跨进程通信方式,Binder还可以理解为一种虚拟的物理设备,它的设备驱动是/dev/binder,该通信方式在Linux中没有;从Android Framework角度来说,Binder是ServiceManager连接各种Manager(ActivityManager、WindowManager,etc)和相应ManagerService的桥梁;从Android应用层来说,Binder是客户端和服务端进行通信的媒介,当你bindService的时候,服务端会返回一个包含了服务端业务调用的Binder对象,通过这个Binder对象,客户端就可以获取服务端提供的服务或者数据,这里的服务包括普通服务和基于AIDL的服务。
Binder一个很重要的作用是:将客户端的请求参数通过Parcel包装后传到远程服务端,远程服务端解析数据并执行对应的操作,同时客户端线程挂起,当服务端方法执行完毕后,再将返回结果写入到另外一个Parcel中并将其通过Binder传回到客户端,客户端接收到返回数据的Parcel后,Binder会解析数据包中的内容并将原始结果返回给客户端,至此,整个Binder的工作过程就完成了。由此可见,Binder更像一个数据通道,Parcel对象就在这个通道中跨进程传输,至于双方如何通信,这并不负责,只需要双方按照约定好的规范去打包和解包数据即可。
为了更好地说明Binder,这里我们先手动实现了一个Binder。为了使得逻辑更清晰,这里简化一下,我们来模拟一个银行系统,这个银行提供的功能只有一个:即查询余额,只有传递一个int的id过来,银行就会将你的余额设置为id*10,满足下大家的发财梦。
1)先定义一个Binder接口(抽象对象角色):
- package com.ryg.design.manualbinder;
-
-
- import android.os.IBinder;
- import android.os.IInterface;
- import android.os.RemoteException;
-
-
- public interface IBank extends IInterface {
-
-
-
-
-
- static final String DESCRIPTOR = "com.ryg.design.manualbinder.IBank";
-
-
-
-
-
- static final int TRANSACTION_queryMoney = (IBinder.FIRST_CALL_TRANSACTION + 0);
-
-
-
-
-
- public long queryMoney(int uid) throws RemoteException;
-
-
- }
2)创建一个Binder并实现上述接口:
- package com.ryg.design.manualbinder;
-
-
- import android.os.Binder;
- import android.os.IBinder;
- import android.os.Parcel;
- import android.os.RemoteException;
-
-
- public class BankImpl extends Binder implements IBank {
-
-
- public BankImpl() {
- this.attachInterface(this, DESCRIPTOR);
- }
-
-
-
-
-
-
- public static IBank asInterface(IBinder obj) {
- if ((obj == null)) {
- return null;
- }
- android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
- if (((iin != null) && (iin instanceof IBank))) {
- return ((IBank) iin);
- }
- return new BankImpl.Proxy(obj);
- }
-
-
- @Override
- public IBinder asBinder() {
- return this;
- }
-
-
-
-
-
-
- @Override
- public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
- throws RemoteException {
- switch (code) {
- case INTERFACE_TRANSACTION: {
- reply.writeString(DESCRIPTOR);
- return true;
- }
- case TRANSACTION_queryMoney: {
- data.enforceInterface(DESCRIPTOR);
- int uid = data.readInt();
- long result = this.queryMoney(uid);
- reply.writeNoException();
- reply.writeLong(result);
- return true;
- }
- }
- return super.onTransact(code, data, reply, flags);
- }
-
-
-
-
-
- @Override
- public long queryMoney(int uid) throws RemoteException {
- return uid * 10l;
- }
-
-
-
-
-
- private static class Proxy implements IBank {
-
-
-
- private IBinder mRemote;
-
-
-
-
-
- Proxy(IBinder remote) {
- >
- mRemote = remote;
- }
-
-
-
-
-
- @Override
- public IBinder asBinder() {
- return mRemote;
- }
-
-
-
-
-
- public java.lang.String getInterfaceDescriptor() {
- return DESCRIPTOR;
- }
-
-
-
-
-
- @Override
- public long queryMoney(int uid) throws RemoteException {
-
-
-
- Parcel data = Parcel.obtain();
- Parcel reply = Parcel.obtain();
- long result;
- try {
- *
- * 在操作前向data中写入一些数据:
- * */
- data.writeInterfaceToken(DESCRIPTOR);
- data.writeInt(uid);
-
-
-
-
-
-
-
-
- mRemote.transact(TRANSACTION_queryMoney, data, reply, 0);
-
-
-
- reply.readException();
- result = reply.readLong();
- } finally {
- reply.recycle();
- data.recycle();
- }
- return result;
- }
- }
- }
ok,到此为止,我们的Binder就完成了,这里只要创建服务端和客户端,二者就能通过我们的Binder来通信了。这里就不做这个示例了,我们的目的是分析代理模式在Binder中的使用。
我们看上述Binder的实现中,有一个叫做“Proxy”的类,它的构造方法如下:
- Proxy(IBinder remote) {
- mRemote = remote;
- }
Proxy类接收一个IBinder参数,这个参数实际上就是服务端Service中的onBind方法返回的Binder对象在客户端重新打包后的结果,因为客户端无法直接通过这个打包的Binder和服务端通信,因此客户端必须借助Proxy类来和服务端通信,这里Proxy的作用就是代理的作用,客户端所有的请求全部通过Proxy来代理,具体工作流程为:Proxy接收到客户端的请求后,会将客户端的请求参数打包到Parcel对象中,然后将Parcel对象通过它内部持有的Ibinder对象传送到服务端,服务端接收数据、执行方法后返回结果给客户端的Proxy,Proxy解析数据后返回给客户端的真正调用者。很显然,上述所分析的就是典型的代理模式。至于Binder如何传输数据,这涉及到很底层的知识,这个很难搞懂,但是数据传输的核心思想是共享内存。3、我们通过一个案例来分析Binder工作原理
我们需要新建一个AIDL示例,SDK会自动为我们生产AIDL所对应的Binder类。
(1)Book.java:这里面没有什么特殊之处,为了实现Parcelable,添加了几个方法,上面在Parcelable部分已经介绍过了。
- package com.ryg.chapter_2.aidl;
-
-
- import android.os.Parcel;
- import android.os.Parcelable;
-
-
-
-
-
-
-
-
-
-
-
-
- public class Book implements Parcelable {
- <span style="white-space:pre"> </span>public int bookId;
- public String bookName;
-
-
-
-
- public Book() {
- <span style="white-space:pre"> </span>
- }
-
-
-
-
- public Book(int bookId, String bookName) {
- this.bookId = bookId;
- this.bookName = bookName;
- }
-
- public int describeContents() {
- return 0;
- }
-
-
-
-
-
- public void writeToParcel(Parcel out, int flags) {
- out.writeInt(bookId);
- out.writeString(bookName);
- }
-
-
-
-
-
- public static final Parcelable.Creator<Book> CREATOR = new Parcelable.Creator<Book>() {
- 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();
- }
-
-
-
-
- @Override
- public String toString() {
- return String.format("[bookId:%s, bookName:%s]", bookId, bookName);
- }
- }
(2)Book.aidl:它是Book在AIDL中的声明。
- package com.ryg.chapter_2.aidl;
-
- parcelable Book;
(3)IBookManager.aidl:虽然Book类已经和IBookManager位于相同的包中,但是这里依然需要导入Book类。这是AIDL的特殊之处。
它是一个接口,里面有四个方法。
- package com.ryg.chapter_2.aidl;
-
- import com.ryg.chapter_2.aidl.Book;
- import com.ryg.chapter_2.aidl.IOnNewBookArrivedListener;
-
- interface IBookManager {
- List<Book> getBookList();
- void addBook(in Book book);
- void registerListener(IOnNewBookArrivedListener listener);
- void unregisterListener(IOnNewBookArrivedListener listener);
- }
(4)下面我们要看一下系统为IBookManager.aidl生产的Binder类,在gen目录下有一个IBookManager.java的类,这就是我们要找的类。
-
-
-
- package com.ryg.chapter_2.aidl;
-
-
-
-
-
-
-
-
-
-
- public interface IBookManager extends android.os.IInterface
- {
-
-
-
-
-
- public static abstract class Stub extends android.os.Binder implements com.ryg.chapter_2.aidl.IBookManager
- {
-
-
-
- private static final java.lang.String DESCRIPTOR = "com.ryg.chapter_2.aidl.IBookManager";
-
-
- public Stub()
- {
- this.attachInterface(this, DESCRIPTOR);
- }
-
-
-
-
-
-
-
-
-
-
-
- public static com.ryg.chapter_2.aidl.IBookManager asInterface(android.os.IBinder obj)
- {
- if ((obj==null)) {
- return null;
- }
- android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
-
- if (((iin!=null)&&(iin instanceof com.ryg.chapter_2.aidl.IBookManager))) {
- return ((com.ryg.chapter_2.aidl.IBookManager)iin);
- }
-
- return new com.ryg.chapter_2.aidl.IBookManager.Stub.Proxy(obj);
- }
-
-
-
-
- @Override public android.os.IBinder asBinder()
- {
- return this;
- }
-
-
-
-
-
-
-
-
-
-
- @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
- {
- switch (code)
- {
- case INTERFACE_TRANSACTION:
- {
- reply.writeString(DESCRIPTOR);
- return true;
- }
- case TRANSACTION_getBookList:
- {
- data.enforceInterface(DESCRIPTOR);
-
-
-
- java.util.List<com.ryg.chapter_2.aidl.Book> _result = this.getBookList();
- reply.writeNoException();
- reply.writeTypedList(_result);
- return true;
- }
- case TRANSACTION_addBook:
- {
- data.enforceInterface(DESCRIPTOR);
- com.ryg.chapter_2.aidl.Book _arg0;
- if ((0!=data.readInt())) {
- _arg0 = com.ryg.chapter_2.aidl.Book.CREATOR.createFromParcel(data);
- }
- else {
- _arg0 = null;
- }
-
-
-
- this.addBook(_arg0);
- reply.writeNoException();
- return true;
- }
- case TRANSACTION_registerListener:
- {
- data.enforceInterface(DESCRIPTOR);
- com.ryg.chapter_2.aidl.IOnNewBookArrivedListener _arg0;
- _arg0 = com.ryg.chapter_2.aidl.IOnNewBookArrivedListener.Stub.asInterface(data.readStrongBinder());
- this.registerListener(_arg0);
- reply.writeNoException();
- return true;
- }
- case TRANSACTION_unregisterListener:
- {
- data.enforceInterface(DESCRIPTOR);
- com.ryg.chapter_2.aidl.IOnNewBookArrivedListener _arg0;
- _arg0 = com.ryg.chapter_2.aidl.IOnNewBookArrivedListener.Stub.asInterface(data.readStrongBinder());
- this.unregisterListener(_arg0);
- reply.writeNoException();
- return true;
- }
- }
- return super.onTransact(code, data, reply, flags);
- }
-
-
-
-
- private static class Proxy implements com.ryg.chapter_2.aidl.IBookManager
- {
-
-
-
- private android.os.IBinder mRemote;
-
- Proxy(android.os.IBinder remote)
- {
- mRemote = remote;
- }
-
- @Override public android.os.IBinder asBinder()
- {
- return mRemote;
- }
-
- public java.lang.String getInterfaceDescriptor()
- {
- return DESCRIPTOR;
- }
-
-
-
-
-
-
-
-
-
-
-
-
- @Override public java.util.List<com.ryg.chapter_2.aidl.Book> getBookList() throws android.os.RemoteException
- {
- android.os.Parcel _data = android.os.Parcel.obtain();
- android.os.Parcel _reply = android.os.Parcel.obtain();
- java.util.List<com.ryg.chapter_2.aidl.Book> _result;
- try {
- _data.writeInterfaceToken(DESCRIPTOR);
- mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
- _reply.readException();
- _result = _reply.createTypedArrayList(com.ryg.chapter_2.aidl.Book.CREATOR);
- }
- finally {
- _reply.recycle();
- _data.recycle();
- }
- return _result;
- }
-
- @Override public void addBook(com.ryg.chapter_2.aidl.Book book) throws android.os.RemoteException
- {
- android.os.Parcel _data = android.os.Parcel.obtain();
- android.os.Parcel _reply = android.os.Parcel.obtain();
- try {
- _data.writeInterfaceToken(DESCRIPTOR);
- if ((book!=null)) {
- _data.writeInt(1);
- book.writeToParcel(_data, 0);
- }
- else {
- _data.writeInt(0);
- }
- mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
- _reply.readException();
- }
- finally {
- _reply.recycle();
- _data.recycle();
- }
- }
-
- @Override public void registerListener(com.ryg.chapter_2.aidl.IOnNewBookArrivedListener listener) throws android.os.RemoteException
- {
- android.os.Parcel _data = android.os.Parcel.obtain();
- android.os.Parcel _reply = android.os.Parcel.obtain();
- try {
- _data.writeInterfaceToken(DESCRIPTOR);
- _data.writeStrongBinder((((listener!=null))?(listener.asBinder()):(null)));
- mRemote.transact(Stub.TRANSACTION_registerListener, _data, _reply, 0);
- _reply.readException();
- }
- finally {
- _reply.recycle();
- _data.recycle();
- }
- }
-
- @Override public void unregisterListener(com.ryg.chapter_2.aidl.IOnNewBookArrivedListener listener) throws android.os.RemoteException
- {
- android.os.Parcel _data = android.os.Parcel.obtain();
- android.os.Parcel _reply = android.os.Parcel.obtain();
- try {
- _data.writeInterfaceToken(DESCRIPTOR);
- _data.writeStrongBinder((((listener!=null))?(listener.asBinder()):(null)));
- mRemote.transact(Stub.TRANSACTION_unregisterListener, _data, _reply, 0);
- _reply.readException();
- }
- finally {
- _reply.recycle();
- _data.recycle();
- }
- }
- }
-
-
-
-
-
- static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
- static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
- static final int TRANSACTION_registerListener = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
- static final int TRANSACTION_unregisterListener = (android.os.IBinder.FIRST_CALL_TRANSACTION + 3);
-
- }
-
-
-
-
-
- public java.util.List<com.ryg.chapter_2.aidl.Book> getBookList() throws android.os.RemoteException;
- public void addBook(com.ryg.chapter_2.aidl.Book book) throws android.os.RemoteException;
- public void registerListener(com.ryg.chapter_2.aidl.IOnNewBookArrivedListener listener) throws android.os.RemoteException;
- public void unregisterListener(com.ryg.chapter_2.aidl.IOnNewBookArrivedListener listener) throws android.os.RemoteException;
- }
注意点一:上面的Book类,就是一个可以Parcelable序列化的简单的Book类,它里面没有任何的方法,就是定义了一个简单的Book类结构。
注意点二:Book.aidl的存在是因为在IBookManager.aidl中出现的对象也必须有aidl声明。
注意点三:在IBookManager.aidl中,对于自动生成的IBookManager.java文件,它是服务器端的代码。当客户端向服务端发送连接请求时,如果客户端和服务端在同一进程中,那么服务端就向客户端返回Stub这个Binder对象,如果客户端和服务端在不同进程中,那么服务端就向客户端返回内部类Stub的内部代理类Proxy,然后客户端根据这个Proxy来调用Proxy内部的方法,这个Proxy内部含有服务端真正的Binder对象也就是那个内部类Stub,在客户端调用Proxy内部的方法也就会导致调用Stub的transact方法,而Stub的transact方法又会回调它自己的onTransact方法,onTransact方法是在服务端运行的,而transact方法是在客户端调用的,这样就实现了客户端调用服务端的方法了。当然这所有的传递过程也少不了Parcel这个数据包的协助。整个过程懂了吗?
这次应该完全懂了吧,再不懂去屎吧!
4、linkToDeath和unlinkToDeath
Binder运行在服务端进程,如果服务端进程由于某些原因异常终止,这个时候我们到服务端的Binder连接断裂,会导致我们的远程调用失败。Binder提供了两个配对的方法linkToDeath和unlinkToDeath,通过linkToDeath我们可以给Binder设置一个死亡代理,当Binder死亡时,我们会收到通知,这个时候我们就可以重新发起连接请求从而恢复连接。
-
-
-
- private IBinder.DeathRecipient mDeathRecipient = new IBinder.DearhRecipient(){
-
- @Override
- public void binderDied(){
- if(mBookManager == null)
- return;
- mBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);
- mBookManager = null;
-
- }
- }
在客户端绑定远程服务之后,给Binder设置死亡代理:
- mService = IMessageBoxManager.Stub.asInterface(binder);
- binder.linkToDeath(mDeathRecipient, 0);
五、使用Messenger
1、特点:
(1)Messenger对AIDL做了封装,使得我们可以更简便地进行进程间通信。由于它一次处理一个请求,所以在服务端我们不考虑线程同步的问题,因为服务端中不存在并发执行的情形。
(2)通过它可以在不同进程中传递Message对象,在Message中仿佛我们需要传递的数据,就可以轻松地实现数据的进程间传递了。
(3)有两个构造函数,分别接收Handler对象和IBinder对象。
2、 实现一个Messenger有如下步骤:
(1)服务端进程:
首先需要在服务端创建一个Service来处理客户端的连接请求,同时创建一个Handler并以它作为参数来创建一个Messenger对象,然后在Service的onBind中返回这个Messenger对象底层的Binder即可。关键点就在于它的返回是返回给了要绑定这个服务端的客户端,然后客户端拿到这个Binder再去创建Messenger,再去发送Message等等。
(2)客户端进程:
客户端进程中,首先要绑定服务端的Service,绑定后服务端的onBind会返回一个Binder对象,然后客户端用服务端返回的这个Binder对象创建一个Messenger,通过这个Messenger就可以向服务器端发送消息了,发送消息类型为Message对象,如果需要服务端能够回应客户端,就像和服务端一个,我们还需要创建一个Handler并创建一个新的Messenger,并把这个Messenger对象在第一次客户端像服务端发送消息时通过Message的replyTo参数传递给服务端,服务端通过读取Message中的replyTo参数就是服务端给客户端的的Messenger,然后就可以回应客户端。
(3)注意点:
客户端给服务端发送消息的时候所用的Messenger是通过绑定服务端,然后依据onBind返回的Binder对象为参数来创建Messenger,而服务端在回应客户端的时候所用的Messenger是客户端在刚刚发送消息的时候将自身创建的Messenger作为刚刚发送消息的Message的replyTo参数传递给服务端的,所以在服务端直接读取出这个Messenger。
3、举例:客户端像服务端发送消息,服务端回应客户端
(1)先看服务端代码:
六、使用AIDL
1、对比Messenger和AIDL:
上一节讲的Messenger来进行进程间的通信,可以发现,Messenger是以串行的方式处理客户端发来的消息的,
如果大量的消息同时发送到服务端,服务端仍然只能一个个处理,如果有大量的并发请求,那么用Messenger就不太合适了。
而且Messenger的主要作用是为了传递消息,很多时候我们可能需要跨进程调用服务端的方法,这种情形用Messenger就无法实现了。
所以我们用AIDL来实现跨进程的方法调用。
AIDL也是Messenger的底层实现,因此Messenger本质上也是AIDL,只不过系统为我们做了封装,从而方便上层的调用而已。
2、AIDL使用的基本思想:
(0)先来放一下我们的Book.java类,它实现了Parcelable接口:
- package com.ryg.chapter_2.aidl;
-
- import android.os.Parcel;
- import android.os.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;
- }
-
- public int describeContents() {
- return 0;
- }
-
- public void writeToParcel(Parcel out, int flags) {
- out.writeInt(bookId);
- out.writeString(bookName);
- }
-
- public static final Parcelable.Creator<Book> CREATOR = new Parcelable.Creator<Book>() {
- 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();
- }
-
- @Override
- public String toString() {
- return String.format("[bookId:%s, bookName:%s]", bookId, bookName);
- }
-
- }
(1)服务端:
服务端首先要创建一个Service用来监听客户端的连接请求,然后创建一个AIDL文件,将暴露给客户端的接口在这个AIDL文件中声明,最后在Service中实现这个AIDL接口即可。
(2)客户端:
客户端所要做的事情就稍微简单一些,首先需要绑定服务端的Service,在绑定成功后,将服务端返回的Binder对象转成AIDL接口所属的类型,接着就可以调用AIDL中的方法了。
(3)AIDL接口的创建:
首先看AIDL接口的创建,如下所示,我们创建一个后缀为AIDL的文件,在里面声明了一个接口和两个接口方法:
这个文件的名称是:IBookManager.aidl。
- package com.ryg.chapter_2.aidl;
-
- import com.ryg.chapter_2.aidl.Book;
-
- interface IBookManager {
- List<Book> getBookList();
- void addBook(in Book book);
- }
在AIDL文件中,并不是所有的数据类型都是可以使用的,那么到底AIDL文件支持哪些类型的数据呢?基本数据类型(int、long、char、boolean、double等);
String和CharSequence;
List:只支持ArrayList,里面每个元素都必须能够被AIDL支持;
Map:只支持HashMap,里面的每个元素都必须被AIDL支持,包括key和value;
Parcelable:所有实现了Parcelable接口的对象;
AIDL:所有的AIDL接口本身也可以在AIDL文件中使用。
(其中自定义的Parcelable对象和AIDL对象必须要显示import进来,不管它们是否和当前的AIDL文件位于同一个包内)
所以上面的IBookManager.aidl文件,里面用到了Book这个类,这个类实现了Parcelable接口并且和IBookManager位于同一个包中,但是遵守AIDL的规范,我们仍然需要显式的import进来:import com.ryg.chapter_2.aidl.Book;
还有一个注意点:
如果AIDL文件中用到了自定义的Parcelable对象,那么必须新建一个和它同名的AIDL文件,并在其中声明它为Parcelable类型。
因为我们在IBookManager.aidl中用到了Book这个类,所以我们必须要创建Book.aidl,然后里面添加的内容如下:
- package com.ryg.chapter_2.aidl;
-
- parcelable Book;
事实上,AIDL中每个实现了Parcelable接口的类都需要按照上面那种方式去创建相应的AIDL文件并声明那个类为Parcelable。
除此之外,AIDL中除了基本数据类型,其他类型的参数必须标上方向:in、out、inout。
我们需要根据实际需要去指定参数类型,不能一概使用out或者inout,因为这在底层实现是有开销的。
最后,AIDL接口中只支持方法,不支持声明静态常量,这一点区别于传统的接口。
为了方便AIDL的开发,建议把所有和AIDL相关的类和文件全部放入同一个包中,这样方便我们直接复制整个包,不容易遗漏。
需要注意的是,AIDL的包结构在服务端和客户端要保持一致,否则运行会出错。因为客户端需要反序列化服务端中和AIDL接口相关的所有类,如果类的完整路径不一样的话,就无法成功反序列化,程序也就无法正常运行。
(4)远程服务端Service的实现:
上面讲的是如何定义AIDL接口,下面讲如何实现这个接口。
- package com.ryg.chapter_2.aidl;
-
- import java.util.List;
- import java.util.concurrent.CopyOnWriteArrayList;
- import java.util.concurrent.atomic.AtomicBoolean;
-
- import android.app.Service;
- import android.content.Intent;
- import android.content.pm.PackageManager;
- import android.os.Binder;
- import android.os.IBinder;
- import android.os.Parcel;
- import android.os.RemoteCallbackList;
- import android.os.RemoteException;
- import android.os.SystemClock;
- import android.util.Log;
-
-
-
-
-
-
- public class BookManagerService extends Service {
-
- private static final String TAG = "BMS";
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<Book>();
-
-
-
-
-
-
- private Binder mBinder = new IBookManager.Stub() {
-
- @Override
- public List<Book> getBookList() throws RemoteException {
- return mBookList;
- }
-
- @Override
- public void addBook(Book book) throws RemoteException {
- mBookList.add(book);
- }
- };
-
- @Override
- public void onCreate() {
- super.onCreate();
- mBookList.add(new Book(1, "Android"));
- mBookList.add(new Book(2, "Ios"));
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- return mBinder;
- }
- }
还需要注意的是,我们需要注册这个服务端,并让它运行在独立的进程中,这样它和客户端的Activity不在同一个进程中,这样就构成了进程间通信的场景:
- <service
- android:name=".aidl.BookManagerService"
- android:process=":remote" >
- </service>
(5)客户端的实现:
客户端比较容易实现,首先需要绑定远程服务,绑定成功后将服务端返回的Binder对象转换成AIDL接口,然后就可以通过这个接口去调用服务端的远程方法了。
- package com.ryg.chapter_2.aidl;
-
-
- import java.util.List;
- import com.ryg.chapter_2.R;
- import com.ryg.chapter_2.aidl.IBookManager;
- import com.ryg.chapter_2.utils.MyConstants;
- import android.annotation.SuppressLint;
- import android.app.Activity;
- import android.content.ComponentName;
- import android.content.Context;
- import android.content.Intent;
- import android.content.ServiceConnection;
- import android.os.Bundle;
- import android.os.Handler;
- import android.os.IBinder;
- import android.os.Message;
- import android.os.RemoteException;
- import android.util.Log;
- import android.view.View;
- import android.widget.Toast;
-
-
- public class BookManagerActivity extends Activity {
-
-
- private static final String TAG = "BookManagerActivity";
-
- private ServiceConnection mConnection = new ServiceConnection() {
- public void onServiceConnected(ComponentName className, IBinder service) {
-
-
-
-
-
- IBookManager bookManager = IBookManager.Stub.asInterface(service);
- try {
-
-
-
- List<Book> list = bookManager.getBookList();
- Log.i(TAG, "query book list, list type:"
- + list.getClass().getCanonicalName());
- Log.i(TAG, "query book list:" + list.toString());
- } catch (RemoteException e) {
- e.printStackTrace();
- }
- }
-
-
- public void onServiceDisconnected(ComponentName className) {
- }
- };
-
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_book_manager);
- Intent intent = new Intent(this, BookManagerService.class);
-
-
-
- bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
- }
-
-
- @Override
- protected void onDestroy() {
- unbindService(mConnection);
- super.onDestroy();
- }
-
-
- }
以上就算是一个比较简单的完整的AIDL进行IPC的过程。
3、AIDL使用过程中的一些问题,应用观察者模式
(1)我们用观察者模式来实现当图书馆接收到新书后,就为申请过新书到来通知的用户发送新书通知。我们需要提供一个AIDL接口,每个用户都需要实现这个接口并且向图书馆申请新书的提醒功能,同时也可以取消这个功能。用AIDL接口而不用普通接口是因为AIDL中无法使用普通接口。
(2)首先我们创建一个IOnNewBookArrivedListener.aidl文件,当服务端有新书到来时,就会通知每一个已经申请提醒功能的用户。从程序上来说就是调用所有IOnNewBookArrivedListener对象中的onNewBookArrived方法,并把新书的对象通过参数传递给客户端。
- package com.ryg.chapter_2.aidl;
-
-
- import java.util.List;
- import java.util.concurrent.CopyOnWriteArrayList;
- import java.util.concurrent.atomic.AtomicBoolean;
-
-
- import android.app.Service;
- import android.content.Intent;
- import android.content.pm.PackageManager;
- import android.os.Binder;
- import android.os.IBinder;
- import android.os.Parcel;
- import android.os.RemoteCallbackList;
- import android.os.RemoteException;
- import android.os.SystemClock;
- import android.util.Log;
-
-
- public class BookManagerService extends Service {
-
-
- private static final String TAG = "BMS";
-
-
- private AtomicBoolean mIsServiceDestoryed = new AtomicBoolean(false);
-
-
- private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<Book>();
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList = new RemoteCallbackList<IOnNewBookArrivedListener>();
-
-
-
-
-
- private Binder mBinder = new IBookManager.Stub() {
-
-
-
-
-
- @Override
- public List<Book> getBookList() throws RemoteException {
- SystemClock.sleep(5000);
- return mBookList;
- }
-
-
- @Override
- public void addBook(Book book) throws RemoteException {
- mBookList.add(book);
- }
-
-
-
-
-
- public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
- throws RemoteException {
- <span style="white-space:pre"> </span>
- int check = checkCallingOrSelfPermission("com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE");
- Log.d(TAG, "check=" + check);
- if (check == PackageManager.PERMISSION_DENIED) {
- return false;
- }
-
-
-
- String packageName = null;
- String[] packages = getPackageManager().getPackagesForUid(
- getCallingUid());
- if (packages != null && packages.length > 0) {
- packageName = packages[0];
- }
- Log.d(TAG, "onTransact: " + packageName);
- if (!packageName.startsWith("com.ryg")) {
- return false;
- }
-
-
- return super.onTransact(code, data, reply, flags);
- }
-
-
-
-
-
- @Override
- public void registerListener(IOnNewBookArrivedListener listener)
- throws RemoteException {
-
-
-
-
-
-
-
- mListenerList.register(listener);
-
-
-
-
-
-
-
- final int N = mListenerList.beginBroadcast();
- mListenerList.finishBroadcast();
- Log.d(TAG, "registerListener, current size:" + N);
- }
-
-
- @Override
- public void unregisterListener(IOnNewBookArrivedListener listener)
- throws RemoteException {
- boolean success = mListenerList.unregister(listener);
-
-
- if (success) {
- Log.d(TAG, "unregister success.");
- } else {
- Log.d(TAG, "not found, can not unregister.");
- }
-
-
-
-
-
-
- final int N = mListenerList.beginBroadcast();
- mListenerList.finishBroadcast();
- Log.d(TAG, "unregisterListener, current size:" + N);
- };
-
-
- };
-
-
- @Override
- public void onCreate() {
- super.onCreate();
- mBookList.add(new Book(1, "Android"));
- mBookList.add(new Book(2, "Ios"));
- new Thread(new ServiceWorker()).start();
- }
-
-
- @Override
- public IBinder onBind(Intent intent) {
-
-
-
-
-
-
-
-
-
-
- int check = checkCallingOrSelfPermission("com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE");
- Log.d(TAG, "onbind check=" + check);
-
-
-
- if (check == PackageManager.PERMISSION_DENIED) {
- return null;
- }
- return mBinder;
- }
-
-
- @Override
- public void onDestroy() {
- mIsServiceDestoryed.set(true);
- super.onDestroy();
- }
-
-
-
-
-
-
-
-
- private void onNewBookArrived(Book book) throws RemoteException {
- mBookList.add(book);
- final int N = mListenerList.beginBroadcast();
- for (int i = 0; i < N; i++) {
- IOnNewBookArrivedListener l = mListenerList.getBroadcastItem(i);
- if (l != null) {
- try {
- l.onNewBookArrived(book);
- } catch (RemoteException e) {
- e.printStackTrace();
- }
- }
- }
- mListenerList.finishBroadcast();
- }
-
-
-
-
-
- private class ServiceWorker implements Runnable {
- @Override
- public void run() {
-
- while (!mIsServiceDestoryed.get()) {
- try {
- Thread.sleep(5000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- int bookId = mBookList.size() + 1;
- Book newBook = new Book(bookId, "new book#" + bookId);
- try {
- onNewBookArrived(newBook);
- } catch (RemoteException e) {
- e.printStackTrace();
- }
- }
- }
- }
-
-
- }
(3)客户端BookManagerActivity.java:
- package com.ryg.chapter_2.aidl;
-
- import java.util.List;
- import com.ryg.chapter_2.R;
- import com.ryg.chapter_2.aidl.IBookManager;
- import com.ryg.chapter_2.utils.MyConstants;
- import android.annotation.SuppressLint;
- import android.app.Activity;
- import android.content.ComponentName;
- import android.content.Context;
- import android.content.Intent;
- import android.content.ServiceConnection;
- import android.os.Bundle;
- import android.os.Handler;
- import android.os.IBinder;
- import android.os.Message;
- import android.os.RemoteException;
- import android.util.Log;
- import android.view.View;
- import android.widget.Toast;
-
- public class BookManagerActivity extends Activity {
-
- private static final String TAG = "BookManagerActivity";
- private static final int MESSAGE_NEW_BOOK_ARRIVED = 1;
-
- private IBookManager mRemoteBookManager;
-
-
-
-
-
-
-
-
- @SuppressLint("HandlerLeak")
- private Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MESSAGE_NEW_BOOK_ARRIVED:
- Log.d(TAG, "receive new book :" + msg.obj);
- break;
- default:
- super.handleMessage(msg);
- }
- }
- };
-
- private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
- @Override
- public void binderDied() {
- Log.d(TAG, "binder died. tname:" + Thread.currentThread().getName());
- if (mRemoteBookManager == null)
- return;
- mRemoteBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);
- mRemoteBookManager = null;
-
- }
- };
-
-
-
-
- private ServiceConnection mConnection = new ServiceConnection() {
- public void onServiceConnected(ComponentName className, IBinder service) {
-
-
-
-
- IBookManager bookManager = IBookManager.Stub.asInterface(service);
- mRemoteBookManager = bookManager;
- try {
-
-
-
- mRemoteBookManager.asBinder().linkToDeath(mDeathRecipient, 0);
- List<Book> list = bookManager.getBookList();
- Log.i(TAG, "query book list, list type:"
- + list.getClass().getCanonicalName());
- Log.i(TAG, "query book list:" + list.toString());
- Book newBook = new Book(3, "Android杩涢樁");
- bookManager.addBook(newBook);
- Log.i(TAG, "add book:" + newBook);
- List<Book> newList = bookManager.getBookList();
- Log.i(TAG, "query book list:" + newList.toString());
-
-
-
- bookManager.registerListener(mOnNewBookArrivedListener);
- } catch (RemoteException e) {
- e.printStackTrace();
- }
- }
-
- public void onServiceDisconnected(ComponentName className) {
- mRemoteBookManager = null;
- Log.d(TAG, "onServiceDisconnected. tname:" + Thread.currentThread().getName());
- }
- };
-
-
-
-
- private IOnNewBookArrivedListener mOnNewBookArrivedListener = new IOnNewBookArrivedListener.Stub() {
-
-
-
-
-
-
-
-
- @Override
- public void onNewBookArrived(Book newBook) throws RemoteException {
- mHandler.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED, newBook)
- .sendToTarget();
- }
- };
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_book_manager);
- Intent intent = new Intent(this, BookManagerService.class);
- bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
- }
-
- public void onButton1Click(View view) {
- Toast.makeText(this, "click button1", Toast.LENGTH_SHORT).show();
- new Thread(new Runnable() {
-
- @Override
- public void run() {
- if (mRemoteBookManager != null) {
- try {
- List<Book> newList = mRemoteBookManager.getBookList();
- } catch (RemoteException e) {
- e.printStackTrace();
- }
- }
- }
- }).start();
- }
-
- @Override
- protected void onDestroy() {
- if (mRemoteBookManager != null
- && mRemoteBookManager.asBinder().isBinderAlive()) {
- try {
- Log.i(TAG, "unregister listener:" + mOnNewBookArrivedListener);
- mRemoteBookManager
- .unregisterListener(mOnNewBookArrivedListener);
- } catch (RemoteException e) {
- e.printStackTrace();
- }
- }
- unbindService(mConnection);
- super.onDestroy();
- }
-
- }
注意点一:客户端调用远程服务的方法,被调用的方法运行在服务端的Binder线程池中,同时客户端线程会被挂起。这个时候如果服务端方法执行比较耗时,就会导致客户端线程长时间的阻塞在这里,而如果这个客户端线程是UI线程的话,就会导致客户端ANR。
注意点二:由于客户端的onServiceConnected和onServiceDisconnected方法都运行在UI线程中,所以也不可以在他们里面直接调用服务端的耗时方法,这点要尤其注意。
注意点三:由于服务端的方法本身就运行在服务端的Binder线程中,所以服务端方法本身就可以执行大量耗时操作,这个时候切记不要在服务端方法中开线程去执行异步任务。懂?就是耗时操作在服务端方法中直接执行,不要再开启其他的线程来执行耗时操作啦。
注意点四:同理,当远程服务端需要调用客户端的listener中的方法时,被调用的方法也运行在Binder线程池中,只不过是客户端的线程池,所以,我们同样不可以在服务端中调用客户端的耗时方法。如果非要调用耗时方法,请确保这个方法运行在非UI线程中,否则将导致服务端无法响应。
注意点五:AIDL使用方法总结:
首先建一个Service和一个AIDL接口,接着创建一个类继承自AIDL接口中的Stub类并实现Stub中的抽象方法,在Service的onBind方法中返回这个类的对象,然后客户端就可以绑定服务端Service,建立连接后就可以访问远程服务端的方法了。