2.4 Android 中的 IPC 方式
2.4.1 使用 Bundle
Bundle 实现了 Parcelable 接口,可以通过 Intent 传递数据,Intent有一个成员变量Bundle mExtras存储自己的数据, 而Bundle实际上就是一个ArrayMap, 以键值对的方式保存着各种类型的数据。
2.4.2 使用文件共享
典型例子是 SharedPreferences , 它通过键值对的方式在 xml 文件中存储数据,文件位于 /data/data/package_name/shared_prefs目录下. 如果应用是多进程模式的, 不同进程就会读写同一份 xml 文件,也就实现了跨进程通信。 但由于 SharedPreferences 有缓存策略, 即在内存中有一份SharedPreferences。因此多进程下容易发生数据丢失, 因此不建议采用 SharedPreferences 实现跨进程通信。
多个进程对同一份文件进行读写,文件共享方式适合在对数据同步要求不高的进程之间进行通信。
2.4.3 使用 Messenger
- Messenger(信使),通过 Messenger 可以在不同进程中传递消息(Message)。
- Messenger 是一种轻量级的 IPC 方法,底层实现是 AIDL。
public Messenger(Handler target) {
mTarget = target.getIMessenger();
}
//Stub.asInterface 就是 AIDL 中的
public Messenger(IBinder target) {
mTarget = IMessenger.Stub.asInterface(target);
}
使用方法很简单,由于它一次处理一个请求,所以服务端我们不同考虑线程同步问题。
1.服务端进程
public class MessengerService extends Service {
private static final String TAG = "MessengerService";
private final Messenger mMessenger = new Messenger(new MessengerHandler());
private static class MessengerHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MyConstants.MSG_FROM_CLIENT:
Log.d(TAG, "消息来自客户端" + msg.getData().getString("msg"));
Messenger client = msg.replyTo;
Message replyMessage = Message.obtain(null, MyConstants.MSG_FROM_SERVICE);
Bundle data = new Bundle();
data.putString("reply", "你好,我是服务端!");
replyMessage.setData(data);
try {
client.send(replyMessage);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
default:
break;
}
super.handleMessage(msg);
}
}
@Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
}
2.客服端进程
public class MainActivity extends AppCompatActivity {
private Messenger mService;
private static final String TAG = "MessengerService";
private Messenger mGetService = new Messenger(new MessengerHandle());
@SuppressLint("HandlerLeak")
private static class MessengerHandle extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MyConstants.MSG_FROM_SERVICE:
Bundle data = msg.getData();
Log.d(TAG, data.getString("reply"));
break;
default:
break;
}
super.handleMessage(msg);
}
}
private ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d(TAG, "链接成功");
// mService 是个信使
mService = new Messenger(service);
// msg 是一个消息,data 是消息内容
Message msg = Message.obtain(null, MyConstants.MSG_FROM_CLIENT);
Bundle data = new Bundle();
data.putString("msg", "你好,服务端");
msg.setData(data);
msg.replyTo = mGetService;
try {
// 信使传递消息。
mService.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.d(TAG, "onServiceDisconnected");
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent(this, MessengerService.class);
//绑定服务
bindService(intent, conn, Context.BIND_AUTO_CREATE);//绑定服务
}
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(conn);//取消绑定
}
}
2.4.4 使用 AIDL
AIDL 支持的类型 :
- 基本数据类型;
- String 和 CharSequence;
- List:只支持 ArrayList,里面每个元素都必须能够被 AIDL 支持;
- Map:只支持 HashMap,里面的每个元素都必须被 AIDL 支持,包括 key 和 value;
- Parcelable:所有实现了 Parcelable 接口的对象;
- AIDL :所有的 AIDL 接口本身也可以在 AIDL 文件中使用。
其中自定义的 Parcelable 对象和 AIDL 对象必须要显示 import 进来,如果 AIDL 文件中用到了自定义的 Parcelabale 对象,那么必须新建一个和它同名的 AIDL 文件。
使用案例来演示 AIDL
1.AIDL文件: Book .aidl、IBookManager .aidl、IOnNewBookArrivedListener .aidl
package com.kjn.chaptermessenger.aidl;
import com.kjn.chaptermessenger.aidl.Book;
parcelable Book ;
package com.kjn.chaptermessenger.aidl;
import com.kjn.chaptermessenger.aidl.Book;
import com.kjn.chaptermessenger.aidl.IOnNewBookArrivedListener;
interface IBookManager {
List getBookList();
void addBook(in Book book);
void registerListener(IOnNewBookArrivedListener listener);
void unregisterListener(IOnNewBookArrivedListener listener);
}
package com.kjn.chaptermessenger.aidl;
import com.kjn.chaptermessenger.aidl.Book;
interface IOnNewBookArrivedListener {
void onNewBookArrived(in Book newBook);
}
2.客户端:BookManagerActivity
public class BookManagerActivity extends AppCompatActivity {
private static final String TAG = "BookManagerActivity";
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.d(TAG, "收到新书:" + msg.obj);
break;
default:
super.handleMessage(msg);
}
}
};
private ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
IBookManager bookManager = IBookManager.Stub.asInterface(service);
mRemoteBookManager = bookManager;
try {
//设置 binder 死亡代理
mRemoteBookManager.asBinder().linkToDeath(mDeathRecipient, 0);
List list = bookManager.getBookList();
Log.d(TAG, "查询图书列表,列表类型: " + list.getClass().getCanonicalName());
Log.d(TAG, "查询图书列表: " + list.toString());
Book book = new Book(3, "Android 开发艺术探索");
bookManager.addBook(book);
Log.d(TAG, "new book:" + book);
List newList = bookManager.getBookList();
Log.d(TAG, "查询图书列表: " + newList.toString());
//注册新书到达监听器
bookManager.registerListener(mOnNewBookArrivedListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
mRemoteBookManager = null;
Log.d(TAG, "binder died.");
}
};
//Binder 链接断裂监听
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
Log.d(TAG, "binder died. tname:" + Thread.currentThread().getName());
if (mRemoteBookManager == null) {
return;
}
mRemoteBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);
mRemoteBookManager = null;
//TODO:这里重新绑定 Service
}
};
//新书到达监听器
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, conn, Context.BIND_AUTO_CREATE);
}
public void checkIt(View view) {
}
@Override
protected void onDestroy() {
if (mRemoteBookManager != null && mRemoteBookManager.asBinder().isBinderAlive()) {
Log.d(TAG, "onDestroy: " + mOnNewBookArrivedListener);
try {
mRemoteBookManager.unregisterListener(mOnNewBookArrivedListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
unbindService(conn);
super.onDestroy();
}
}
3.服务端:BookManagerService
public class BookManagerService extends Service {
private static final String TAG = "BookManagerService";
// 原子操作,保证在多线程中不是出现意外的变化
private AtomicBoolean mIsServiceDestoryed = new AtomicBoolean(false);
// CopyOnWriteArrayList 支持并发写
private CopyOnWriteArrayList mBookList = new CopyOnWriteArrayList<>();
// RemoteCallbackList 专门用于跨进程删除监听
private RemoteCallbackList mListenerList = new RemoteCallbackList<>();
//mBinder 对象获得来自 AIDL
private Binder mBinder = new IBookManager.Stub() {
//获取图书列表
@Override
public List getBookList() throws RemoteException {
return mBookList;
}
//添加图书
@Override
public void addBook(Book book) throws RemoteException {
mBookList.add(book);
}
//注册监听器:监听新书到来通知客户端
@Override
public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
mListenerList.register(listener);
listenerSize();
}
//注销监听器
@Override
public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
boolean success = mListenerList.unregister(listener);
if (success) {
Log.d(TAG, "注销成功.");
} else {
Log.d(TAG, "没有找到,不能注销.");
}
listenerSize();
}
// onTransact 方法会被 getBookList()、addBook(Book book)、registerListener和unregisterListener调用
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
int check = checkCallingOrSelfPermission("com.kjn.chaptermessenger.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 != null && !packageName.startsWith("com.kjn")) {
return false;
}
return super.onTransact(code, data, reply, flags);
}
};
// N 的获取要调用 beginBroadcast()和finishBroadcast() 两个对应方法。
private void listenerSize() {
final int N = mListenerList.beginBroadcast();
mListenerList.finishBroadcast();
Log.d(TAG, "registerListener, current size:" + N);
}
//新书到达时,通知监听器
public void onNewBookArrived(Book book) throws RemoteException {
mBookList.add(book);
Log.d(TAG, "新书到达时,通知监听器:" + mListenerList);
final int N = mListenerList.beginBroadcast();
for (int i = 0; i < N; i++) {
IOnNewBookArrivedListener listener = mListenerList.getBroadcastItem(i);
Log.d(TAG, "新书到达时,通知监听器:" + listener);
if (listener != null) {
listener.onNewBookArrived(book);
}
}
mListenerList.finishBroadcast();
}
//耗时5秒发送新书通知
private class ServiceWorker implements Runnable {
@Override
public void run() {
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();
}
}
}
}
@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() {
super.onDestroy();
mIsServiceDestoryed.set(true);
}
}
4.AndroidManifest 中设置权限和多进程
或者
...
代码多,含义写法都写在代码注释中,这里讲上述代码实现的步骤
- 远程服务端 Service 的实现,完成 mBinder 的返回,并在 onCreate 中添加两本书。
- 客户端的实现,绑定 Service,并获取书籍列表。
- Service 中完成注册绑定新书监听器,添加新书 ServiceWorker 的操作。
- 客户端注册新书监听器,用 handler 完成接收。
- 设置权限验证和死亡代理。
2.4.5 使用 ContentProvider
ContentProvider 是 Android 中提供的专门用于不同应用间进行数据共享的方式。和 Messenger 一样,ContentProvider 的底层同样也是 Binder。
系统预置了许多 ContentProvider,比如通讯录信息、日程信息表等,只要通过 ContentReslover 的 query、update、insert 和 delete 方法即可。
2.4.6 使用 Socket
Scoket 也称为“套接字”,是网络通信中的概念。
- 流式套接字:网络的传输控制层中的 TCP;
- TCP 协议是面向连接的协议,提供稳定的双向通信功能,TCP链接的建立需要经过 “三次握手” 才能完成,本身提供了超时重传机制,因此具有很高的稳定性;
- 用户数据报套接字:网络的传输控制层中的 UDP协议;
- UDP 是无链接的,提供不稳定的单向通信功能,也可以实现双向通信功能,但是不稳定不推荐。
下面一个简单的演示案例,敲一边,梳理一下就清楚 如何 用Socket 来进程间通信的实例
客户端代码
public class TCPClientActivity extends Activity implements View.OnClickListener {
private static final int MESSAGE_RECEIVE_NEW_MSG = 1;
private static final int MESSAGE_SOCKET_CONNECTED = 2;
private Button mSendButton;
private TextView mMessageTextView;
private EditText mMessageEditText;
private PrintWriter mPrintWriter;
private Socket mClientSocket;
@SuppressLint("HandlerLeak")
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {//接收到新消息
case MESSAGE_RECEIVE_NEW_MSG: {
mMessageTextView.setText(mMessageTextView.getText()
+ (String) msg.obj);
break;
}//链接失败
case MESSAGE_SOCKET_CONNECTED: {
mSendButton.setEnabled(true);
break;
}
default:
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tcpclient);
mMessageTextView = (TextView) findViewById(R.id.msg_container);
mSendButton = (Button) findViewById(R.id.send);
mSendButton.setOnClickListener(this);
mMessageEditText = (EditText) findViewById(R.id.msg);
Intent service = new Intent(this, TCPServerService.class);
startService(service);
new Thread() {
@Override
public void run() {
connectTCPServer();
}
}.start();
}
@Override
protected void onDestroy() {
if (mClientSocket != null) {
try {
mClientSocket.shutdownInput();
mClientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
super.onDestroy();
}
@Override
public void onClick(View v) {
if (v == mSendButton) {
final String msg = mMessageEditText.getText().toString();
if (!TextUtils.isEmpty(msg) && mPrintWriter != null) {
mPrintWriter.println(msg);
mMessageEditText.setText("");
String time = formatDateTime(System.currentTimeMillis());
final String showedMsg = "self " + time + ":" + msg + "\n";
mMessageTextView.setText(mMessageTextView.getText() + showedMsg);
}
}
}
@SuppressLint("SimpleDateFormat")
private String formatDateTime(long time) {
return new SimpleDateFormat("(HH:mm:ss)").format(new Date(time));
}
private void connectTCPServer() {
Socket socket = null;
//超市重连策略:socket 为null 时,不停的重连
while (socket == null) {
try {
socket = new Socket("localhost", 8688);
mClientSocket = socket;
mPrintWriter = new PrintWriter(new BufferedWriter(
new OutputStreamWriter(socket.getOutputStream())), true);
mHandler.sendEmptyMessage(MESSAGE_SOCKET_CONNECTED);
System.out.println("connect server success");
} catch (IOException e) {
SystemClock.sleep(1000);
System.out.println("connect tcp server failed, retry...");
}
}
try {
// 接收服务器端的消息
//这是通信编程,clientSocket.getInputStream()获取服务端传来的字节流、
//在用InputStreamReader(类是从字节流到字符流的桥梁)在缓存到BufferedReader缓冲区
BufferedReader br = new BufferedReader(new InputStreamReader(
socket.getInputStream()));
while (!TCPClientActivity.this.isFinishing()) {
String msg = br.readLine();
System.out.println("receive :" + msg);
if (msg != null) {
String time = formatDateTime(System.currentTimeMillis());
final String showedMsg = "server " + time + ":" + msg
+ "\n";
mHandler.obtainMessage(MESSAGE_RECEIVE_NEW_MSG, showedMsg)
.sendToTarget();
}
}
System.out.println("quit...");
MyUtils.close(mPrintWriter);
MyUtils.close(br);
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
服务器
public class TCPServerService extends Service {
private boolean mIsServiceDestoryed = false;
private String[] mDefinedMessages = new String[]{
"你好啊,哈哈", "请问你叫什么名字",
"今天杭州的天气不错啊,shy",
"你知道吗,我可是可以和多个人聊天的",
"给你讲个笑话,我中五百万了。"
};
@Override
public void onCreate() {
new Thread(new TcpServer()).start();
super.onCreate();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onDestroy() {
mIsServiceDestoryed = true;
System.out.println("Service onDestroy");
super.onDestroy();
}
private class TcpServer implements Runnable {
@Override
public void run() {
ServerSocket serverSocket = null;
try {
//监听/建立本地 8688 端口
serverSocket = new ServerSocket(8688);
} catch (IOException e) {
System.out.println("建立 TCP 服务器断开失败,端口:8688");
e.printStackTrace();
return;
}
while (!mIsServiceDestoryed) {
try {
//接收客户端请求
final Socket client = serverSocket.accept();
System.out.println("接受");
new Thread() {
@Override
public void run() {
try {
responseClient(client);
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//响应客户端,把响应的信息转换成字符流传入客户端
private void responseClient(Socket client) throws IOException {
//用于接收客户端消息转换成字符流
BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));
//用于向客户端发送消息
PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(client.getOutputStream())), true);
out.println("欢迎来到聊天室");
while (!mIsServiceDestoryed) {
String str = in.readLine();
System.out.println("msg from client:" + str);
if (str == null) {
//客户端断开链接
break;
}
int i = new Random().nextInt(mDefinedMessages.length);
String msg = mDefinedMessages[i];
out.println(msg);
System.out.println("send:" + msg);
}
System.out.println("client quit.");
MyUtils.close(out);
MyUtils.close(in);
client.close();
}
}
加入权限和多进程
...
2.5 Binder 连接池
简单讲就是使用一个 Service,一个 Binder 连接池来替代多个 Service 的实现。毕竟多个服务进程会让应用看起来繁重,用户有想卸载的欲望。
源码链接
- 服务端 onBind 返回 BinderPool 的 IBinder 实例,它实现了 queryBinder 方法,这个接口能够根据业务模块的特征来返回相应的 Binder 对象给它们,不同的业务模块拿到所需的 Binder 对象后就可以进行远程方法调用了。
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "onBind");
return new BinderPool.BinderPoolImpl();
}
- Binder 连接池的主要作用就是将每个业务模块的 Binder 请求统一转发到远程Service去执行,从而避免了创建多个 Service。
@Override
public IBinder queryBinder(int binderCode) throws RemoteException {
IBinder binder = null;
switch (binderCode) {
case BINDER_SECURITY_CENTER: {
binder = new SecurityCenterImpl();
break;
}
case BINDER_COMPUTE: {
binder = new ComputeImpl();
break;
}
default:
break;
}
return binder;
}
2.6 选用合适的 IPC 方式
名称 | 有点 | 缺点 | 使用场景 |
---|---|---|---|
Bundle | 简单易用 | 智能传输 Bundle 支持的数据 | 四大组件的进程间通信 |
文件共享 | 简单易用 | 不合适高并发场景,并且无法做到进程间的即时通信 | 无并发访问情形,交互含简单的数据实时性不高的场景 |
AIDL | 功能强大,支持一对多并发通信 | 使用稍微复杂,需要处理好线程同步 | 一对多通信且有 RPC 需求 |
Messenger | 功能一般,支持一对多串行通信,支持实时通信 | 不能很好处理高并发情形,不支持 RPC ,数据通过 Messager 进行传输,因此只能传输 Bundle 支持的数据类型 | 第并发的一对多即时通信,无 RPC 需求,或者无须要返回结果的 RPC 需求 |
ContentProvider | 在数据源访问方面功能强大,支持一对多并发数据共享,可通过 Call 方法扩展其他操作 | 可以理解为受约束的 AIDL,主要提供数据源的 CRUD 操作 | 一对多进程间的数据共享 |
Socket | 功能强大,可以通过网络传输字节流,支持一对多并发实时通信 | 实现细节稍微有点繁琐,不支持直接的 RPC | 网络数据交换 |