第2章- IPC 机制读书笔记
目录
- 1 Android IPC 简介
- 2 Android 中的多进程模式
- 3 IPC 基础概念介绍
- 4 Android 中的 IPC 方式
- 4.1 A 进程正在进行一个计算,计算完成后需要启动 B 进程的一个组件并把计算结果传递给 B 进程,但是计算结果不支持放在 Bundle 里面,该如何处理?
- 4.2 SharedPreferences 在多进程通信中使用有什么弊端,为什么?
- 4.3 实现一个 Messenger 的步骤
- 4.4 Message 的 Object obj 字段的使用有什么特殊之处?
- 4.5 AIDL 文件支持哪些数据类型?
- 4.6 使用 AIDL 需要注意的地方有哪些?
- 4.7 当客户端需要监听服务端的一些操作时,会通过注册监听器的方式来实现,那么服务端如何实现解除注册的监听器呢?
- 4.8 说一说客户端和服务端进行通信时,一些方法或回调运行的线程
- 4.9 如何在 AIDL 总使用权限验证功能?
- 4.10 ContentProvider 设置单独的进程,那么它的方法分别运行在什么线程?
- 4.11 ContentProvider 如何实现支持自定义调用?
- 6 选用合适的 IPC 方式
1 Android IPC 简介
1.1 简单介绍一下 Android IPC
IPC 是 Inter-Process Communication 的缩写,含义为进程间通信或跨进程通信,是指两个进程之间进行数据交换的过程。IPC 并不是 Android 所独有的,任何一个操作系统都需要有相应的 IPC 机制。
在 Android 中,最有特色的进程间通信方式就是 Binder
了,通过 Binder
可以轻松地实现跨进程通信。除了 Binder
,Android 还支持 Socket
,通过 Socket
也可以实现任意两个终端之间的通信,以及一个设备上的两个进程之间的通信。
1.2 什么情况下需要使用到多进程?
说到 IPC 就必须提到多进程,多进程的情况分为两种:第一种情况是一个应用因为某些原因自身需要采用多进程模式来实现;另一种情况是当前应用需要向其它应用获取数据。
第一种情况的例子有:应用的某些模块由于特殊原因需要运行在单独的进程里,比如把 WebView
模块放在一个单独的进程里;为了加大一个应用可使用的内存,这是因为 Android 对单个应用可使用的最大内存作了限制。
第二种情况的例子有:通过系统提供的 ContentProvider
去查询数据。
2 Android 中的多进程模式
2.1 如何开启多进程模式
常规方法:在 AndroidManifest.xml
中给四大组件(Activity
,Service
,Receiver
,ContentProvider
)指定 android:process
属性,来开启多进程模式。
非常规方法:通过 JNI 在 native 层去 fork 一个新的进程。
2.2 查看进程信息的 shell 命令
wangzhichao@wangzhichao:~$ adb shell ps | grep com.wzc.chapter_2
u0_a1022 17463 1048 2261012 52116 SyS_epoll_wait 0 S com.wzc.chapter_2
u0_a1022 17516 1048 2261984 49064 SyS_epoll_wait 0 S com.wzc.chapter_2:remote
u0_a1022 17552 1048 2260840 46256 SyS_epoll_wait 0 S com.wzc.chapter_2.remote
下面的写法会输出很多进程的信息,但是会有表头信息:
wangzhichao@wangzhichao:~$ adb shell ps
USER PID PPID VSZ RSS WCHAN ADDR S NAME
...
u0_a1022 17463 1048 2261012 51844 SyS_epoll_wait 0 S com.wzc.chapter_2
u0_a1022 17516 1048 2261984 48820 SyS_epoll_wait 0 S com.wzc.chapter_2:remote
u0_a1022 17552 1048 2260968 46264 SyS_epoll_wait 0 S com.wzc.chapter_2.remote
...
wangzhichao@wangzhichao:~$
2.3 android:process
指定为 :remote
和 com.wzc.chapter_2.remote
的区别是什么?
在命名方式上不同:android:process=":remote"
中 :
的含义是指要在当前进程名(remote
)前面附加上当前的包名(com.wzc.chapter_2
),这是一种简写的方式,完整的进程名为 com.wzc.chapter_2:remote
。
在进程类型上不同:进程名以 :
开头的进程属于当前应用的私有进程,其它应用的组件不可以和它跑在同一个进程中;而进程名不以 :
开头的进程属于全局进程,其它应用可以通过 ShareUID 方式和它跑在一个进程中。
需要注意的是,两个应用通过 ShareUID 跑在同一个进程中是有要求的,需要这两个应用有相同的 ShareUID 并且签名相同才可以。
自己测试发现,不能自己指定 android:process
为 com.wzc.chapter_2:remote
,会导致安装失败。
2.4 多进程会造成哪些问题以及原因是什么?
Android 会给每个应用或者说每个进程分配一个独立的虚拟机,而不同的虚拟机在内存分配上有不同的地址空间,这就导致在不同的虚拟机*问同一个类的对象会产生多份副本。因此,所有运行在不同进程的四大组件,只要它们之间需要通过内存来共享数据,都会共享失败。
多进程会造成的问题有:
- 静态成员和单例模式失效:这是因为多进程下,即便是静态成员和单例,也会有多个副本;
- 线程同步机制完全失效:这是因为多进程下,不同进程锁的不是一个对象;
-
SharedPreferences
的可靠性下降:SharedPreferences
不支持两个进程同时去执行写操作,否则会有一定几率的数据丢失(SharedPreferences
底层是通过读写 xml 文件来实现的,并发写是可能出问题的); -
Application
会多次创建:这是因为系统在创建新的进程同时会分配新的虚拟机,其实就是启动一个应用的过程,而应用启动会创建新的Application
。
3 IPC 基础概念介绍
3.1 使用 Serializable
接口需要注意的地方有哪些?
手动指定 serialVersionUID
,serialVersionUID
是用来辅助序列化和反序列化过程。它的工作机制是:序列化时会把当前类的 serialVersionUID
写入到文件中(或其他中介),反序列化时会去检测文件中的 serialVersionUID
是否跟当前类的 serialVersionUID
一致,如果一致就说明序列化的类的版本和反序列化的类的版本是一致的,这个时候可以成功反序列化,否则说明当前类和序列化的类相比发生了某些变换,无法正常反序列化。为什么要手动指定 serialVersionUID
呢?如果不手动指定,那么系统会根据类结构生成一个 serialVersionUID
,只要类结构发生了变化,就可能得到一个不同的 serialVersionUID
。而手动指定后,程序能够最大限度地恢复数据。
静态成员变量属于类不属于对象,不会参与序列化过程;
使用transient
关键字修饰的成员变量不参与序列化过程;
重写系统默认的序列化和反序列化过程,通过实现下面的方法:
private void writeObject(ObjectOutputStream oos) throws IOException {
oos.defaultWriteObject(); // 注意:这个是默认的,必须要写
System.out.println("writeObject");
}
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ois.defaultReadObject(); // 注意:这个是默认的,必须要写
System.out.println("readObject");
}
3.2 说一说Parcelable
序列化和反序列化过程
从一个实际的实现了 Parcelable
接口的类来看:
public class Person implements Parcelable {
public int userId;
public String userName;
public boolean isMale;
public Person(int userId, String userName, boolean isMale) {
this.userId = userId;
this.userName = userName;
this.isMale = isMale;
}
@Override
public String toString() {
return "Person{" +
"userId=" + userId +
", userName='" + userName + '\'' +
", isMale=" + isMale +
'}';
}
// 从序列化的对象中创建原始对象
protected Person(Parcel in) {
userId = in.readInt();
userName = in.readString();
isMale = in.readInt() == 1;
}
// 注意 CREATOR 是一个静态的 final 类型的字段
public static final Creator<Person> CREATOR = new Creator<Person>() {
// 从序列化的对象中创建原始对象
@Override
public Person createFromParcel(Parcel in) {
return new Person(in);
}
// 创建指定长度的原始对象的数组
@Override
public Person[] newArray(int size) {
return new Person[size];
}
};
// 返回当前对象的文件描述符
@Override
public int describeContents() {
return 0;
}
// 将当前对象写入序列化结构中。
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(userId);
dest.writeString(userName);
dest.writeInt(isMale ? 1 : 0);
}
}
Parcel
内部包装了可序列化的数据,可以在 Binder
中*传输。
序列化功能由 writeToParcel
方法来完成,最终是通过 Parcel
中的一系列 write
方法来完成的。其中 int flags
只有两种值:0
或者 1
(即Parcelable.PARCELABLE_WRITE_RETURN_VALUE
)。为 1
时标识当前对象需要作为返回值返回,不能立即释放资源,几乎所有情况都为 0
。
反序列化功能由 CREATOR
来完成,其内部标明了如何创建反序列化对象(createFromParcel
方法)和数组(newArray
方法),通过一系列 read
方法来完成反序列化过程。
内容描述符功能由 describeContents
方法来完成,几乎在所有情况下这个方法都应该返回 0
,仅当当前对象中存在文件描述符时,此方法返回 1
(即 Parcelable.CONTENTS_FILE_DESCRIPTOR
)。
3.3 使用 Parcelable
实现序列化到存储设备上
序列化:
Person p = new Person(1, "hello", false);
FileOutputStream fos = null;
try {
File dir = new File(MyConstants.CHAPTER_2_PATH);
if (!dir.exists()) {
dir.mkdirs();
}
File cacheFile = new File(MyConstants.CACHE_PARCELABLE_FILE_PATH);
fos = new FileOutputStream(cacheFile, false);
Parcel parcel = Parcel.obtain();
parcel.writeParcelable(p, 0);
// 获取 Parcel 对象的字节数组
byte[] bytes = parcel.marshall();
fos.write(bytes);
fos.flush();
parcel.recycle();
} catch (Exception e) {
e.printStackTrace();
} finally {
MyUtils.closeQuietly(fos);
}
反序列化:
File dir = new File(MyConstants.CHAPTER_2_PATH);
if (!dir.exists()) {
dir.mkdirs();
}
File cacheFile = new File(MyConstants.CACHE_PARCELABLE_FILE_PATH);
FileInputStream fis = null;
try {
fis = new FileInputStream(cacheFile);
byte[] bytes = new byte[fis.available()];
fis.read(bytes);
Parcel parcel = Parcel.obtain();
parcel.unmarshall(bytes, 0, bytes.length);
parcel.setDataPosition(0);
Person person = parcel.readParcelable(Thread.currentThread().getContextClassLoader());
Log.d(TAG, "recoverFromFileByParcelable: person = " + person);
parcel.recycle();
} catch (Exception e) {
e.printStackTrace();
} finally {
MyUtils.closeQuietly(fis);
}
3.4 比较 Parcelable
和 Serializable
相同点:它们都能实现序列化并且都用于 Intent
间的数据传递。
不同点:
- 所属不同:
Serializable
是 Java 中的序列化接口,Parcelable
是 Android 中的序列化接口。 - 使用难易不同:
Serializable
使用简单,而Parcelable
使用起来有些麻烦。 - 效率不同:
Serializable
开销很大,因为序列化和反序列化过程在 Java 层实现,要大量的 I/O 操作;Parcelable
内存开销小, 在 native 层实现,效率很高,是 Android 推荐的序列化方式。 - 使用场景不同:
Serializable
使用广泛,包括内存序列化,序列化到存储设备以及序列化后通过网络传输;而Parcelable
主要用于内存序列化,对于序列化到存储设备以及序列化后通过网络传输实现有些复杂。
参考:每日一问 Parcelable 为什么效率高于 Serializable ?
3.5 说一说 Binder 是什么?
-
从代码上看,
Binder
是 Android 中的一个类,它实现了IBinder
接口;public class Binder implements IBinder
-
从 IPC 角度来看,
Binder
是 Android 中的一种跨进程通信方式,Binder
还可以理解为一种虚拟的物理设备,它的设备驱动是 /dev/binder,在 Linux 中没有这种通信方式; -
从 Android Framework 角度来看,
Binder
是ServiceManager
连接各种Manager
(ActivityManager
,WindowManager
等)和相应的ManagerService
(ActivityManagerService
,WindowManagerService
等)的桥梁; -
从 Android 应用层角度来看,
Binder
是客户端和服务端进行通信的媒介,当bindService
的时候,服务端会返回一个包含了服务端业务调用的Binder
对象,客户端通过这个Binder
对象就可以获取服务端提供的服务或者数据。
3.6 给定一个 IBookManager.aidl
,绘制生成的 IBookManager.java
的类结构图
IBookManager.aidl
如下:
package com.wzc.chapter_2_common_lib;
import com.wzc.chapter_2_common_lib.Book;
interface IBookManager {
// 从远程服务端获取图书列表
List<Book> getBookList();
// 往图书列表中添加一本书, in 表示输入型参数
void addBook(in Book book);
}
类图如下:
所有可以在 Binder
中传输的接口都需要继承 IInterface
接口。
-
DESCRIPTOR
Binder
的唯一标识,一般用当前Binder
的类名表示。 -
public static IBookManager asInterface(android.os.IBinder obj)
这是个静态方法,作用是将服务端的
Binder
对象转换成客户端所需的 AIDL 接口类型的对象。需要特别注意的是,这种转换过程是区分进程的,如果客户端和服务端位于同一进程,那么此方法返回的就是服务端的Stub
对象本身,否则返回的是系统封装后的Stub.Proxy
对象。 -
Binder asBinder()
方法用于返回当前的
Binder
对象。 -
boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)
方法运行在服务端的
Binder
线程池里面,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由此方法来处理。服务端会通过
code
来确定客户端请求的目标方法是什么,接着从data
里取出目标方法所需的参数,然后执行目标方法。当目标方法执行完毕后,就向reply
中写入返回值。这个方法返回
false
,那么客户端的请求会失败,所以可以利用这个特性来做权限验证。 -
Proxy
类的Book getBookList()
方法这个方法运行在客户端,是给客户端调用的。内部实现是:1, 先创建该方法远程调用所需要的输入型
Parcel
对象_data
,输出型Parcel
对象_reply
和返回值对象List
;2, 然后把方法的参数写入_data
中(如果有参数的话);3, 调用mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0)
来发起 RPC (远程过程调用)请求,同时当前线程被挂起;4, 然后服务端的onTransact
方法会被调用,直到 RPC 过程返回后,当前线程继续执行,并从_reply
中取出 RPC 过程的返回结果;5, 返回从_reply
中取出的数据。 -
Proxy
类的void addBook(Book book)
方法这个方法运行在客户端,没有返回值,有参数,其余和
getBookList()
方法一致。
3.7 远程方法是耗时方法,客户端和服务端分别如何处理?
客户端:因为客户端发起远程请求时,当前线程会被挂起直至服务端进程返回数据,因此远程方法是耗时方法,客户端就不能在 UI 线程发起远程请求。
服务端:因为服务端的 Binder
方法运行在 Binder
的线程池里,所以 Binder
方法不管是否耗时都应该采用同步的方式实现。
3.8 不借助 aidl,手写出 Binder
采用分离的方式来写,而不是像系统生成的那样存在多层类嵌套。
1,创建继承 IInterface
接口的 IBookManager
接口:
public interface IBookManager extends IInterface {
String DESCRIPTOR = "com.wzc.chapter_2.manualbinder2.IBookManager";
int TRANSACTION_addBook = IBinder.FIRST_CALL_TRANSACTION + 0;
int TRANSACTION_getBookList = IBinder.FIRST_CALL_TRANSACTION + 1;
void addBook(Book book) throws RemoteException;
List<Book> getBookList() throws RemoteException;
}
2,创建抽象类 Stub
,继承于 Binder
类,实现于 IBookManager
接口:
public abstract class Stub extends Binder implements IBookManager {
private static final String TAG = "Stub";
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
// 这个方法的参数至关重要
public static IBookManager asInterface(IBinder obj) {
if (obj == null) {
return null;
}
IInterface iInterface = obj.queryLocalInterface(DESCRIPTOR);
if (iInterface != null && iInterface instanceof IBookManager) {
return (IBookManager) iInterface;
}
return new Proxy(obj);
}
// 这个方法运行在服务端中的 Binder 线程池里面
// 这个方法是在调用了 IBinder 的 transact 方法后调用的
@Override
protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
switch (code) {
case TRANSACTION_addBook:
Log.d(TAG, "onTransact: TRANSACTION_addBook, currThread="
+ Thread.currentThread().getName());
data.enforceInterface(DESCRIPTOR);
Book book;
if (0 != data.readInt()) {
book = Book.CREATOR.createFromParcel(data);
} else {
book = null;
}
addBook(book);
reply.writeNoException();
return true;
case TRANSACTION_getBookList:
Log.d(TAG, "onTransact: TRANSACTION_getBookList, currThread="
+ Thread.currentThread().getName());
data.enforceInterface(DESCRIPTOR);
List<Book> _result = getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
return super.onTransact(code, data, reply, flags);
}
@Override
public IBinder asBinder() {
return this;
}
}
3,创建 Proxy
类,实现了 IBookManager
接口:
public class Proxy implements IBookManager {
private static final String TAG = "Proxy";
private IBinder mRemote;
public Proxy(IBinder remote) {
this.mRemote = remote;
}
// 这个方法运行在客户端
@Override
public void addBook(Book book) throws RemoteException {
Log.d(TAG, "addBook: book=" + book + ", currThread=" + Thread.currentThread().getName());
// 输入型 Parcel 对象
Parcel _data = Parcel.obtain();
// 输出型 Parcel 对象
Parcel _reply = Parcel.obtain();
try {
_data.writeInterfaceToken(IBookManager.DESCRIPTOR);
if (book != null) {
_data.writeInt(1);
book.writeToParcel(_data, 0);
} else {
_data.writeInt(0);
}
Log.d(TAG, "addBook: book=" + book + ", currThread=" + Thread.currentThread().getName()
+ ", 远程请求开始");
mRemote.transact(TRANSACTION_addBook, _data, _reply, 0);
Log.d(TAG, "addBook: book=" + book + ", currThread=" + Thread.currentThread().getName()
+ ", 远程请求结束");
_reply.readException();
} finally {
_data.recycle();
_reply.recycle();
}
}
// 这个方法运行在客户端
@Override
public List<Book> getBookList() throws RemoteException {
Log.d(TAG, "getBookList: currThread=" + Thread.currentThread().getName());
// 输入型 Parcel 对象
Parcel _data = Parcel.obtain();
// 输出型 Parcel 对象
Parcel _reply = Parcel.obtain();
List<Book> _result;
try {
_data.writeInterfaceToken(IBookManager.DESCRIPTOR);
Log.d(TAG, "getBookList: currThread=" + Thread.currentThread().getName() + ", 远程请求开始");
mRemote.transact(TRANSACTION_getBookList, _data, _reply, 0);
Log.d(TAG, "getBookList: currThread=" + Thread.currentThread().getName() + ", 远程请求结束");
_reply.readException();
_result = _reply.createTypedArrayList(Book.CREATOR);
} finally {
_data.recycle();
_reply.recycle();
}
return _result;
}
@Override
public IBinder asBinder() {
return mRemote;
}
}
手写的好处:方便通过添加日志的方式,来了解哪个方法运行在客户端进程,哪个方法运行在服务端的 Binder
线程池里面;明白了 AIDL 文件的本质是系统为我们提供的一种快速实现 Binder
的工具。
3.9 如何给 Binder 设置死亡代理?
1,声明一个 DeathRecipient
对象,DeathRecipient
是一个接口,其内部只有一个 binderDied
方法。
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
// 当 Binder 死亡的时候,系统就会回调 binderDied 方法
@Override
public void binderDied() {
Log.d(TAG, "binderDied: currentThread = " + Thread.currentThread().getName()); // Binder:25808_2
if (mBookManager == null) {
return;
}
// 移除之前设置的死亡代理
mBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);
mBookManager = null;
// 重新绑定远程服务
Intent intent = new Intent(ManualBookManagerActivity.this, ManualBookManagerService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
mIsBound = true;
}
};
2,在客户端绑定远程服务成功后,给 Binder
设置死亡代理:
public void onServiceConnected(ComponentName name, IBinder service) {
mBookManager = Stub.asInterface(service);
try {
// 在客户端绑定远程服务成功后,给 binder 设置死亡代理。
mBookManager.asBinder().linkToDeath(mDeathRecipient, 0);
} catch (RemoteException e) {
e.printStackTrace();
}
}
通过 Binder
类的 isBinderAlive()
方法也可以判断 Binder
是否死亡。
4 Android 中的 IPC 方式
4.1 A 进程正在进行一个计算,计算完成后需要启动 B 进程的一个组件并把计算结果传递给 B 进程,但是计算结果不支持放在 Bundle 里面,该如何处理?
既然计算结果不支持放入 Bundle
里面,就无法通过 Intent
来传输。可以考虑通过 Intent
启动 B 进程的一个 Service
组件,让 Service
在后台执行计算,计算完成后再启动 B 进程中真正要启动的那个目标组件。由于 Service
运行在 B 进程中,因此目标组件可以直接获取计算结果。这是采用了避免进程间通信的办法。
4.2 SharedPreferences 在多进程通信中使用有什么弊端,为什么?
从本质上来说,SharedPreferences
属于文件的一种,但是系统对它的读/写有一定的缓存策略,也就是说在内存中会有一份 SharedPreferences
文件的缓存,因此在多进程模式下,系统对它的读/写就变得不可靠,当面对高并发的读/写访问时,SharedPreferences
有很大几率丢失数据,因此,不建议在进程间通信中使用 SharedPreferences
。可以考虑使用 mmkv。
4.3 实现一个 Messenger 的步骤
包括客户端向服务端发送消息以及服务端向客户端回传消息的功能:
1,构建服务端进程
// 1-1, 在服务端创建一个 Service,来处理客户端的连接请求
public class MessengerService extends Service {
private static final String TAG = MessengerService.class.getSimpleName();
// 服务端要持有一个 Handler 来处理来自客户端的调用
private static class MessengerHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
// 处理来自客户端的消息
case MyConstants.MSG_FROM_CLIENT:
Log.d(TAG, "handleMessage: currThread=" + Thread.currentThread().getName());
Log.d(TAG, "handleMessage: receive msg from client : " + msg.getData().getString(MyConstants.MSG));
// 3-4, 从 msg.replyTo 中取出处理服务端消息的 Messenger 对象
Messenger replyMessenger = msg.replyTo;
Log.d(TAG, "handleMessage: replyMessenger=" + replyMessenger);
try {
Field mTargetField = Messenger.class.getDeclaredField("mTarget");
mTargetField.setAccessible(true);
Object o = mTargetField.get(replyMessenger);
Log.d(TAG, "handleMessage: mTarget=" + o);
} catch (Exception e) {
e.printStackTrace();
}
Log.d(TAG, "handleMessage: replyMessenger.getBinder()=" + replyMessenger.getBinder());
// Book book = (Book) msg.obj;
// Log.d(TAG, "handleMessage: book : " + book);
Point point = (Point) msg.obj;
Log.d(TAG, "handleMessage: point : " + point);
Message replyMessage = Message.obtain(null, MyConstants.MSG_FROM_SERVICE);
Bundle bundle = new Bundle();
bundle.putString(MyConstants.REPLY, "嗯, 你的消息我已经收到, 稍后会回复你.");
replyMessage.setData(bundle);
// 3-5, 向客户端发送消息
try {
replyMessenger.send(replyMessage);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
default:
super.handleMessage(msg);
break;
}
}
}
// 1-2, 使用 Handler 来创建一个 Messenger 对象
private Messenger mMessenger = new Messenger(new MessengerHandler());
@Override
public IBinder onBind(Intent intent) {
// 1-3, 返回 Messenger 对象所持有的 Binder 对象
return mMessenger.getBinder();
}
}
2,构建客户端进程
public class MessengerActivity extends Activity {
private static final String TAG = MessengerActivity.class.getSimpleName();
private AtomicBoolean mBound = new AtomicBoolean(false);
private Messenger mMessenger;
// 3-1, 创建处理来自服务端消息的Handler
private static class MessengerHandler extends Handler {
@Override
public void handleMessage(Message msg) {
Log.d(TAG, "handleMessage: currThread=" + Thread.currentThread().getName());
// 2-6, 处理从服务端返回的消息
switch (msg.what) {
case MyConstants.MSG_FROM_SERVICE:
Log.d(TAG, "handleMessage: receive msg from service : " + msg.getData().getString(MyConstants.REPLY));
break;
default:
super.handleMessage(msg);
break;
}
}
}
// 3-2, 创建处理服务端消息的 Messenger
private Messenger mGetReplyMessenger = new Messenger(new MessengerHandler());
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// 2-2, 绑定成功后,使用服务端返回过来的 IBinder 对象来初始化一个 Messenger 对象
mMessenger = new Messenger(service);
mBound.set(true);
}
@Override
public void onServiceDisconnected(ComponentName name) {
mMessenger = null;
mBound.set(false);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_messenger);
findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Message sendMessage = Message.obtain(null, MyConstants.MSG_FROM_CLIENT);
// 非系统的Parcelable对象不能通过obj传输 Caused by: java.lang.ClassNotFoundException: com.wzc.chapter_2_common_lib.Book
// Book book = new Book(1, "wzc");
// sendMessage.obj = book;
// 系统的Parcelable对象能够通过obj传输
sendMessage.obj = new Point(4, 6);
Bundle bundle = new Bundle();
bundle.putString(MyConstants.MSG, "hello, this is client.");
sendMessage.setData(bundle);
// 3-3, 把处理服务端消息的 Messenger ,通过msg.replyTo,带给服务端
Log.d(TAG, "mGetReplyMessenger=" + mGetReplyMessenger + ",looper="+ Looper.myLooper());
sendMessage.replyTo = mGetReplyMessenger;
try {
Field mTargetField = Messenger.class.getDeclaredField("mTarget");
mTargetField.setAccessible(true);
Object o = mTargetField.get(mGetReplyMessenger);
Log.d(TAG, "onClick: mTarget=" + o);
} catch (Exception e) {
e.printStackTrace();
}
Log.d(TAG, "onClick: mGetReplyMessenger.getBinder()=" + mGetReplyMessenger.getBinder());
try {
// 2-3 通过创建好的 Messenger 对象,向服务端发送消息, 发起远程调用
mMessenger.send(sendMessage);
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
}
@Override
protected void onStart() {
super.onStart();
// 2-1, 在客户端进程中,首先要绑定服务端的 Service
Intent service = new Intent(this, MessengerService.class);
bindService(service, mConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mBound.get()) {
unbindService(mConnection);
mBound.set(false);
}
}
}
4.4 Message 的 Object obj 字段的使用有什么特殊之处?
在进程间通信时,在 Android 2.2 之前 object
字段不支持跨进程通信,在 2.2 之后,仅仅是系统提供的实现了 Parcelable
接口的对象才能通过它来传输。因此,自定义的 Parcelable
对象无法通过 object
字段传输。解决办法是,通过 Bundle
来传输:
public final class Message implements Parcelable {
Bundle data;
public Bundle getData() {
if (data == null) {
data = new Bundle();
}
return data;
}
public void setData(Bundle data) {
this.data = data;
}
}
4.5 AIDL 文件支持哪些数据类型?
- 基本数据类型(
int
,long
,char
,boolean
,double
等); -
String
和CharSequence
; -
List
:只支持ArrayList
,并且里面的每个元素都必须能够被 AIDL 支持; -
Map
:只支持HashMap
,并且里面的每个元素都必须 能够被 AIDL 支持,包括 key 和 value; -
Parcelable
:所有实现了Parcelable
接口的对象; - AIDL:所有 AIDL 接口本身也可以在 AIDL 文件中使用。
4.6 使用 AIDL 需要注意的地方有哪些?
- 当定义 AIDL 文件时,对于文件需要的自定义的
Parcelable
对象和 AIDL 对象必须要显式import
进来,不管它们是否和定义的 AIDL 文件位于同一个包内。 - 当定义 AIDL 文件时,如果用到了自定义的
Parcelable
对象,那么必须新建一个和它同名的 AIDL 文件,并在其中声明它为Parcelable
类型。
比如,对于Book.java
这个自定义的Parcelable
类,
如果要在 AIDL 文件中使用它,就新建一个package com.wzc.chapter_2_common_lib; import android.os.Parcel; import android.os.Parcelable; public class Book implements Parcelable { }
Book.aidl
文件:// Book.aidl package com.wzc.chapter_2_common_lib; // Book.java 在 aidl 中的声明 parcelable Book;
- 对于 AIDL 中方法参数中使用到的数据类型,除了基本数据类型,其他类型必须标上方向:
in
,out
和inout
,in
表示输入型参数,out
表示输出型参数,inout
表示输入输出型参数。 - 当定义 AIDL 文件时,只能声明方法,不能声明静态变量,这一点和传统的接口是不一样的。
- 在实际开发中,建议把所有和 AIDL 相关的类和文件全部放在一个包中(对于使用 AndroidStudio 开发,可以放在同一个 module 里面),这样可以方便客户端和服务端依赖使用。
- AIDL 文件的包结构在客户端和服务端要保持一致,否则会反序列化失败。
- 当需要通过 AIDL 文件中定义的方法设置监听器或取消监听器时,不能使用普通的接口,而要使用 AIDL 接口,这是因为在 AIDL 中不能使用普通接口。
- 如果在 AIDL 文件中定义了几个接口,即以
interface
开头的,那么就生成几套对应的 Java 类(包括接口名.java,Stub
类,以及Proxy
类)。
4.7 当客户端需要监听服务端的一些操作时,会通过注册监听器的方式来实现,那么服务端如何实现解除注册的监听器呢?
如果我们打算在服务端使用普通的 List
来保存注册的监听器对象,而在服务端解除注册时从这个集合里移除注册的监听器,这是不会成功的。但是,在解注册时,传递过来的监听器对象在集合里是不存在的,尽管客户端在注册和解注册时传递的监听器是同一个。这是因为 Binder
会把客户端传递过来的对象重新转化为一个新的对象。记住:对象是不能跨进程直接传输的,对象的跨进程传输本质上都是反序列化的过程。
我们需要使用 RemoteCallbackList
,它是系统提供的专门用于管理跨进程监听器。
public class RemoteCallbackList<E extends IInterface>
虽然监听器对象不是同一个了,但是它们底层的 Binder
对象是同一个,这点是非常重要的。
@Override
public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
// IOnNewBookArrivedListener 在注册与解注册是不同的,但是它们底层的 IBinder 是同一个。
Log.d(TAG, "registerListener: listener = " + listener); // com.wzc.chapter_2_common_lib.IOnNewBookArrivedListener$Stub$Proxy@249af64
Log.d(TAG, "registerListener: listener.asBinder() = " + listener.asBinder()); // android.os.BinderProxy@82855cd
}
@Override
public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
Log.d(TAG, "unregisterListener: listener = " + listener); // com.wzc.chapter_2_common_lib.IOnNewBookArrivedListener$Stub$Proxy@cdf2e82
Log.d(TAG, "unregisterListener: listener.asBinder() = " + listener.asBinder()); // android.os.BinderProxy@82855cd
}
RemoteCallbackList
正是利用了不同的 listener 底层的 Binder
对象是一样的这一特性,并借助死亡代理(IBinder.DeathRecipient
)来实现删除 listener 的。
RemoteCallbackList
通过使用 synchronized
同步代码块方式显示了线程同步的功能。
RemoteCallbackList
还包括了遍历监听器的方法,代码如下:
final int n = mListenerList.beginBroadcast();
for (int i = 0; i < n; i++) {
IOnNewBookArrivedListener listener = mListenerList.getBroadcastItem(i);
if (listener != null) {
listener.onNewBookArrived(newBook);
}
}
mListenerList.finishBroadcast();
需要注意的是 beginBroadcast()
和 finishBroadcast()
必须成对使用。
4.8 说一说客户端和服务端进行通信时,一些方法或回调运行的线程
类 | 方法 | 运行线程 |
---|---|---|
ServiceConnection | onServiceConnected() | main |
ServiceConnection | onServiceDisconnected() | main |
IBinder.DeathRecipient | binderDied() | Binder 线程池 |
Proxy | getBookList() | 客户端线程 |
Proxy | addBook() | 客户端线程 |
Proxy | registerListener() | 客户端线程 |
Proxy | unregisterListener() | 客户端线程 |
IBookManager.Stub | onTransact() | 服务端的 Binder 线程池 |
IBookManager.Stub | getBookList() | 服务端的 Binder 线程池 |
IBookManager.Stub | addBook() | 服务端的 Binder 线程池 |
IBookManager.Stub | registerListener() | 服务端的 Binder 线程池 |
IBookManager.Stub | unregisterListener() | 服务端的 Binder 线程池 |
IOnNewBookArrivedListener.Stub | onNewBookArrived() | 客户端的 Binder 线程池 |
IOnNewBookArrivedListener | onNewBookArrived() | 服务端线程 |
4.9 如何在 AIDL 总使用权限验证功能?
第一种方法,在 onBind
方法中验证,验证不通过就直接返回 null
,验证通过才返回 Stub
对象。
第二种方法,在服务端的 onTransact
方法中验证,验证失败就返回 false
,这时服务端会终止执行 AIDL 中的方法。
4.10 ContentProvider 设置单独的进程,那么它的方法分别运行在什么线程?
6 个方法都运行在 ContentProvider
的进程里,其中 onCreate
运行在主线程,其他 5 个方法(query
、getType
、insert
、delete
、update
)运行在 Binder
线程池里。需要注意的是,query
、insert
、delete
、update
四个方法是存在多线程并发访问的,因此这些方法内部要做好线程同步。
4.11 ContentProvider 如何实现支持自定义调用?
通过 ContentResolver
的 call
方法和 ContentProvider
的 call
方法来完成。
6 选用合适的 IPC 方式
6.1 Android 中有哪些 IPC 方式,以及优缺点是什么?
IPC 方式 | 特点 | 优点 | 缺点 |
---|---|---|---|
使用 Bundle
|
基于序列化的思想 | 四大组件中的三大组件(Activity ,Service ,Receiver )都支持在 Intent 中传递 Bundle 数据,Bundle 实现了 Parcelable 接口,可以方便地在不同进程间传输。 |
Bundle 不支持的类型无法通过 Bundle 来进行跨进程通信 |
使用文件共享 | 文件共享 | 使用方便,A 进程把数据写入文件,B 进程通过读取这个文件来获取数据;对文件格式没有具体要求,只要读/写双方约定好数据格式即可。 | 文件共享方式只适合在对数据同步要求不高的进程之间进行通信,并且要处理好并发读/写问题。 |
使用 Messenger
|
轻量级的 IPC 方案,它的底层实现是 AIDL | 1,通过 Messenger ,可以在不同进程中传递 Message 对象,因此只要把要传递的数据放在 Message 中就可以轻松实现数据的进程间传递;2,一次只处理一个请求,不会造成服务端并发执行的情形,因此在服务端不用考虑线程同步的问题。 |
1,传输的数据类型需要是 Bundle 支持的类型,2,Messenger 是以串行的方式处理客户端的消息,不适用于有大量并发请求发送到服务端的情形;3,Messenger 只实现了消息的传递,无法做到跨进程调用服务端的方法。 |
使用 AIDL | Binder 机制 | 可以做到跨进程调用服务端的方法 | 实现过程有些复杂 |
使用 ContentProvider
|
使用过程比 AIDL 要简单 | 在数据源访问方面功能强大,支持一对多并发数据共享,可通过 call 方法扩展其他操作 | 可以理解为受约束的 AIDL,主要提供数据源的 CRUD 操作。 |
Socket | 网络数据交换类 | 功能强大,可以通过网络传输字节流,支持一对多并发实时通信 | 实现细节稍微有点复杂,不支持直接的 RPC. |
本文地址:https://blog.csdn.net/willway_wang/article/details/108423720