目录
1.每日一句
生活不是为了赶路,而是为了感受路
2. 作者简介
🏡个人主页:XiaoChen
📚学习专栏:Android
🕒发布日期:2022/9/27
本文是基于Android开发艺术探索中IPC机制所写,就当做做笔记啦
3. Android中的几种IPC方式
3.1 使用Bundle
在Android中,四大组件中的三者(Activity、Service、Receiver)都是支持在Intent中传递Bundle数据的,Bundle本身实现了Parcelable接口,所以它可以方便的在不同的进程间传输。基于这一点,当我们在一个进程中启动了另一个进程的Activity、Service和Receiver,我们就可以在Bundle中附加我们需要传输给远程进程的信息并通过Intent发送出去。当然我们传输的数据必须能够被序列化,比如基本类型,实现了Parcelable接口的对象、实现了Serializable接口的对象以及一些Android支持的特殊对象,具体内容可以看Bundle这个类,就可以看到所有它支持的类型。这是最简单的一种进程间通信方式。
3.2 使用文件共享
两个进程通过读/写同一个文件夹来交换数据,比如A进程把数据写入文件B, B进程通过读取这个文件来获取数据。我们知道在Windows系统上,一个文件如果被加了排斥锁将会导致其他线程无法对其进行访问,包括读写,但是Android是基于Linux系统的,这样并发读/写文件可以没限制的进行,甚至两个进程对同一个文件进行写操作都是允许的,尽管这可能会出现问题。通过文件交换数据,除了可以交换一些文本信息外,我们还可以序列化一个对象到文件系统中的同时从另一个进程恢复这个对象,下面将进行这种方法的展示
第一个Activity:
private void persistToFile(final Serializable serializable){
new Thread(new Runnable() {
@Override
public void run() {
File file = new File(FILE_PATH);
ObjectOutputStream objectOutputStream = null;
try {
objectOutputStream = new ObjectOutputStream(new FileOutputStream(file));
objectOutputStream.writeObject(serializable);
Log.d("TAG1" , serializable.toString());
} catch (IOException e) {
e.printStackTrace();
} finally {
if (objectOutputStream != null) {
try {
objectOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}).start();
}
第二个Activity:
public static void recoverFromFile() {
new Thread(new Runnable() {
@Override
public void run() {
Serializable serializable = null;
File file = new File(FILE_PATH);
ObjectInputStream objectInputStream = null;
try {
objectInputStream = new ObjectInputStream(new FileInputStream(file));
serializable = (Serializable) objectInputStream.readObject();
Log.d("TAG2" , serializable.toString());
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
if (objectInputStream != null) {
try {
objectInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}).start();
}
通过文件共享的方式也是有局限性的,比如并发读/写问题,像上面那个例子,如果是并发读/写,那我们读出的内容可能就不是最新的了,如果是并发写那么问题将会更加严重,文件共享方式适合在对数据同步要求不高的进程间进行通信,并且要处理好并发读/写问题。
3.3 使用Messenger
Messenger是一种轻量级的IPC方案,它的底层实现是AIDL,通过它可以在不同进程中传递Message对象,在Message中放入我们需要传递的数据,就能轻松地实现数据在进程间的传递。 Messenger的使用方法很简单,它对AIDL做了封装,使得我们可以很方便地进行进程间通信。同时由于它一次处理一个请求,也就是说服务端中不存在并发的情形,所以我们不用考虑线程同步的问题。
实现Messenger的几个步骤:
- 服务端进程:首先就是在服务端创建一个Service来处理客户端的连接请求,同时创建一个Handler并通过它来创建一个Messenger对象,然后再Service的onBind方法中返回这个Messenger对象底层的Binder即可
- 客户端进程:首先就是要绑定服务端的Service,绑定好了之后用服务端返回的IBinder对象创建一个Messenger,通过这个Messenger就可以向服务端发送消息了,消息的类型为Message对象。但是如果要想服务端能回应客户端的消息,那么我们还需要创建一个Handler对象,并同时创建一个新的Messenger,并把这个Messenger对象通过Message的replyTo参数传递给服务端,服务端通过这个replyTo参数就可以回应客户端了
接下来看代码实现:
首先看服务端,
创建一个静态内部类MessengerHandler ,接收并处理客户端消息,利用MessengerHandler 创建一个Messenger, 并在onBind 方法里返回Messenger里面的Binder对象;
同时使用msg.replyTo 的Messenger 给客户端回复一个Message,告知客户端已接收到消息。
import android.app.Service;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.gms.vision.clearcut.LogUtils;
public class MessengerService extends Service {
//处理客户端发来的消息
private static class MessengerHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case Constant.MSG_FROMCLIENT:
//接受客户端消息
Bundle bundle = msg.getData();
Log.i("MessengerService", "从客户端收到的消息:" + bundle.getString("msg"));
//通知客户端消息已收到(发送消息给客户端)
Messenger replyTo = msg.replyTo;
Message replyMessage = Message.obtain(null, Constant.MSG_FROMSERVICE);
Bundle data = new Bundle();
data.putString("reply", "你好客户端, 信息已收到,感谢。");
replyMessage.setData(data);
try {
replyTo.send(replyMessage);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
default:
break;
}
super.handleMessage(msg);
}
}
private Messenger messenger = new Messenger(new MessengerHandler());
@Override
public IBinder onBind(Intent intent) {
//messenger将消息传递给MessengerHandler处理
return messenger.getBinder();
}
}
此时需要注册Service并运行在单独的进程中
<service
android:name="com.example.fileshare.MessengerService"
android:process=":remote">
</service>
接下来看客户端的实现,客户端首先要绑定远程进程的MessengerService,绑定成功后,根据服务端返回的binder对象创建Messenger对象并使用此对象向服务端发送消息
import androidx.appcompat.app.AppCompatActivity;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.util.Log;
public class MessengerActivity extends Activity {
private static final String TAG = "MessengerActivity";
private Messenger mService;
private ServiceConnection myConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mService = new Messenger(service);
Log.i("bind" , "服务器绑定成功");
Message msg = Message.obtain(null , Constant.MSG_FROMCLIENT);
Bundle data = new Bundle();
data.putString("msg" , "你好,我是客户端。");
msg.setData(data);
msg.replyTo = replyMessenger;
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 , myConnection , Context.BIND_AUTO_CREATE);
}
private static class ServiceReplyMessengerHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case Constant.MSG_FROMSERVICE:
//接受客户端回复
Log.i("replyFromService","receive from service:" + msg.getData().getString("reply") );
break;
default:
super.handleMessage(msg);
}
}
}
private Messenger replyMessenger = new Messenger(new ServiceReplyMessengerHandler());
@Override
protected void onDestroy() {
unbindService(myConnection);
super.onDestroy();
}
}
到这里工作就都已经完成了,接下来让我们通过log来看看进程间的通信:
在Messenger中传递数据必须将数据放入Message中,Messenger 和Message都实现了Parcelable 接口,因此可以进行跨进程通信。也就是说,Message 支持的数据类型就是Messenger 支持的传输类型。
在Android 2.2以前,msg.obj字段不支持跨进程传输,Android 2.2之后,也只是系统提供的实现了Parcelable 接口的对象才能通过它跨进程传输,我们自己定义的Parcelable 对象是无法通过object字段传输的,这导致object 字段的实用性大大降低,所幸我们还有Bundle 字段。
下面给出一张Messenger的工作原理图,以便更好地理解Messenger,如图所示:
3.4 使用AIDL
通过上面的阅读,我们能看到Messenger 是以串行的方式处理客户端发来的请求,如果客户端同时发送大量并发请求,使用Messenger就不太合适了。同时,Messenger主要是为了跨进程传输数据,如果想实现跨进程调用服务端方法,Messenger 无法做到,我们可以使用AIDL来实现跨进程的方法调用。下面,我们从服务端和客户端两个方面介绍AIDL的使用。
3.4.1 服务端
创建Service监听连接请求,创建AIDL文件,在文件中声明要暴露给客户端的接口,在Service中实现这些接口。
3.4.2 客户端
首先绑定服务端的Service,成功后将服务端返回的Binder转化为AIDL接口所属类型,然后就可以调用AIDL中的方法了
3.4.3 AIDL接口的创建
首先,创建AIDL接口,接口里面声明两个接口方法,作用分别是获取图书列表和添加图书:
// IBookManager.aidl
package com.ryg.chapter_2.aidl;
// Declare any non-default types here with import statements
import com.ryg.chapter_2.aidl.Book;
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
}
AIDL文件支持的类型:
- Java基本数据类型
- String和 CharSequence
- List:只支持ArrayList,里面的每个元素必须能够被AIDL支持
- Map:只支持HashMap,里面的每个元素必须能够被AIDL支持,包括key和value
- Parcelable :所有实现了Parcelable 接口的对象
- AIDL:所有的AIDL接口本身也可以在AIDL文件中使用
以上AIDL支持的类型中,
自定义的Parcelable 对象和AIDL对象必须显示地import进来,例如IBookManager 中使用Book类必须声明 "import com.ryg.chapter_2.aidl.Book;"。
另一个需要注意的地方是,如果AIDL文件中使用了自定义的Parcelable 对象,那么必须创建一个与该对象同名的AIDL文件,并在其中声明它是Parcelable 类型,我们为Book类创建Book.aidl文件:
// Book.aidl
package com.ryg.chapter_2.aidl;
// Declare any non-default types here with import statements
parcelable Book;
还有一个地方需要注意,AIDL中除基本类型之外的其他类型的参数都必须指定方向:in(输入型参数)、out(输出型参数)或inout(输入输出型参数)。AIDL接口中只支持方法,不支持声明静态常量,这点有别于传统接口。
3.4.4 远程服务端Service的实现
首先创建一个BookManagerService,并在其中实现我们定义的AIDL接口:
package com.example.ipc;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import androidx.annotation.Nullable;
import com.ryg.chapter_2.aidl.Book;
import com.ryg.chapter_2.aidl.IBookManager;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
public class BookManagerService extends Service {
private static final String TAG = "BMS";
private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();
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 , "Android开源艺术探索"));
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
接着我们需要在AndroidManifest.xml中注册BookManagerService ,让他运行在独立进程中。
<service android:name=".BookManagerService"
android:process=":remote">
</service>
3.4.5 客户端的实现
客户端的实现就比较简单一些,首先要绑定远程服务,绑定成功后将服务端返回的Binder对象转换成AIDL接口,然后就可以通过这个接口去调用远程服务端的远程方法了
package com.example.ipc;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import com.ryg.chapter_2.aidl.Book;
import com.ryg.chapter_2.aidl.IBookManager;
import java.util.List;
public class BookManagerActivity extends AppCompatActivity {
private static final String TAG = "BookManagerActivity";
private ServiceConnection myConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
IBookManager bookManager = IBookManager.Stub.asInterface(service);
try {
List<Book> list = bookManager.getBookList();
Log.i(TAG , "query book list,list type:" + list.getClass().getCanonicalName());
for (Book ls : list){
Log.i(TAG , "书籍:"+ls.toString());
}
Book newBook = new Book(3 , "JavaEE");
bookManager.addBook(newBook);
Log.i(TAG , "添加新书后列表:" + bookManager.getBookList().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_bookmanager);
Intent intent = new Intent(this , BookManagerService.class);
bindService(intent , myConnection , Context.BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
unbindService(myConnection);
super.onDestroy();
}
}
接着再调用另一个接口addBook,添加一本新书,最后查看log发现成功的向服务端添加了一本JavaEE新书
到此为止,我们已经成功完整地使用了 AIDL进行了一次跨进程通信
假设用户提出一个新的需求:要求有新书增加的时候服务端自动通知用户,而不需要用户自己去主动获取图书信息。这种情形在我们的日常开发中很常见,我们很容易想到观察者模式,接下来我们就试着简单实现一下这个需求。
首先我们新建一个IOnNewBookArrivedListener .aidl 用于监听"新增新书",用户通过注册这个接口来申请新书提醒功能。
// IOnNewBookArrivedListener.aidl
package com.ryg.chapter_2.aidl;
// Declare any non-default types here with import statements
import com.ryg.chapter_2.aidl.Book;
interface IOnNewBookArrivedListener {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void onNewBookArrived(in Book newBook);
}
接着在IBookManager 中新增注册与解除注册方法,并且在服务端实现这两个方法:
// IBookManager.aidl
package com.ryg.chapter_2.aidl;
// Declare any non-default types here with import statements
import com.ryg.chapter_2.aidl.Book;
import com.ryg.chapter_2.aidl.IOnNewBookArrivedListener;
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
void registerListener(IOnNewBookArrivedListener listener);
void unregisterListener(IOnNewBookArrivedListener listener);
}
服务端:
package com.example.ipc;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import androidx.annotation.Nullable;
import com.ryg.chapter_2.aidl.Book;
import com.ryg.chapter_2.aidl.IBookManager;
import com.ryg.chapter_2.aidl.IOnNewBookArrivedListener;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
public class BookManagerService extends Service {
private static final String TAG = "BMS";
private AtomicBoolean mIsServiceDestroyed = new AtomicBoolean(false);
private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();
private CopyOnWriteArrayList<IOnNewBookArrivedListener> myListener = new CopyOnWriteArrayList<>();
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(com.ryg.chapter_2.aidl.IOnNewBookArrivedListener listener) throws RemoteException {
if(!myListener.contains(listener)){
myListener.add(listener);
}else {
Log.i(TAG , "已经存在");
}
Log.i(TAG , "registerListener , size:" + myListener.size());
}
@Override
public void unregisterListener(com.ryg.chapter_2.aidl.IOnNewBookArrivedListener listener) throws RemoteException {
if(!myListener.contains(listener)){
myListener.remove(listener);
Log.i(TAG , "取消注册成功");
}else {
Log.i(TAG , "没有注册者,不能取消注册");
}
Log.i(TAG , "unregisterListener , size:" + myListener.size());
}
};
@Override
public void onCreate() {
super.onCreate();
mBookList.add(new Book(1 , "Android"));
mBookList.add(new Book(2 , "Android开源艺术探索"));
new Thread(new ServiceWorker()).start();
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
@Override
public void onDestroy() {
mIsServiceDestroyed.set(true);
super.onDestroy();
}
private void onNewBookArrived(Book book) throws RemoteException{
mBookList.add(book);
Log.i(TAG , "onNewBookArrived,notify listeners:" + myListener.size());
for(int i = 0 ; i < myListener.size() ; i++){
IOnNewBookArrivedListener listener = myListener.get(i);
Log.i(TAG , "onNewBookArrived,notify listener:" + listener);
listener.onNewBookArrived(book);
}
}
private class ServiceWorker implements Runnable{
@Override
public void run() {
while(!mIsServiceDestroyed.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();
}
}
}
}
}
最后我们在客户端上自定义一个IOnNewBookArrivedListener 接口,并且将它注册到服务端接口。
客户端:
package com.example.ipc;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.RemoteException;
import android.util.Log;
import com.ryg.chapter_2.aidl.Book;
import com.ryg.chapter_2.aidl.IBookManager;
import com.ryg.chapter_2.aidl.IOnNewBookArrivedListener;
import java.util.List;
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 handler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case MESSAGE_NEW_BOOK_ARRIVED:
Log.i(TAG , "收到新书:" + msg.obj);
break;
default:
super.handleMessage(msg);
}
}
};
private ServiceConnection myConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
IBookManager bookManager = IBookManager.Stub.asInterface(service);
try {
mRemoteBookManager = bookManager;
List<Book> list = bookManager.getBookList();
Log.i(TAG , "query book list,list type:" + list.getClass().getCanonicalName());
for (Book ls : list){
Log.i(TAG , "书籍:"+ls.toString());
}
Book newBook = new Book(3 , "JavaEE");
bookManager.addBook(newBook);
for (Book ls : list){
Log.i(TAG , "书籍:"+ls.toString());
}
bookManager.registerListener(mOnNewBookArrivedListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
mRemoteBookManager = null;
Log.e(TAG , "binder died");
}
};
private IOnNewBookArrivedListener mOnNewBookArrivedListener = new IOnNewBookArrivedListener.Stub() {
@Override
public void onNewBookArrived(Book newBook) throws RemoteException {
handler.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED , newBook).sendToTarget();
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_bookmanager);
Intent intent = new Intent(this , BookManagerService.class);
bindService(intent , myConnection , Context.BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
if(mRemoteBookManager != null && mRemoteBookManager.asBinder().isBinderAlive()){
try {
Log.i(TAG , "解除注册:" + mOnNewBookArrivedListener);
mRemoteBookManager.unregisterListener(mOnNewBookArrivedListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
unbindService(myConnection);
super.onDestroy();
}
}
可以发现每隔五秒会新增一本新书
当按back键退出后,会在onDestroy方法中自动解除注册到服务端的listener,相当于我们不再接受提醒了,下面是打印的日志:
从上面的日志可以看出,注册者还有一个,也就是说解除注册的过程中,服务端无法找到我们之前注册的那个listener,但是我们在注册和解注册的时候明明就是传的同一个listener,所以最终解除注册失败!
但是仔细想想,好像这种方法确实无法完成反注册功能,因为对象的跨进程传输本质上都是反序列化的过程,对象通过Binder传递到客户端后,得到的对象将会是一个全新的对象,自然也就无法完成反注册过程。
那么我们到底该怎么才能实现解注册功能呢?答案就是使用RemoteCallbackList,请看下面的分析:
RemoteCallbackList使用起来很方便,我们首先用它代替现有的CopyOnWriteArrayList,然后直接在相应位置调用它的register和unRegister方法,我们看看核心代码。
它是系统专门提供的用于删除跨进程listener的接口。它是一个泛型,支持管理任意的AIDL接口。
我们首先用它代替现有的CopyOnWriteArrayList,然后直接在相应位置调用它的register和unRegister方法
接着就是给所有注册了通知的客户端发送通知,RemoteCallbackList 的遍历方式很有意思,我们必须按照以下方式遍历RemoteCallbackList ,而且beginBroadcast和finishBroadcast必须成对使用,否则程序会出错。
private RemoteCallbackList<IOnNewBookArrivedListener> mListener = new RemoteCallbackList<>();
@Override
public void registerListener(com.ryg.chapter_2.aidl.IOnNewBookArrivedListener listener) throws RemoteException {
mListener.register(listener);
}
@Override
public void unregisterListener(com.ryg.chapter_2.aidl.IOnNewBookArrivedListener listener) throws RemoteException {
mListener.unregister(listener);
}
从结果来看,可以发现,已经解除注册成功了 ,RemoteCallbackList的确可以完成跨进程的解注册功能。
3.5 使用ContentProvider
ContentProvider 是Android中提供的专门用于不同进程间数据共享的方式,ContentProvider作为Android中的四大组件之一,可见它在Android中是比较重要的。它的底层实现同样是Binder,但是它的使用过程比AIDL方便得多,因为系统为我们做了封装,使得我们不需要关心底层细节就可以轻松实现进程间通信。
系统也为开发者提供了很多内置的ContentProvider ,例如通讯录、相册信息等,要跨进程访问这些数据,我们就必须先了解ContentProvider 的创建以及使用规则。
推荐阅读: