内容和案例参考自《Android开发艺术探索》
Android中的IPC方式
- 通过在Intent中附加extras(Bundle)来传递信息
- 通过共享文件、SharedPreference的方式共享数据
- Messenger方式(AIDL、Binder)跨进程通信
- ContentProvider(Binder)实现跨进程访问
- Socket实现IPC
一、 Bundle
- 四大组件中的三大组件(Activity、Service、Receiver)均支持在Intent中传递Bundle数据
- Bundle实现了Parcelable接口,因此可以跨进程通信
- 传输的数据必须要能够被序列化:基本类型、实现了Parcelable接口的对象、实现了Serializable接口的对象及一些Android支持的特殊对象。
二、 文件共享
- 使用文件,通过ObjectInputStream、ObjectOutputStream输入输出数据;不适合并发场景。
- 使用SharedPreferences,SharedPreferences也属于文件的一种,但是系统对它的读写有一定的缓存策略,即内存中会有一份SharedPreferences文件的缓存。
三、 Binder
3.1 Messenger
- Messenger的特点:以串行的方式处理客户端发来的消息。主要用于传递消息,不能做到跨进程调用服务端的方法。
- 利用Messenger使得客户端与服务端能相互通信:
- 定义或声明客户端与服务端的成员。
- 创建服务,在AndroidManifest.xml文件中注册服务。
- 客户端:声明
mServiceMessenger
。 - 客户端:创建
mClientHandler
(handleMessage()
已重写)。 - 客户端:创建
mClientMessenger
(通过mClientHandler
初始化)。 - 服务端:声明
mClientMessenger
。 - 服务端:创建
mServiceHandler
(handleMessage()
已重写)。 - 服务端:创建
mServiceMessenger
(通过mClientHandler
初始化)。
- 进程通信时客户端方法的执行
- 客户端:利用
onServiceConnected()
中的参数Binder初始化mServiceMessenger
。 - 客户端:创建Message对象,并设置what、data、replyTo(设置为
mClientMessenger
)参数。 - 客户端:通过
mServiceMessenger.send(Message)
将Message发送给服务端。
- 客户端:利用
- 进程通信时服务端方法的执行。
- 服务端:在
handleMessage()
中获取并输出Message中data的数据;获取Message中replyTo的Messenger并初始化mClientMessenger
。 - 服务端:通过
mClientMessenger.send(Message)
回复Message给客户端。
- 服务端:在
// 客户端:MainActivity.java
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
// 创建Messenger对象
private Messenger mService;
private Messenger mReplyMessenger = new Messenger(new MessengerHandler());
@Override
protected void onCreate(Bundle savedInstanceState) {
// ... 省略
}
private void sendMessage() {
Message msg = Message.obtain(null, MessengerService.MSG_FROM_CLIENT);
Bundle data = new Bundle();
data.putString("msg", mEtMsg.getText().toString());
msg.setData(data);
// 将信使Messenger传递给Service
msg.replyTo = mReplyMessenger;
try {
mService.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
private void bind() {
Intent intent = new Intent(this, MessengerService.class);
bindService(intent, mConnection, BIND_AUTO_CREATE);
}
private void unbind() {
unbindService(mConnection);
}
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// 利用onBind()返回的Binder创建Messenger
mService = new Messenger(service);
Log.d(TAG, "onServiceConnected: 服务已绑定");
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
public void onClick(View v) {
// ... 省略
}
private static class MessengerHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MessengerService.MSG_FROM_SERVICE:
Log.d(TAG, "receive message from service: " + msg.getData().getString("reply"));
break;
default:
super.handleMessage(msg);
}
}
}
}
// 服务端:MessengerService.java
public class MessengerService extends Service {
public static final String TAG = "MessengerService";
public static final int MSG_FROM_CLIENT = 0;
public static final int MSG_FROM_SERVICE = 1;
private final Messenger mMessenger = new Messenger(new MessengerHandler());
private static class MessengerHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_FROM_CLIENT:
// 打印客户端消息
Log.d(TAG, "handleMessage: receive message from Client : " + msg.getData().getString("msg"));
// 回复消息给客户端
Messenger client = msg.replyTo;
Message replyMsg = Message.obtain(null, MSG_FROM_SERVICE);
Bundle data = new Bundle();
data.putString("reply", "消息已收到");
replyMsg.setData(data);
try {
client.send(replyMsg);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
default:
super.handleMessage(msg);
}
}
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
}
3.2 AIDL
- 服务端:
- 创建Service来监听客户端的连接请求,然后创建一个AIDL文件,将暴露给客户端的接口在AIDL文件中声明,最后在Service实现这个AIDL。
- 客户端:
- 绑定服务端的Service,绑定成功后,将服务端返回的Binder对象转成AIDL接口所属的类型,接下来就可以调用AIDL中的方法。
- AIDL接口中支持的数据类型:
- 基本数据类型;
String
和CharSequence
;List
:只支持ArrayList,里面的每个元素必须能够被AIDL支持;Map
:只支持HashMap,里面的每个元素必须能够被AIDL支持,包括key和value;Parcelable
:所有实现了Parcelable接口的对象(需导入);AIDL
:所有的AIDL接口本身也可以在AIDL文件中使用(需导入);- 注意:
- 自定义的Parcelable对象和AIDL必须要显式import进来;
- 如果AIDL中用到了自定义Parcelable对象,必须新建一个与其同名的AIDL,并在其中声明它为Parcelable对象;
- AIDL中除了基本数据类型,其他类型的参数必须标上方向:in、out或者inout,in表示输入型参数,out表示输出型参数,inout表示输入输出型参数,不能一概使用out或者inout,因为底层实现有开销;
- AIDL的包结构在服务端和客户端要保持一致,哪怕客户端是另外一个应用;
- AIDL实现客户端与服务端通信
- AIDL方法是在服务端的Binder线程池中执行的,因此当多个客户端同时连接会存在多线程同时访问的情况,所以需要在AIDL方法中处理线程同步;
- 当服务端返回
CopyOnWriteArrayList
、ConcurrentHashMap
时,Binder会按照List、Map的规范去访问数据并最终形成ArrayList
、HashMap
返回给客户端; onNewBookArrivied
方法在客户端的Binder
线程池中执行,为了便于UI操作,我们需要通过Handler切换到客户端的主线程中去执行;Binder
会把客户端传递过来的对象重新转化并生成一个新的对象;服务端存储客户端的监听对象时,不能用普通的List,应使用RemoteCallbackList
;RemoteCallbackList
是一个泛型,支持管理任意的AIDL接口;客户端进程终止后,它能够自动移除客户端所注册的listener,另外,其内部自动实现了线程同步的功能;RemoteCallbackList
的beginBroadcat
、finihBroadcat
必须要配对使用,哪怕仅仅是获取RemoteCallbackList
中的元素个数。- 注意:
- 客户端调用远程服务的方法,被调用的方法运行在客户端的Binder线程池中,同时客户端线程会被挂起,因此调用客户端调用服务端的耗时方法时,不能在客户端的UI线程;
- 发起远程调用请求的方法需考虑ANR问题;
- 服务端远程调用客户端的方法时,被调用的方法运行在客户端的
Binder
线程池中,因此被调用的方法不能操作UI线程,需要使用Handler
将操作切换到UI线程; - 服务端的方法本身运行在服务端的
Binder
线程池中,服务端方法本身就可以执行大量耗时操作,此时无需在服务端方法中开线程进行异步任务,除非你明确知道自己在做什么。
Binder
意外死亡时重新连接服务:
- 第一种方法是给
Binder
设置DeathRecipient
监听,当Binder死亡时,我们会收到;binderDied
方法的回调,在binderDied
方法中我们可以重新连接远程服务 - 第二种方法是在
onServiceDisconnected
中重新连接远程服务; - 区别是
onServiceDisconnected
在客户端的UI线程中被回调,而binderDied
在客户端的Binder
线程池中被回调。
- 第一种方法是给
// Book.aidl
package com.slim.ipcaidlobserver;
parcelable Book;
// IOnNewBookArrivedListener.aidl
package com.slim.ipcaidlobserver;
import com.slim.ipcaidlobserver.Book;
interface IOnNewBookArrivedListener {
void onNewBookArrived(in Book newBook);
}
// IBookManager.aidl
package com.slim.ipcaidlobserver;
import com.slim.ipcaidlobserver.Book;
import com.slim.ipcaidlobserver.IOnNewBookArrivedListener;
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
void registerListener(IOnNewBookArrivedListener listener);
void unregisterListener(IOnNewBookArrivedListener listener);
}
// 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;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(bookId);
dest.writeString(bookName);
}
public static final Creator<Book> CREATOR = new Creator<Book>(){
@Override
public Book createFromParcel(Parcel source) {
return new Book(source);
}
@Override
public Book[] newArray(int size) {
return new Book[size];
}
};
private Book(Parcel source) {
bookId = source.readInt();
bookName = source.readString();
}
@Override
public String toString() {
return "Book{" +
"bookId=" + bookId +
", bookName='" + bookName + '\'' +
'}';
}
}
// BookManagerService.java
public class BookManagerService extends Service {
private static final String TAG = "BookManagerService";
private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();
private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList = new RemoteCallbackList<>();
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);
Log.d(TAG, "书籍添加成功");
onNewBookArrived(book);
}
@Override
public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
mListenerList.register(listener);
}
@Override
public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
mListenerList.unregister(listener);
}
};
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
public void onNewBookArrived(Book book) {
final int N = mListenerList.beginBroadcast();
for (int i = 0; i < N; i++) {
try {
IOnNewBookArrivedListener listener = mListenerList.getBroadcastItem(i);
if (listener != null) {
listener.onNewBookArrived(book);
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
mListenerList.finishBroadcast();
}
}
// MainActivity.java
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
private static final String TAG = "MainActivity";
private static final int MESSAGE_BOOK_ARRIVED = 1;
private EditText mEtBookId;
private EditText mEtBookName;
private Button mBtBookAdd;
private Button mBtBookList;
private Button mBtBind;
private Button mBtUnbind;
private Button mBtRegister;
private Button mBtUnregister;
private IBookManager mBookManager;
private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MESSAGE_BOOK_ARRIVED:
Log.d(TAG, "receive new book: " + msg.obj);
break;
default:
break;
}
}
};
private IOnNewBookArrivedListener mListener = new IOnNewBookArrivedListener.Stub() {
@Override
public void onNewBookArrived(Book newBook) throws RemoteException {
mHandler.obtainMessage(MESSAGE_BOOK_ARRIVED, newBook).sendToTarget();
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mEtBookId = (EditText) findViewById(R.id.et_book_id);
mEtBookName = (EditText) findViewById(R.id.et_book_name);
mBtBookAdd = (Button) findViewById(R.id.bt_book_add);
mBtBookList = (Button) findViewById(R.id.bt_book_list);
mBtBind = (Button) findViewById(R.id.bt_bind);
mBtUnbind = (Button) findViewById(R.id.bt_unbind);
mBtRegister = (Button) findViewById(R.id.bt_register);
mBtUnregister = (Button) findViewById(R.id.bt_unregister);
mBtBookAdd.setOnClickListener(this);
mBtBookList.setOnClickListener(this);
mBtBind.setOnClickListener(this);
mBtUnbind.setOnClickListener(this);
mBtRegister.setOnClickListener(this);
mBtUnregister.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.bt_book_add:
addBook();
break;
case R.id.bt_book_list:
printBookList();
break;
case R.id.bt_bind:
bind();
break;
case R.id.bt_unbind:
unbind();
break;
case R.id.bt_register:
register();
break;
case R.id.bt_unregister:
unregister();
break;
default:
break;
}
}
public void addBook() {
if (mBookManager == null) { return; }
try {
int bookId = Integer.parseInt(mEtBookId.getText().toString());
String bookName = mEtBookName.getText().toString();
mBookManager.addBook(new Book(bookId, bookName));
} catch (RemoteException | NumberFormatException e) {
e.printStackTrace();
}
}
public void printBookList(){
if (mBookManager == null) { return; }
List<Book> bookList;
try {
bookList = mBookManager.getBookList();
Log.d(TAG, "query book list: " + bookList.toString());
} catch (RemoteException e) {
e.printStackTrace();
}
}
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d(TAG, "onServiceConnected: 服务已绑定");
mBookManager = IBookManager.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
mBookManager = null;
// 服务意外死亡时执行此方法,在此处设置重新连接至服务
}
};
public void bind() {
Intent intent = new Intent(this, BookManagerService.class);
bindService(intent, mConnection, BIND_AUTO_CREATE);
}
public void unbind() {
unbindService(mConnection);
}
public void register(){
try {
mBookManager.registerListener(mListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
public void unregister(){
try {
mBookManager.unregisterListener(mListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
IPC方式的优缺点和适用场景
名称 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
Bundle | 简单易用 | 只能传输Bundle支持的数据类型 | 四大组件间的进程间通信 |
文件共享 | 简单易用 | 不适合高并发场景,并且无法做到进程间的即时通信 | 无并发访问情形,交换简单的数据实时性不高的场景 |
AIDL | 功能强大,支持一对多并发通信,支持实时通信 | 使用稍复杂,需要处理好线程同步 | 一对多通信且有RPC需求 |
Messenger | 功能一般,支持一对多串行通信,支持实时通讯 | 不能很好的处理高并发情况,不支持RPC,数据通过Messenger进行传输,因此只能传输Bundle支持的数据类型 | 低并发的一对多即时通信,无RPC需求,或无需要返回结果的RPC需求 |
ContentProvider | 在数据访问方面功能强大,支持一对多并发数据共享,可通过Call方法扩展其他操作 | 可以理解为受约束的AIDL,主要提供数据源的CRUD操作 | 一对多的进程间的数据共享 |
Socket | 功能强大,可以通过网络传输字节流,支持一对多并发实时通讯 | 实现细节稍微有点繁琐,不支持直接的RPC | 网络数据交换 |