Android中常见的IPC机制

一:关于IPC机制

当我们在Android开发中,有时候会遇到应用因为某些原因需要采用多进程模式,此时如果要在应用内的不同进程间进行通信,就需要使用到 IPC 机制。或者是两个不同的应用需要进行数据交换,此时也一样需要依靠 Android 系统提供的 IPC机制。

关于IPC的概念这里不做过多的介绍,大家请自行学习,下面就是Android常见的IPC机制的介绍,这里先给出常见IPC机制列表:

  • Bundle
  • 文件共享
  • Messenger
  • AIDL
  • Content Provider
  • Socket

二:常见IPC机制

1.Bundle

我们开发中经常提到的四大组件(Activity,Service, Receiver)都支持在Intent中传递Bundle对象,剩下的一个组件更是天生就支持跨进程通信Content Provider。

public final class Bundle extends BaseBundle implements Cloneable, Parcelable

通过这段代码,我们也可以清楚的看到BUndle实现了Parcelable接口,这不足为奇,所以它很方便的在进程间传输,通过Intent发送出去。但是注意:我们传输的数据必须能被序列化,可以实现Parcelable,Serializable的对象, 基本类型和一些Android支持的特定对象。不支持的类型我们无法通过它在进程间传递。

2.文件共享

顾名思义,此方式就是两个进程通过读 / 写同一个文件来交换数据,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<Messenger> CREATOR
            = new Parcelable.Creator<Messenger>() {
        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);
            }
        }
    }

运行结果如下: 

 关于Messenger的使用就到这里完成。

3.AIDL:

关于AIDL的使用以及分析在我的另外一篇单独的博客中,以下是地址https://blog.csdn.net/qq_40834350/article/details/102537760

4.Content Provider

此机制是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.<authority>.<path>
    * */
    @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的使用就到这里结束。

5.Socket

关于Sicket相信大家已经非常熟悉了,在各种网络情境下使用的不少,但是对于多进程通信却不一定使用过,但是基本的使用方式都一样,这里就不给出示例了。

三:总结

以上就是常见的IPC机制的应用了,从以上的了解,我们应该认识到Binde的基础重要性,然后选择在不同的场合下合理使用不同的多进程IPC机制,适配,适配,适配最重要。文章中如若有错误的地方,希望大家积极指出,共同进步。

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值