本章主要讲解Android中的IPC机制。首先介绍Android中的多进程概念以及多进程开发模式中常见的注意事项,接着介绍Android中的序列化机制和Binder,然后详细介绍Bundle、文件共享、AIDL、Messenger、ContentProvider和Socket等进程间通信的方式。为了更好地使用AIDL来进行进程间通信,本章还引入了Binder连接池的概念。最后,本章讲解各种进程间通信方式的优缺点和适用场景。通过本章,可以让读者对Android中的IPC机制和多进程开发模式有深入的理解。
简介
IPC (Inter-Process Communication):进程间通信或者跨进程通信,指 2 个进程之间进行数据交换的过程。
线程:CPU 调度的最小单元。
进程:一个执行单元,在 PC 和移动设备上指一个程序或者一个应用。
一个进程可以包含多个线程。
其他操作系统的 IPC 机制:
Windows:剪贴板,管道,邮槽
多进程
- App 因为某些原因,有些模块需要运行在单独的线程中。
- 加大一个 App 的可使用内存。多线程可以获取更多的可使用内存限制。
- 2 个 App 之间传递数据。
Android 中的多进程模式
开启多进程
- 在 AndroidMenifest 文件中指定四大组件(Activity、Service、Receiver、ContentProvider)的
android:process
属性。 - 通过 JNI 在 native 层 fork 一个新的进程。
<activity android:name=".BAct"
android:process=":ThreadB"/>
<activity android:name=".BAct"
android:process=".ThreadB"/>
复制代码
process 的命名:
- 以
:
为前缀:在当前进程前面加上包名。属于当前App的私有进程,其他App的组件不可以和她跑在同一个进程中。 - 以
.
为前缀:不会附加包名。全局进程,其他App通过ShareUID
的方式可以和她跑在一个进程中。
运行机制
例子
<activity android:name=".SecondActivity"
android:process=":secondThread"
/>
复制代码
UserManager
public class UserManager {
public static int sUserId = 1;
}
复制代码
MainActivity#onCreate
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
UserManager.sUserId += 1;
Log.d(TAG, "userId = " + UserManager.sUserId);
startActivity(new Intent(this, SecondActivity.class));
}
复制代码
SecondActivity#onCreate
@Override protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "userId = " + UserManager.sUserId);
}
复制代码
指定 SecondActivity 的 process
为 :secondThread
, 在主线程中先将 userId +1,再打开 SecondActivity, 分别打印 userId 的值。
06-11 18:06:49.639 5078-5078/com.venjer.ibinderdemo D/MainActivity: userId = 2
06-11 18:06:50.668 5171-5171/com.venjer.ibinderdemo:secondThread D/SecondActivity: userId = 1
复制代码
结果他们的值是不一样的。
原因
Android 为每一个 App 分配一个独立的虚拟机,不同的虚拟机在内存分配上有不同的地址空间,导致在不同的虚拟机中访问同一个类的对象会产生多个副本。
使用多进程造成的问题
- 静态成员和单例模式完全失效
- 线程同步机制完全失效
锁的不是同一块内存的对象
- Sharepreferences 的可靠性下降
SharedPreferences不支持两个进程同时去执行写操作,否则会导致一定几率的数据丢失, 因为 SharedPreferences 底层是通过读/写XML文件来实现的,并发写显然是可能出问题的,甚至并发读/写都有可能出问题。
- Application 会多次创建
运行在同一个进程中的组件是属于同一个虚拟机和同一个Application的,同理,运行在不同进程中的组件是属于两个不同的虚拟机和Application。
系统提供跨进程方式解决
- 使用 Intent 传递数据
- 基于 Binder 的 Messenger 和 AIDL 以及 Socket。
IPC 基础概念
Serializable 接口
Serializable 是 Java 所提供的一个序列化接口。
在类的声明中指定类似下面的标识
private static final long serialVersionUID = 8711368828010083044L;
复制代码
不添加 serialVersionUID
也可以序列化,但是会对反序列化过程产生影响。
使用 ObjectOutputStream
和 ObjectInputStream
实现正反序列化。
例子
public class User {
private static final long serialVersionUID = 519067123721295773L;
public int userId;
public String userName;
public boolean isMale;
}
复制代码
User user = new User();
user.isMale = true;
user.userName = "djkh";
user.userId = 0;
try {
// 序列化
ObjectOutputStream outputStream =
new ObjectOutputStream(new FileOutputStream("cache.txt"));
outputStream.writeObject(user);
outputStream.close();
// 反序列化
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("cache.txt"));
User newUser = (User) inputStream.readObject();
inputStream.close();
Log.d(TAG,newUser.userName);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
复制代码
serialVersionUID 的工作机制
原则上序列化后的数据中的 serialVersionUID 只有和当前类的 serialVersionUID 相同时才能正常的被反序列化。
序列化的时候系统会把当前类的 serialVersionUID 写入序列化的文件中(也可能是其他中介)。反序列化的时候检查 serialVersionUID, 如果和之前不一致会报如下错误。
java.io.InvalidClassException: Main;
local class incompatible:
stream classdesc serialVersionUID = 8711368828010083044,
local class serial- VersionUID = 8711368828010083043。
复制代码
注意
- 静态成员变量不属于对象,不会参与序列化过程
- 使用 transient 关键字标记的成员变量不会参与序列化过程
Parcelable 接口
实现 Parcelable 接口的对象可以通过 Intent 和 Binder 传递。
public class User implements Parcelable {
public int asd;
public String asd1;
public String asd2;
protected User(Parcel in) {
asd = in.readInt();
asd1 = in.readString();
asd2 = in.readString();
}
public static final Creator<User> CREATOR = new Creator<User>() {
@Override public User createFromParcel(Parcel in) {
return new User(in);
}
@Override public User[] newArray(int size) {
return new User[size];
}
};
@Override public int describeContents() {
return 0;
}
@Override public void writeToParcel(Parcel parcel, int i) {
parcel.writeInt(asd);
parcel.writeString(asd1);
parcel.writeString(asd2);
}
}
复制代码
Parcel
内部包装了可序列化的数据,可以在 Binder 中自由传输。
writeToParcel: 序列化
CREATOR:反序列化,内部标明了如何创建序列化对象和数组。
describeContents: 内容描述功能,默认返回0;仅当对象中存在文件描述时返回 1。
区别
Parcelable 效率较高,主要用在内存序列化上。
将对象序列化到存储设备中或者将对象序列化后通过网络传输建议使用 Serializable。
Binder
android.os.Binder 是一个类,实现了 IBinder 接口。
public class Binder implements IBinder
复制代码
Binder 是 Android 中的一种跨进程通信方式,可以理解为一种虚拟的物理设备,设备驱动是 dev/binder。
- 从 Framework 角度来说,Binder 是 ServiceManager 连接各种 Manager(ActivityManager、WindowManager..)和相应的 ManagerService 的桥梁。
- 从 Android 应用层来说,Binder 是客户端和服务端进行通信的媒介。
AIDL 的例子
新建实体类 Book,实现 Parcelable 接口。在 aidl 文件中引用自定义的对象需要同时声明那个对象的aidl。
Book.java
package me.luwenjie.myapplication;
import android.os.Parcel;
import android.os.Parcelable;
/**
* created by venjer on 15/06/2017 14:13
*/
public class Book implements Parcelable {
public static final Creator<Book> CREATOR = new Creator<Book>() {
@Override public Book createFromParcel(Parcel in) {
return new Book(in);
}
@Override public Book[] newArray(int size) {
return new Book[size];
}
};
private int bookId;
private String bookName;
protected Book(Parcel in) {
bookId = in.readInt();
bookName = in.readString();
}
public int getBookId() {
return bookId;
}
public void setBookId(int bookId) {
this.bookId = bookId;
}
public String getBookName() {
return bookName;
}
public void setBookName(String bookName) {
this.bookName = bookName;
}
@Override public int describeContents() {
return 0;
}
@Override public void writeToParcel(Parcel parcel, int i) {
parcel.writeInt(bookId);
parcel.writeString(bookName);
}
}
复制代码
Book.aidl
// Book.aidl
package me.luwenjie.myapplication;
// Declare any non-default types here with import statements
parcelable Book;
复制代码
IBookManager.aidl
// IBookManager.aidl
package me.luwenjie.myapplication;
// Declare any non-default types here with import statements
import me.luwenjie.myapplication.Book;
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
}
复制代码
系统根据 IBookManager.aidl 自动生成的 IBookManager.java 接口。
它继承了 IInterface 接口,定义了 aidl 中声明的方法。同时内部有一个继承 android.os.Binder 的 Stub 静态抽象类。Stub 内部还有一个静态 Proxy 代理类。
/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: /Users/venjerLu/Documents/AndroidStudioStorage/MyApplication2/app/src/main/aidl/me/luwenjie/myapplication/IBookManager.aidl
*/
package me.luwenjie.myapplication;
public interface IBookManager extends android.os.IInterface {
public java.util.List<me.luwenjie.myapplication.Book> getBookList()
throws android.os.RemoteException;
public void addBook(me.luwenjie.myapplication.Book book) throws android.os.RemoteException;
/** Local-side IPC implementation stub class. */
/**
* 客户端和服务端都位于同一个进程时,方法调用不会走 transact 过程,位于不同进程时,
* 方法调用需要走 transact 过程,这个逻辑由 Proxy 代理类完成。
*/
public static abstract class Stub extends android.os.Binder
implements me.luwenjie.myapplication.IBookManager {
/** 声明2个整型的id分别用于标识这2个方法,用于标识在 transact 过程中客户端请求的是哪个方法。 */
static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
/** Binder 的唯一标识 */
private static final java.lang.String DESCRIPTOR = "me.luwenjie.myapplication.IBookManager";
/** Construct the stub at attach it to the interface. */
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an me.luwenjie.myapplication.IBookManager interface,
* generating a proxy if needed.
*/
/**
* 将服务端的 Binder 对象转换成客户端所需的 AIDL 接口类型的对象
* 如果是同一进程,返回 Stub 对象本身。不同进程返回的是 Stub.Proxy
*/
public static me.luwenjie.myapplication.IBookManager asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof me.luwenjie.myapplication.IBookManager))) {
return ((me.luwenjie.myapplication.IBookManager) iin);
}
return new me.luwenjie.myapplication.IBookManager.Stub.Proxy(obj);
}
/**
* 返回当前的 Binder 对象
*/
@Override public android.os.IBinder asBinder() {
return this;
}
/**
* 运行在服务端中的 Binder 线程池中。当客户端发起跨进程请求时,远程请求会通过系统底层封装
* 后交由此方法处理。
* @param code 通过 code 确定客户端请求的目标方法是什么
* @param data 从 data 中取出目标方法所需要的参数,然后执行目标方法。
* @param reply 目标方法执行完毕后向 reply 中写入返回值
* @param flags 额外的操作标记,默认的RPC是0,单向的RPC是{@link #FLAG_ONEWAY}
* @return 返回false客户端的请求会失败。因此我们可以利用这个特性来做权限验证,毕竟我们也不希望随便一个进程都能远程调
* 用我们的服务
* @throws android.os.RemoteException
*/
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)
throws android.os.RemoteException {
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_getBookList: {
data.enforceInterface(DESCRIPTOR);
java.util.List<me.luwenjie.myapplication.Book> _result = this.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBook: {
data.enforceInterface(DESCRIPTOR);
me.luwenjie.myapplication.Book _arg0;
if ((0 != data.readInt())) {
_arg0 = me.luwenjie.myapplication.Book.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
this.addBook(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements me.luwenjie.myapplication.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;
}
/**
* 运行在客户端
* @return
* @throws android.os.RemoteException
*/
@Override public java.util.List<me.luwenjie.myapplication.Book> getBookList()
throws android.os.RemoteException {
// 创建输入型 Parcel 对象 _data, 输出型对象 _reply
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<me.luwenjie.myapplication.Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
// 发起 RPC(远程过程调用)请求,同时当前线程挂起,服务端的 onTarnsact 会被调用。
// RPC 过程返回后,当前线程继续执行,从 _reply 中取出 RPC 过程的返回结果,最后返回 _reply
// 中的数据。
mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(me.luwenjie.myapplication.Book.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
/**
* 和 getBookList 一样。它没有返回值,所以不需要从 _reply 中取值。
* @param book
* @throws android.os.RemoteException
*/
@Override public void addBook(me.luwenjie.myapplication.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();
}
}
}
}
}
复制代码
Binder 的工作机制
- 当客户端发起远程请求时,当前线程会被挂起直至服务端返回数据,如果一个远程方法是很耗时的,那么不能在 UI 线程中发起请求。
- 因为服务端的 Binder 方法运行在 Binder 的线程池中,所以 Binder 方法不管是否耗时都应该采用同步的方式去实现。因为它已经运行在一个线程中了。
手动实现 Binder
AIDL 是为了方便生成代码。系统根据 AIDL 生成代码的格式是固定的。
将 Stub 从生成的代码中剥离出来。
IBookManager
public interface IBookManager extends IInterface{
/** 声明2个整型的id分别用于标识这2个方法,用于标识在 transact 过程中客户端请求的是哪个方法。 */
int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION);
int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
/** Binder 的唯一标识 */
java.lang.String DESCRIPTOR = "me.luwenjie.myapplication.IBookManager";
List<Book> getBookList() throws RemoteException;
void addBook(Book book)throws RemoteException;
}
复制代码
BookManagerImpl
public class BookManagerImpl extends Binder implements IBookManager {
public BookManagerImpl() {
//super();
this.attachInterface(this, IBookManager.DESCRIPTOR);
}
/**
* 将服务端的 Binder 对象转换成客户端所需的 AIDL 接口类型的对象
* 如果是同一进程,返回 Stub 对象本身。不同进程返回的是 Stub.Proxy
*/
public static IBookManager asInterface(android.os.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 Proxy(obj);
}
@Override public List<Book> getBookList() throws RemoteException {
return null;
}
@Override public void addBook(Book book) throws RemoteException {
}
@Override public IBinder asBinder() {
return this;
}
/**
* 运行在服务端中的 Binder 线程池中。当客户端发起跨进程请求时,远程请求会通过系统底层封装
* 后交由此方法处理。
*
* @param code 通过 code 确定客户端请求的目标方法是什么
* @param data 从 data 中取出目标方法所需要的参数,然后执行目标方法。
* @param reply 目标方法执行完毕后向 reply 中写入返回值
* @param flags 额外的操作标记,默认的RPC是0,单向的RPC是{@link #FLAG_ONEWAY}
* @return 返回false客户端的请求会失败。因此我们可以利用这个特性来做权限验证,毕竟我们也不希望随便一个进程都能远程调
* 用我们的服务
* @throws android.os.RemoteException
*/
@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply,
int flags) throws android.os.RemoteException {
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_getBookList: {
data.enforceInterface(DESCRIPTOR);
java.util.List<me.luwenjie.myapplication.Book> _result = this.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBook: {
data.enforceInterface(DESCRIPTOR);
me.luwenjie.myapplication.Book _arg0;
if ((0 != data.readInt())) {
_arg0 = me.luwenjie.myapplication.Book.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
this.addBook(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements 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;
}
/**
* 运行在客户端
* @return
* @throws android.os.RemoteException
*/
@Override public java.util.List<Book> getBookList()
throws android.os.RemoteException {
// 创建输入型 Parcel 对象 _data, 输出型对象 _reply
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<me.luwenjie.myapplication.Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
// 发起 RPC(远程过程调用)请求,同时当前线程挂起,服务端的 onTarnsact 会被调用。
// RPC 过程返回后,当前线程继续执行,从 _reply 中取出 RPC 过程的返回结果,最后返回 _reply
// 中的数据。
mRemote.transact(TRANSACTION_getBookList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(me.luwenjie.myapplication.Book.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
/**
* 和 getBookList 一样。它没有返回值,所以不需要从 _reply 中取值。
* @param book
* @throws android.os.RemoteException
*/
@Override public void addBook(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(TRANSACTION_addBook, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
}
}
复制代码
linkToDeath 和 unlinkToDeath
Binder 运行在服务端进程,如果服务端进程异常终止,这时客户端到服务端的连接断开(Binder死亡),导致调用失败。
通过 linkToDeath 给 Binder 设置一个死亡代理,当 Bidner 死亡时,我们就会收到通知。
/**
* Register the recipient for a notification if this binder
* goes away. If this binder object unexpectedly goes away
* (typically because its hosting process has been killed),
* then the given {@link DeathRecipient}'s
* {@link DeathRecipient#binderDied DeathRecipient.binderDied()} method
* will be called.
*
* <p>You will only receive death notifications for remote binders,
* as local binders by definition can't die without you dying as well.
*
* @throws RemoteException if the target IBinder's
* process has already died.
*
* @see #unlinkToDeath
*/
public void linkToDeath(DeathRecipient recipient, int flags)
throws RemoteException;
/**
* Interface for receiving a callback when the process hosting an IBinder
* has gone away.
*
* @see #linkToDeath
*/
public interface DeathRecipient {
public void binderDied();
}
复制代码
当 Binder 死亡时,系统就会回调 binderDied 方法。
Android 中的 IPC 方式
Bundle
public final class Bundle extends BaseBundle implements Cloneable, Parcelable
复制代码
Bundle 实现了 Parcelable 接口,可以方便的在不同的进程之间传输。
只能传输 Bundle 支持的数据。
使用文件共享
2 个文件通过读/写同一个文件来交换数据。有一定的局限性,比如并发读/写的问题。
适合在对数据同步要求不高的进程之间进行通信,并且要求妥善处理并发读/写的问题。
SharePreference 是 Android 中的轻量级存储方案。
它通过键值对的方式来存储数据,底层采用 XML 文件来存储键值对。本质上 SharePreference 也属于文件的一种,由于系统对它的读写有一定的缓存策略,内存中会有一份 SharePreference 文件的缓存,多进程的时候变得不可靠,有很大几率丢失数据。
使用 Messenger
一种轻量级的 IPC 方案,底层实现是 AIDL。
构造方法可以看出 AIDL 的痕迹。
public Messenger(Handler target) {
mTarget = target.getIMessenger();
}
public Messenger(IBinder target) {
mTarget = IMessenger.Stub.asInterface(target);
}
复制代码
例子:
客户端 MessengerActivity
public class MessengerActivity extends AppCompatActivity {
private static final String TAG = "MessengerActivity";
private final Messenger clientMessenger = new Messenger(new WeakHandler(this));
private ServiceConnection mConnection = new ServiceConnection() {
@Override public void onServiceConnected(ComponentName name, IBinder service) {
// 得到服务端的 Messenger
Messenger service1 = new Messenger(service);
Message message = Message.obtain(null, MessengerService.MSG_FROM_CLIENT);
Bundle bundle = new Bundle();
bundle.putString("msg", "hello,this is client");
message.setData(bundle);
message.replyTo = clientMessenger;
try {
service1.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override public void onServiceDisconnected(ComponentName name) {
}
};
@Override protected void onCreate(@Nullable 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() {
super.onDestroy();
unbindService(mConnection);
}
private static class WeakHandler extends Handler {
private final WeakReference<MessengerActivity> mActivity;
public WeakHandler(MessengerActivity activity) {
mActivity = new WeakReference<>(activity);
}
@Override public void handleMessage(Message msg) {
MessengerActivity activity = mActivity.get();
if (activity != null) {
switch (msg.what) {
case MessengerService.MSG_FROM_SERVICE:
Log.d(TAG, "receive msg from Service:" + msg.getData().getString("reply"));
break;
}
}
}
}
}
复制代码
服务端 MessengerService
public class MessengerService extends Service {
public static final int MSG_FROM_CLIENT = 0X1000;
public static final int MSG_FROM_SERVICE = 0X1001;
private static final String TAG = "MessengerService";
private final Messenger messenger = new Messenger(new MessengerHandler());
@Nullable @Override public IBinder onBind(Intent intent) {
return messenger.getBinder();
}
private static class MessengerHandler extends Handler {
@Override public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_FROM_CLIENT:
Log.d(TAG,"receive msg from Client:" + msg.getData().getString("msg"));
// 得到客户端的 Messenger
Messenger client = msg.replyTo;
Message replyMsg = Message.obtain(null, MSG_FROM_SERVICE);
Bundle bundle = new Bundle();
bundle.putString("reply","yes,i have received, i will reply to you after a moment");
replyMsg.setData(bundle);
try {
client.send(replyMsg);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
}
}
}
}
复制代码
<service
android:name=".MessengerService"
android:process=":remote"/>
复制代码
在 Messenger 中进行数据传输必须将数据放入 Message 中,Messenger 和 Message 都实现了 Parcelable 接口,因此可以跨进程传输。
Message 可以使用的载体:
- arg1
- arg2
- object
2.2 之前不支持跨进程, 2.2 之后只支持系统提供的 Parcelable 对象传输
- Bundle
- replyTo
Messenger 的工作原理:
使用 AIDL
Messenger 以串行的方式处理客户端发来的消息,如果有大量的消息同时发送,服务端仍然只能一个个处理。
ADIL 支持的数据类型:
- 基本数据类型, String, CharSequence
- List, 只支持 ArrayList, 里面每个元素都必须能够被 AIDL 支持
- Map, 只支持 HashMap,里面每个元素都必须被 AIDL 支持,包括 key 和 value
- Parcelable
- AIDL, 所有 AIDL 接口本身也可以在 AIDL 文件中使用
除了基本类,其他类型的参数必须标上方向:in(输入型参数), out(输出型参数), inout(输入输出型参数)
- 客户端调用服务端的方法,被调用的方法运行在 Binder 线程池中,同时客户端被挂起,如果服务端的方法比较耗时,并且客户端运行在UI线程就会发生 ANR。避免在客户端的 UI 线程访问远程方法。
- 服务端的方法本身就运行在 Binder 线程池,可以执行大量任务。非常不建议在服务端方法中开启线程去进行异步任务。
- 当远程客户端需要调用客户端的 listener 中的方法时,被调用的方法也运行在 Binder 客户端的线程池中。同样不可以在服务端的 UI 线程调用客户端的耗时方法。
- 如果要在客户端的 Binder 线程池的方法里访问 UI,必须使用 Handler 切换到 UI 线程。
RemoteCallbackList
public class RemoteCallbackList<E extends IInterface>
复制代码
系统专门提供的用于删除跨进程 Listener 的类。支持任意的 AIDL 类型。
内部有一个 ArrayMap<IBinder, Callback>
来保存所有的 AIDL 回调。
虽然多进程时服务端会生成一个不同的对象,但是他们对应的底层的 Binder 是同一个。只需要根据 Binder 查找对应的 listener 删除。
当客户端进程终止后,它可以自动移除客户端所注册的 listener。
内部自动实现了线程同步的功能。各个方法都加了 synchronized 锁。
final int N = mListenerList.beginBroadcast();
for (int i = 0; i < N; i++) {
IOnNewBookArrivedListener l = mListenerList.getBroadcastItem(i);
if (l != null) {
//TODO handle l
}
}
mListenerList.finishBroadcast();
复制代码
beginBroadcast
和finishBroadcast
必须配对使用,哪怕只是想获取里面的元素数量。
Binder 意外死亡
服务端意外停止,需要重新连接服务。
- 给 Binder 设置 DeathRecipient 监听,Binder 死亡时会收到 binderDied() 回调,此方法在 Binder 线程池中被调用,不能访问 UI
- 在 ServiceConnection 中 onServiceDisconnected() 方法重连,此方法在 UI 线程中调用,可以访问 UI。
权限
- 在 onBind() 中验证。例如使用 permission 验证。
- 在服务端的 onTransact 方法进行验证,验证失败直接返回 false。 可以采用 permission,Uid 和 Pid
例子:图书管理类,每隔5s后台加一本书并通知前台加了什么书。
Book.aidl
// IBook.aidl
package me.luwenjie.myapplication;
// Declare any non-default types here with import statements
//import me.luwenjie.myapplication.Book;
parcelable Book;
复制代码
IBookListener.aidl
// IOnNewBookArrivedListener.aidl
package me.luwenjie.myapplication;
// Declare any non-default types here with import statements
import me.luwenjie.myapplication.Book;
interface IBookListener {
void onAddBook(in Book book);
}
复制代码
IBookManager.aidl
// IBookManager.aidl
package me.luwenjie.myapplication;
// Declare any non-default types here with import statements
import me.luwenjie.myapplication.Book;
import me.luwenjie.myapplication.IBookListener;
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
void registerListener(IBookListener listener);
void unregisterListener(IBookListener listener);
}
复制代码
BookManagerService
public class BookManagerService extends Service {
private static final String TAG = "BookManagerService";
private CopyOnWriteArrayList<Book> mBooks = new CopyOnWriteArrayList<>();
private RemoteCallbackList<IBookListener> mBookListeners = new RemoteCallbackList<>();
private AtomicBoolean mIsServiceDestoryed = new AtomicBoolean(false);
private Binder mBinder = new IBookManager.Stub() {
@Override public List<Book> getBookList() throws RemoteException {
return mBooks;
}
@Override public void addBook(Book book) throws RemoteException {
mBooks.add(book);
}
@Override public void registerListener(IBookListener listener) throws RemoteException {
mBookListeners.register(listener);
//if (!mBookListeners.contains(listener)) {
// mBookListeners.add(listener);
//} else {
// Log.d(TAG, listener + "already exits");
//}
Log.d(TAG, "listeners.size = " + getListenerSize());
}
@Override public void unregisterListener(IBookListener listener) throws RemoteException {
mBookListeners.unregister(listener);
//if (mBookListeners.contains(listener)) {
// mBookListeners.remove(listener);
//} else {
// Log.d(TAG, listener + ",not exits");
//}
Log.d(TAG, "listeners.size = " + getListenerSize());
}
@Override public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
throws RemoteException {
// 验证 permission
int i = checkCallingOrSelfPermission("me.luwenjie.permission.BOOKMANAGER");
if (i == PackageManager.PERMISSION_DENIED) {
Log.d(TAG, "PERMISSION_DENIED:me.luwenjie.permission.BOOKMANAGER");
return false;
}
// 验证包名
String packageName;
String[] packages = getPackageManager().getPackagesForUid(getCallingUid());
if (packages != null && packages.length > 0) {
packageName = packages[0];
if (!packageName.startsWith("me.luwenjie")) {
return false;
}
}
return super.onTransact(code, data, reply, flags);
}
};
private int getListenerSize() {
int i = mBookListeners.beginBroadcast();
mBookListeners.finishBroadcast();
return i;
}
@Nullable @Override public IBinder onBind(Intent intent) {
int i = checkCallingOrSelfPermission("me.luwenjie.permission.BOOKMANAGER");
if (i == PackageManager.PERMISSION_DENIED) {
Log.d(TAG, "PERMISSION_DENIED:me.luwenjie.permission.BOOKMANAGER");
return null;
}
Log.d(TAG, "PERMISSION_GRANTED");
return mBinder;
}
@Override public void onCreate() {
super.onCreate();
mBooks.add(new Book("think in java", "48.90"));
mBooks.add(new Book("Android开发艺术探索", "78.90"));
new Thread(new ServiceWorker()).start();
}
/**
* 避免直接在 UI 线程调用客户端的耗时方法,防止服务端无响应
*/
private void onAddBook(Book book) {
mBooks.add(book);
int size = mBookListeners.beginBroadcast();
for (int i = 0; i < size; i++) {
IBookListener item = mBookListeners.getBroadcastItem(i);
if (item != null) {
try {
item.onAddBook(book);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
mBookListeners.finishBroadcast();
//for (IBookListener listener : mBookListeners) {
// try {
// listener.onAddBook(book);
// } catch (RemoteException e) {
// e.printStackTrace();
// }
//}
}
@Override public void onDestroy() {
super.onDestroy();
mIsServiceDestoryed.set(true);
}
private class ServiceWorker implements Runnable {
@Override public void run() {
while (!mIsServiceDestoryed.get()) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
int bookId = mBooks.size() + 1;
int price = new Random().nextInt(100);
Book newBook = new Book("newBook#" + bookId, price + "");
onAddBook(newBook);
}
}
}
}
复制代码
BookManagerActivity
public class BookManagerActivity extends AppCompatActivity {
private static final String TAG = "BookManagerActivity";
private static final int MESSAGE_NEW_BOOK_ADD = 1;
private IBookManager mRemoteManager;
private WeakHandler mHandler = new WeakHandler(this);
private IBookListener mBookListener = new IBookListener.Stub() {
@Override public void onAddBook(Book book) throws RemoteException {
Message message = mHandler.obtainMessage(MESSAGE_NEW_BOOK_ADD, book);
message.sendToTarget();
// 如果要在客户端的 Binder 线程池的方法里访问 UI,必须使用 Handler 切换到 UI 线程。
// mHandler.post()
}
};
private final ServiceConnection mConnection = new ServiceConnection() {
@Override public void onServiceConnected(ComponentName name, IBinder service) {
mRemoteManager = IBookManager.Stub.asInterface(service);
try {
mRemoteManager.registerListener(mBookListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override public void onServiceDisconnected(ComponentName name) {
}
};
@Override protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_bookmanager);
Intent intent = new Intent(this, BookManagerService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
findViewById(R.id.bt_add).setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
// 在子线程去调用远程服务端的耗时方法
new Thread(new Runnable() {
@Override public void run() {
try {
int price = new Random().nextInt(100);
int name = mRemoteManager.getBookList().size() + 1;
addABook("new Book#" + name, "" + price);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}).start();
}
});
}
private void addABook(String name, String price) {
try {
List<Book> list = mRemoteManager.getBookList();
Log.d(TAG, "query book list,list type:" + list.getClass().getCanonicalName());
Log.d(TAG, "query book list:" + list.toString());
for (Book book : list) {
Log.d(TAG, "book info: " + book.getName() + ", " + book.getPrice());
}
// 添加一本书
final Book newBook = new Book(name, price);
mRemoteManager.addBook(newBook);
List<Book> newBookList = mRemoteManager.getBookList();
Log.i(TAG, "query book list:" + newBookList.toString());
for (Book book : newBookList) {
Log.d(TAG, "newBook info: " + book.getName() + ", " + book.getPrice());
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override protected void onDestroy() {
super.onDestroy();
unbindService(mConnection);
// 反注册监听器,这里会发现不起作用。后台找不到注册过的这个监听器,
// 这里的监听器是从前台传到后台的,难道中间发生了变化?
// 因为这是多进程,操作的不是同一个对象,Binder 会把客户端传过来的对象
// 重新转化并生成一个新的对象。对象是不能跨进程传输的,本质上是通过序列化反序列化来完成。
// 所以 AIDL 中的自定义对象都需要实现 Parcelable。
// 必须使用 RemoteCallbackList 来解决这个问题。
if (mRemoteManager != null && mRemoteManager.asBinder().isBinderAlive()) {
Log.d(TAG, "unregister listener:" + mBookListener);
try {
mRemoteManager.unregisterListener(mBookListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
private static class WeakHandler extends Handler {
private final WeakReference<BookManagerActivity> mActivity;
WeakHandler(BookManagerActivity activity) {
mActivity = new WeakReference<>(activity);
}
@Override public void handleMessage(Message msg) {
BookManagerActivity activity = mActivity.get();
if (activity != null) {
switch (msg.what) {
case MESSAGE_NEW_BOOK_ADD:
Log.d(TAG, "receive new book :" + msg.obj);
break;
}
}
}
}
}
复制代码
使用 ContentProvider
用于不同应用共享数据,天生适合进程间通信。
与 Messenger 一样,底层实现也是 Binder。
Uri uri = Uri.parse("content://me.luwenjie.BookContentProvider");
getContentResolver().query(uri,null,null,null,null);
getContentResolver().query(uri,null,null,null,null);
getContentResolver().query(uri,null,null,null,null);
复制代码
调用 3 次 query,onCreate 在主线程执行,三次 query 在 Binder 线程池的 3 个不同的线程执行。
onCreate, main
query, Binder:6895_2
query, Binder:6895_3
query, Binder:6895_2
复制代码
例子
BookContentProvider
**
* created by venjer on 23/06/2017 18:52
* 除了(onCreate()) 方法都运行在 ContentProvider 的进程中
*/
public class BookContentProvider extends ContentProvider {
public static final String AUTHORITY = "me.luwenjie.BookContentProvider";
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);
private static final String TAG = "BookContentProvider";
static {
// 分别为 book 表和 user 表指定了 Uri。
sUriMatcher.addURI(AUTHORITY, "book", BOOK_URI_CODE);
sUriMatcher.addURI(AUTHORITY, "user", USER_URI_CODE);
}
private ContentResolver mContentResolver;
private SQLiteDatabase mDb;
/**
* 由系统回调运行在主线程中
*/
@Override public boolean onCreate() {
Log.d(TAG, "onCreate, " + Thread.currentThread().getName());
initProviderData();
mContentResolver = getContext().getContentResolver();
return false;
}
private void initProviderData() {
mDb = new DbOpenHelper(getContext()).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);");
}
@Nullable @Override public String getType(@NonNull Uri uri) {
Log.d(TAG, "getType, " + Thread.currentThread().getName());
return null;
}
@Nullable @Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection,
@Nullable String[] selectionArgs, @Nullable String sortOrder) {
Log.d(TAG, "query, " + Thread.currentThread().getName());
String tableName = getTableName(uri);
if (TextUtils.isEmpty(tableName)) {
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
return mDb.query(tableName, projection, selection, selectionArgs, null, null, sortOrder, null);
}
@Nullable @Override public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
Log.d(TAG, "insert, " + Thread.currentThread().getName());
String tableName = getTableName(uri);
if (TextUtils.isEmpty(tableName)) {
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
mDb.insert(tableName, null, values);
if (mContentResolver != null) mContentResolver.notifyChange(uri, null);
return uri;
}
@Override public int delete(@NonNull Uri uri, @Nullable String selection,
@Nullable String[] selectionArgs) {
Log.d(TAG, "delete, " + Thread.currentThread().getName());
String table = getTableName(uri);
if (table == null) {
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
int raw = mDb.delete(table, selection, selectionArgs);
if (raw > 0 && mContentResolver != null) mContentResolver.notifyChange(uri, null);
return raw;
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection,
@Nullable String[] selectionArgs) {
Log.d(TAG, "update, " + Thread.currentThread().getName());
return 0;
}
/**
* 通过uri获取表名
*/
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;
}
return tableName;
}
}
复制代码
DbOpenHelper
public class DbOpenHelper extends SQLiteOpenHelper {
public static final String BOOK_TABLE_NAME = "book";
public static final String USER_TABLE_NAME = "user";
private static final String TAG = "DbOpenHelper";
private static final String DB_NAME = "book_provider.db";
private static final int DB_VERSION = 1;
// 图书和用户信息表
private String CREATE_BOOK_TABLE =
"CREATE TABLE IF NOT EXISTS " + BOOK_TABLE_NAME + "(_ID INTEGER PRIMARY KEY," + "name TEXT)";
private String CREATE_USER_TABLE = "CREATE TABLE IF NOT EXISTS "
+ USER_TABLE_NAME
+ "(_ID INTEGER PRIMARY KEY,"
+ "name TEXT,"
+ "sex INT)";
public DbOpenHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
public DbOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory,
int version) {
super(context, name, factory, version);
}
public DbOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory,
int version, DatabaseErrorHandler errorHandler) {
super(context, name, factory, version, errorHandler);
}
@Override public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_USER_TABLE);
db.execSQL(CREATE_BOOK_TABLE);
}
@Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
复制代码
ContentProviderActivity
public class ContentProviderActivity extends AppCompatActivity {
private static final String TAG = "ContentProviderActivity";
@Override protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_contentprovider);
//Uri uri = Uri.parse("content://me.luwenjie.BookContentProvider");
//getContentResolver().query(uri,null,null,null,null);
//getContentResolver().query(uri,null,null,null,null);
//getContentResolver().query(uri,null,null,null,null);
Uri bookUri = Uri.parse("content://me.luwenjie.BookContentProvider/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);
if (bookCursor != null) {
while (bookCursor.moveToNext()) {
Book book = new Book();
book.setBookId(bookCursor.getInt(0));
book.setName(bookCursor.getString(1));
Log.d(TAG, "query book: " + book.toString());
}
bookCursor.close();
}
Uri userUri = Uri.parse("content://me.luwenjie.BookContentProvider/user");
Cursor userCursor =
getContentResolver().query(userUri, new String[] { "_id", "name", "sex" }, null, null,
null);
if (userCursor != null) {
while (userCursor.moveToNext()) {
User user = new User();
user.setUserId(userCursor.getInt(0));
user.setUserName(userCursor.getString(1));
user.setMale(userCursor.getInt(2) == 1);
Log.d(TAG, "query user: " + user.toString());
}
userCursor.close();
}
}
}
复制代码
AndroidManifest.xml
<provider
android:name=".BookContentProvider"
android:authorities="me.luwenjie.BookContentProvider"
android:permission="me.luwenjie.BOOK_PROVIDER"
android:process=":provider"
/>
复制代码
使用 Socket
套接字,分为流式套接字和用户数据报套接字。
对应于网络的传输控制层的 TCP 和 UDP 协议。TCP 协议是面向连接的协议,提供稳定的双向通信功能。 UDP 是无连接的,提供不稳定的单向通信功能,也可实现双向通信。
UDP 拥有更好的效率,但是不保证数据的正确传输。
Binder 连接池
当有非常多的业务模块都需要使用 AIDL 时,不能无限制的增加 Service 的数量。应该将所有的 AIDL 放在同一个 Service 中去管理。
每个业务模块创建自己的 AIDL 接口并实现此接口,不同的业务模块不能有耦合,向服务端提供自己的唯一标识和其对应的 Binder 对象。
public class BindPool {
public static final int BINDER_SECURITY_CENTER = 1;
public static final int BINDER_COMPUTE = 2;
private static final String TAG = "BindPool";
private static volatile BindPool sInstance;
private IBindPool mIBinderPool;
private CountDownLatch mCountDownLatch;
private Context mContext;
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
@Override public void binderDied() {
Log.d(TAG, "binder died");
mIBinderPool.asBinder().unlinkToDeath(mDeathRecipient, 0);
mIBinderPool = null;
connectBinderPoolService();
}
};
private ServiceConnection mBinderPoolConnection = new ServiceConnection() {
@Override public void onServiceConnected(ComponentName name, IBinder service) {
mIBinderPool = IBindPool.Stub.asInterface(service);
try {
mIBinderPool.asBinder().linkToDeath(mDeathRecipient, 0);
} catch (RemoteException e) {
e.printStackTrace();
}
mCountDownLatch.countDown();
}
@Override public void onServiceDisconnected(ComponentName name) {
}
};
public BindPool(Context context) {
mContext = context.getApplicationContext();
connectBinderPoolService();
}
public static BindPool getInstance(Context context) {
if (sInstance == null) {
synchronized (BindPool.class) {
if (sInstance == null) {
sInstance = new BindPool(context);
}
}
}
return sInstance;
}
private synchronized void connectBinderPoolService() {
mCountDownLatch = new CountDownLatch(1);
Intent service = new Intent(mContext, BinderPoolService.class);
mContext.bindService(service, mBinderPoolConnection, Context.BIND_AUTO_CREATE);
try {
mCountDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public IBinder queryBinder(int bindCode) {
IBinder binder = null;
if (mIBinderPool != null) {
try {
binder = mIBinderPool.queryBinder(bindCode);
} catch (RemoteException e) {
e.printStackTrace();
}
}
return binder;
}
public static class BinderPoolImpl extends IBindPool.Stub {
private static final String TAG = "BinderPoolImpl";
@Override public IBinder queryBinder(int binderCode) {
IBinder iBinder = null;
switch (binderCode) {
case BINDER_SECURITY_CENTER:
iBinder = new SecurityCenterImpl();
break;
case BINDER_COMPUTE:
iBinder = new ComputeImpl();
break;
}
return iBinder;
}
}
}
复制代码
选用合适的 IPC