文章目录
1.Android中的多进程模式
通过给四大组件指定android: process
属性,我们可以轻易地开启多进程模式。
1.1.多进程模式示例
先看一个简单示例:
AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.hgy.multprocess">
<application
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".SecondActivity"
android:process=":remote" />
<activity android:name=".ThirdActivity"
android:process="com.hgy.multprocess.remote"/>
</application>
</manifest>
分别为SecondActivity
和ThirdActivity
指定了process
属性,并且它们的属性值不同,这意味着当前应用又增加了两个新进程。
—>让MainActivity
拉起SecondActivity
Intent intent = new Intent(MainActivity.this, SecondActivity.class);
startActivity(intent);
—>SecondActivity
再拉起ThirdActivity
Intent intent = new Intent(SecondActivity.this, ThirdActivity.class);
startActivity(intent);
—>查看进程列表
C:\Users\Administrator>adb shell
shell@santoni:/ $ ps |grep "com.hgy"
u0_a156 15940 686 1518720 67812 SyS_epoll_ 0000000000 S com.hgy413.multprocess
u0_a156 16016 686 1514900 66384 SyS_epoll_ 0000000000 S com.hgy413.multprocess:remote
u0_a156 16037 686 1522216 61756 SyS_epoll_ 0000000000 S com.hgy.multprocess.remote
当SecondActivity
启动时,系统会为它创建一个单独的进程,进程名为"com.hgy.multprocess:remote"
当ThirdActivity
启动时,系统会为它创建一个单独的进程,进程名为"com.hgy.multprocess.remote"
它们都属于同一个应用,所以UID
是相同的(u0_a156
) ,SecondActivity
使用了 android:process=":remote"
,“: ”
的含义是指在冒号前面附加上当前的包名,这是一种简写的方法,你也可以写成android:process="com.hgy.multprocess:remote"
,它们表示的意义相同
同时进程名以“: ”
开头的进程属于当前应用的私有进程,其他应用的组件不可以和它跑在同一个进程中,而进程名不以“: ”
开头的进程属于全局进程,其他应用通过ShareUID方式可以和它跑在同一个进程中。
我们知道Android系统会为每个应用分配一个唯一的UID
,具有相同UID
的应用才能共享数据。
这里要说明的是,两个应用通过ShareUID跑在同一个进程中是有要求的,需要这两个应用有相同的ShareUID并且签名相同才可以。在这种情况下,它们可以互相访问对方的私有数据,比如data目录、组件信息等,无论它们是否跑在同一个进程中。当然如果它们跑在同一个进程中,那么除了能共享data目录、组件信息,还可以共享内存数据,或者说它们看起来就像是一个应用的两个部分。
1.2.多进程模式的运行机制
我们新建了一个类,叫做UserManager,这个类中有一个public的静态成员变量,如下所示。
public class UserManager {
public static int sUserId = 1;
}
然后在MainActivity
的onCreate
中我们把这个sUserId
重新赋值为2,打印出这个静态变量的值后,再启动SecondActivity
,在SecondActivity
中我们再打印一下sUserId
的值。
07-12 13:19:53.408 17092-17092/com.hgy413.multprocess E/hgy413: MainActivity sUserId: 2
07-12 13:19:56.074 17152-17152/com.hgy413.multprocess:remote E/hgy413: SecondActivity sUserId: 1
可以看到,多进程数据肯定是不共享的。Android为每个进程都分配一个独立的虚拟机,不同的虚拟机在内存分配上有不同的地址空间,这就导致在不同的虚拟机中访问同一个类的对象会产生多份副本。
拿我们这个例子来说,在进程 com.hgy413.multprocess
和进程com.hgy413.multprocess:remote
中都存在一个UserManager
类,并且这两个类是互不干扰的,在一个进程中修改sUserId
的值只会影响当前进程,对其他进程不会造成任何影响。
一般来说,使用多进程会造成如下几方面的问题:
1.静态成员和单例模式完全失效。
2.线程同步机制完全失效。
3.SharedPreferences
的可靠性下降。
4.Application
会多次创建。
2.IPC基础概念
2.1.Serializable接口
Serializable
是Java所提供的一个序列化接口,它是一个空接口,为对象提供标准的序列化和反序列化操作。使用Serializable
来实现序列化相当简单,只需要在类的声明中指定一个类似下面的标识即可自动实现默认的序列化过程。
private static final long serialVersionUID = 8711368828010083044L
实际上,甚至这个serialVersionUID
也不是必需的,我们不声明这个serialVersionUID
同样也可以实现序列化,但是这将
会对反序列化过程产生影响,具体什么影响后面再介绍。
User
类就是一个实现了Serializable
接口的类,它是可以被序列化和反序列化的,如下所示。
public class User implements Serializable {
private static final long serialVersionUID = 519067123721295773L;
public int userId;
public String userName;
public boolean isMale;
public User(int userId, String userName, boolean isMale) {
this.userId = userId;
this.userName = userName;
this.isMale = isMale;
}
}
通过Serializable
方式来实现对象的序列化,实现起来非常简单,几乎所有工作都被系统自动完成了。如何进行对象的序列化和反序列化也非常简单,只需要采用ObjectOutputStream
和ObjectInputStream
即可轻松实现。下面举个简单的例子。
–>MainActivity
中加入:(openFileOutput
方法生成的文件位于/data/data/包名/files/
下)
try {
User user = new User(0, "jake", true);
ObjectOutputStream out = new ObjectOutputStream(openFileOutput("cache.txt", MODE_PRIVATE));
out.writeObject(user);
out.close();
Log.e("hgy413", "MainActiviy write user success");
} catch (IOException e) {
}
–>SecondActivity
中读取:
try {
ObjectInputStream in = new ObjectInputStream(openFileInput("cache.txt"));
User newUser = (User)in.readObject();
in.close();
Log.e("hgy413", "SecondActivity read user success:" + newUser.userName);
} catch (Exception e) {
e.printStackTrace();
}
–>打印结果:
07-12 14:14:42.828 19907-19907/com.hgy413.multprocess E/hgy413: MainActiviy write user success
07-12 14:14:47.374 19965-19965/com.hgy413.multprocess:remote E/hgy413: SecondActivity read user success:jake
上述代码演示了采用Serializable
方式序列化对象的典型过程,很简单,只需要把实现了Serializable
接口的User对象写到文件中就可以快速恢复了,恢复后的对象newUser和user的内容完全一样,当然,很明显两者并不是同一个对象。
serialVersionUID
是用来辅助序列化和反序列化过程的,原则上序列化后的数据中的serialVersionUID
只有和当前类的serialVersionUID
相同才能够正常地被反序列化。
serialVersionUID
的详细工作机制是这样的:
序列化的时候,系统会把当前类的serialVersionUID
写入序列化的文件中(也可能是其他中介)
当反序列化的时候,系统会去检测文件中的serialVersionUID
,看它是否和当前类的serialVersionUID
一致,如果一致就说明序列化的类的版本和当前类的版本是相同的,这个时候可以成功反序列化。
否则就说明当前类和序列化的类相比发生了某些变换,比如成员变量的数量、类型可能发生了改变。这个时候是无法正常反序列化的,因此会报如下错误:
java.io.InvalidClassException: Main; local class incompatible: stream
classdesc serialVersionUID = 8711368828010083044,local class serialVersionUID = 8711368828010083043。
一般来说,我们应该手动指定serialVersionUID
的值,比如1L。
当我们手动指定了它以后,就可以在很大程度上避免反序列化过程的失败。比如当版本升级后,我们可能删除了某个成
员变量也可能增加了一些新的成员变量,这个时候我们的反向序列化过程仍然能够成功,程序仍然能够最大限度地恢复数据,相反,如果不指定serialVersionUID
的话,程序则会挂掉。
以下两点需要特别提一下:
1.首先静态成员变量属于类不属于对象,所以不会参与序列化过程。
2.其次用transient
关键字标记的成员变量不参与序列化过程。
2.2.Parcelable接口
Parcelable
也是一个接口,只要实现这个接口,一个类的对象就可以实现序列化并可以通过Intent
和Binder
传递。下面的示例是一个典型的用法。
public class ParcelableUser implements Parcelable {
public int userId;
public String userName;
public boolean isMale;
public ParcelableUser(int userId,String userName,boolean isMale) {
this.userId = userId;
this.userName = userName;
this.isMale = isMale;
}
protected ParcelableUser(Parcel in) {
userId = in.readInt();
userName = in.readString();
isMale = in.readByte() != 0;
}
public static final Creator<ParcelableUser> CREATOR = new Creator<ParcelableUser>() {
@Override
public ParcelableUser createFromParcel(Parcel in) {
return new ParcelableUser(in);
}
@Override
public ParcelableUser[] newArray(int size) {
return new ParcelableUser[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(userId);
dest.writeString(userName);
dest.writeByte((byte) (isMale ? 1 : 0));
}
}
以上代码,只要写完成员变量,alt+enter
就可以全部自动生成了!,Android Studi
–>MainActivity
中加入:启动SecondActivity
时传入
findViewById(R.id.button1).setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this, SecondActivity.class);
ParcelableUser parcelableUser = new ParcelableUser(0, "parceljake", true);
intent.putExtra("extra_parcel", parcelableUser);
Log.e("hgy413", "MainActiviy write parcelableUser success");
startActivity(intent);
}
});
–>SecondActivity
中读取:
ParcelableUser parcelableUser = getIntent().getParcelableExtra("extra_parcel");
Log.e("hgy413", "SecondActivity read parcelableUser success:" + parcelableUser.userName);
–>打印结果:
07-12 14:55:21.654 21732-21732/com.hgy413.multprocess E/hgy413: MainActiviy write parcelableUser success
07-12 14:55:22.029 21792-21792/com.hgy413.multprocess:remote E/hgy413: SecondActivity read parcelableUser success:parceljake
这里先说一下Parcel
,Parcel
内部包装了可序列化的数据,可以在Binder
中自由传输。从上述代码中可以看出,在序列化过程中需要实现的功能有序列化、反序列化和内容描述。
序列化由writeToParcel
方法来完成,最终是通过Parcel
中的一系列write
方法来完成的
反序列化由CREATOR
来完成,其内部标明了如何创建序列化对象和数组,并通过Parcel
的一系列read
方法来完成反序列化过程
内容描述由descri beContents方法来完成,几乎在所有情况下这个方法都应该返回0,仅当当前对象中存在文件描述符时,此方法返回1
系统已经为我们提供了许多实现了Parcelable
接口的类,它们都是可以直接序列化的,比如intent
、Bundle
、Bitmap
等,同时List
和Map
也可以序列化,前提是它们里面的每个元素都是可序列化的。
相对于Serializable
,Parcelable
的效率要高得多,Parcelable
主要用在内存序列化上,通过Parcelable
将对象序列化到存储设备中或者将对象序列化后通过网络传输也都是可以的,但是这个过程会稍显复杂,因此在这两种情况下建议大家使用Serializable
。
2.3 Binder
直观来说,Binder
是Android中的一个类,它继承了IBinder
接口。
从IPC角度来说,Binder
是Android
中的一种跨进程通信方式,Binder
还可以理解为一种虚拟的物理设备,它的设备驱动是/dev/binder
从Android Framework
角度来说,Binder
是ServiceManager
连接各种Manager(ActivityManager
、WindowManager
,等等)和相应ManagerService
的桥梁
从Android应用层来说,Binder
是客户端和服务端进行通信的媒介,当bindService
的时候,服务端会返回一个包含了服务端业务调用的Binder
对象,通过这个Binder
对象,客户端就可以获取服务端提供的服务或者数据,这里的服务包括普通服务和基于AIDL
的服务。
Android开发中,Binder
主要用在Service
中,包括AIDL
和Messenger
,其中普通Service
中的Binder
不涉及进程间通信,所以较为简单,无法触及Binder
的核心,而Messenger
的底层其实是AIDL
,所以这里选择用AIDL
来分析Binder
的工作机制。
新建aidl
的package,然后新建三个文件Book. java、Book. aidl 和IBookManager. aidl ,代码如下所示。
Android Studio要先建Book. java,然后建aidl
文件:
用AIDL File
填写名字的时候先随便填写(因为如果直接填Book
为名字会报错,提示同名冲突,只有先创建完之后再Rename
成Book.aidl
才不会报错)
为什么要有Book.aidl
类,因为只有将类在aidl
中声明时候,AIDL
才能调用Book
类
Book. aidl
// Book.aidl
package com.hgy.multprocess.aidl;
parcelable Book;
要让AIDL能够传送自定义类需要 ①继承Parcelable接口 ②创建同名.aidl文件声明自己。
Book. java
public class Book implements Parcelable {
public int bookId;
public String bookName;
protected Book(Parcel in) {
bookId = in.readInt();
bookName = in.readString();
}
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];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(bookId);
dest.writeString(bookName);
}
}
IBookManager.aidl
// IBookManager.aidl
package com.hgy413.multprocess;
import com.hgy.multprocess.aidl.Book;
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
}
上面三个文件中
Book. java是一个表示图书信息的类,它实现了Parcelable
接口。
Book.aidl 是Book
类在AIDL
中的声明。
IBookManager. aidl 是我们定义的一个接口,里面有两个方法:getBookList
和addBook
,其中getBookList
用于从远程服务端获取图书列表,而addBook
用于往图书列表中添加一本书,当然这两个方法仅做为示例。
我们可以看到,尽管
Book
类已经和IBookManager
位于相同的包中,但是在IBookManager
中仍然要导入Book
类,这就是AIDL
的特殊之处。
特别注意要导入import com.hgy.multprocess.aidl.Book;
,Android Studio对AIDL
没有自动错误提示的,不导入直接编译会报错的。
2.3.1 aidl自动生成类分析
在generatedJava目录下的 com.hgy.multprocess.aidl
包中自动生成了IBookManager. java
的类:
public interface IBookManager extends android.os.IInterface {
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements com.hgy.multprocess.aidl.IBookManager {
private static final java.lang.String DESCRIPTOR = "com.hgy.multprocess.aidl.IBookManager";
/**
* Construct the stub at attach it to the interface.
*/
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.hgy.multprocess.aidl.IBookManager interface,
* generating a proxy if needed.
*/
public static com.hgy.multprocess.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.hgy.multprocess.aidl.IBookManager))) {
return ((com.hgy.multprocess.aidl.IBookManager) iin);
}
return new com.hgy.multprocess.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.hgy.multprocess.aidl.Book> _result = this.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBook: {
data.enforceInterface(descriptor);
com.hgy.multprocess.aidl.Book _arg0;
if ((0 != data.readInt())) {
_arg0 = com.hgy.multprocess.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.hgy.multprocess.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.hgy.multprocess.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.hgy.multprocess.aidl.Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.hgy.multprocess.aidl.Book.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override
public void addBook(com.hgy.multprocess.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.hgy.multprocess.aidl.Book> getBookList() throws android.os.RemoteException;
public void addBook(com.hgy.multprocess.aidl.Book book) throws android.os.RemoteException;
}
它继承了android.os.IInterface
这个接口,同时它自己也还是个接口,所有可以在Binder中传输的接口都需要继承IInterface接口
这个类的结构其实很简单,首先,它声明了两个方法接口getBookList
和addBook
,显然这就是我们在IBookManag er. aidl
中所声明的方法。
public java.util.List<com.hgy.multprocess.aidl.Book> getBookList() throws android.os.RemoteException;
public void addBook(com.hgy.multprocess.aidl.Book book) throws android.os.RemoteException;
所以我们可以在服务端来重载Stub
,实现真正的功能,后面会多处提到这个重载Stub
的真正实现类
private final IBookManager.Stub mBinder = new IBookManager.Stub() {
@Override
public List<Book> getBookList() throws RemoteException {
synchronized (mBookList) {
return mBookList;
}
}
@Override
public void addBook(Book book) throws RemoteException {
synchronized (mBookList) {
if (!mBookList.contains(book)) {
mBookList.add(book);
}
}
}
}
IInterface
接口的好处是,在Service
端通过子类化IBookManager.Stub
实现真正的函数功能,
而IBookManager asInterface(IBinder obj)
又提供了将IBinder
转换成IBookManager
对象,从而在Client
端调用addBook
这些接口函数。
同时它还声明了两个整型的id分别用于标识这两个方法,这两个id用于标识在transact
过程中客户端所请求的到底是哪个方法。
public static abstract class Stub extends android.os.Binder implements com.hgy.multprocess.aidl.IBookManager {
....
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
case TRANSACTION_getBookList: {
....
}
case TRANSACTION_addBook: {
....
}
}
}
static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
接着,它声明了一个内部类Stub
,这个Stub
就是一个Binder
类,当客户端和服务端都位于同一个进程时,方法调用不会走跨进程的transact
过程,而当两者位于不同进程时,方法调用需要走transact
过程,这个逻辑由Stub
的内部代理类Proxy
来完成。
这个接口的核心实现就是它的内部类Stub
和Stub
的内部代理类Proxy
,下面详细介绍针对这两个类的每个方法的含义。
private static final java.lang.String DESCRIPTOR = "com.hgy.multprocess.aidl.IBookManager";
DESCRIPTOR
是Binder
的唯一标识,一般用当前Binder
的类名表示
public static com.hgy.multprocess.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.hgy.multprocess.aidl.IBookManager))) {
return ((com.hgy.multprocess.aidl.IBookManager) iin); // 本进程返回sub本身
}
return new com.hgy.multprocess.aidl.IBookManager.Stub.Proxy(obj);// 跨进程返回Proxy
}
asInterface
方向用于将服务端的Binder
对象转换成客户端所需的AIDL
接口类型的对象,这种转换过程是区分进程的,如果客户端和服务端位于同一进程,那么此方法返回的就是服务端的Stub
对象本身,否则返回的是系统封装后的Stub.proxy
对象
这两个类都是implements IBookManager
的实现体, Stub
由Service
端重载真正实现相关接口,参看前面mBinder
,而Stub.proxy
会跨进程转发到Stub
的onTransact
方法。
public android.os.IBinder asBinder() {
return this;
}
此方法用于返回当前Binder
对象, 其实就是Service
端对 Stub
的真正实现类,参看前面mBinder
。
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.hgy.multprocess.aidl.Book> _result = this.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBook: {
data.enforceInterface(descriptor);
com.hgy.multprocess.aidl.Book _arg0;
if ((0 != data.readInt())) {
_arg0 = com.hgy.multprocess.aidl.Book.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
this.addBook(_arg0);
reply.writeNoException();
return true;
}
default: {
return super.onTransact(code, data, reply, flags);
}
}
}
onTransact
方法运行在服务端中的Binder线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由此方法来处理。
方法原型为 public boolean transact(int code, Parcel data, Parcel reply, int flags) throws RemoteException
服务端通过code
可以确定客户端所请求的目标方法是什么(如TRANSACTION_addBook
)
接着从data
中取出目标方法所需的参数(如 _arg0 = aidl.Book.CREATOR.createFromParcel(data);
)
然后执行目标方法,当目标方法执行完毕后,就向reply中写入返回值(如this.addBook(_arg0);
)
onTransact
方法的执行过程就是这样的。需要注意的是,如果此方法返回false
,那么客户端的请求会失败,因此我们可以利用这个特性来做权限验证,毕竟我们也不希望随便一个进程都能远程调用我们的服务。
以下是Stub.proxy
的远程调用函数
public java.util.List<com.hgy.multprocess.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.hgy.multprocess.aidl.Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.hgy.multprocess.aidl.Book.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
这个方法运行在客户端,当客户端远程调用此方法时,它的内部实现是这样的:
首先创建该方法所需要的输入型Parcel
对象_data
、输出型Parcel
对象_reply
和返回值对象List
然后把该方法的参数信息写入_data
中(如果有参数的话),接着调用transact
方法来发起RPC
(远程过程调用)请求, 同时当前线程挂起
然后服务端的onTransact
方法会被调用,直到RPC
过程返回后,当前线程继续执行,并从_reply
中取出RPC
过程的返回结果;最后返回_reply
中的数据。
public void addBook(com.hgy.multprocess.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();
}
}
这个方法运行在客户端,它的执行过程和getBookList
是一样的,addBook
没有返回值,所以它不需要从_reply
中取出返回值。
有两点还是需要额外说明一下:
首先,当客户端发起远程请求时,由于当前线程会被挂起直至服务端进程返回数据,所以如果一个远程方法是很耗时的,那么不能在UI线程中发起此远程请求;
其次,由于服务端的Binder
方法运行在Binder
的线程池中,所以Binder
方法不管是否耗时都应该采用同步的方式去实现,因为它已经运行在一个线程中了。
为了更好地说明Binder
,下面给出一个Binder
的工作机制图
这两个函数其实也给了我们怎么远程调用的示例封装,virtualapp就用了这种方式
从上述分析过程来看,我们完全可以不提供AIDL
文件即可实现Binder
,之所以提供AIDL
文件,是为了方便系统为我们生成代码。
系统根据AIDL
文件生成Java文件的格式是固定的,我们可以抛开AIDL
文件直接写一个Binder
出来,接下来我们就介绍如何手动写一个Binder
。
2.3.2 aidl手动生成类
参考上面系统自动生成的IBookManager. java
这个类的代码,可以发现这个类是相当有规律的,根据它的特点,我们完全可以自己写一个和它一模一样的类出来,然后这个不借助AIDL
文件的Binder
就完成了。
但是我们发现系统生成的类看起来结构不清晰,可以试着对它进行结构上的调整,手动实现一个Binder
可以通过如下步骤来完成:
(1)声明一个AIDL
性质的接口,只需要继承IInterface
接口即可,IInterface
接口中只有一个asBinder
方法。这个接口的实现如下:
public interface IBookManager extends IInterface {
static final java.lang.String DESCRIPTOR = "com.hgy.multprocess.aidl.IBookManager";
static final int TRANSACTION_getBookList = (IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addBook = (IBinder.FIRST_CALL_TRANSACTION + 1);
public List<Book> getBookList() throws RemoteException;
public void addBook(Book book) throws RemoteException;
}
可以看到,在接口中声明了一个Binder
描述符和另外两个id
,这两个id
分别表示的是getBookList
和addBook
方法,这段代码原本也是系统生成的,我们仿照系统生成的规则去手动书写这部分代码。
(2)实现Stub
类和Stub
类中的Proxy
代理类,这段代码我们可以自己写,但是写出来后会发现和系统自动生成的代码是一样的,因此这个Stub
类我们只需要参考系统生成的代码即可,只是结构上需要做一下调整,调整后的代码如下所示。
public class BookManagerImpl extends Binder implements IBookManager{
/** Construct the stub at attach it to the interface. */
public BookManagerImpl() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an IBookManager interface, generating a proxy
* if needed.
*/
public static IBookManager asInterface(IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof IBookManager))) {
return ((IBookManager) iin);
}
return new BookManagerImpl.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_getBookList: {
data.enforceInterface(DESCRIPTOR);
List<Book> result = this.getBookList();
reply.writeNoException();
reply.writeTypedList(result);
return true;
}
case TRANSACTION_addBook: {
data.enforceInterface(DESCRIPTOR);
Book arg0;
if ((0 != data.readInt())) {
arg0 = Book.CREATOR.createFromParcel(data);
} else {
arg0 = null;
}
this.addBook(arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
@Override
public List<Book> getBookList() throws RemoteException {
// TODO 待实现
return null;
}
@Override
public void addBook(Book book) throws RemoteException {
// TODO 待实现
}
private static class Proxy implements IBookManager {
private IBinder mRemote;
Proxy(IBinder remote) {
mRemote = remote;
}
@Override
public IBinder asBinder() {
return mRemote;
}
public java.lang.String getInterfaceDescriptor() {
return DESCRIPTOR;
}
@Override
public List<Book> getBookList() throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
List<Book> result;
try {
data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(TRANSACTION_getBookList, data, reply, 0);
reply.readException();
result = reply.createTypedArrayList(Book.CREATOR);
} finally {
reply.recycle();
data.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);
}
mRemote.transact(TRANSACTION_addBook, data, reply, 0);
reply.readException();
} finally {
reply.recycle();
data.recycle();
}
}
}
}
通过将上述代码和系统生成的代码对比,可以发现简直是一模一样的。也许有人会问:既然和系统生成的一模一样,那我们为什么要手动去写呢?我们在实际开发中完全可以通过AIDL
文件让系统去自动生成,手动去写的意义在于可以让我们更加理解Binder
的工作原理,同时也提供了一种不通过AIDL
文件来实现Binder
的新方式。也就是说,AIDL
文件并不是实现Binder
的必需品。
如果是我们手写的Binder
,那么在服务端只需要创建一个BookManagerImpl
的对象并在Service
的onBind
方法中返回即可。
最后,是否手动实现Binder
没有本质区别,二者的工作原理完全一样,AIDL
文件的本质是系统为我们提供了一种快速实现
Binder
的工具,仅此而已。
2.3.3 异常断开处理
我们知道,Binder
运行在服务端进程,如果服务端进程由于某种原因异常终止,这个时候我们到服务端的Binder
连接断
裂(称之为Binder
死亡),会导致我们的远程调用失败。Binder
中提供了两个配对的方法linkToDeath
和unlinkToDeath
,通过linkToDeath
我们可以给Binder
设置一个死亡代理,当Binder
死亡时,我们就会收到通知,这个时候我们就可以重新发起连接请求从而恢复连接。
首先,声明一个DeathRecipient
对象。DeathRecipient
是一个接口,其内部只有一个方法binderDied
,我们需要实现这个方法,当Binder
死亡的时候,系统就会回调binderDied
方法,然后我们就可以移出之前绑定的binder
代理并重新绑定远程服务:
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
if (mBookManager == null)
return;
mBookManager.asBinder().unlinkToDeath(mDeathRecipient,0);
mBookManager = null;
// TODO:这里重新绑定远程Service
}
};
其次,在客户端绑定远程服务成功后,给binder
设置死亡代理:
mService = IMessageBoxManager.Stub.asInterface(binder);
binder.linkToDeath(mDeathRecipient,0);
其中linkToDeath
的第二个参数是个标记位,我们直接设为0即可。经过上面两个步骤,就给我们的Binder
设置了死亡代理,当Binder
死亡的时候我们就可以收到通知了。另外,通过Binder
的方法isBinderAlive
也可以判断Binder
是否死亡。
3.IPC通用方式
3.1 使用Bundle
四大组件中的三大组件(Activity
、Service
、Receiver
)都是支持在Intent
中传递Bundle
数据的,由于Bundle
实现了Parcelable
接口,所以它可以方便地在不同的进程间传输。
基于这一点,当我们在一个进程中启动了另一个进程的Activity
、Service
、Receiver
,我们就可以在Bundle
中附加我们需要传输给远程进程的信息并通过Intent
发送出去。
当然,我们传输的数据必须能够被序列化,比如基本类型、实现了Parcellable
接口的对象、实现了Serializable
接口的对象以及一些Android支持的特殊对象,具体内容可以看Bundle
这个类,就可以看到所有它支持的类型。Bundle
不支持的类型我们无法通过它在进程间传递数据。
3.2 使用文件共享
参考前面的User
对象读写,很明显,不支持并发读写这种多线程操作。
3.3 使用Messenger
Messenger
可以翻译为信使,顾名思义,通过它可以在不同进程中传递Messenger
对象,在Messenger
中放入我们需要传递的数据,就可以轻松地实现数据的进程间传递了。Messenger
是一种轻量级的IPC方案,它的底层实现是AIDL
,为什么这么说呢,我们大致看一下Messenger
这个类的构造方法就明白了。下面是Messenger
的两个构造方法,从构造方法的实现上我们可以明显看出AIDL
的痕迹,不管是IMessenger
还是Stub. asInterface
,这种使用方法都表明它的底层是AIDL
。
public Messenger(Handler target) {
mTarget = target.getIMessenger();
}
public Messenger(IBinder target) {
mTarget = IMessenger.Stub.asInterface(target);
}
Messenger
的使用方法很简单,它对AIDL
做了封装,使得我们可以更简便地进行进程间通信。同时,由于它一次处理一个请求,因此在服务端我们不用考虑线程同步的问题,这是因为服务端中不存在并发执行的情形。实现一个Messenger
有如下几个步骤,分为服务端和客户端。
1. 服务端进程
首先,我们需要在服务端创建一个Service
来处理客户端的连接请求,同时创建一个Handler
并通过它来创建一个Messenger
对象,然后在Service
的onBind
中返回这个Messenger
对象底层的Binder
即可。
2. 客户端进程
客户端进程中,首先要绑定服务端的Service
,绑定成功后用服务端返回的IBinder
对象创建一个Messenger
,通过这个Messenger
就可以向服务端发送消息了,发消息类型为Messenger
对象。
如果需要服务端能够回应客户端,就和服务端一样,我们还需要创建一个Handler
并创建一个新的Messenger
,并把这个Messenger
对象通过Message
的replyTo
参数传递给服务端,服务端通过这个replyTo
参数就可以回应客户端。
示例1.服务端无法回应客户端
首先看服务端的代码,这是服务端的典型代码,可以看到MessengerHandler
用来处理客户端发送的消息,并从消息中取出客户端发来的文本信息。
而mMessenger
是一个Messenger
对象,它和MessengerHandler
相关联,并在onBind
方法中返回它里面的Binder
对象,可以看出,这里Messenger
的作用是将客户端发送的消息传递给MessengerHandler
处理。
public class MessengerService extends Service {
private static class MessageHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MyConstants.MSG_FROM_CLIENT:
Log.i("hgy413","receive msg from Client:" + msg.getData().getString("msg"));
break;
default:
super.handleMessage(msg);
}
}
}
private final Messenger mMessenger = new Messenger(new MessageHandler());
@Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
}
然后,注册service
,让其运行在单独的进程中:
<service
android:name=".messenger.MessengerService"
android:process=":remote_messenger"/>
接下来再看看客户端的实现,客户端的实现也比较简单,首先需要绑定远程进程的MessengerService
,绑定成功后,根据服务端返回的binder
对象创建Messenger
对象并使用此对象向服务端发送消息。下面的代码在Bundle
中向服务端发送了一句话,在上面的服务端代码中会打印出这句话。
public class MessengerActivity extends AppCompatActivity {
private Messenger mService;
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mService = new Messenger(service);
Message msg = Message.obtain(null, MyConstants.MSG_FROM_CLIENT);
Bundle data = new Bundle();
data.putString("msg","hello,this is client.");
msg.setData(data);
try {
mService.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_messenger);
Intent intent = new Intent(this, MessengerService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
unbindService(mConnection);
super.onDestroy();
}
}
–>打印结果:
com.hgy.multprocess.multprocess:remote_messenger E/hgy413: receive msg from Client:hello,this is client.
通过上面的例子可以看出,在Messenger
中进行数据传递必须将数据放入Message
中,而Messenger
和Message
都实现了Parcelable
接口,因此可以跨进程传输。
简单来说,Message
中所支持的数据类型就是Messenger
所支持的传输类型。实际上,通过Messenger
来传输Messag e,Message
中能使用的载体只有what
、arg1
、arg2
、Bundle
以及replyTo
, object
字段仅支持系统提供的实现了Parcelable
接口的对象(不支持自定义的Parcelable
接口的对象)。
示例1.服务端回应客户端
还是采用上面的例子,但是稍微做一下修改,每当客户端发来一条消息,服务端就会自动回复一条嗯,你的消息我已经收到,稍后会回复你。
服务端只需要修改MessengerHandler
,当收到消息后,会立即回复一条消息给客户端。
Log.e(BuildConfig.TAG,"receive msg from Client:" + msg.getData().getString("msg"));
Messenger client = msg.replyTo;
Message relpyMessage = Message.obtain(null, MyConstants.MSG_FROM_SERVICE);
Bundle bundle = new Bundle();
bundle.putString("reply", "嗯,你的消息我已经收到,稍后会回复你。");
relpyMessage.setData(bundle);
try {
client.send(relpyMessage);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
接着再看客户端的修改,为了接收服务端的回复,客户端也需要准备一个接收消息的Messenger
和Handler
,如下所示。
private Messenger mGetReplyMessenger = new Messenger(new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MyConstants.MSG_FROM_SERVICE:
Log.e(BuildConfig.TAG, "receive msg from Service:" + msg.getData().getString("reply"));
break;
default:
break;
}
}
});
很明显,我们还要把mGetReplyMessenger
赋值到replyTo
(服务端从replyTo
取得并回复):
mService = new Messenger(service);
Message msg = Message.obtain(null,MyConstants.MSG_FROM_CLIENT);
Bundle data = new Bundle();
data.putString("msg","hello,this is client.");
msg.setData(data);
//注意下面这句
msg.replyTo = mGetReplyMessenger;
try {
mService.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
–>打印结果:
com.hgy.multprocess.multprocess:remote_messenger E/hgy413: receive msg from Client:hello,this is client.
com.hgy.multprocess.multprocess E/hgy413: receive msg from Service:嗯,你的消息我已经收到,稍后会回复你。
下面给出一张Messenger
的工作原理图以方便更好地理解Messenger
3.4 使用AIDL
Messenger
是以串行的方式处理客户端发来的消息,如果大量的消息同时发送到服务端,服务端仍然只能一个个处理,如
果有大量的并发请求,那么用Messenger
就不太合适了。
同时,Messenger
的作用主要是为了传递消息,很多时候我们可能需要跨进程调用服务端的方法,这种情形用Messenger
就无法做到了,但是我们可以使用AIDL
来实现跨进程的方法调用。
前面我们介绍了Binder
的概念,大家对Binder
也有了一定的了解,在Binder
的基础上我们可以更加容易地理解AIDL
。这里先介绍使用 AIDL
来进行进程间通信的流程,分为服务端和客户端两个方面。
3.4.1 AIDL服务端
服务端首先要创建一个Service
用来监听客户端的连接请求,然后创建一个AIDL
文件,将暴露给客户端的接口在这个AIDL
文件中声明,最后在Service
中实现这个AIDL
接口即可(参看上面的Binder
示例,服务端利用IBookManager.aidl
, 重载了IBookManager.Stub
相关接口)。
3.4.2 AIDL客户端
客户端所要做事情就稍微简单一些,首先需要绑定服务端的Service
,绑定成功后,将服务端返回的Binder
对象转成AIDL
接口所属的类型,接着就可以调用AIDL
中的方法了。
3.4.3 AIDL接口的创建
首先看AIDL
接口的创建,如下所示,我们创建了一个后缀为AIDL
的文件,在里面声明了一个接口和两个接口方法。
import com.hgy.multprocess.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
文件中使用
以上6种数据类型就是AIDL
所支持的所有类型,其中自定义的 Parcelable
对象和AIDL
对象必须要显式import
进来,不管它们是否和当前的AIDL
文件位于同一个包内,不然就会报错。
另外一个需要注意的地方是,如果AIDL
文件中用到了自定义的 Parcelable
对象,那么必须新建一个和它同名的AIDL
文件,并在其中声明它为 Parcelable
类型。在上面的IBookManager. aidl
中,我们用到了Book
这个类,所以,我们必须要创建Book. aidl
,然后在里面添加如下内容:
// Book.aidl
package com.hgy.multprocess.aidl;
parcelable Book;
Book.java
public class Book implements Parcelable {
public int bookId;
public String bookName;
public Book(int bookId, String bookName) {
this.bookId = bookId;
this.bookName = bookName;
}
protected Book(Parcel in) {
bookId = in.readInt();
bookName = in.readString();
}
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];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(bookId);
dest.writeString(bookName);
}
@Override
public String toString() {
return String.format("[bookId:%s, bookName:%s]", bookId, bookName);
}
}
我们需要注意,AIDL
中每个实现了 Parcelable
接口的类都需要按照上面那种方式去创建相应的AIDL
文件并声明那个类为Parcelable
。
在Android Studio中,
Book.aidl
和Book.java
不但同名,而且要同包名,Book.java
位于com.hgy.multprocess.aidl
下,
那么Book.aidl
就位于aidl.com.hgy.multprocess.aidl
下(最上层多了一层aidl
目录)。不然编译就会报错
除此之外,AIDL
中除了基本数据类型,其他类型的参数必须标上方向:in
、out
或者inout
(输入型参数, 输出型参数或者输入输出型参数),我们要根据实际需要去指定参数类型,不能一概使用out
或者inout
,因为这在底层实现是有开销的。
最后,AIDL
接口中只支持方法,不支持声明静态常量,这一点区别于传统的接口。
AIDL
的包结构在服务端和客户端要保持一致,否则运行会出错,这是因为客户端需要反序列化服务端中和AIDL
接口相关的所有类,如果类的完整路径不一样的话,就无法成功反序列化,程序也就无法正常运行。
3.4.4 AIDL远程服务端Service的实现
上面讲述了如何定义AIDL
接口,接下来我们就需要实现这个接口了。我们先创建一个Service
,称为BookManagerService
,代码如下:
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;
}
}
上面是一个服务端Service
的典型实现,首先在onCreate
中初始化添加了两本图书的信息,然后创建了一个Binder
对象并在onBind
中返回它,这个对象继承自IBookManager.Stub
并实现了它内部的AIDL
方法
注意这里采用了CopyOnWriteArrayList
,这个CopyOnWriteArrayList
支持并发读/写。在前面我们提到,AIDL
方法是在服务端的Binder
线程池中执行的,因此当多个客户端同时连接的时候,会存在多个线程同时访问的情形,所以我们要在AIDL
方法中处理线程同步,而我们这里直接使用CopyOnWriteArrayList
来进行自动的线程同步。
前面我们提到,AIDL
中能够使用的List
只有ArrayList
,但是我们这里却使用了CopyOnWriteArrayList
(注意它不是继承自ArrayList
),为什么能够正常工作呢?这是因为AIDL
中所支持的是抽象的List
,而List
只是一个接口,因此虽然服务端返回的是CopyOnWriteArrayList
,但是在Binder
中会按照List
的规范去访问数据并最终形成一个新的ArrayList
传递给客户端。所以,我们在服务端采用CopyOnWriteArrayList
是完全可以的。和此类似的还有ConcurrentHashMap
。
然后我们需要在XML
中注册这个Service
,让它运行在独立进程中
<service
android:name=".aidl.BookManagerService"
android:process=":remote_bookmanager"/>
3.4.5 AIDL客户端的实现
客户端的实现就比较简单了,首先要绑定远程服务,绑定成功后将服务端返回的Binder
对象转换成AIDL
接口,然后就可以通过这个接口去调用服务端的远程方法了
public class BookManagerActivity extends Activity {
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
IBookManager bookManager = IBookManager.Stub.asInterface(service);
try {
List<Book> list = bookManager.getBookList();
Log.e(BuildConfig.TAG,"query book list,list type:" + list.getClass().getCanonicalName());
Log.e(BuildConfig.TAG,"query book list:" + list.toString());
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@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();
}
}
绑定成功以后,会通过bookManager
去调用getBookList
方法,然后打印出所获取的图书信息。需要注意的是,服务端的方法有可能需要很久才能执行完毕,这个时候下面的代码就会导致ANR
,这一点是需要注意的,后面会再介绍这种情况,之所以先这么写是为了让大家更好地了解AIDL
的实现步骤。
运行程序,log 如下所示:
E/hgy413: query book list,list type:java.util.ArrayList
E/hgy413: query book list:[[bookId:1,bookName:Android],[bookId:2,bookName:Ios]]
第一行表明虽然我们在服务端返回的是CopyOnWriteArrayList
类型,但是客户端收到的仍然是ArrayList
类型,这也证实了我们在前面所做的分析。第二行表明客户端成功地得到了服务端的图书列表信息。
3.4.6 AIDL难点
3.4.6.1 绑定监听回调
我们接着再调用一下另外一个接口addBook
,我们在客户端给服务端添加一本书,然后再获取一次,看程序是否能够正常工作。还是上面的代码,客户端在服务连接后,在onServiceConnected
中做如下改动:
List<Book> list = bookManager.getBookList();
Book newBook = new Book(3, "andriod开发");
bookManager.addBook(newBook);
List<Book> newlist = bookManager.getBookList(); // 重新获取
Log.e(BuildConfig.TAG,"query book list:" + newlist.toString());
很明显,我们成功的向服务端添加了一本书
E/hgy413: query book list:[[bookId:1, bookName:Android], [bookId:2, bookName:Ios], [bookId:3, bookName:andriod开发]]
现在我们考虑一种情况,假设有一种需求:用户不想时不时地去查询图书列表了,太累了,于是,他去问图书馆,“当有新书时能不能把书的信息告诉我呢?”。大家应该明白了,这就是一种典型的观察者模式,每个感兴趣的用户都观察新书,当新书到的时候,图书馆就通知每一个对这本书感兴趣的用户,这种模式在实际开发中用得很多,下面我们就来模拟这种情形。
首先,我们需要提供一个AIDL
接口,每个用户都需要实现这个接口并且向图书馆申请新书的提醒功能,当然用户也可以随时取消这种提醒。之所以选择AIDL
接口而不是普通接口,是因为AIDL
中无法使用普通接口。
这里我们创建一个IOnNewBookArrivedListener. aidl
文件,我们所期望的情况是:当服务端有新书到来时,就会通知每一个已经申请提醒功能的用户。从程序上来说就是调用所有IOnNewBookArrivedListener
对象中的onNewBookArrived
方法,并把新书的对象通过参数传递给客户端,内容如下所示。
// IOnNewBookArrivedListener.aidl
package com.hgy.multprocess.aidl;
import com.hgy.multprocess.aidl.Book;
interface IOnNewBookArrivedListener {
void onNewBookArrived(in Book newBook);
}
除了要新加一个AIDL
接口,还需要在原有的接口中添加两个新方法用于注册和反注册,代码如下所示。
package com.hgy.multprocess.aidl;
import com.hgy.multprocess.aidl.Book;
import com.hgy.multprocess.aidl.IOnNewBookArrivedListener;
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
void registerListener(IOnNewBookArrivedListener listener);
void unregisterListener(IOnNewBookArrivedListener listener);
}
接着,服务端中Service
的实现也要针对这两个方法给出实现,同时,在BookManagerService
中还开启了一个线程,每隔5s就向书库中增加一本新书并通知所有感兴趣的用户,整个代码如下所示。
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 CopyOnWriteArrayList<IOnNewBookArrivedListener> mListenerList = new CopyOnWriteArrayList<IOnNewBookArrivedListener>();
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 registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
if (!mListenerList.contains(listener)) {
mListenerList.add(listener);
}
Log.e(BuildConfig.TAG,"registerListener,size:" + mListenerList.size());
}
@Override
public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
if (mListenerList.contains(listener)) {
mListenerList.remove(listener);
Log.e(BuildConfig.TAG,"unregister listener succeed.");
} else {
Log.e(BuildConfig.TAG,"not found,can not unregister.");
}
Log.e(BuildConfig.TAG,"unregisterListener,size:" + mListenerList.size());
}
};
@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) {
return mBinder;
}
@Override
public void onDestroy() {
mIsServiceDestoryed.set(true);
super.onDestroy();
}
private class ServiceWorker implements Runnable {
@Override
public void run() {
// do background processing here....
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();
}
}
}
private void onNewBookArrived(Book book) throws RemoteException{
mBookList.add(book);
for (int i=0; i<mListenerList.size(); i++) {
IOnNewBookArrivedListener listener = mListenerList.get(i);
listener.onNewBookArrived(book);
}
}
}
}
最后,我们还需要修改一下客户端的代码,主要有两方面:
首先客户端要注册IOnNewBookArrivedListener
到远程服务端,这样当有新书时服务端才能通知当前客户端,同时我们要在Activity
退出时解除这个注册
另一方面,当有新书时,服务端会回调客户端的IOnNewBookArrivedListener
对象中的onNewBookArrived
方法,但是这个方法是在客户端的Binder线程池中执行的,因此,为了便于进行UI操作,我们需要有一个Handler
可以将其切换到客户端的主线程中去执行,客户端的代码修改如下:
public class BookManagerActivity extends Activity {
private static final int MESSAGE_NEW_BOOK_ARRIVED = 1;
private IBookManager mRemoteBookManager;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MESSAGE_NEW_BOOK_ARRIVED:
Log.e(BuildConfig.TAG,"receive new book :" + msg.obj);
break;
default:
break;
}
}
};
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
IBookManager bookManager = IBookManager.Stub.asInterface(service);
try {
mRemoteBookManager = bookManager;
List<Book> list = bookManager.getBookList();
Book newBook = new Book(3, "andriod开发");
bookManager.addBook(newBook);
List<Book> newlist = bookManager.getBookList();
Log.e(BuildConfig.TAG,"query book list:" + newlist.toString());
bookManager.registerListener(mOnNewBookArrivedListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
mRemoteBookManager = null;
}
};
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);
}
@Override
protected void onDestroy() {
if (mRemoteBookManager != null && mRemoteBookManager.asBinder().isBinderAlive()) {
try {
mRemoteBookManager.unregisterListener(mOnNewBookArrivedListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
unbindService(mConnection);
super.onDestroy();
}
}
打印结果如下:
E/hgy413: query book list:[[bookId:1, bookName:Android], [bookId:2, bookName:Ios], [bookId:3, bookName:andriod开发]]
E/hgy413: registerListener,size:1
E/hgy413: onNewBookArrived,notify listener:com.hgy.multprocess.aidl.IOnNewBookArrivedListener$Stub$Proxy@928ecf2
E/hgy413: receive new book :[bookId:4, bookName:new book#4]
.....
可以看出,客户端的确收到了服务端每5s一次的新书推送.
3.4.6.2 RemoteCallbackList解除绑定监听回调
从上面的代码可以看出,当BookManagerActivity
关闭时,我们会在onDestroy
中去解除已经注册到服务端的listener
,这就相当于我们不想再接收图书馆的新书提醒了,按back键退出BookManagerActivity
,下面是打印出的log:
E/hgy413: not found,can not unregister.
从上面的log 可以看出,程序没有像我们所预期的那样执行。在解注册的过程中,服务端竟然无法找到我们之前注册的那个listener
,在客户端我们注册和解注册时明明传递的是同一个listener
啊!
其实,这是必然的,这种解注册的处理方式在日常开发过程中时常使用到,但是放到多进程中却无法奏效,因为Binder
会把客户端传递过来的对象重新转化并生成一个新的对象。虽然我们在注册和解注册过程中使用的是同一个客户端对象,但是通
过Binder
传递到服务端后,却会产生两个全新的对象。别忘了对象是不能跨进程直接传输的,对象的跨进程传输本质上都是反序列化的过程,这就是为什么AIDL
中的自定义对象都必须要实现Parcelable
接口的原因。
那么到底我们该怎么做才能实现解注册功能呢?答案是使用RemoteCallbackList
,RemoteCallbackList
系统专门提供的用于删除跨进程listener
的接口。RemoteCallbackList
是一个泛型,支持管理任意的AIDL
接口, 这点从它的声明就可以看出,因为所有的AIDL
接口都继承自IInterface
接口
public class RemoteCallbackList<E extends IInterface>
它的工作原理很简单,在它的内部有一个Map
结构专门用来保存所有的AIDL
回调,这个Map
的key
是IBinder
类型,value
是Callback
类型,如下所示。
ArrayMap<IBinder,Callback> mCallbacks = new ArrayMap<IBinder,Callback>();
其中Callback
中封装了真正的远程listener
。当客户端注册listener
的时候,它会把这个listener
的信息存入mCallbacks
中,其中key
和value
分别通过下面的方式获得:
IBinder key= listener.asBinder()
Callback value = new Callback(listener,cookie)
虽然说多次跨进程传输客户端的同一个对象会在服务端生成不同的对象,但是这些新生成的对象有一个共同点,那就是它们底层的Binder
对象是同一个,也就是key
相同,利用这个特性,就可以实现上面我们无法实现的功能。
当客户端解注册的时候,我们只要遍历服务端所有的listener
,找出那个和解注册listener
具有相同Binder
对象的服务端listener
并把它删掉即可,这就是RemoteCallbackList
为我们做的事情。
同时RemoteCallbackList
还有一个很有用的功能,那就是当客户端进程终止后,它能够自动移除客户端所注册的listener
。另外,RemoteCallbackList
内部自动实现了线程同步的功能,所以我们使用它来注册和解注册时,不需要做额外的线程同步工作。
RemoteCallbackList
使用起来很简单,我们要对BookManagerService
做一些修改,首先要创建一个RemoteCallbackList
对象来替代之前的CopyOnWriteArrayList
,如下所示。
private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<Book>();
private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList = new RemoteCallbackList<IOnNewBookArrivedListener>();
然后修改registerListener
和unregisterListener
这两个接口的实现:
特别注意beginBroadcast
和finishBroadcast
要成对出现
@Override
public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
mListenerList.register(listener);
Log.e(BuildConfig.TAG,"registerListener,size:" + mListenerList.beginBroadcast());
mListenerList.finishBroadcast();
}
@Override
public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
mListenerList.unregister(listener);
Log.e(BuildConfig.TAG,"unregisterListener,size:" + mListenerList.beginBroadcast());
mListenerList.finishBroadcast();
}
接着要修改onNewBookArrived
方法,当有新书时,我们就要通知所有已注册的listener
,如下所示。
private void onNewBookArrived(Book book) throws RemoteException{
mBookList.add(book);
int count = mListenerList.beginBroadcast();
for (int i=0; i<count; i++) {
IOnNewBookArrivedListener listener = mListenerList.getBroadcastItem(i);
Log.e(BuildConfig.TAG,"onNewBookArrived,notify listener:" + listener);
listener.onNewBookArrived(book);
}
}
输出结果:
com.hgy.multprocess.multprocess:remote_bookmanager E/hgy413: registerListener,size:1
com.hgy.multprocess.multprocess:remote_bookmanager E/hgy413: unregisterListener,size:0
3.4.6.3 避免ANR耗时操作
我们知道,客户端调用远程服务的方法,被调用的方法运行在服务端的Binder
线程池中,同时客户端线程会被挂起,这个时候如果服务端方法执行比较耗时,就会导致客户端线程长时间地阻塞在这里,而如果这个客户端线程是UI线程的话,就会导致客户端ANR
因此,如果我们明确知道某个远程方法是耗时的,那么就要避免在客户端的UI线程中去访问远程方法。
由于客户端的
onServiceConnected
和onServiceDisconnected
方法都运行在UI
线程中,所以也不可以在它们里面直接调用服务端的耗时方法,这点要尤其注意
由于服务端的方法本身就运行在服务端的Binder
线程池中,所以服务端方法本身就可以执行大量耗时操作
下面我们稍微改造一下服务端的getBookList方法,我们假定这个方法是耗时的,那么服务端可以这么实现:
public List<Book> getBookList() throws RemoteException {
SystemClock.sleep(5000);
return mBookList;
}
然后在客户端中放一个按钮,单击它的时候就会调用服务端的getBookList
方法
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/button1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/textView1"
android:onClick="onButton1Click"
android:text="Button" />
</LinearLayout>
public void onButton1Click(View view) {
try {
List<Book> newList = mRemoteBookManager.getBookList();
} catch (RemoteException e) {
e.printStackTrace();
}
}
–>可以预知,连续单击几次,客户端就ANR
了
我们只需要把调用放在非UI线程即可避免这种ANR
了
public void onButton1Click(View view) {
new Thread(new Runnable() {
@Override
public void run() {
try {
List<Book> newList = mRemoteBookManager.getBookList();
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
}
同理,当远程服务端需要调用客户端的listener
中的方法时,被调用的方法也运行在Binder
线程池中,只不过是客户端的线程池,所以,我们同样不可以在服务端中调用客户端的耗时方法。比如针对BookManagerService
的onNewBookArrived
方法,如下所示。在它内部调用了客户端的IOnNewBookArrivedListener
中的onNewBookArrived
方法,如果客户端的这个onNewBookArrived
方法
比较耗时的话,那么请确保BookManagerService
中的onNewBookArrived
运行在非UI线程中,否则将导致服务端无法响应。
另外,由于客户端的IOnNewBookArrivedListener
中的onNewBookArrived
方法运行在客户端的Binder
线程池中,所以要借助Handler
切换到UI线程
3.4.6.4 Bindler重连
第一种方法是给Binder
设置DeathRecipient
监听,当Binder
死亡时,我们会收到binderDied
方法的回调,在binderDied
方法中我们可以重连远程服务
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
if (mRemoteBookManager == null)
return;
mRemoteBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);
mRemoteBookManager = null;
// TODO:这里重新绑定远程Service
}
};
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
IBookManager bookManager = IBookManager.Stub.asInterface(service);
try {
mRemoteBookManager = bookManager;
mRemoteBookManager.asBinder().linkToDeath(mDeathRecipient, 0);
另一种方法是在onServiceDisconnected
中重连远程服务。
@Override
public void onServiceDisconnected(ComponentName name) {
mRemoteBookManager = null;
// TODO:这里重新绑定远程Service
}
它们的区别在于:onServicesconnected
在客户端的UI线程中被回调,而binderDied
在客户端的Binder
线程池中
被回调。也就是说,在binderDied
方法中我们不能访问UI。
3.4.6.5 权限问题
默认情况下,我们的远程服务任何人都可以连接,但这应该不是我们愿意看到的,所以我们必须给服务加入权限验证功能,权限验证失败则无法调用服务中的方法。
第一种方法,我们可以在onBind
中进行验证,验证不通过就直接返回null
,这样验证失败的客户端直接无法绑定服务。
至于验证方式可以有多种,比如使用permission
验证。使用这种验证方式,我们要先在AndroidMenifest
中声明所需的权限,比如:
<permission
android:name="com.hgy.multprocess.permission.ACCESS_BOOK_SERVICE"
android:protectionLevel="normal" />
定义了权限以后,就可以在BookManagerService
的onBind
方法中做权限验证了,如下所示:
public IBinder onBind(Intent intent) {
int check = checkCallingOrSelfPermission("com.hgy.multprocess.permission.ACCESS_BOOK_SERVICE");
if (check == PackageManager.PERMISSION_DENIED) {
return null;
}
return mBinder;
}
如果我们自己内部的应用想绑定到我们的服务中,只需要在它的AndroidMenifest
中申请所需的权限:
<uses-permission android:name="com.hgy.multprocess.permission.ACCESS_BOOK_SERVICE" />
第二种方法,我们可以在服务端的onTransact
方法中进行权限验证,如果验证失败就直接返回false
,至于具体的验证方式
有很多,可以采用permission
验证,具体实现方式和第一种方法一样。还可以采用Uid
和Pid
来做验证,通过getCallingUid
和getCallingPid
可以拿到客户端所属应用的Uid
和Pid
,通过这两个参数我们可以做一些验证工作,比如验证包名。
private Binder mBinder = new IBookManager.Stub() {
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
int check = checkCallingOrSelfPermission("com.hgy.multprocess.permission.ACCESS_BOOK_SERVICE");
if (check == PackageManager.PERMISSION_DENIED) {
return false;
}
String packageName = null;
String[] packages = getPackageManager().getPackagesForUid(getCallingUid());
if (packages != null && packages.length > 0) {
packageName = packages[0];
}
if (!packageName.startsWith("com.hgy")) {
return false;
}
return super.onTransact(code, data, reply, flags);
}
3.5 使用ContentProvider
3.5.1 使用ContentProvider方式
ContentProvider
是Android
中提供的专门用于不同应用间进行数据共享的方式,从这一点来看,它天生就适合进程间通信。和Messenger
一样,ContentProvider
的底层实现同样也是Binder
系统预置了许多ContentProvider
,比如通讯录信息、日程表信息等,要跨进程访问这些信息,只需要通过ContentResolver
的query
、update
、insert
和delete
方法即可。
我们来实现一个自定义的ContentProvider
,并演示如何在其他应用中获取ContentProvider
中的数据从而实现进程间通信这一目的。
首先,我们创建一个ContentProvider
,名字就叫BookProvider
。实现六个抽象方法即可:onCreate
、query
、update
、insert
、delete
和getType
。
onCreate
代表ContentProvider
的创建,一般来说我们需要做一些初始化工作.
getType
用来返回一个Uri
请求所对应的MIME类型, 比如图片、视频等.
剩下的四个方法对应于CRUD
操作,即实现对数据表的增删改查
功能.
根据Binder
的工作原理,我们知道这六个方法均运行在ContentProvider
的进程中,除了onCreate
由系统回调并运行在主线程里,其他五个方法均由外界回调并运行在Binder线程池中
ContentProvider
主要以表格的形式来组织数据,并且可以包含多个表,对于每个表格来说,它们都具有行和列的层次性,行往往对应一条记录,而列对应一条记录中的一个字段,这点和数据库很类似。
除了表格的形式,ContentProvider
还支持文件数据,比如图片、视频等。文件数据和表格数据的结构不同,因此处理这类数据时可以在ContentProvider
中返回文件的句柄给外界从而让文件来访问ContentProvider
r中的文件信息。
虽然ContentProvider
的底层数据看起来很像一个SQLite
数据库,但是ContentProvider
对底层的数据存储方式没有任何要求,我们既可以使用SQLite
数据库,也可以使用普通的文件,甚至可以采用内存中的一个对象来进行数据的存储。
下面看一个最简单的示例,它演示了ContentProvider
的工作流程:
在下面的代码中,我们什么都没干,尽管如此,这个BookProvider
也是可以工作的,只是它无法向外界提供有效的数据而已。
public class BookProvider extends ContentProvider {
private static final String TAG = "BookProvider";
@Override
public boolean onCreate() {
Log.d(TAG, "onCreate, current thread:" + Thread.currentThread().getName());
return false;
}
@Override
public Cursor query( Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
Log.d(TAG, "query, current thread:" + Thread.currentThread().getName());
return null;
}
@Override
public String getType( Uri uri) {
Log.d(TAG, "getType, current thread:" + Thread.currentThread().getName());
return null;
}
@Override
public Uri insert( Uri uri, ContentValues values) {
Log.d(TAG, "insert, current thread:" + Thread.currentThread().getName());
return null;
}
@Override
public int delete( Uri uri, String selection, String[] selectionArgs) {
Log.d(TAG, "delete, current thread:" + Thread.currentThread().getName());
return 0;
}
@Override
public int update( Uri uri, ContentValues values, String selection, String[] selectionArgs) {
Log.d(TAG, "update, current thread:" + Thread.currentThread().getName());
return 0;
}
}
接着我们需要注册这个BookProvider
,如下所示
<provider
android:name=".provider.BookProvider"
android:authorities="com.hgy.multprocess.provider.BookProvider"
android:permission="com.hgy.multprocess.PROVIDER"
android:process=":provider"/>
android:name
指定类所在路径。
android:authorities
是ContentProvider
的唯一标识,通过这个属性外部应用就可以访问我们的BookProvider
,所以一定要是唯一的,一般建议是命名前加上包名前缀。
android:permission
指定了一个权限,这样外界应用如果想访问BookProvider
,就必须声明com.hgy.multprocess.PROVIDER
这个权限。
注册了ContentProvider
以后,我们就可以在外部应用中访问它了。注意要声明权限:
<uses-permission android:name="com.hgy.multprocess.PROVIDER" />
public class ProviderActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_provider);
Uri uri = Uri.parse("content://com.hgy.multprocess.provider.BookProvider");
getContentResolver().query(uri, null,null, null, null);
getContentResolver().query(uri, null,null, null, null);
getContentResolver().query(uri, null,null, null, null);
}
}
在上面的代码中,我们通过ContentResolver
对象的query
方法去查询BookProvider
中的数据,们运行后看一下log
:
multprocess:provider D/BookProvider: onCreate, current thread:main
multprocess.multprocess:provider D/BookProvider: query, current thread:Binder:26610_2
multprocess:provider D/BookProvider: query, current thread:Binder:26610_1
multprocess:provider D/BookProvider: query, current thread:Binder:26610_2
可以看出,BookProvider
中的onCreate
在主线程main
中调用,query
方法被调用了三次,并且这三次调用不在同一个线程中,可以看出,它们运行在一个Binder线程池中,前面提到update
、insert
和delete
方法同样也运行在Binder线程池中。另
外,onCreate
在主线程main
中调用,也就是UI线程,所以我们不能在onCreate
中做耗时操作。
接下来,在上面的基础上,我们继续完善BookProvider
,从而使其能够对外部应用提供数据。现在我们要提供一个BookProvider
,外部应用可以通过BookProvider
来访问图书信息,为了更好地演示ContentProvider
的使用,用户还可以通过BookProvider
访问到用户信息。
为了完成上述功能,我们需要一个数据库来管理图书和用户信息,代码如下:
public class DbOpenHelper extends SQLiteOpenHelper {
private static final String DB_NAME = "book_provider.db";
public static final String BOOK_TABLE_NAME = "book";
public static final String USER_TABLE_NAME = "user";
private static final int DB_VERSION = 3;
// _id integer primary key 表示设置字段_id,类型为int整形,为主键
// name为string
private String CREATE_BOOK_TABLE = "CREATE TABLE IF NOT EXISTS "
+ BOOK_TABLE_NAME + "(_id INTEGER PRIMARY KEY," + "name TEXT)";
// _id integer primary key 表示设置字段_id,类型为int整形,为主键
// name为string, sex为int
private String CREATE_USER_TABLE = "CREATE TABLE IF NOT EXISTS "
+ USER_TABLE_NAME + "(_id INTERGER PRIMARY KEY," + "name TEXT," +"sex INT)";
public DbOpenHelper(Context context) {
super(context, DB_NAME, null, DB_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) {
// TODO ignored
}
}
注意DB_NAME
的命名是book_provider.db
,如果你写成book_provider_db
,可能就挂了。
我们借助SQLiteOpenHelper
来管理数据库的创建、升级和降级。下面我们就要通过BookProvider
向外界提供上述数据库中的信息了。我们知道,ContentProvider
通过Uri
来区分外界要访问的的数据集合,在本例中支持外界对BookProvider
中的book
表和user
表进行访问
为了知道外界要访问的是哪个表,我们需要为它们定义单独的Uri
和Uri_Code
,并将Uri
和对应的Uri_Code
相关联,我们可以使用UriMatcher
的addURI
方法将Uri
和Uri_Code
关联到一起。这样,当外界请求访问BookProvider
时,我们就可以根据请求的Uri
来得到Uri_Code
,有了Uri_Code
我们就可以知道外界想要访问哪个表,然后就可以进行相应的数据操作了,具体代码如下所示。
public class BookProvider extends ContentProvider {
private static final String TAG = "BookProvider";
// 和xml中的android:authorities一样
public static final String AUTHORITY = "com.hgy.multprocess.provider.BookProvider";
public static final Uri BOOK_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/book");
public static final Uri USER_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/user");
public static final int BOOK_URI_CODE = 0;
public static final int USER_URI_CODE = 1;
private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
sUriMatcher.addURI(AUTHORITY, "book", BOOK_URI_CODE);
sUriMatcher.addURI(AUTHORITY, "user", USER_URI_CODE);
}
从上面代码可以看出,我们分别为book
表和user
表指定了Uri
,分别为content://com.hgy.multprocess.provider.BookProvider/book
和content://com.hgy.multprocess.provider.BookProvider/user
, 这两个Uri
所关联的Uri_Code
分别为0和1:
sUriMatcher.addURI(AUTHORITY, "book", BOOK_URI_CODE);
sUriMatcher.addURI(AUTHORITY, "user", USER_URI_CODE);
根据Uri
先取出Uri_Code
,根据Uri_Code
再得到数据表的名称,知道了外界要访问的表,接下来就可以响应外界的增删改查请求了。
private String getTableName(Uri uri) {
String tableName = null;
switch (sUriMatcher.match(uri)) {
case BOOK_URI_CODE:
tableName = DbOpenHelper.BOOK_TABLE_NAME;
break;
case USER_URI_CODE:
tableName = DbOpenHelper.USER_TABLE_NAME;
break;
default:
break;
}
return tableName;
}
接着,我们就可以实现query
、update
、insert
、delete
方法了。如下是query
方法的实现,首先我们要从Uri
中取出外界要访问的表的名称,然后根据外界传递的查询参数就可以进行数据库的查询操作了,这个过程比较简单。
public Cursor query( Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
Log.d(TAG, "query, current thread:" + Thread.currentThread().getName());
String table =getTableName(uri);
if (table == null) {
throw new IllegalArgumentException("Unsupported URI" + uri);
}
return mDb.query(table, projection, selection, selectionArgs, null, null, sortOrder, null);
}
另外三个方法的实现思想和query
是类似的,只有一点不同,那就是update
、insert
和delete
方法会引起数据源的改变,这个时候我们需要通过ContentResolver
的notifyChange
方法来通知外界当前ContentProvider
中的数据已经发生改变。
要观察一个ContentProvider
中的数据改变情况,可以通过ContentResolver
的registerContentObserver
方法来注册观察者,通过unregisterContentObserver
方法来解除观察者。BookProvider
的完整代码如下:
public class BookProvider extends ContentProvider {
private static final String TAG = "BookProvider";
// 和xml中的android:authorities一样
public static final String AUTHORITY = "com.hgy.multprocess.provider.BookProvider";
public static final Uri BOOK_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/book");
public static final Uri USER_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/user");
public static final int BOOK_URI_CODE = 0;
public static final int USER_URI_CODE = 1;
private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
sUriMatcher.addURI(AUTHORITY, "book", BOOK_URI_CODE);
sUriMatcher.addURI(AUTHORITY, "user", USER_URI_CODE);
}
private Context mContext;
private SQLiteDatabase mDb;
@Override
public boolean onCreate() {
Log.d(TAG, "onCreate, current thread:" + Thread.currentThread().getName());
mContext = getContext();
//ContentProvider创建时,初始化数据库。注意:这里仅仅是为了演示,实际使用中不推荐在主线程
initProviderData();
return true;
}
private void initProviderData() {
mDb = new DbOpenHelper(mContext).getWritableDatabase();
mDb.execSQL("delete from " + DbOpenHelper.BOOK_TABLE_NAME);
mDb.execSQL("delete from " + DbOpenHelper.USER_TABLE_NAME);
mDb.execSQL("insert into book values(3, 'Android');");
mDb.execSQL("insert into book values(4, 'Ios');");
mDb.execSQL("insert into book values(5, 'Html5');");
mDb.execSQL("insert into user values(1, 'jake', 1);");
mDb.execSQL("insert into user values(2, 'jasmine', 0);");
}
@Override
public Cursor query( Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
Log.d(TAG, "query, current thread:" + Thread.currentThread().getName());
String table =getTableName(uri);
if (table == null) {
throw new IllegalArgumentException("Unsupported URI" + uri);
}
return mDb.query(table, projection, selection, selectionArgs, null, null, sortOrder, null);
}
@Override
public String getType( Uri uri) {
Log.d(TAG, "getType, current thread:" + Thread.currentThread().getName());
return null;
}
@Override
public Uri insert( Uri uri, ContentValues values) {
Log.d(TAG, "insert, current thread:" + Thread.currentThread().getName());
String table =getTableName(uri);
if (table == null) {
throw new IllegalArgumentException("Unsupported URI" + uri);
}
mDb.insert(table, null, values);
mContext.getContentResolver().notifyChange(uri, null);
return uri;
}
@Override
public int delete( Uri uri, String selection, String[] selectionArgs) {
Log.d(TAG, "delete, current thread:" + Thread.currentThread().getName());
String table =getTableName(uri);
if (table == null) {
throw new IllegalArgumentException("Unsupported URI" + uri);
}
int count = mDb.delete(table, selection, selectionArgs);
if (count > 0) {
getContext().getContentResolver().notifyChange(uri, null);
}
return count;
}
@Override
public int update( Uri uri, ContentValues values, String selection, String[] selectionArgs) {
Log.d(TAG, "update, current thread:" + Thread.currentThread().getName());
String table =getTableName(uri);
if (table == null) {
throw new IllegalArgumentException("Unsupported URI" + uri);
}
int row = mDb.update(table, values, selection, selectionArgs);
if (row > 0) {
getContext().getContentResolver().notifyChange(uri, null);
}
return row;
}
private String getTableName(Uri uri) {
String tableName = null;
switch (sUriMatcher.match(uri)) {
case BOOK_URI_CODE:
tableName = DbOpenHelper.BOOK_TABLE_NAME;
break;
case USER_URI_CODE:
tableName = DbOpenHelper.USER_TABLE_NAME;
break;
default:
break;
}
return tableName;
}
}
需要注意的是,query
、update
、insert
、delete
四大方法是存在多线程并发访问的,因此方法内部要做好线程同步。在本例中,由于采用的是SQLite
并且只有一个SQLiteDatabase
的连接,所以可以正确应对多线程的情况。具体原因是SQLiteDatabase
内部对数据库的操作是有同步处理的,接着我们在外部访问一下它,看看是否能够正常工作。
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_provider);
Uri bookUri = Uri.parse("content://com.hgy.multprocess.provider.BookProvider/book");
ContentValues values = new ContentValues();
values.put("_id", 6);
values.put("name", "程序的艺术");
getContentResolver().insert(bookUri, values);
Cursor bookCursor = getContentResolver().query(bookUri, new String[]{"_id", "name"}, null, null, null);
while (bookCursor.moveToNext()) {
Log.d(TAG, "query book:" + bookCursor.getInt(0) + ":" + bookCursor.getString(1));
}
bookCursor.close();
Uri userUri = Uri.parse("content://com.hgy.multprocess.provider.BookProvider/user");
Cursor userCursor = getContentResolver().query(userUri, new String[]{"_id", "name","sex"}, null, null, null);
while (userCursor.moveToNext()) {
Log.d(TAG, "query user:" + userCursor.getInt(0)
+ ":" + userCursor.getString(1)
+ ":" + userCursor.getInt(2));
}
userCursor.close();
}
Cursor
可以理解为游标,简单的说,它就是得到table表中满足条件的所有行,AbstractCursor.java
为它的具体实现体,它的遍历操作就是移动内部的mPos
,默认mPos
为-1
public AbstractCursor() {
mPos = -1;
}
public final boolean moveToFirst() {
return moveToPosition(0);
}
public final boolean moveToLast() {
return moveToPosition(getCount() - 1);
}
public final boolean moveToNext() { // 开始如果调用moveToNext,就是-1 + 1 刚好是moveToFirst
return moveToPosition(mPos + 1);
}
public final boolean moveToPosition(int position) {
// Make sure position isn't past the end of the cursor
final int count = getCount();
if (position >= count) {
mPos = count;
return false;
}
// Make sure position isn't before the beginning of the cursor
if (position < 0) {
mPos = -1;
return false;
}
ContentProvider
除了支持对数据源的增删改查这四个操作,还支持自定义调用,这个过程是通过ContentResolve
的Call
方法和ContentProvider
的Call
方法来完成的。
3.5.2 ContentProvider源码分析
3.5.2.1 新ContentProvider进程启动流程
当一个应用启动时,入口方法为ActivityThread
的main
方法
下面分析单实例的ContentProvider
的启动过程。统一使用API23
做调试分析:
Cursor bookCursor = getContentResolver().query(
–>
public ContentResolver getContentResolver() {
return mBase.getContentResolver();
}
mBase
为android.app.ContextImpl
–>
public ContentResolver getContentResolver() {
return mContentResolver;
}
getContentResolver
方法获取的实际上是ApplicationContentResolver
对象,ApplicationContentResolver
类继承了ContentResolver
并实现了ContentResolver
中的抽象方法。
搜索可以知道,在mBase
初始化时mContentResolver
也跟着初始化
–>
private ContextImpl(ContextImpl container, ActivityThread mainThread,
LoadedApk packageInfo, IBinder activityToken, UserHandle user, boolean restricted,
Display display, Configuration overrideConfiguration, int createDisplayWithId) {
....
mContentResolver = new ApplicationContentResolver(this, mainThread, user);
}
之后进入query
方法, 这里会调用到acquireUnstableProvider
,最终会调用到ActivityThread::acquireProvider
,来获取ContentProvider
public final @Nullable Cursor query(final @NonNull Uri uri, @Nullable String[] projection,
@Nullable String selection, @Nullable String[] selectionArgs,
@Nullable String sortOrder, @Nullable CancellationSignal cancellationSignal) {
Preconditions.checkNotNull(uri, "uri");
IContentProvider unstableProvider = acquireUnstableProvider(uri);
--》
protected IContentProvider acquireUnstableProvider(Context c, String auth) {
return mMainThread.acquireProvider(c,
ContentProvider.getAuthorityWithoutUserId(auth),
resolveUserIdFromAuthority(auth), false);
}
ActivityThread
的acquireProvider
方法的源码如下所示:
public final IContentProvider acquireProvider(
Context c, String auth, int userId, boolean stable) {
final IContentProvider provider = acquireExistingProvider(c, auth, userId, stable);
if (provider != null) {
return provider;
}
// There is a possible race here. Another thread may try to acquire
// the same provider at the same time. When this happens, we want to ensure
// that the first one wins.
// Note that we cannot hold the lock while acquiring and installing the
// provider since it might take a long time to run and it could also potentially
// be re-entrant in the case where the provider is in the same process.
IActivityManager.ContentProviderHolder holder = null;
try {
holder = ActivityManagerNative.getDefault().getContentProvider(
getApplicationThread(), auth, userId, stable);
} catch (RemoteException ex) {
}
if (holder == null) {
Slog.e(TAG, "Failed to find provider info for " + auth);
return null;
}
// Install provider will increment the reference count for us, and break
// any ties in the race.
holder = installProvider(c, holder, holder.info,
true /*noisy*/, holder.noReleaseNeeded, stable);
return holder.provider;
}
上面的代码首先会从ActivityThread
中查找是否已经存在目标ContentProvider
了,如果存在就直接返回,并增加引用计数, ActivityThread
中通过mProviderMap
来存储已经启动的ContentProvider
对象。
final ArrayMap<ProviderKey, ProviderClientRecord> mProviderMap
public final IContentProvider acquireExistingProvider(
Context c, String auth, int userId, boolean stable) {
synchronized (mProviderMap) {
final ProviderKey key = new ProviderKey(auth, userId);
final ProviderClientRecord pr = mProviderMap.get(key);
if (pr == null) {
return null;
}
IContentProvider provider = pr.mProvider;
IBinder jBinder = provider.asBinder();
if (!jBinder.isBinderAlive()) {
// The hosting process of the provider has died; we can't
// use this one.
Log.i(TAG, "Acquiring provider " + auth + " for user " + userId
+ ": existing object's process dead");
handleUnstableProviderDiedLocked(jBinder, true);
return null;
}
// Only increment the ref count if we have one. If we don't then the
// provider is not reference counted and never needs to be released.
ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
if (prc != null) {
incProviderRefLocked(prc, stable);
}
return provider;
}
}
如果目前ContentProvider
没有启动,acquireExistingProvider
返回null, 那么就发送一个进程间请求给AMS
让其启动目标ContentProvider
try {
holder = ActivityManagerNative.getDefault().getContentProvider(
getApplicationThread(), auth, userId, stable);
} catch (RemoteException ex) {
}
–>
这时调试器要附加到system_process
进程,并切换,用于观察AMS
,mRemote.transact
会调到ActivityManagerService
的onTransact
中。
ActivityManagerService
的onTransact
会调回它所在进程的ActivityManagerNative
的onTransact
,
case GET_CONTENT_PROVIDER_TRANSACTION: {
.....
ContentProviderHolder cph = getContentProvider(app, name, userId, stable);
getContentProvider
对应IActivityManager
的接口,也就是ActivityManagerService
的接口
public final ContentProviderHolder getContentProvider(
IApplicationThread caller, String name, int userId, boolean stable) {
....
return getContentProviderImpl(caller, name, null, stable, userId);
}
–>
private final ContentProviderHolder getContentProviderImpl(IApplicationThread caller,
String name, IBinder token, boolean stable, int userId) {
.....
// If the provider is not already being launched, then get it
// started.
if (i >= N) {
....
proc = startProcessLocked(cpi.processName,
cpr.appInfo, false, 0, "content provider",
new ComponentName(cpi.applicationInfo.packageName,
cpi.name), false, false, false);
}
那么AMS
是如何启动ContentProvider
的呢?我们知道,ContentProvider
被启动时会伴随着进程的启动,在AMS
中,首先会启动ContentProvider
所在的进程,然后再启动ContentProvider
。由上可知启动进程是由AMS
的startProcessLocked
方法来完成的。其内部主要是通过Process
的start
方法来完成一个新进程的启动,
新进程启动后其入口方法为ActivityThread
的main
方法
public static void main(String[] args) {
....
ActivityThread thread = new ActivityThread();
thread.attach(false);
...
Looper.loop();
}
可以看到,ActivityThread
的main
方法是一个静态方法,在它内部首先会创建ActivityThread
的实例并调用attach
方法来进行一系列初始化,接着就开始进行消息循环了。
ActivityThread
的attach
方法会将ApplicationThread
对象通过AMS
的attachApplication
方法跨进程传递给AMS
final ApplicationThread mAppThread = new ApplicationThread();
private void attach(boolean system) {
...
mgr.attachApplication(mAppThread);
...
}
所以我们在AMS
的attachApplication
方法下个断点
AMS
的attachApplication
方法调用了attachApplicationLocked
方法
private final boolean attachApplicationLocked(IApplicationThread thread,
int pid) {
....
final String processName = app.processName;
...
thread.bindApplication(processName, appInfo, providers, app.instrumentationClass,
}
调试到这里,我们可以看到新进程的名字了:
attachApplicationLocked
方法又调用了ApplicationThread
的bindApplication
,注意这个过程也是进程间调用
ApplicationThread
的bindApplication
会发送一个BIND_APPLICATION
类型的消息给mH,mH是一个Handler
,它收到消息后会调用ActivityThread
的handleBindApplication
方法
ActivityThread
的handleBindApplication
方法则完成了Application
的创建以及ContentProvider
的创建,所以要调入新进程,我们只需新建一个我们自定义的Application
,在构造函数中waitForDebugger
, 我们就可以附加到新ContentProvider
进程了,如下:
调用方来自ActivityThread
,它创建Application
对象
Application app = data.info.makeApplication(data.restrictedBackupMode, null);
mInitialApplication = app;
// don't bring up providers in restricted mode; they may depend on the
// app's custom Application class
if (!data.restrictedBackupMode) {
List<ProviderInfo> providers = data.providers;
if (providers != null) {
installContentProviders(app, providers);
在它的后面设置一个断点,就可以继续调试新ContentProvider
进程启动流程了,之后调用到installContentProviders
private void installContentProviders(
Context context, List<ProviderInfo> providers) {
final ArrayList<IActivityManager.ContentProviderHolder> results =
new ArrayList<IActivityManager.ContentProviderHolder>();
for (ProviderInfo cpi : providers) {
if (DEBUG_PROVIDER) {
StringBuilder buf = new StringBuilder(128);
buf.append("Pub ");
buf.append(cpi.authority);
buf.append(": ");
buf.append(cpi.name);
Log.i(TAG, buf.toString());
}
IActivityManager.ContentProviderHolder cph = installProvider(context, null, cpi,
false /*noisy*/, true /*noReleaseNeeded*/, true /*stable*/);
if (cph != null) {
cph.noReleaseNeeded = true;
results.add(cph);
}
}
try {
ActivityManagerNative.getDefault().publishContentProviders(
getApplicationThread(), results);
} catch (RemoteException ex) {
}
}
installContentProviders
完成了ContentProvider
的启动工作,它的实现如上所示。首先会遍历当前进程的ProviderInfo
的列表并一一调用install Provider
方法来启动它们,接着将已经启动的ContentProvider
发布到AMS
中,AMS
会把它们存储在ProviderMap
中,这样一来外部调用者就可以直接从AMS
中获取ContentProvider
了。
下面看一下ContentProvider
对象的创建过程,在install Provider
方法中有下面一段代码,其通过类加载器完成了ContentProvider
对象的创建:
final java.lang.ClassLoader cl = c.getClassLoader();
localProvider = (ContentProvider)cl.
loadClass(info.name).newInstance();
provider = localProvider.getIContentProvider();
if (provider == null) {
Slog.e(TAG, "Failed to instantiate class " +
info.name + " from sourceDir " +
info.applicationInfo.sourceDir);
return null;
}
if (DEBUG_PROVIDER) Slog.v(
TAG, "Instantiating local provider " + info.name);
// XXX Need to create the correct context for this provider.
localProvider.attachInfo(c, info);
在上述代码中,除了完成ContentProvider
对象的创建,还会通过ContentProvider
的attachInfo
方法来调用它的onCreate
方法,如下所示。
private void attachInfo(Context context, ProviderInfo info, boolean testing) {
mNoPerms = testing;
/*
* Only allow it to be set once, so after the content service gives
* this to us clients can't change it.
*/
if (mContext == null) {
mContext = context;
if (context != null) {
mTransport.mAppOpsManager = (AppOpsManager) context.getSystemService(
Context.APP_OPS_SERVICE);
}
mMyUid = Process.myUid();
if (info != null) {
setReadPermission(info.readPermission);
setWritePermission(info.writePermission);
setPathPermissions(info.pathPermissions);
mExported = info.exported;
mSingleUser = (info.flags & ProviderInfo.FLAG_SINGLE_USER) != 0;
setAuthorities(info.authority);
}
ContentProvider.this.onCreate();
}
}
到此为止,ContentProvider
已经被创建并且其onCreate
方法也已经被调用,这意味着ContentProvider
已经启动完成了。
之后我们回到ActivityThread
的handleBindApplication
方法,
if (!data.restrictedBackupMode) {
List<ProviderInfo> providers = data.providers;
if (providers != null) {
installContentProviders(app, providers); // `ContentProvider`已经被创建并且其`onCreate`方法也已经被调用
}
}
......
try {
mInstrumentation.callApplicationOnCreate(app);
} catch (Exception e) {
mInstrumentation.callApplicationOnCreate(app);
调用Application
的onCreate
方法,
可以看出
ContentProvider
类的onCreate
方法要先于Application
的onCreate
方法启动
这时,ContentProvider
已经成功启动,并且其所在进程的Application
也已经启动,这意味着ContentProvider
所在的进程已经完成了整个的启动过程,然后其他应用就可以通过AMS
来访问这个ContentProvider
了。
拿到了ContentProvider
以后,就可以通过它所提供的接口方法来访问它了。需要注意的是,这里的ContentProvider
并不是原始的ContentProvider
,而是ContentProvider
的Binder类型的对象IContentProvider
, IContentProvider
的具体实现是ContentProviderNative
和ContentProvider.Transport
,其中ContentProvider.Transport
继承了ContentProviderNative
。
因此其他应用调用IContentProvider
的query
方法时最终会以进程间通信的方式调用到ContentProvider.Transport
的query
方法,它的实现如下所示。
public Cursor query(String callingPkg, Uri uri, String[] projection,
String selection, String[] selectionArgs, String sortOrder,
ICancellationSignal cancellationSignal) {
validateIncomingUri(uri);
uri = getUriWithoutUserId(uri);
if (enforceReadPermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) {
// The caller has no access to the data, so return an empty cursor with
// the columns in the requested order. The caller may ask for an invalid
// column and we would not catch that but this is not a problem in practice.
// We do not call ContentProvider#query with a modified where clause since
// the implementation is not guaranteed to be backed by a SQL database, hence
// it may not handle properly the tautology where clause we would have created.
if (projection != null) {
return new MatrixCursor(projection, 0);
}
// Null projection means all columns but we have no idea which they are.
// However, the caller may be expecting to access them my index. Hence,
// we have to execute the query as if allowed to get a cursor with the
// columns. We then use the column names to return an empty cursor.
Cursor cursor = ContentProvider.this.query(uri, projection, selection,
selectionArgs, sortOrder, CancellationSignal.fromTransport(
cancellationSignal));
if (cursor == null) {
return null;
}
// Return an empty cursor for all columns.
return new MatrixCursor(cursor.getColumnNames(), 0);
}
很显然,ContentProvider.Transport
的query
方法调用了ContentProvider
的query
方法,query
方法的执行结果再通过Binder
返回给调用者,这样一来整个调用过程就完成了