Android-多进程通信
文章目录
一、简介
Android的多进程通信即IPC是指两个进程之间进行数据交换。进程一般指一个执行单元,在PC和移动设备中指一个程序或应用。最简单的情况下,Android应用中只有一个进程,包含一个线程,即主线程,也叫作UI线程,只能在此线程更新操作UI。普通情况下是不需要多进程的,但是当应用需要更多的内存或者某些特殊的Module或特殊的需求需要运行在多进程条件下。
在Android中多进程的方式有许多,Bundle、文件共享、Socket、Messenger、ContentProvider、AIDL。因为Android是Linux内核,所以文件的并发读写是被允许的,可写数据或将对象序列化写入文件,但是并没有提供什么便捷性,还容易出问题。Socket因为是端到端的通信,天然支持多进程,一个进程为服务端,一个进程作客户端。Messenger信使,可以在不同的进程传递Message对象,是一种轻量级的多进程方法。ContentProvider是Android提供的专门用于不同应用之间数据共享的方式,当然可用来进程间通信。系统也提供了很多内置的ContentProvider,通讯录、短信等。AIDL是Messenger的底层,实现相对复杂,但是能处理大量的并发请求及跨进程调用服务端的方法,Android将AIDL做了封装,便有了更方便上层调用的Messenger。
二、多进程模式
2.1 开启多进程
Android开启多进程的方式不多,正常情况只能在Manifest中给四大组件(Activity、Service、ContentProvider、Receiver)指定process属性。也有一个非常规的手段,通过JNI在native层去fork一个进程。以下是Activity的多进程示例,其他组件类似。:remote和com.example.package.remote表示此Activity运行在名为com.exmaple.package:remote和com.exmaple.package.remote的进程中。
<activity
android:name=".activity.RemoteActivity2"
android:label="Android多进程/.remote"
android:process="com.example.package.remote" />
<activity
android:name=".activity.RemoteActivity1"
android:label="Android多进程/:remote"
android:process=":remote" />
通过ADB命令查看
adb shell ps | findstr com.libo
可以看到有三个进程,其中以包名命名的进程是默认进程,.remote和*:remote是自定义的多进程,两个自定义进程之间的区别就是:是一种简写形式,实际是在:前附加上packagename;.是定义了完整的进程名,不会附加包名信息,显示com.libok.androidnote.remote*是因为定义的名称如此。:开头的进程是属于当前应用的私有进程,其他应用的组件不能和它跑在同一个进程中;.进程名的是属于全局进程,其他应用可以通过ShareUID方式和它跑在同一个进程中。
2.2 多进程带来的问题
-
静态变量失效
在一个Activity中新建一个静态变量TEST_STATIC,并在RemoteActivity1中的onStartOtherRemoteActivity方法中自增,之后启动RemoteActivity2,并在2中打印TEST_STATIC的值。
public static int TEST_STATIC = 21; public void onStartOtherRemoteActivity(View view) { TEST_STATIC++; Log.e(TAG, "onStartOtherRemoteActivity: " + TEST_STATIC); startActivity(new Intent(this, RemoteActivity2.class)); }
结果:
// RemoteActivity1 log E/RemoteActivity1: onStartOtherRemoteActivity: 22 // RemoteActivity2 log E/RemoteActivity2: onCreate: 21
并不相同的数值说明在多进程中静态变量是失效的,同样的因为静态变量带来的问题是单例模式的失效。原因就是多进程时Android为其他进程分配了一个新的虚拟机,导致不同的虚拟机在内存上有不同的内存地址, 当在新的进程访问变量时,访问的其实是这个类在新的虚拟机中的副本,也就是相当于在:remote和.remote中各有一个RemoteActivity1类,而.remote访问的那个副本中的TEST_STATIC是没有进行自增操作的,所以还是会打印出21的初始数值,而在:remote中是自增过的22。单例模式也是同样的解释,当在另一个进程中访问单例类时,在此进程中其实并没有进行初始化,所以才会失效。
-
线程同步机制失效
本质上跟静态变量类似,在一个进程锁住的是副本的对象,而在另一个副本中,内存都不同,所以肯定是无效的。
-
SharedPreferences可靠性下降
因为SharedPreferences不支持两个进程同时去执行写操作,否则会导致一定几率的数据丢失。SharedPreferences的底层是通过读写XML文件实现的,并发写很可能导致问题,并发读写都不能保证不会出问题。
-
Application会被创建多次
当一个组件跑在一个新的进程中时,系统给新的进程分配一个新的虚拟机,就相当于应用又一次的重新启动,Application作为应用基础肯定也会被重新创建。
新建Application类,继承自Application,并在onCreate方法中输出当前进程的PID:
public class LApplication extends Application { private static final String TAG = "LApplication"; @Override public void onCreate() { super.onCreate(); Log.e(TAG, "onCreate: " + android.os.Process.myPid()); } }
当依次开启进程后输出如下:
// Main E/LApplication: onCreate: 16031 // RemoteActivity1 E/LApplication: onCreate: 16127 // RemoteActivity2 E/LApplication: onCreate: 16202
Application被创建多次带来的问题是,有些时候会需要在Application中初始化些依赖,但是多进程就会随着Application的创建而重复初始化,可以在Application中设置一些条件跳过重复初始化部分。
// 根据pid获取进程名 private String getAppName(int pid) { String processName = null; ActivityManager am = (ActivityManager) this.getSystemService(ACTIVITY_SERVICE); List<ActivityManager.RunningAppProcessInfo> list = am.getRunningAppProcesses(); for (ActivityManager.RunningAppProcessInfo info : list) { try { if (info.pid == pid) { processName = info.processName; return processName; } } catch (Exception e) { e.printStackTrace(); return null; } } return null; }
通过PID获取进程名,与包名做对比,只有跟包名一致时才做一些初始化工作。
三、Binder
Binder是一个实现了IBinder接口的类。从IPC的角度来说,是一种跨进程通信方式,还可以理解为一种在Linux中没有的虚拟物理设备,设备驱动是/dev/binder;从Android Framework角度说,Binder是ServiceManager连接各种Manager和相应的ManagerService的桥梁;从Android应用层来说,Binder是服务端和客户端进行通信的媒介,当bindService时,服务端会返回一个包含服务端调用的Binder对象,通过这个对象,客户端就可以获取服务端提供的服务或信息数据,这里的服务包括普通服务和基于AIDL的服务。
3.1 初识Binder
Binder主要用在Service中,包括AIDL和Messenger,普通的Service中的Binder不涉及进程间的通信,新建一个AIDL的示例如下。
-
在项目目录下,新建aidl文件夹,并新建实现Parcelable接口的Book类。
public class Book implements Parcelable { public static final Creator<Book> CREATOR = new Creator<Book>() { @Override public Book createFromParcel(Parcel in) { return new Book(in); } @Override public Book[] newArray(int size) { return new Book[size]; } }; private int mBookId; private String mBookName; public Book(int bookId, String bookName) { mBookId = bookId; mBookName = bookName; } private Book(Parcel in) { mBookId = in.readInt(); mBookName = in.readString(); } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mBookId); dest.writeString(mBookName); } @Override public int describeContents() { return 0; } public int getBookId() { return mBookId; } public void setBookId(int bookId) { mBookId = bookId; } public String getBookName() { return mBookName; } public void setBookName(String bookName) { mBookName = bookName; } @Override public String toString() { return "Book{" + "mBookId=" + mBookId + ", mBookName='" + mBookName + '\'' + '}'; } }
-
在项目的main路径下,新建aidl文件夹及子目录com.example.name.aidl,并在其中创建IBookManager.aidl,如果是在AndroidStudio中,就省去了创建目录的操作,直接在main->java->aidl下右键new->new AIDL File创建IBookManager.aidl就会自动创建到main->aidl->com.example.name.aidl目录下。
// IBookManager.aidl package com.libok.androidnote.aidl; // Declare any non-default types here with import statements import com.libok.androidnote.aidl.Book; interface IBookManager { List<Book> getBookList(); void addBook(in Book book); }
其中getBookList方法是获取所有的Book,addBook方法是添加一个Book。addBook的参数中有个关键字in,表示数据是从客户端传递到服务端,相应的还会有out以及inout,这个等后面的IPC方式时再做探讨。
-
有了aidl在main路径下的目录后,再创建一个Book.aidl,IBookManager.aidl中也有import Book类,但是引入的不是Book.java而是Book.aidl,之所以后建Book.aidl,是因为需要先创建IBookManager.aidl所自动创建的路径。
package com.libok.androidnote.aidl; parcelable Book;
-
等Java类、类对应AIDL、业务数据处理AIDL三个文件(根据自身需要做调整)建好后执行AndroidStudio->Build->Make Project即可自动生成IBookManager.java。AndroidStudio3.6.1+gradle3.6.1目录如下:
/* * This file is auto-generated. DO NOT MODIFY. */ package com.libok.androidnote.aidl; public interface IBookManager extends android.os.IInterface { /** * Local-side IPC implementation stub class. */ public static abstract class Stub extends android.os.Binder implements com.libok.androidnote.aidl.IBookManager { private static final java.lang.String DESCRIPTOR = "com.libok.androidnote.aidl.IBookManager"; /** * Construct the stub at attach it to the interface. */ public Stub() { this.attachInterface(this, DESCRIPTOR); } /** * Cast an IBinder object into an com.libok.androidnote.aidl.IBookManager interface, * generating a proxy if needed. */ public static com.libok.androidnote.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.libok.androidnote.aidl.IBookManager))) { return ((com.libok.androidnote.aidl.IBookManager) iin); } return new com.libok.androidnote.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 { java.lang.String descriptor = DESCRIPTOR; switch (code) { case INTERFACE_TRANSACTION: { reply.writeString(descriptor); return true; } case TRANSACTION_getBookList: { data.enforceInterface(descriptor); java.util.List<com.libok.androidnote.aidl.Book> _result = this.getBookList(); reply.writeNoException(); reply.writeTypedList(_result); return true; } case TRANSACTION_addBook: { data.enforceInterface(descriptor); com.libok.androidnote.aidl.Book _arg0; if ((0 != data.readInt())) { _arg0 = com.libok.androidnote.aidl.Book.CREATOR.createFromParcel(data); } else { _arg0 = null; } this.addBook(_arg0); reply.writeNoException(); return true; } default: { return super.onTransact(code, data, reply, flags); } } } private static class Proxy implements com.libok.androidnote.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.libok.androidnote.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.libok.androidnote.aidl.Book> _result; try { _data.writeInterfaceToken(DESCRIPTOR); mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0); _reply.readException(); _result = _reply.createTypedArrayList(com.libok.androidnote.aidl.Book.CREATOR); } finally { _reply.recycle(); _data.recycle(); } return _result; } @Override public void addBook(com.libok.androidnote.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(); } } } static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1); } public java.util.List<com.libok.androidnote.aidl.Book> getBookList() throws android.os.RemoteException; public void addBook(com.libok.androidnote.aidl.Book book) throws android.os.RemoteException; }
至此Binder就已经创建完毕,接下来各方面解析一下自动生成的IBookManager.java。
3.2 再识Binder
IBookManager类乍看有点乱,其实还是比较清晰的。
IBookManager接口继承自android.os.IInterface,并在内部实现一个静态抽象内部类Stub,除此之外还声明了两个IBookManager.aidl中定义的getBookList()方法和addBook(com.libok.androidnote.aidl.Book book)方法。重点关注的应该是其内部类Stub。
Stub内部解析
-
Stub类
1.Stub继承了android.os.Binder并实现了IBookManager接口,继承Binder重写了onTransact方法,使其能处理自定的业务和数据。
2.实现了IBookManager接口的继承方法asBinder。
-
DESCRIPTOR
Binder唯一标识,通常用当前Binder的类名表示,防止冲突。
-
TRANSACTION_getBookList和TRANSACTION_addBook
标识声明的方法,在方法onTransact中区分远程调用的方法是什么。范围在IBinder.FIRST_CALL_TRANSACTION到IBinder.LAST_CALL_TRANSACTION之间。
-
asInterface方法
用于将服务端的Binder对象转换成客户端所需要的AIDL接口类型的对象,如果客户端和服务端在同一进程,则返回的是服务端的Stub对象本身,不在同一进程,返回的就是系统封装后的Stub.Proxy对象。
-
asBinder方法
返回当前Binder对象。
-
onTransact方法
此方法运行在服务端中的Binder线程池中,当客户端发起跨进程请求时,远程会通过系统底层封装后交由此方法处理。
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
服务端通过code区分被调用的方法,从data中取出方法所需的参数,如果有参数的话,然后执行方法。当方法执行完毕,通过reply写入返回值,如果有返回值的话。返回值表示客户端是否请求成功,如果返回false,那么客户端的请求会失败,可用来作权限验证,是否是准许的客户端调用而不是其他的任意客户端。
-
Proxy类
代理类,当客户端和服务端不在同一进程时会被从asInterface方法中创建,客户端通过调用Proxy的方法实现调用服务端的方法。
Proxy内部解析
-
getBookList方法
方法运行在客户端,当客户端远程调用此方法时,内部过程是这样的:首先创建输入型Parcel对象_data、输出型Parcel对象_reply以及返回值对象List;接着把方法的参数写进_data中(如果有参数的话),调用transact方法发起RPC(远程过程调用)请求,同时将当前线程挂起;然后服务端的onTransact方法会被调用,将远程方法结果写入_reply中(如果有结果的话),并返回请求结果true或false,直到RPC过程返回后,当前线程才会继续执行,并从_reply中获取RPC过程的方法结果;最后返回_reply中的数据。
-
addBook方法
调用过程与上一个方法一样,只是多了参数,少了返回值。
总结
- 因为在客户端发起请求后,所在的线程会被挂起直到服务端进程返回结果,所以如果一个远程方法是耗时的,那么不能在UI线程中发起请求,会导致不必要的ANR。
- 由于服务端的Binder方法运行在Binder的线程池中,所以Binder方法不管是否耗时都应该采用同步的方式实现,因为它已经运行在一个线程中了。
3.3 手写Binder
public interface IMyBookManager extends IInterface {
public static final String TAG = "IMyBookManager";
public static abstract class Stub extends Binder implements IMyBookManager {
private static final String DESCRIPTOR = "com.libok.androidnote.core.IMyBookManager";
private static final int FUNCTION_GET_BOOK_LIST = FIRST_CALL_TRANSACTION + 1;
private static final int FUNCTION_ADD_BOOK = FIRST_CALL_TRANSACTION + 2;
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
public static IMyBookManager asInterface(IBinder binder) {
if (binder == null) {
return null;
}
IInterface iInterface = binder.queryLocalInterface(DESCRIPTOR);
if (iInterface instanceof IMyBookManager) {
Log.e(TAG, "asInterface: local interface");
return (IMyBookManager) iInterface;
}
Log.e(TAG, "asInterface: proxy interface");
return new Proxy(binder);
}
@Override
public IBinder asBinder() {
return this;
}
@Override
protected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException {
switch (code) {
case INTERFACE_TRANSACTION:
if (reply != null) {
reply.writeString(DESCRIPTOR);
}
return true;
case FUNCTION_GET_BOOK_LIST:
data.enforceInterface(DESCRIPTOR);
List<Book> bookList = this.getBookList();
if (reply != null) {
reply.writeNoException();
// reply.writeException(new NullPointerException("Test Proxy reply.readException"));
reply.writeTypedList(bookList);
}
return true;
case FUNCTION_ADD_BOOK:
data.enforceInterface(DESCRIPTOR);
Book book = null;
if (data.readInt() != 0) {
book = Book.CREATOR.createFromParcel(data);
}
this.addBook(book);
if (reply != null) {
reply.writeNoException();
}
return true;
default:
return super.onTransact(code, data, reply, flags);
}
}
public static class Proxy implements IMyBookManager {
private IBinder mBinder;
public Proxy(IBinder binder) {
mBinder = binder;
}
@Override
public IBinder asBinder() {
return mBinder;
}
public String getInterfaceDescriptor() {
return DESCRIPTOR;
}
@Override
public List<Book> getBookList() throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
List<Book> result = null;
try {
data.writeInterfaceToken(DESCRIPTOR);
// data.writeInterfaceToken(DESCRIPTOR + "1");
mBinder.transact(FUNCTION_GET_BOOK_LIST, data, reply, 0);
reply.readException();
result = reply.createTypedArrayList(Book.CREATOR);
} finally {
data.recycle();
reply.recycle();
}
return result;
}
@Override
public void addBook(Book book) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
try {
data.writeInterfaceToken(DESCRIPTOR);
if (book != null) {
data.writeInt(1);
book.writeToParcel(data, 0);
} else {
data.writeInt(0);
}
mBinder.transact(FUNCTION_ADD_BOOK, data, reply, 0);
} finally {
data.recycle();
reply.recycle();
}
}
}
}
public List<Book> getBookList() throws RemoteException;
public void addBook(Book book) throws RemoteException;
}
纸上得来终觉浅,绝知此事要躬行。
不手动写一遍还体会不到这个流程到底是怎样的,不知道Stub中onTransact方法执行时首先需要data参数执行一个enforceInterface
方法,需要检验一下client和service执行的是不是同一个方法,没写到Proxy类时,还在纳闷这个方法到底是什么作用,源码中也没有作什么解释,在Stack Overflow上搜加写完Proxy类后才知道是一个检验方法,在Proxy类中执行接口的方法第一步就是先在data执行writeInterfaceToken
,这个方法有注释是这样写的:
/** * Store or read an IBinder interface token in the parcel at the current * {@link #dataPosition}. This is used to validate that the marshalled * transaction is intended for the target interface. */
与enforceInterface
是一对函数,这才恍然大悟,这也就是自己手动写一遍的好处。
在写的时候慢慢的有了更深的认识,当然再深都只是应用层的东西,想要更了解可以去看底层源码。
Stub中的DESCRIPTOR、FUNCTION_GET_BOOK_LIST以及FUNCTION_ADD_BOOK没什么好说的了。
-
Stub要继承Binder,这是必然的,如果不继承那么就没多进程通信什么事了。
-
还要实现IMyBookManager接口,当然这也是必然的,不然到了服务端都没有业务方法可执行。
-
Stub的构造函数,只有一句
this.attachInterface(this, DESCRIPTOR);
但是却关系着服务端运行在与客户端相同或不同的进程时asInterface
方法返回的Binder,也就是绑定Service时返回的Binder。只有在Binder中绑定Interface才会在Binder的queryLocalInterface
方法查询到,才能在不同的环境下返回正确的Binder。 -
asInterface
静态函数,返回相应的继承了相应接口的对象,在客户端和服务端相同进程时返回Stub构造函数中注册的本身,在不同进程时返回代理。其中首先参数binder执行queryLocalInterface方法,查询是否存在跟调用端也就是客户端同一进程的接口对象。参数binder从何而来,在bindService时需要的ServiceConnection中的onServiceConnected方法参数,在此方法的Binder肯定不会查询到接口对象,所以才会创建一个Proxy代理对象。Proxy同样也是运行在客户端进程的。 -
asBinder方法没啥可讲的,不管是同进程还是不同进程,都是返回服务端的Binder。
-
先说Proxy类再说Stub的onTransact方法,Proxy类是要实现IMyBookManager接口的,这也是必然,既然叫Proxy就得有代理的地方,也就是实现接口供客户端调用方法,继而在方法中完成跨进程的方法调用。
-
Proxy类的构造函数接收一个Binder对象,此Binder对象是Stub的asInterface方法中接收的Binder对象,同样也是onServiceConnected的方法参数。
-
Proxy实现的接口方法中创建Parcel对象data和reply,需要注意的是Parcel对象的获得及回收,因为Parcel类是有一个静态的Parcel数组作Parcel池,要正确的维护此ParcelPool。其中data存放的是需要执行的方法参数,reply存放的是方法的执行结果。在执行远程方法执行请求之前要先在data中写入一个token,以便在onTransact方法中作检验,之后执行远程方法的执行请求——Binder的transact方法,此方法需要的参数是执行的远程方法的标识、data、reply,及一个flags,方法注释中描述常规的远程调用默认是0,如果是单向调用那么flags需要填写FLAG_ONEWAY,此标记的意思是Proxy发送远程方法的调用请求后立即返回,不再等远程方法的执行结果,看情况选择哪一个flags。
/** * Flag to {@link #transact}: this is a one-way call, meaning that the * caller returns immediately, without waiting for a result from the * callee. Applies only if the caller and callee are in different * processes. */
之后一步是执行reply的readException方法读取一下方法执行是否有异常,异常的写入同样是在远程的onTransact方法中,如果有异常会在此步抛出异常。如果没有异常那么就可以从reply中读取正确的返回值,之后将Parcel对象data和reply回收,最后将返回值返回到调用方。
-
asBinder方法没啥可讲的,返回服务端的Binder。
-
Stub的onTransact方法,服务端具体执行接口方法的地方
protected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException
方法参数code标明Proxy想要调用的是哪个方法,data保存方法参数,reply保存方法执行结果。执行自定义方法时先执行data的enforceInterface,正常来说是没问题的,但可以通过人为的操作让其不正常,见手写示例的Proxy类中的注释语句,如果检验不成功的话是会报以下异常。
Caused by: java.lang.SecurityException: Binder invocation to an incorrect interface
at android.os.Parcel.createException(Parcel.java:2071)
at android.os.Parcel.readException(Parcel.java:2039)
at android.os.Parcel.readException(Parcel.java:1987)
at com.libok.androidnote.core.IMyBookManager S t u b Stub StubProxy.getBookList(IMyBookManager.java:111)
at com.libok.androidnote.activity.IPCActivity.onGetBookList(IPCActivity.java:65)之后就是调用远程方法,远程方法的实现在Service中创建Binder时实现的方法,此时就已经是真正自己的业务逻辑位置。如果方法执行没问题就可以在reply中写入NoException,要是有异常可以执行reply的writeException方法,将异常写入reply,需要记得的是writeException方法和readException方法得成对出现。故意在其中写入一个NullPointerException,见手写示例的Stub类中的注释语句,会在Proxy中执行readException时抛出以下异常。
Caused by: java.lang.NullPointerException: Test Proxy reply.readException
at android.os.Parcel.createException(Parcel.java:2077)
at android.os.Parcel.readException(Parcel.java:2039)
at android.os.Parcel.readException(Parcel.java:1987)
at com.libok.androidnote.core.IMyBookManager S t u b Stub StubProxy.getBookList(IMyBookManager.java:111)
at com.libok.androidnote.activity.IPCActivity.onGetBookList(IPCActivity.java:65)之后再将方法执行结果写入reply中。onTransact方法是具有返回值的,返回true表示远程方法调用成功,false则失败,可以用此返回值做一些其他的检验,比如数据校验等,不正常可直接返回false终止调用。
下面附上测试代码及一点说明。
IPCActivity作为本地客户端,布局就不列出了,只有两个Button,其中注释的代码是用的通过AIDL来自动创建的IBookManager.java。
public class IPCActivity extends AppCompatActivity {
private static final String TAG = "IPCActivity";
private static final int BOOK_START_ID = 7873;
private int mBookId = BOOK_START_ID;
// private IBookManager mIBookManager = null;
private IMyBookManager mOwnBookManager = null;
private ServiceConnection mBookServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// mIBookManager = IBookManager.Stub.asInterface(service);
mOwnBookManager = IMyBookManager.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_ipc);
Intent remoteBookServiceIntent = new Intent(this, BookManagerService.class);
bindService(remoteBookServiceIntent, mBookServiceConnection, Context.BIND_AUTO_CREATE);
}
public void onGetBookList(View view) {
// if (mIBookManager != null) {
// try {
// List<Book> bookList = mIBookManager.getBookList();
// Log.e(TAG, "onGetBookList: " + bookList.toString());
// } catch (RemoteException e) {
// e.printStackTrace();
// }
// }
if (mOwnBookManager != null) {
try {
List<Book> bookList = mOwnBookManager.getBookList();
Log.e(TAG, "onGetBookList: " + bookList.toString());
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
public void onAddBookToList(View view) {
// if (mIBookManager != null) {
// try {
// mBookId++;
// mIBookManager.addBook(new Book(mBookId, "Added Book" + mBookId));
// } catch (RemoteException e) {
// e.printStackTrace();
// }
// }
if (mOwnBookManager != null) {
try {
mBookId++;
mOwnBookManager.addBook(new Book(mBookId, "Own Added Book" + mBookId));
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(mBookServiceConnection);
}
}
BookManagerService作服务端,并在Manifest中设置Service的Process属性让其运行在另一个进程。
public class BookManagerService extends Service {
private static final String TAG = "BookManagerService";
private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();
private Binder mBinder = new IBookManager.Stub() {
@Override
public List<Book> getBookList() {
Log.e(TAG, "getBookList: " + mBookList.size());
return mBookList;
}
@Override
public void addBook(Book book) {
Log.e(TAG, "addBook: " + book.toString());
mBookList.add(book);
}
};
private Binder mOwnBinder = new IMyBookManager.Stub() {
@Override
public List<Book> getBookList() throws RemoteException {
return mBookList;
}
@Override
public void addBook(Book book) throws RemoteException {
mBookList.add(book);
}
};
public BookManagerService() {
mBookList.add(new Book(123, "Love & Peace"));
mBookList.add(new Book(321, "Play Boy"));
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
// throw new UnsupportedOperationException("Not yet implemented");
Log.e(TAG, "onBind: " + (mOwnBinder.queryLocalInterface("com.libok.androidnote.core.IMyBookManager") == null) + mOwnBinder.toString());
return mOwnBinder;
}
}
在服务端创建IMyBookManager的内部类Stub的对象mBinder,mBinder不仅是个Binder对象还是个IMyBookManager对象,并需要实现IMyBookManager接口的所有方法,即服务端业务逻辑处理。在Service被绑定时返回mBinder对象供客户端中的服务会话ServiceConnection调用。ServiceConnection中onServiceConnected方法的参数IBinder service
与mBinder是有不同点的,在Stub中能否通过DESCRIPTOR找到对应的接口对象,只有通过创建的Binder才会被注册。
当服务端不是另一个进程时,流程就简单了,只是继承的Binder类跑到了IMyBookManager中,在绑定Service时onBind方法返回的和ServiceConnection中onServiceConnected方法参数是同一个Binder,而获取接口对象的asInterface方法会直接返回找到的IInterface对象,即在构造函数中绑定的Binder this,然后跟Proxy类就没什么关系了。
3.4 Binder死亡代理
Service不管是运行在另一个进程还是跟客户端同进程,都可能会碰到被杀死的情况,此时Binder不能正常工作,导致调用失败,当然Binder在设计是也是充分考虑到了这种情况,所以Binder中有一对很重要方法linkToDeath和unlinkToDeath可以做一些针对性的工作。
首先得声明一个DeathRecipient对象,实现其binderDied方法,当代理的Binder死亡是会调用此方法,我们可以在此方法中作重新绑定Service等操作。
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
Log.e(TAG, "binderDied: ");
if (mOwnBookManager == null) {
return;
}
mOwnBookManager.asBinder().unlinkToDeath(this, 0);
mOwnBookManager = null;
bindService(mRemoteBookServiceIntent, mBookServiceConnection, Context.BIND_AUTO_CREATE);
// 其他操作
}
};
在ServiceConnection中的onServiceConnected方法绑定死亡代理,即客户端绑定远程服务成功后,等Binder死亡时就可以收到通知并会执行既定方法。
private ServiceConnection mBookServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mIBookManager = IBookManager.Stub.asInterface(service);
Log.e(TAG, "onServiceConnected: " + service.toString());
mOwnBookManager = IMyBookManager.Stub.asInterface(service);
try {
service.linkToDeath(mDeathRecipient, 0);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.e(TAG, "onServiceDisconnected: ");
}
};
除此之外还可以通过Binder的isBinderAlive方法判断Binder是否死亡。
四、IPC方式
4.1 Bundle
Android四大组件中的三大组件Activity、Service、Receiver都是支持在Intent中传递Bundle数据的,Bundle也实现了Parcelable接口,所以可以方便的在不同的进程间传输。当一个进程通过Intent启动另一个进程的三大组件时,就可以在Bundle中附加所需要传输的信息通过Intent传输。显然,传输的信息必然得是能够被序列化的,不管是实现的Serializable接口还是Parcelable接口。
除启动三大组件时直接传递数据外,还有一种特别的使用场景。比如A进程启动B进程的一个组件时时需要将一个数据传递过去,但是此数据并不支持放入Bundle中,也就不能通过Intent来传输,而采用其他的IPC方式会略显复杂,此时就可以考虑通过放入数据获取的必要条件的Intent并启动B进程的一个Service组件,(比如IntentService,可在执行完毕后自行销毁)。然后让Service在后台对数据进行获取,获取完毕后再启动真正想要启动的B进程组件,因为Service也同样是运行在B进程的,数据可以直接传递,此操作也就解决了跨进程的问题。该方法的核心在于将数据获取的步骤转移到另一进程进行,只传递必要条件,如果必要条件也是不支持放入Bundle的,那么再往前找必要条件,但是再这么找下去反而是适得其反了,可以考虑下其他的IPC方式。
4.2 文件共享
文件共享也是一种不错的进程间通信方式,两个进程通过读/写,A进程把数据写入文件,B进程从文件读取数据。因为Android基于Linux,所以并发读/写是没有限制的,甚至同时两个线程对同一个文件进行写操作都是允许的,当然是很容易出问题的。下面的例子展示两个进程对文件的读/写达到传递数据的目的。
数据传递方
public void onIPCFileMode(View view) {
SerializableBean bean = new SerializableBean();
new Thread(new Runnable() {
@Override
public void run() {
String dataSavePath = getCacheDir().getPath() + File.separator + "ipc_data_save";
File dataFile = new File(dataSavePath);
ObjectOutputStream objectOutputStream = null;
try {
objectOutputStream = new ObjectOutputStream(new FileOutputStream(dataFile));
objectOutputStream.writeObject(bean);
objectOutputStream.flush();
startActivity(new Intent(RemoteActivity1.this, RemoteActivity2.class));
} catch (IOException e) {
e.printStackTrace();
}finally {
if (objectOutputStream != null) {
try {
objectOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}).start();
}
数据接收方
private void onIPCFileMode() {
new Thread(new Runnable() {
@Override
public void run() {
String dataSavePath = getCacheDir().getPath() + File.separator + "ipc_data_save";
File dataFile = new File(dataSavePath);
if (!dataFile.exists()) {
return;
}
ObjectInputStream objectInputStream = null;
Object result = null;
try {
objectInputStream = new ObjectInputStream(new FileInputStream(dataFile));
result = objectInputStream.readObject();
if (result instanceof SerializableBean) {
Log.e(TAG, "onIPCFileMode run: " + result.toString());
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (objectInputStream != null) {
try {
objectInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}).start();
}
可以在接收方进程看到这样的log,表明数据传递成功。
E/RemoteActivity2: onIPCFileMode run: SerializableBean{mString=‘ABC’, mInt=21, mBoolean=true, mLong=221, mShort=2, mByte=96, mChar=a}
通过这种方式共享数据对文件格式是没有具体要求的,可以文本文件,也可以是XML文件等,只要读/写双方约定数据格式即可。文件共享的方式也是有局限性的,比如并发读/写,如果并发读/写,那么读出的数据有可能不是祖新的,如果是并发写那就更完蛋了。尽量避免此情况发生或者使用线程同步限制多个线程的写操作。
既然任何文件格式都可以,那么SharedPreferences应该也是可行的。SharedPreferences是Android提供的轻量级的存储方案,通过键值对的方式存储数据,在底层是XML文件来存储键值对,理论上是完全支持的。但是由于系统对它的读/写有一定的缓存策略,即在内存中也会有一份SharedPreferences文件的缓存,因此在多进程模式下系统对其读/写就变得不可靠,当面对高并发的读/写访问时,SharedPreferences有很大几率丢失数据,因此不适合在进程间通信用SharedPreferences。SharedPreferences中是有多进程MODE的MODE_MULTI_PROCESS
,但现在已经被弃用了,在Android-SharedPreferences中会有所阐述。
4.3 Messenger
介绍
Messenger轻量级的多进程通信方法,可以通过它在不同的进程中传递Message对象,而需要传递的数据就可以放在Message中。其底层实现是AIDL,可以在Messenger的构造方法中发现,与章节三中的AIDL方式一模一样。
public Messenger(Handler target) {
mTarget = target.getIMessenger();
}
public Messenger(IBinder target) {
mTarget = IMessenger.Stub.asInterface(target);
}
Messenger的使用方法也很简单,对AIDL作了封装使得更简单了。尤其是Messenger一次只能处理一个请求,不能并发请求,也不需要作线程同步的考虑。
使用
-
服务端
在Service中创建一个继承Handler的内部类MessengerHandler,此类的作用就是处理从客户端传过来的消息,以Handler作参数创建一个Messenger,并在Service的onBind方法返回此Messenger的Binder供客户端构建Messenger使用。服务端和客户端的信息传递是用Message对象作载体,在MessengerHandler中的handleMessage方法进行处理。
Message是肯定实现了Parcelable接口的,作为载体,Message能支持的对象传输类型,Messenger肯定也是能支持的,包括能被序列化的对象及基本类型。Message中有几个属性可以携带信息,what、arg1、arg2、Bundle、replyTo、object,其中what、arg1、arg2都是int类型可携带一些标识符啥的;replyTo是Messenger类型作用在完成一次客户端到服务端的信息传递后需要将信息再回传客户端,在让客户端将Message封装时附带上客户端的Messenger;Bundle是主要的信息传输,毕竟支持序列化对象及其他对象;object对象在跨进程传输时作用不是很大,因为object不能传递自定义的序列化对象,不光是Parcelable对象,传输Serializable对象也会导致
java.lang.RuntimeException: Can't marshal non-Parcelable objects across processes.
这样的异常。服务端代码如下:
public class MessengerService extends Service { private static final String TAG = "MessengerService"; public static final int OPERATION_GET_BOOK_LIST = 1; public static final int OPERATION_ADD_BOOK = 2; private ArrayList<Book> mBookList = new ArrayList<>(); private Messenger mMessenger = new Messenger(new MessengerHandler(this)); public MessengerService() { mBookList.add(new Book(123, "Love & Peace")); mBookList.add(new Book(321, "Play Boy")); } @Override public IBinder onBind(Intent intent) { // TODO: Return the communication channel to the service. // throw new UnsupportedOperationException("Not yet implemented"); return mMessenger.getBinder(); } private static class MessengerHandler extends Handler { private WeakReference<MessengerService> mWeakReference; MessengerHandler(MessengerService service) { mWeakReference = new WeakReference<>(service); } @Override public void handleMessage(Message msg) { Log.e(TAG, "handleMessage: " + msg.what); switch (msg.what) { case OPERATION_GET_BOOK_LIST: if (mWeakReference.get() != null) { Bundle data = new Bundle(); data.putParcelableArrayList("result_get_list", mWeakReference.get().mBookList); Message message = Message.obtain(); message.what = IPCActivity.RESULT_MESSENGER_BOOK_LIST; message.setData(data); if (msg.replyTo != null) { try { msg.replyTo.send(message); } catch (RemoteException e) { e.printStackTrace(); } } } break; case OPERATION_ADD_BOOK: if (mWeakReference.get() != null) { Bundle data = msg.getData(); data.setClassLoader(getClass().getClassLoader()); Parcelable parcelable = data.getParcelable("add_book"); if (parcelable instanceof Book) { mWeakReference.get().mBookList.add((Book) parcelable); } } break; default: super.handleMessage(msg); } } } }
-
客户端
跟服务端一样,同样创建一个Handler,并作为参数传递创建一个客户端Messenger,如果客户端传递给服务端消息后需要有回执的话就将此Messenger设置在Message的replyTo属性。其他的就是正常的绑定Service,并在onServiceConnected方法中创建一个客户端持有的服务端Messenger,用来向服务端传递Message。
传递就很简单了,不管是示例中onGetBookListByMessenger或onAddBookToListByMessenger方法都是先获取一个Message,然后塞入需要传递的对象,有回执需求就配置上replyTo,传递完就在Handler中等服务端回传的Message。有一个非常需要注意的点是从Bundle中获取自定义Parcelable对象时需要对Bundle设置一次ClassLoader,不然会找不到自定义对象报此异常
android.os.BadParcelableException: ClassNotFoundException when unmarshalling: com.libok.androidnote.aidl.Book
,同样的服务端的也是如此。客户端代码如下:
public class IPCActivity extends AppCompatActivity { private static final String TAG = "IPCActivity"; public static final int RESULT_MESSENGER_BOOK_LIST = 9876; private Messenger mServerMessenger = null; private ServiceConnection mMessengerConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { mServerMessenger = new Messenger(service); } @Override public void onServiceDisconnected(ComponentName name) { mServerMessenger = null; } }; private Handler mMessengerHandler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { switch (msg.what) { case RESULT_MESSENGER_BOOK_LIST: Bundle data = msg.getData(); Log.e(TAG, "handleMessage: get class loader:" + data.getClassLoader()); data.setClassLoader(getClassLoader()); ArrayList<Parcelable> result = data.getParcelableArrayList("result_get_list"); Log.e(TAG, "handleMessage: RESULT_MESSENGER_BOOK_LIST " + result.toString()); break; default: super.handleMessage(msg); } } }; private Messenger mClientMessenger = new Messenger(mMessengerHandler); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_ipc); } @Override protected void onDestroy() { unbindService(mMessengerConnection); super.onDestroy(); } /** * 绑定Messenger方式的Service */ public void onBindMessengerService(View view) { Intent messengerServiceIntent = new Intent(this, MessengerService.class); bindService(messengerServiceIntent, mMessengerConnection, BIND_AUTO_CREATE); } /** * 通过Messenger添加书本 */ public void onAddBookToListByMessenger(View view) { if (mServerMessenger != null) { Book book = new Book(158, "声微饭否"); Bundle data = new Bundle(); data.putParcelable("add_book", book); Message addBookMessage = Message.obtain(); addBookMessage.what = MessengerService.OPERATION_ADD_BOOK; addBookMessage.setData(data); try { mServerMessenger.send(addBookMessage); } catch (RemoteException e) { e.printStackTrace(); } } } /** * 通过Messenger获取所有的书本 */ public void onGetBookListByMessenger(View view) { if (mServerMessenger != null) { Message getListMessage = Message.obtain(); getListMessage.what = MessengerService.OPERATION_GET_BOOK_LIST; getListMessage.replyTo = mClientMessenger; try { mServerMessenger.send(getListMessage); } catch (RemoteException e) { e.printStackTrace(); } } } }
-
总结
4.4 AIDL
在Binder章节中已经接触过AIDL,简单的接口,简单的调用,但是其实已经讲述了AIDL的基本用法,此节中除了再回顾一下基本操作还有些新东西。
AIDL简单介绍
依然是分为客户端和服务端两个部分:
服务端
创建一个Service来监听客户端的连接请求,然后创建一个AIDL文件,将暴露给客户端的接口在此文件中声明,最后在Service中实现AIDL接口。
客户端
需要先绑定服务端Service,在绑定成功后将服务端返回的Binder对象转成AIDL接口所属的类型,然后就能调用AIDL中的方法了。
具体使用上需要三个步骤,创建AIDL接口、实现远程服务端Service、实现客户端。
-
创建AIDL接口
AIDL的创建在Binder中已经操作过,在此不再赘述,可以直接用以前的例子,这里着重讲一下AIDL本身的一些知识点。
在AIDL中不是所有的数据类型都能用的,仅仅是以下的类型可以在AIDL中使用,包括:
- 基本数据类型(int、long、char、Boolean、double等)
- String和CharSequence
- List只支持ArrayList,并且内部的所有元素都能被AIDL支持
- Map只支持HashMap,并且内部的所有元素都能被AIDL支持,包括key和value
- Parcelable,所有实现了Parcelable接口的对象
- AIDL,所有的AIDL接口本身也可以在AIDL中使用
以上类型就是AIDL所支持的所有类型,其中定义的Parcelable对象和AIDL对象都需要显式import引用,不管是不是和当前AIDL文件处于同一个包下,IBookManager.aidl中有所体现。
// IBookManager.aidl package com.libok.androidnote.aidl; // Declare any non-default types here with import statements import com.libok.androidnote.aidl.Book; interface IBookManager { List<Book> getBookList(); void addBook(in Book book); }
另一个需要注意的点是如果AIDL文件中用到了自定义的Parcelable对象,就必须创建一个同名的AIDL文件,并在里面声明为Parcelable类型,在Book.aidl中有所体现。
package com.libok.androidnote.aidl; parcelable Book;
第三点就是在AIDL文件中除了节本类型,其他类型的参数必须在参数前标上方向:in、out、inout,in表示输入型参数,out表示输出型参数,inout表示输入输出型参数。这个留作以后有时间再作讨论。
最后,AIDL接口有一点跟传统接口不同,就是接口中只支持方法,不支持静态常量。
-
实现远程服务端Service
在Binder中也有介绍,就是一个Process属性的Service,并在其中创建一个Binder,然后在onBind方法中返回此Binder。这里着重说一下CopyOnWriteArrayList,为什么采用这个List。是因为AIDL的方法是在服务端的Binder线程池中执行的,是支持并发的,当多个线程同时访问时需要在数据端作线程同步,而CopyOnWriteArrayList本身是自带同步。
有一个疑问是,在AIDL介绍中,AIDL只支持ArrayList但是为什么能用CopyOnWriteArrayList,是因为AIDL中所支持的是抽象的List,即List接口,因此虽然服务端返回的是CopyOnWriteArrayList,但是在Binder中会按照List的规范去访问数据并最终形成一个ArrayList传递给客户端,所以是可行的。同样的还有ConcurrentHashMap。Binder中的示例就可以在getBookList方法中打印一下返回的List确实是ArrayList。
E/IPCActivity: onGetBookList: [Book{mBookId=123, mBookName='Love & Peace'}, Book{mBookId=321, mBookName='Play Boy'}, Book{mBookId=523, mBookName='Own Added Book523'}] ArrayList
-
实现客户端
客户端的实现也比较简单,仅仅是绑定下服务然后挨个调用方法测试下,Binder章节中也有IPCActivity的代码。再着重重复一点的是,在客户端中,特别要注意的是尽量不要将远程调用放在主线程,因为远程调用会将当前线程先挂起,然后执行远程方法,之后再唤醒客户端线程,要是远程方法是耗时操作的话,可能会导致ANR。
进阶使用
-
AIDL接口
还是上面的例子,当每次addBook后只有getBookList才能获取到最新的列表,太麻烦不说还不够优雅,其实AIDL是支持AIDL接口完成Callback的。新建如下IOnNewBookListener.aidl文件。
// IOnNewBookListener.aidl package com.libok.androidnote.aidl; import com.libok.androidnote.aidl.Book; interface IOnNewBookListener { void onNewBookArrived(in Book book); }
显然的在以前的IBookManager.aidl中需要加上注册接口和解绑接口的方法。
// IBookManager.aidl package com.libok.androidnote.aidl; // Declare any non-default types here with import statements import com.libok.androidnote.aidl.Book; import com.libok.androidnote.aidl.IOnNewBookListener; interface IBookManager { List<Book> getBookList(); void addBook(in Book book); void registerListener(IOnNewBookListener listener); void unregisterListener(IOnNewBookListener listener); }
此时就不需要用手写IBookManager的对应的Java类了,都是相似的,现在直接用系统给生成的Java类。类被改变了相应的Service中也需要做些改动,Binder中实现两个方法,新开一个Thread,每5秒添加一本书。
public class BookManagerService extends Service { private static final String TAG = "BookManagerService"; private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>(); private CopyOnWriteArrayList<IOnNewBookListener> mNewBookListeners = new CopyOnWriteArrayList<>(); private AtomicBoolean mServiceDestroyed = new AtomicBoolean(false); private Binder mBinder = new IBookManager.Stub() { @Override public List<Book> getBookList() { Log.e(TAG, "getBookList: " + mBookList.size()); return mBookList; } @Override public void addBook(Book book) { Log.e(TAG, "addBook: " + book.toString()); mBookList.add(book); } @Override public void registerListener(IOnNewBookListener listener) throws RemoteException { if (!mNewBookListeners.contains(listener)) { mNewBookListeners.add(listener); } Log.e(TAG, "registerListener: size " + mNewBookListeners.size()); } @Override public void unregisterListener(IOnNewBookListener listener) throws RemoteException { mNewBookListeners.remove(listener); Log.e(TAG, "unregisterListener: size " + mNewBookListeners.size()); } }; private Binder mOwnBinder = new IMyBookManager.Stub() { @Override public List<Book> getBookList() throws RemoteException { return mBookList; } @Override public void addBook(Book book) throws RemoteException { mBookList.add(book); } }; public BookManagerService() { mBookList.add(new Book(123, "Love & Peace")); mBookList.add(new Book(321, "Play Boy")); new ServiceWorker().start(); } @Override public IBinder onBind(Intent intent) { // TODO: Return the communication channel to the service. // throw new UnsupportedOperationException("Not yet implemented"); return mBinder; } private void notifyNewBookArrived(Book newBook) throws RemoteException { Log.e(TAG, "notifyNewBookArrived: listener size " + mNewBookListeners.size()); mBookList.add(newBook); for (IOnNewBookListener newBookListener : mNewBookListeners) { newBookListener.onNewBookArrived(newBook); } } @Override public void onDestroy() { mServiceDestroyed.set(true); super.onDestroy(); } private class ServiceWorker extends Thread { @Override public void run() { super.run(); while (!mServiceDestroyed.get()) { try { sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } int bookId = mBookList.size(); String bookName = ("BLEACH " + bookId); Book newBook = new Book(bookId, bookName); try { notifyNewBookArrived(newBook); } catch (RemoteException e) { e.printStackTrace(); } } } } }
客户端的改动如下:
public class IPCActivity extends AppCompatActivity { private static final String TAG = "IPCActivity"; private static final int BOOK_START_ID = 522; public static final int RESULT_MESSENGER_BOOK_LIST = 9876; private int mBookId = BOOK_START_ID; private IBookManager mIBookManager = null; // private IMyBookManager mOwnBookManager = null; private Intent mRemoteBookServiceIntent; private IOnNewBookListener mNewBookListener = new IOnNewBookListener.Stub() { @Override public void onNewBookArrived(Book book) throws RemoteException { Log.e(TAG, "onNewBookArrived: " + book); } }; private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() { @Override public void binderDied() { Log.e(TAG, "binderDied: "); // if (mOwnBookManager == null) { // return; // } // mOwnBookManager.asBinder().unlinkToDeath(this, 0); // mOwnBookManager = null; if (mIBookManager == null) { return; } mIBookManager.asBinder().unlinkToDeath(this, 0); mIBookManager = null; bindService(mRemoteBookServiceIntent, mBookServiceConnection, Context.BIND_AUTO_CREATE); // 其他操作 } }; private ServiceConnection mBookServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { mIBookManager = IBookManager.Stub.asInterface(service); // mOwnBookManager = IMyBookManager.Stub.asInterface(service); try { service.linkToDeath(mDeathRecipient, 0); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { Log.e(TAG, "onServiceDisconnected: "); } }; private Messenger mServerMessenger = null; private ServiceConnection mMessengerConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { mServerMessenger = new Messenger(service); } @Override public void onServiceDisconnected(ComponentName name) { mServerMessenger = null; } }; private Handler mMessengerHandler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { switch (msg.what) { case RESULT_MESSENGER_BOOK_LIST: Bundle data = msg.getData(); Log.e(TAG, "handleMessage: get class loader:" + data.getClassLoader()); data.setClassLoader(getClassLoader()); ArrayList<Parcelable> result = data.getParcelableArrayList("result_get_list"); Log.e(TAG, "handleMessage: RESULT_MESSENGER_BOOK_LIST " + result.toString()); break; default: super.handleMessage(msg); } } }; private Messenger mClientMessenger = new Messenger(mMessengerHandler); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_ipc); } /** * 绑定AIDL方式的Service */ public void onBindAIDLBookManagerService(View view) { mRemoteBookServiceIntent = new Intent(this, BookManagerService.class); bindService(mRemoteBookServiceIntent, mBookServiceConnection, Context.BIND_AUTO_CREATE); } public void onGetBookList(View view) { if (mIBookManager != null) { try { List<Book> bookList = mIBookManager.getBookList(); Log.e(TAG, "onGetBookList: " + bookList.toString()); } catch (RemoteException e) { e.printStackTrace(); } } // if (mOwnBookManager != null) { // try { // List<Book> bookList = mOwnBookManager.getBookList(); // Log.e(TAG, "onGetBookList: " + bookList.toString() + " " + bookList.getClass().getSimpleName()); // } catch (RemoteException e) { // e.printStackTrace(); // } // } } public void onAddBookToList(View view) { if (mIBookManager != null) { try { mBookId++; mIBookManager.addBook(new Book(mBookId, "Added Book" + mBookId)); } catch (RemoteException e) { e.printStackTrace(); } } // if (mOwnBookManager != null) { // try { // mBookId++; // mOwnBookManager.addBook(new Book(mBookId, "Own Added Book" + mBookId)); // } catch (RemoteException e) { // e.printStackTrace(); // } // } } /** * 注册AIDL接口 */ public void onRegisterListener(View view) { try { mIBookManager.registerListener(mNewBookListener); } catch (RemoteException e) { e.printStackTrace(); } } /** * 解绑AIDL接口 */ public void onUnRegisterListener(View view) { try { mIBookManager.unregisterListener(mNewBookListener); } catch (RemoteException e) { e.printStackTrace(); } } @Override protected void onDestroy() { // mOwnBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0); if (mIBookManager != null && mIBookManager.asBinder().isBinderAlive()) { mIBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0); try { mIBookManager.unregisterListener(mNewBookListener); } catch (RemoteException e) { e.printStackTrace(); } } unbindService(mBookServiceConnection); unbindService(mMessengerConnection); super.onDestroy(); } /** * 绑定Messenger方式的Service */ public void onBindMessengerService(View view) { Intent messengerServiceIntent = new Intent(this, MessengerService.class); bindService(messengerServiceIntent, mMessengerConnection, BIND_AUTO_CREATE); } /** * 通过Messenger添加书本 */ public void onAddBookToListByMessenger(View view) { if (mServerMessenger != null) { Book book = new Book(158, "声微饭否"); Bundle data = new Bundle(getClassLoader()); data.putParcelable("add_book", book); Message addBookMessage = Message.obtain(); addBookMessage.what = MessengerService.OPERATION_ADD_BOOK; addBookMessage.setData(data); try { mServerMessenger.send(addBookMessage); } catch (RemoteException e) { e.printStackTrace(); } } } /** * 通过Messenger获取所有的书本 */ public void onGetBookListByMessenger(View view) { if (mServerMessenger != null) { Message getListMessage = Message.obtain(); getListMessage.what = MessengerService.OPERATION_GET_BOOK_LIST; getListMessage.replyTo = mClientMessenger; try { mServerMessenger.send(getListMessage); } catch (RemoteException e) { e.printStackTrace(); } } } }
当绑定Service点击注册Listener完成后,会收到每5秒返回的newBook。Log如下:
2020-03-15 17:40:07.306 28248-28293/com.libok.androidnote E/IPCActivity: onNewBookArrived: Book{mBookId=2, mBookName='BLEACH 2'} 2020-03-15 17:40:12.308 28248-28293/com.libok.androidnote E/IPCActivity: onNewBookArrived: Book{mBookId=3, mBookName='BLEACH 3'} 2020-03-15 17:40:17.311 28248-28384/com.libok.androidnote E/IPCActivity: onNewBookArrived: Book{mBookId=4, mBookName='BLEACH 4'} 2020-03-15 17:40:22.313 28248-28384/com.libok.androidnote E/IPCActivity: onNewBookArrived: Book{mBookId=5, mBookName='BLEACH 5'} 2020-03-15 17:40:27.316 28248-28384/com.libok.androidnote E/IPCActivity: onNewBookArrived: Book{mBookId=6, mBookName='BLEACH 6'} 2020-03-15 17:40:32.318 28248-28384/com.libok.androidnote E/IPCActivity: onNewBookArrived: Book{mBookId=7, mBookName='BLEACH 7'} 2020-03-15 17:40:37.319 28248-28384/com.libok.androidnote E/IPCActivity: onNewBookArrived: Book{mBookId=8, mBookName='BLEACH 8'} 2020-03-15 17:40:42.322 28248-28384/com.libok.androidnote E/IPCActivity: onNewBookArrived: Book{mBookId=9, mBookName='BLEACH 9'} 2020-03-15 17:40:47.324 28248-28384/com.libok.androidnote E/IPCActivity: onNewBookArrived: Book{mBookId=10, mBookName='BLEACH 10'}
但是当点击解绑Listener时可以发现在服务端并不会删除此Listener,点击后Log如下:
E/BookManagerService: unregisterListener: size 1
仔细想想出现这种情况也是合理的,就像是服务端Service返回的Binder,和在客户端Activity中收到的Binder并不是同一个,所以才能根据此特性在asInterface时去区分是客户端还是服务端的Binder。因此在服务端注册解绑时从客户端传过来的肯定不是同一个Listener,所以也就无法正确的解绑。要想正确的解绑就需要用到一个特殊的“List”——RemoteCallbackList。
RemoteCallbackList是系统专门提供的用于删除跨进程listener的接口。RemoteCallbackList是一个泛型,支持管理任何的AIDL接口,从此类的声明上即可看出。
public class RemoteCallbackList<E extends IInterface>
这个类的工作原理很简单,内部有一个以IBinder作Key,以Callback作Value的ArrayMap,用来保存所有的Callback,AIDL回调最终是以Callback形式保存,在创建Callback时需要将AIDL接口传入。以Callback作Map的Value可以理解,但是为什么是IBinder作Key。盲生你发现了华点。这就涉及到了跨进程的另一个特性,虽然说每次从客户端传递过来的Listener都是新的,但是这些Listener都具有一个特点,那就是Listener的asBinder返回的都是同一个Binder。这是可以通过打印Binder验证的,但是并不是很想动手。
RemoteCallbackList还有一个很有用的功能就是当客户端进程终止后,会自动的将此客户端注册的Listener给删除。这是因为Callback实现了Binder的死亡代理,当进程死亡Binder也死亡时会回调binderDied方法,继而会自动删除相应的AIDL接口。最后一点RemoteCallbackList内部是是实现了线程同步的,无需做额外线程同步工作。
所以Service中mBinder又需要作以下的改动:
@Override public void registerListener(IOnNewBookListener listener) throws RemoteException { if (!mNewBookListeners.contains(listener)) { mNewBookListeners.add(listener); } mRemoteCallbackList.register(listener); Log.e(TAG, "registerListener: size " + mRemoteCallbackList.getRegisteredCallbackCount()); } @Override public void unregisterListener(IOnNewBookListener listener) throws RemoteException { mRemoteCallbackList.unregister(listener); Log.e(TAG, "unregisterListener: size " + mRemoteCallbackList.getRegisteredCallbackCount()); }
notifyNewBookArrived方法作以下改动:
private void notifyNewBookArrived(Book newBook) throws RemoteException { Log.e(TAG, "notifyNewBookArrived: listener size " + mNewBookListeners.size()); mBookList.add(newBook); // for (IOnNewBookListener newBookListener : mNewBookListeners) { // newBookListener.onNewBookArrived(newBook); // } // for (int i = 0; i < mRemoteCallbackList.getRegisteredCallbackCount(); i++) { // mRemoteCallbackList.getRegisteredCallbackItem() // } int callbackCount = mRemoteCallbackList.beginBroadcast(); for (int i = 0; i < callbackCount; i++) { mRemoteCallbackList.getBroadcastItem(i).onNewBookArrived(newBook); } mRemoteCallbackList.finishBroadcast(); }
RemoteCallbackList的遍历有些别致,需要先调用beginBroadcast方法获取callback总数,然后调用getBroadcastItem方法获取回调,最后需要调用finishBroadcast方法清理内部的临时变量。其中beginBroadcast方法和finishBroadcast方法必须成对出现,否则会报异常,看内部源码就能看出所以然来,不再赘述。
题外话:但是有一点不是很懂,不管是获取当前RemoteCallbackList中回调的数量还是获取回调都有两种方法。获取数量可以成对的调用beginBroadcast,也可以调用getRegisteredCallbackCount,同样都是获取的内部Map的数量,但是前者是将Map中的所有回调保存到一个临时数组变量mActiveBroadcast中,后者是直接返回Map的size。获取回调还是可以调用beginBroadcast,然后调用getBroadcastItem方法获取回调,也可以直接调用getRegisteredCallbackItem方法获取,但是这个方法是需要API26才能使用的,前者还是先将Map中的回调保存到mActiveBroadcast中,然后从此数组中获取,后者直接从Map中获取。所以问题来了,这两种方式有什么不同,看了一顿方法注释也没看懂。。太过愚钝,希望有懂的人给讲一讲。
至此,Listener就可以正确的解绑了,AIDL接口也全部介绍完毕。
但是还有两点需要注意:方法运行在哪个线程问题,注意区分主线程和Binder线程,注意主线程中尽量不要出现的远程操作及耗时操作,注意Binder线程不要访问UI。具体的可以在不明白的方法内部打印当前线程。Binder的意外死亡问题,可以为Binder设置死亡代理,前面Binder中已经说过,在死亡代理的回调中重新绑定服务。另外一种方法是在ServiceConnection中的onServiceDisconnected方法中重新绑定服务,区别就是Binder死亡代理是运行在Binder线程池中的,onServiceDisconnected方法是运行在主线程的。
-
权限验证
权限验证的方法很多,在此只介绍几种。
-
在Service的onBind方法中通过Manifest文件的permission进行验证
首先在Manifest文件中创建一个自定义的permission如下:
<permission android:name="com.libok.androidnote.aidl.IBookManager.permission.ACCESS_BIND_SERVICE" android:protectionLevel="normal" />
并在uses-permission中声明此权限
<uses-permission android:name="com.libok.androidnote.aidl.IBookManager.permission.ACCESS_BIND_SERVICE" />
然后就可以在Service的onBind方法进行验证了。
@Override public IBinder onBind(Intent intent) { int checkCallingOrSelfPermission = checkCallingOrSelfPermission("com.libok.androidnote.aidl.IBookManager.permission.ACCESS_BIND_SERVICE"); Log.e(TAG, "onBind: " + checkCallingOrSelfPermission); if (checkCallingOrSelfPermission == PackageManager.PERMISSION_DENIED) { return null; } return mBinder; }
至于权限应该配置在客户端还是服务端以及checkCallingOrSelfPermission方法的用法在此方法的注释中已经说的很清楚了。
Determine whether the calling process of an IPC <em>or you</em> have been granted a particular permission. This is the same as {@link #checkCallingPermission}, except it grants your own permissions if you are not currently processing an IPC. Use with care!
显然需要客户端配置声明权限。
-
在服务端的onTransact方法中作权限验证
在此方法中既可以用permission验证也可以通过getCallingUid和getCallingPid获取到客户端所属应用的UID和PID作验证。验证不成功直接返回false,表示不允许调用远程方法。以下为示例:
private Binder mBinder = new IBookManager.Stub() { @Override public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { int checkCallingOrSelfPermission = checkCallingOrSelfPermission("com.libok.androidnote.aidl.IBookManager.pe if (checkCallingOrSelfPermission == PackageManager.PERMISSION_DENIED) { return false; } String[] packages = getPackageManager().getPackagesForUid(getCallingUid()); Log.e(TAG, "onTransact: " + Arrays.toString(packages)); String packageName = null; if (packages != null && packages.length > 0) { packageName = packages[0]; } if (TextUtils.isEmpty(packageName) || !TextUtils.equals(packageName, "com.libok.androidnote")) { return false; } return super.onTransact(code, data, reply, flags); } @Override public List<Book> getBookList() { Log.e(TAG, "getBookList: " + mBookList.size()); return mBookList; } @Override public void addBook(Book book) { Log.e(TAG, "addBook: " + book.toString()); mBookList.add(book); } @Override public void registerListener(IOnNewBookListener listener) throws RemoteException { if (!mNewBookListeners.contains(listener)) { mNewBookListeners.add(listener); } mRemoteCallbackList.register(listener); Log.e(TAG, "registerListener: size " + mRemoteCallbackList.getRegisteredCallbackCount()); } @Override public void unregisterListener(IOnNewBookListener listener) throws RemoteException { mRemoteCallbackList.unregister(listener); Log.e(TAG, "unregisterListener: size " + mRemoteCallbackList.getRegisteredCallbackCount()); } };
-
4.5 ContentProvider
ContentProvider是Android中提供的专门用于不同应用间进行数据共享的方式,从这一点上看,天生就适合进程间通信。和Messenger一样,ContentProvider的底层实现同样也是Binder。但使用过程要比AIDL简单些,系统已经做好了封装,可以无需关注底层即可轻松实现进程间通信。
系统预置了很多ContentProvider,比如通讯录、短信、日程表等,想要访问这些信息必然是跨进程的,而且只需要通过ContentProvider的query、update、insert、delete方法即可。接下来通过创建一个ContentProvider来看看如何进行进程间通信。
新建一个ContentProvider很简单,只需要继承ContentProvider并实现六个抽象方法即可:onCreate、getType、insert、delete、update、query。onCreate方法就是ContentProvider创建时调用;getType用来返回一个Uri请求的MIME类型,详细的参考MIME 参考手册;剩下的四个方法就是CRUD操作。根据Binder的原理,六个方法都是运行在ContentProvider进程,除onCreate方法由系统回调运行在主线程外,其余五个方法都是运行在Binder线程。
ContentProvider主要是以表格的形式组织数据,并且可以包含多个表,对于每个表来说,它们都具有行和列的层次性,行往往代表一条记录,列往往对应一条记录中的一个字段,跟数据库的表类似。除了表格形式外,ContentProvider还支持文件数据,比如图片、视频等。文件数据和表数据的结构不同,因此处理这类数据时可以在ContentProvider中返回文件的句柄给外界,让外界来访问文件信心。Android系统中所提供的MediaStore就是文件类型的ContentProvider,可供参考。另外,虽然ContentProvider的底层数据看起来很像SQLite数据库,但是ContentProvider对底层的数据存储方式并没有任何要求,既可以是SQLite数据库,也可以是普通文件,json、xml等的都可以,只要是符合需求。
ContentProvider示例:
public class BookProvider extends ContentProvider {
private static final String TAG = "BookProvider";
@Override
public boolean onCreate() {
Log.e(TAG, "onCreate: " + Thread.currentThread().getName());
return true;
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
Log.e(TAG, "query: " + Thread.currentThread().getName());
return null;
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
Log.e(TAG, "getType: ");
return null;
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
Log.e(TAG, "insert: " + Thread.currentThread().getName());
return null;
}
@Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
Log.e(TAG, "delete: " + Thread.currentThread().getName());
return 0;
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
Log.e(TAG, "update: " + Thread.currentThread().getName());
return 0;
}
}
当然除了新建类之外还得在Manifest中声明注册这个ContentProvider。
<provider
android:name=".provider.BookProvider"
android:authorities="com.libok.androidnote.provider"
android:permission="com.libok.androidnote.BOOK_PROVIDER"
android:process=":provider" />
android:authorities是ContentProvider的唯一标识,通过这个属性外部应用就可以访问我们的BookProvider,最好在命名时加上包名以示区分。
android:permission表示要想访问ContentProvider,就必须声明com.libok.androidnote.BOOK_PROVIDER这个权限。除了permission外,ContentProvider还细分的有写权限android:writePermission以及读权限android:readPermission,只有正确声明才能正确的访问。
Activity访问示例:
public class IPCActivity extends AppCompatActivity {
private static final String TAG = "IPCActivity";
private Uri mProviderUri = Uri.parse("content://com.libok.androidnote.provider");
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_ipc);
}
/**
* ContentProvider执行新增
*/
public void onContentProviderAdd(View view) {
getContentResolver().insert(mProviderUri, null);
}
/**
* ContentProvider执行删除
*/
public void onContentProviderDelete(View view) {
getContentResolver().delete(mProviderUri, null, null);
}
/**
* ContentProvider执行修改
*/
public void onContentProviderUpdate(View view) {
getContentResolver().update(mProviderUri, null, null, null);
}
/**
* ContentProvider执行查询
*/
public void onContentProviderQuery(View view) {
Cursor cursor = null;
try {
cursor = getContentResolver().query(mProviderUri, null, null, null, null);
} finally {
if (cursor != null) {
cursor.close();
}
}
}
}
当然现在的insert和update操作还是会引起程序异常,但是执行delete和query方法可以从log发现onCreate方法确实是在main线程,不能做耗时操作,而其他的方法都是在Binder线程。
E/BookProvider: onCreate: main
E/BookProvider: query: Binder:1399_3
E/BookProvider: delete: Binder:1399_3
流程现在是能够跑通了,现在再为ContentProvider提供一个SQLite数据库作为底层数据存储来实现ContentProvider的跨进程通信,当然前面也说过不一定非得是数据库,其他的能存储数据的能够进行数据处理的都是可以的。
MySQLiteHelper
public class MySQLiteHelper extends SQLiteOpenHelper {
private static final String TAG = "MySQLiteHelper";
private static final int DB_VERSION = 1;
private static final String DB_NAME = "test_content_provider.db";
public static final String BOOK_TABLE_NAME = "book";
public static final String USER_TABLE_NAME = "user";
public static final String ID = "_id";
public static final String BOOK_ID = "book_id";
public static final String BOOK_NAME = "name";
public static final String USER_ID = "user_id";
public static final String USER_NAME = "name";
public static final String USER_SEX = "sex";
private static final String CREATE_BOOK_TABLE = "CREATE TABLE IF NOT EXISTS " + BOOK_TABLE_NAME + "(" +
ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
BOOK_ID + " INTEGER, " +
BOOK_NAME + " TEXT" +
")";
private static final String CREATE_USER_TABLE = "CREATE TABLE IF NOT EXISTS " + USER_TABLE_NAME + "(" +
ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
USER_ID + " INTEGER, " +
USER_NAME + " TEXT, " +
USER_SEX + " INT" +
")";
private static MySQLiteHelper sHelper = null;
public static SQLiteDatabase getInstance(Context context) {
if (sHelper == null) {
synchronized (MySQLiteHelper.class) {
if (sHelper == null) {
sHelper = new MySQLiteHelper(context, DB_NAME, null, DB_VERSION);
}
}
}
return sHelper.getWritableDatabase();
}
private MySQLiteHelper(@Nullable Context context, @Nullable String name, @Nullable SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_BOOK_TABLE);
db.execSQL(CREATE_USER_TABLE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
数据的就用此SQLiteOpenHelper来操作。
相应的provider也要作相应更改:
- 用UriMatcher作Uri匹配,并在类初始化时添加匹配规则
- getTableName方法用作获取Uri匹配的表名
其他的就是对数据库的操作,并无特别之处,当Provider进行insert、update、delete时如果需要知晓数据发生了改变,可以针对操作的Uri作监听,即通过ContentResolver的registerContentObserver方法注册观察者,通过unregisterContentObserver来解除观察者。
public class BookProvider extends ContentProvider {
private static final String TAG = "BookProvider";
public static final String AUTHORITIES = "com.libok.androidnote.provider";
public static final Uri CONTENT_BOOK_URI = Uri.parse("content://" + AUTHORITIES + "/book");
public static final Uri CONTENT_USER_URI = Uri.parse("content://" + AUTHORITIES + "/user");
public static final int CODE_BOOK = 0;
public static final int CODE_USER = 1;
private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
sUriMatcher.addURI(AUTHORITIES, "book", CODE_BOOK);
sUriMatcher.addURI(AUTHORITIES, "user", CODE_USER);
}
private SQLiteDatabase mDatabase;
private Context mContext;
@Override
public boolean onCreate() {
Log.e(TAG, "onCreate: " + Thread.currentThread().getName());
mContext = getContext();
mDatabase = MySQLiteHelper.getInstance(mContext);
return true;
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
Log.e(TAG, "query: " + Thread.currentThread().getName());
String tableName = getTableName(uri);
if (!TextUtils.isEmpty(tableName)) {
return mDatabase.query(tableName, projection, selection, selectionArgs, null, null, sortOrder);
}
return null;
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
Log.e(TAG, "getType: ");
return null;
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
Log.e(TAG, "insert: " + Thread.currentThread().getName());
String tableName = getTableName(uri);
if (!TextUtils.isEmpty(tableName)) {
mDatabase.insert(tableName, null, values);
mContext.getContentResolver().notifyChange(uri, null);
return uri;
}
return null;
}
@Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
Log.e(TAG, "delete: " + Thread.currentThread().getName());
String tableName = getTableName(uri);
if (!TextUtils.isEmpty(tableName)) {
int count = mDatabase.delete(tableName, selection, selectionArgs);
if (count > 0) {
mContext.getContentResolver().notifyChange(uri, null);
}
return count;
}
return -1;
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
Log.e(TAG, "update: " + Thread.currentThread().getName());
String tableName = getTableName(uri);
if (!TextUtils.isEmpty(tableName)) {
int row = mDatabase.update(tableName, values, selection, selectionArgs);
if (row > 0) {
mContext.getContentResolver().notifyChange(uri, null);
}
return row;
}
return -1;
}
private String getTableName(Uri uri) {
String tableName = null;
switch (sUriMatcher.match(uri)) {
case CODE_BOOK:
tableName = MySQLiteHelper.BOOK_TABLE_NAME;
break;
case CODE_USER:
tableName = MySQLiteHelper.USER_TABLE_NAME;
break;
}
return tableName;
}
}
补充:具体操作ContentProvider
/**
* ContentProvider执行新增
*/
public void onContentProviderAdd(View view) {
ContentValues bookValues = new ContentValues();
bookValues.put(MySQLiteHelper.BOOK_ID, mBookId);
bookValues.put(MySQLiteHelper.BOOK_NAME, "BLEACH " + mBookId);
getContentResolver().insert(BookProvider.CONTENT_BOOK_URI, bookValues);
mSavedBookIdList.add(mBookId);
Log.e(TAG, "onContentProviderAdd: " + mSavedBookIdList);
mBookId++;
ContentValues userValues = new ContentValues();
userValues.put(MySQLiteHelper.USER_ID, mBookId);
userValues.put(MySQLiteHelper.USER_NAME, "BLEACH " + mBookId);
userValues.put(MySQLiteHelper.USER_SEX, mBookId % 2);
getContentResolver().insert(BookProvider.CONTENT_USER_URI, userValues);
mBookId++;
}
/**
* ContentProvider执行删除
*/
public void onContentProviderDelete(View view) {
if (!mSavedBookIdList.isEmpty()) {
int bookId = mSavedBookIdList.remove(0);
int count = getContentResolver().delete(BookProvider.CONTENT_BOOK_URI, MySQLiteHelper.BOOK_ID + " = ?", new String[]{St
Log.e(TAG, "onContentProviderDelete: delete count " + count);
} else {
Log.e(TAG, "onContentProviderDelete: 当前并没有存储Book");
}
}
/**
* ContentProvider执行修改
*/
public void onContentProviderUpdate(View view) {
if (!mSavedBookIdList.isEmpty()) {
int bookId = mSavedBookIdList.remove(0);
ContentValues updateValues = new ContentValues();
updateValues.put(MySQLiteHelper.BOOK_NAME, "FIRE " + bookId);
int row = getContentResolver().update(BookProvider.CONTENT_BOOK_URI, updateValues, MySQLiteHelper.BOOK_ID + " = ?", new
Log.e(TAG, "onContentProviderUpdate: update row " + row);
} else {
Log.e(TAG, "onContentProviderDelete: 当前并没有存储Book");
}
}
/**
* ContentProvider执行查询
*/
public void onContentProviderQuery(View view) {
Cursor bookCursor = null;
Cursor userCursor = null;
StringBuilder stringBuilder = new StringBuilder();
mSavedBookIdList.clear();
try {
bookCursor = getContentResolver().query(BookProvider.CONTENT_BOOK_URI, new String[]{MySQLiteHelper.BOOK_ID, MySQLiteHel
stringBuilder.append("[\n");
if (bookCursor != null) {
while (bookCursor.moveToNext()) {
int bookId = bookCursor.getInt(bookCursor.getColumnIndex(MySQLiteHelper.BOOK_ID));
mSavedBookIdList.add(bookId);
stringBuilder.append("\tbook id=")
.append(bookId)
.append(" name=")
.append(bookCursor.getString(bookCursor.getColumnIndex(MySQLiteHelper.BOOK_NAME)))
.append("\n");
}
Log.e(TAG, "onContentProviderQuery: " + mSavedBookIdList);
}
stringBuilder.append("]\n\n");
userCursor = getContentResolver().query(BookProvider.CONTENT_USER_URI, new String[]{MySQLiteHelper.USER_ID, MySQLiteHel
stringBuilder.append("[\n");
if (userCursor != null) {
while (userCursor.moveToNext()) {
stringBuilder.append("\tuser id=")
.append(userCursor.getInt(userCursor.getColumnIndex(MySQLiteHelper.USER_ID)))
.append(" name=")
.append(userCursor.getString(userCursor.getColumnIndex(MySQLiteHelper.USER_NAME)))
.append(" sex=")
.append(userCursor.getInt(userCursor.getColumnIndex(MySQLiteHelper.USER_SEX)) == 0 ? "女" : "男")
.append("\n");
}
}
stringBuilder.append("]\n");
} finally {
if (bookCursor != null) {
bookCursor.close();
}
if (userCursor != null) {
userCursor.close();
}
}
mShowQueryText.setText(stringBuilder.toString());
}
4.6 Socket
Socket—套接字,是网络通信中的概念,分为流式套接字和用户数据报套接字,分别对应网络的传输控制层中的TCP和UDP协议。TCP协议是面向连接的协议,提供稳定的双向通信功能,TCP连接的建立需要经过”三次握手“才能完成,为了提供稳定的数据传输功能,其本身提供了超时重传机制,因此具有很高的稳定性;而UDP是无连接的,提供不稳定的通信功能。但在性能上UDP具有更好的效率,缺点是不能够保证正确传输,尤其是在网络拥堵的情况下。
服务端Service
public class SocketService extends Service {
private static final String TAG = "SocketService";
public SocketService() {
initSocketServer();
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
private void initSocketServer() {
new Thread() {
@Override
public void run() {
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(7873);
Log.e(TAG, "initSocketServer: wait client accept...");
Socket client = serverSocket.accept();
Log.e(TAG, "initSocketServer: client accepted");
handleClient(client);
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
}
private void handleClient(Socket client) throws IOException {
PrintWriter printWriter = new PrintWriter(client.getOutputStream(), true);
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(client.getInputStream()));
String line = bufferedReader.readLine();
Log.e(TAG, "handleClient: receive client:" + line);
printWriter.println("Hello, this is server.");
}
}
客户端
public class IPCActivity extends AppCompatActivity {
private static final String TAG = "IPCActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_ipc);
}
/**
* 绑定socket
*
* @param view
*/
public void onStartRemoteServer(View view) {
startService(new Intent(this, SocketService.class));
}
/**
* 给服务端发送消息
*
* @param view
*/
public void onSendToServer(View view) {
new Thread() {
@Override
public void run() {
try {
Socket socket = new Socket("localhost", 7873);
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter printWriter = new PrintWriter(socket.getOutputStream(), true);
printWriter.println("Hello, this is client.");
String line = bufferedReader.readLine();
Log.e(TAG, "onBindSocket: from server:" + line);
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
}
}
麻雀虽小五脏俱全,简单的小栗子,客户端和服务端通一次信就拉倒,也并没有对资源进行释放,仅仅是示例。当然能传递的信息并非只有字符串,只要能进行网络传输的都能行得通。
五、Binder连接池
Binder连接池的作用是在多个业务模块下需要多个AIDL文件中体现的。按照普通的思路来说是单个AIDL对应单个Service,但是当AIDL越来越多时,这么不计数量的创建Service并不是一个好办法,而且也并不是只有一个AIDL对应一个Service的方法,而且多次创建Service是重复性质的,Service的绑定也是重复的。所以可以采用一种更简洁的方法去管理多个AIDL,将AIDL升华一下,用一个AIDL总管去管理将所有的AIDL,这样就可以将所有的AIDL放在同一个Service中,并且通过识别码的形式去按需获取AIDL。
5.1 BinderPool
新建一个BinderPool AIDL文件,当Activity绑定Service时,返回的是此AIDL的Binder,然后通过调用queryBinder方法查找另一个进程真正所需的Binder。
package com.libok.androidnote.aidl;
interface IBinderPool {
IBinder queryBinder(in int binderCode);
}
此AIDL所对应的Service跟普通的多进程Service也是如出一辙。
public class BinderPoolService extends Service {
private static final String TAG = "BinderPoolService";
public static final int BINDER_CODE_SECURITY = 0;
public static final int BINDER_CODE_COMPUTE = 1;
private BinderPoolImpl mBinderPool = new BinderPoolImpl();
public BinderPoolService() {
}
@Override
public IBinder onBind(Intent intent) {
return mBinderPool;
}
private static class BinderPoolImpl extends IBinderPool.Stub {
@Override
public IBinder queryBinder(int binderCode) throws RemoteException {
IBinder binder = null;
switch (binderCode) {
case BINDER_CODE_SECURITY:
binder = new SecurityCenterImpl();
break;
case BINDER_CODE_COMPUTE:
binder = new ComputeImpl();
break;
}
return binder;
}
}
}
但是接下来就大相径庭了,首先最大的不同就是本来在Activity中进行的绑定Service现在需要用一个单例去绑定管理,即BinderPool。
public static class BinderPool {
private static final String TAG = "BinderPool";
private static BinderPool sBinderPool = null;
private Context mContext;
private CountDownLatch mCountDownLatch;
private IBinderPool mBinderPool = null;
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mBinderPool = BinderPoolImpl.asInterface(service);
try {
mBinderPool.asBinder().linkToDeath(mDeathRecipient, 0);
} catch (RemoteException e) {
e.printStackTrace();
}
Log.e(TAG, "onServiceConnected: bind success " + Thread.currentThread().getName() + " " + android.os.Process.myPid());
mCountDownLatch.countDown();
Log.e(TAG, "onServiceConnected: count down");
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
mBinderPool.asBinder().unlinkToDeath(this, 0);
mBinderPool = null;
bindService();
}
};
private BinderPool(Context context) {
mContext = context.getApplicationContext();
bindService();
}
public static BinderPool getInstance(Context context) {
if (sBinderPool == null) {
synchronized (BinderPool.class) {
if (sBinderPool == null) {
sBinderPool = new BinderPool(context);
}
}
}
return sBinderPool;
}
private synchronized void bindService() {
mCountDownLatch = new CountDownLatch(1);
mContext.bindService(new Intent(mContext, BinderPoolService.class), mServiceConnection, Context.BIND_AUTO_CREATE);
Log.e(TAG, "bindService: start " + Thread.currentThread().getName());
try {
mCountDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.e(TAG, "bindService: finished " + Thread.currentThread().getName() + " " + android.os.Process.myPid());
}
public IBinder queryBinder(int bindCode) throws RemoteException {
IBinder binder = null;
if (mBinderPool != null) {
binder = mBinderPool.queryBinder(bindCode);
}
return binder;
}
}
首先,这个类是单例类,全局用一个类去管理,当然此类是运行在主进程的。
其次,获取单例之后再bindService,而且此bind非彼bind,是一个附加了些额外操作的套娃方法,在方法中创建一个同步辅助,并在真正的Context执行BindService方法后暂停当前线程,等待服务的成功绑定,因为绑定的过程可能会耗费很长时间。
最后,等mBinderPool变量被赋值后,即可向外部提供queryBinder服务。
实际提供服务的两个AIDL,这两个AIDL无需过多解释,与普通的AIDL服务大同小异,只是在需要时再进行懒加载。
package com.libok.androidnote.aidl;
interface ICompute {
int add(in int a, in int b);
}
package com.libok.androidnote.aidl;
interface ISecurityCenter {
String encrypt(in String content);
String decrypt(in String password);
}
具体实现:
public static class ComputeImpl extends ICompute.Stub {
@Override
public int add(int a, int b) throws RemoteException {
return a + b;
}
}
public static class SecurityCenterImpl extends ISecurityCenter.Stub {
private static final char SECRET_CODE = '^';
@Override
public String encrypt(String content) throws RemoteException {
char[] chars = content.toCharArray();
for (int i = 0; i < chars.length; i++) {
chars[i] ^= SECRET_CODE;
}
return new String((chars));
}
@Override
public String decrypt(String password) throws RemoteException {
return encrypt(password);
}
}
在queryBinder方法中按需返回远程Binder。
BinderPool类的所有方法都是运行在主进程,调用另外进程的是queryBinder方法中的binder = mBinderPool.queryBinder(bindCode);
,mBinderPool变量在BinderPoolImpl绑定成功后赋值,并在之后提供另外进程向主进程返回Binder的服务。
5.2 使用
经过上面的代码逻辑,在使用上肯定会跟普通的多进程通信有点差别。下面是Activity的代码:
public class BinderPoolActivity extends AppCompatActivity {
private static final String TAG = "BinderPoolActivity";
private BinderPoolService.BinderPool mBinderPool;
private String mContent = "QWER";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_binder_pool);
}
public void onBindBinderPool(View view) {
new Thread(){
@Override
public void run() {
Log.e(TAG, "run: ");
mBinderPool = BinderPoolService.BinderPool.getInstance(BinderPoolActivity.this);
Log.e(TAG, "run: finish");
}
}.start();
// mBinderPool = BinderPoolService.BinderPool.getInstance(BinderPoolActivity.this);
}
public void onEncrypt(View view) {
try {
IBinder binder = mBinderPool.queryBinder(BinderPoolService.BINDER_CODE_SECURITY);
ISecurityCenter securityCenter = BinderPoolService.SecurityCenterImpl.asInterface(binder);
mContent = securityCenter.encrypt(mContent);
Log.e(TAG, "onEncrypt: " + mContent);
} catch (RemoteException e) {
e.printStackTrace();
}
}
public void onDecrypt(View view) {
try {
IBinder binder = mBinderPool.queryBinder(BinderPoolService.BINDER_CODE_SECURITY);
ISecurityCenter securityCenter = BinderPoolService.SecurityCenterImpl.asInterface(binder);
mContent = securityCenter.decrypt(mContent);
Log.e(TAG, "onDecrypt: " + mContent);
} catch (RemoteException e) {
e.printStackTrace();
}
}
public void onCompute(View view) {
try {
IBinder binder = mBinderPool.queryBinder(BinderPoolService.BINDER_CODE_COMPUTE);
ICompute compute = BinderPoolService.ComputeImpl.asInterface(binder);
int result = compute.add(21, 20);
Log.e(TAG, "onCompute: " + result);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
除了onCreate方法其他的四个方法都是Button的Click方法,在绑定方法onBindBinderPool中就是5.1
中所述,获取单例并绑定方法。但是有一点特别的显眼,眼尖的人已经看出来了,为什么getInstance
方法需要在子线程,为什么不能在主线程。
在5.1
中描述了一个BindService方法中的同步变量,此变量的作用就是保证此方法的调用线程等待Service的绑定成功,会在成功后执行countDown
方法释放线程锁。如果是子线程被暂停完全没有问题,但是如果是主线程被暂停,问题可就大了。要是绑定的过程相当漫长,是会引起ANR的,这是原因一;其二也是最主要的原因,服务绑定成功的回调方法onServiceConnected
是会返回到主线程的,而释放线程的代码在方法里面,这就导致了一个死循环。onServiceConnected
方法需要在主线程中执行,所以主线程得是畅通的而不是被锁住的,但是要想解锁得先执行onServiceConnected
方法。综上,子线程是必须的。
获取到服务的Binder之后,逻辑就简单明了了,就是将Binder转化成相应的AIDL实现上即可。
至于为什么onServiceConnected
会在主线程回调就是后话了,Service的工作过程 coming soon。
六、选择合适的IPC方式
说完这么多IPC方式后,非常需要一个总结表格,更直观简单的描述优缺点及适用场景。
名称 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
Bundle | 简单易用 | 只能传输Bundle支持的数据类型 | 四大组件间的进程间通信 |
文件共享 | 简单易用 | 不适合高并发的场景,并且无法做到进程间的即时通信 | 无并发访问情形,交换简单的实时性不高的数据 |
AIDL | 功能强大,支持一对多并发通信 | 使用复杂,需要处理好线程同步 | 一对多且有RPC1需求 |
Messenger | 功能一般,支持一对多串行通信,支持实时通信 | 不能很好的处理高并发情形,不支持RPC,数据通过Message进行传输,因此只能传输Bundle支持的数据类型 | 低并发的一对多即时通信,无RPC需求,或者无需要返回结果的RPC需求 |
ContentProvider | 在数据源访问方面功能强大,支持一对多并发数据共享,可通过Call方法扩展其他操作 | 可以理解为受约束的AIDL,主要提供数据源的CRUD操作 | 一对多的进程间的数据共享 |
Socket | 功能强大,可通过网络传输字节流,支持一对多并发实时通信 | 实现细节稍微有点繁琐,不支持直接的RPC | 网络数据交换 |
远程过程调用 ↩︎