当我们在Android开发中,有时候会遇到应用因为某些原因需要采用多进程模式,此时如果要在应用内的不同进程间进行通信,就需要使用到 IPC 机制。或者是两个不同的应用需要进行数据交换,此时也一样需要依靠 Android 系统提供的 IPC机制。
关于IPC的概念这里不做过多的介绍,大家请自行学习,下面就是Android常见的IPC机制的介绍,这里先给出常见IPC机制列表:
我们开发中经常提到的四大组件(Activity,Service, Receiver)都支持在Intent中传递Bundle对象,剩下的一个组件更是天生就支持跨进程通信Content Provider。
public final class Bundle extends BaseBundle implements Cloneable, Parcelable
通过这段代码,我们也可以清楚的看到BUndle实现了Parcelable接口,这不足为奇,所以它很方便的在进程间传输,通过Intent发送出去。但是注意:我们传输的数据必须能被序列化,可以实现Parcelable,Serializable的对象, 基本类型和一些Android支持的特定对象。不支持的类型我们无法通过它在进程间传递。
顾名思义,此方式就是两个进程通过读 / 写同一个文件来交换数据,Android基于Linux,所以对于文件的读写可以并发的执行。
下面是部分测试代码的展示:
MainActivity中保存共享文件信息的代码:
private void saveSharedMessage() {
new Thread(new Runnable() {
@Override
public void run() {
message = new mMessage(001, "banzh", "我要测试多进程间通信用文件共享的方式正确完成!!!");
File sharedMessageFile = new File(getBaseContext().getFilesDir().getPath().toString() + "/SharedMessage.txt");
ObjectOutputStream out = null;
try {
out = new ObjectOutputStream(new FileOutputStream(sharedMessageFile));
out.writeObject(message);
Log.d(TAG, "run: save file: " + message.toString() +";;"+ message.toStringMsg());
runOnUiThread(new Runnable() {
@Override
public void run() {
tv_display_saveMessage.setText(message.toString() +";;"+ message.toStringMsg());
}
});
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if (out != null){
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}).start();
}
single_pro_one_act中取出共享文件信息的代码:
private void receiveSharedMessage() {
new Thread(new Runnable() {
@Override
public void run() {
File sharedFile = new File(getBaseContext().getFilesDir().getPath().toString() + "/SharedMessage.txt");
if (sharedFile.exists()){
ObjectInputStream in = null;
try {
in = new ObjectInputStream(new FileInputStream(sharedFile));
message = (mMessage) in.readObject();
Log.d(TAG, "run: receive messgae: " + message.toString() +";;"+ message.toStringMsg());
runOnUiThread(new Runnable() {
@Override
public void run() {
tv_display_sharedmessage.setText(message.toString() +";;"+ message.toStringMsg());
}
});
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}).start();
}
测试结果:
从以上结果可以看出,程序的两个活动确实运行在不同的进程中,接下来看传输信息的测试结果:
从TextView现实的结果可以看出,使用文件共享功能完成了信息的多进程传输。
三:Messenger
通过它可以在不同进程间传递Message对象。Messenger是一种轻量级的IPC机制,它的底层实现是AIDL,我们看下面一部分代码:
public Messenger(IBinder target) {
mTarget = IMessenger.Stub.asInterface(target);
}
}
...
public IBinder getBinder() {
return mTarget.asBinder();
}
...
public void writeToParcel(Parcel out, int flags) {
out.writeStrongBinder(mTarget.asBinder());
}
public static final Parcelable.Creator CREATOR
= new Parcelable.Creator() {
public Messenger createFromParcel(Parcel in) {
IBinder target = in.readStrongBinder();
return target != null ? new Messenger(target) : null;
}
public Messenger[] newArray(int size) {
return new Messenger[size];
}
};
...
相信了解AIDL的读者已经看出了AIDL的痕迹,所以Messenger是对AIDL做了封装,使得我们更简单的进行进程间通信。
下面是部分相关代码:
单独进程的Service:
private final Messenger mMessenger = new Messenger(new MessengerHandler());
...
@Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
private static class MessengerHandler extends Handler{
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
switch (msg.what){
case myContents.MSG_FROM_CLIENT:
Log.d(TAG, "handleMessage: 我收到了远程客户端的信息!!!");
Log.d(TAG, "handleMessage: receive msg from client: " + msg.getData().getString("msg"));
Messenger client = msg.replyTo;
Message replyMessage = Message.obtain(null, myContents.MSG_FROM_SERVICE);
Bundle bundle = new Bundle();
bundle.putString("reply", "收到客户端(你)的消息后,自动返回的消息");
replyMessage.setData(bundle);
try{
client.send(replyMessage);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
default:
super.handleMessage(msg);
}
}
}
MainActivity客户端的相关部分代码:
private Messenger mGetReplyMessenger = new Messenger(new MessengerHandler());
private ServiceConnection remoteConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mService = new Messenger(service);
Message message = Message.obtain(null, myContents.MSG_FROM_CLIENT);
Bundle data = new Bundle();
data.putString("msg", "我是远程客户端发来的请求消息!!!");
message.setData(data);
message.replyTo = mGetReplyMessenger;
try {
mService.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
}
...
private static class MessengerHandler extends Handler{
@Override
public void handleMessage(@NonNull Message msg) {
switch (msg.what){
case myContents.MSG_FROM_SERVICE:
Log.d(TAG, "handleMessage: msg from service = " + msg.getData().getString("reply"));
break;
default:
super.handleMessage(msg);
}
}
}
运行结果如下:
关于AIDL的使用以及分析在我的另外一篇单独的博客中,以下是地址https://blog.csdn.net/qq_40834350/article/details/102537760
此机制是Android官方提供的跨进程数据共享方式,天生就适合跨进程通信,它的底层实现同样是Binder,但却简化了AIDL的操作。
在正式使用Content Provider之前,我们先来看ContentResolver,我们需要借助此类访问CP中共享的数据:
下面是对通讯录的内容访问,部分代码如下:
private void readContacts() {
Cursor cursor = null;
try {
//查询联系人数据
cursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI
,null, null, null, null);
if (cursor != null){
while (cursor.moveToNext()){
//获取联系人姓名
String displayName = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
//获取联系人手机号
String number = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
contactsList.add(displayName +"\n" + number);
}
adapter.notifyDataSetChanged();
}
} catch (Exception e) {
e.printStackTrace();
}finally {
if (cursor != null){
cursor.close();
}
}
}
运行结果如下所示:
好了,现在可以正式开始使用Content Provider了。
关于Content Provider的使用,因为代码量的原因只给出查询的实现,CRDU始终实现原理相同,只是在一些具体对数据的操作方式不一样,部分代码如下:
myProvider继承了ContentProvicer,并且运行在单独的进程中,向外提供自己的数据:
public class myProvider extends ContentProvider {
private static final String TAG = "myProvider simulate::";
public static final int TABLE1_DIR = 0;
public static final int TABLE1_ITEM = 1;
public static final int TABLE2_DIR = 2;
public static final int TABLE2_ITEM = 3;
private static final String AUTHORITY = "com.banzh.contentprovidertest.provider.myProvider";
/*
* 利用addURI()方法,返回能够匹配Uri对象所对应的自定义代码
* 然后利用这个代码,判断出调用方法期望访问的是哪里的什么数据
* */
private static UriMatcher uriMatcher;
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(AUTHORITY, "table1", TABLE1_DIR);
uriMatcher.addURI(AUTHORITY, "table1/#", TABLE1_ITEM);
uriMatcher.addURI(AUTHORITY, "table2", TABLE2_DIR);
uriMatcher.addURI(AUTHORITY, "table2/#", TABLE2_ITEM);
}
/*
* 初始化内容提供器的时候调用
* 通常在这里完成对数据库的创建和升级等操作
* 返回true:内容提供器创建成功
* 返回false:创建失败
* 只有当存在ContentResolver尝试访问我们程序的数据时,内容提供器才会被舒适化
* */
@Override
public boolean onCreate() {
return true;
}
/*
* 从内容提供器中查询数据
* 使用Uri来确定查询那张表
* 查询的结果存放于Cursor对象中返回
* */
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
Cursor cursor = null;
switch (uriMatcher.match(uri)) {
case TABLE1_DIR:
//查询table1表中所有的数据
Log.d(TAG, "query: URI匹配进入" + TABLE1_DIR);
cursor = getContext().getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI
,null, null, null, null);
if (cursor != null){
Log.d(TAG, ">>>>>>>>>>>>>>>>curosor != null <<<<<<<<<<<<<<<<<<");
}
break;
case TABLE1_ITEM:
//查询table1表中的单条数据
Log.d(TAG, "query: URI匹配进入" + TABLE1_ITEM);
cursor = getContext().getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI
,null, null, null, null);
break;
case TABLE2_DIR:
//查询table2表中所有的数据
Log.d(TAG, "query: URI匹配进入" + TABLE2_DIR);
cursor = getContext().getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI
,null, null, null, null);
break;
case TABLE2_ITEM:
//查询table2表中的单条数据
Log.d(TAG, "query: URI匹配进入" + TABLE2_ITEM);
cursor = getContext().getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI
,null, null, null, null);
break;
default:
break;
}
return cursor;
}
/*
* 根据传入的内容URI来返回相应的MIME类型(从uri中分析出调用方期望访问的表和数据)
* *:匹配任意长度字符
* #:匹配任意长度数字
* */
/*
* 一个内容URI对应的MIME类型有三部分:
* 1.必须以vnd开头
* 2.如果内容URI以路径结尾,则后接android.cursor.dir/; 如果以id结尾,则后接android.cursor.item/
* 3.最后接上vnd..
* */
@Nullable
@Override
public String getType(@NonNull Uri uri) {
switch (uriMatcher.match(uri)) {
case TABLE1_DIR:
return "vnd.android.cursor.dir/vnd.com.banzh.contentprovider.provider.table1";
case TABLE1_ITEM:
return "vnd.android.cursor.item/vnd.com.banzh.contentprovider.provider.table1";
case TABLE2_DIR:
return "vnd.android.cursor.dir/vnd.com.banzh.contentprovider.provider.table2";
case TABLE2_ITEM:
return "vnd.android.cursor.item/vnd.com.banzh.contentprovider.provider.table2";
default:
break;
}
return null;
}
...(Update,Insert,Delete操作省略)
}
接下来就是客户端请求代码的实现:
public class ProviderActivity extends AppCompatActivity {
private static final String TAG = "ProviderActivity::";
private TextView tv_display_cpContent;
private Button btn_cp_query;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_provider);
tv_display_cpContent = findViewById(R.id.tv_display_cpContent);
btn_cp_query = findViewById(R.id.btn_cp_query);
btn_cp_query.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//查询数据
Uri uri = Uri.parse("content://com.banzh.contentprovidertest.provider.myProvider/table1");
Cursor cursor = getContentResolver().query(uri, null, null, null, null);
if (cursor != null) {
while (cursor.moveToNext()) {
//获取联系人姓名
String displayName = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
//获取联系人手机号
String number = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
tv_display_cpContent.setText("Name:" + displayName + " ||| Number: " + number);
}
cursor.close();
}else {
Log.d(TAG, "onClick: 你的cursor为空,请检查代码");
tv_display_cpContent.setText("cursor == null");
}
}
});
}
}
其实这些代码没有难以理解的地方,所以就不做过多说明,接下来直接给出结束。
此图表示他们运行在不同的进程。
关于Content Provider的使用就到这里结束。
关于Sicket相信大家已经非常熟悉了,在各种网络情境下使用的不少,但是对于多进程通信却不一定使用过,但是基本的使用方式都一样,这里就不给出示例了。
以上就是常见的IPC机制的应用了,从以上的了解,我们应该认识到Binde的基础重要性,然后选择在不同的场合下合理使用不同的多进程IPC机制,适配,适配,适配最重要。文章中如若有错误的地方,希望大家积极指出,共同进步。