Android进程间(IPC机制)通信(Bundler,Messenger,AIDL,ContentProvider)


在Android中每个app都由Activity和Service组成的,这而Activity和Service可能运行在同一个进程中,也可能在不同的进程中。

那么,不在同一个进程的Activity或者Service是如何通信的呢?这时候就要用到Binder进程间通信机制了。

而接下来要使用的一切都是基于Binder完成的,在Android中,无Binder不Andorid.

Bundle

public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
}

Bundle已经实现了Parcelable接口,在它的序列化与反序列化特性下,他可以在不同的进程中进行数据的传输.

常规的是Intent又可以对Bundle进行传递数据,因此我们经常在一个activity,service中利用intent传输数据就是Bundle的利用之一

  • ProcessActivity数据传输方
Bundle bundle = new Bundle();
        bundle.putString("ipc_bundle", "BundleName");
        Intent intent=new Intent(ProcessActivity.this,ProcessActivity2.class);
        intent.putExtras(bundle);
        startActivity(intent);

  • ProcessActivity2数据接收方
  Bundle bundle=getIntent().getExtras();
        String ipc=bundle.getString("ipc_bundle");

Messenger

Messenger属于轻量化的IPC通信,他的底层是基于AIDL实现.可以在不同的进程中传输Message对象,客户端发送一个Message到服务端,服务端借到消息后对消息进行处理在包装成Message再次反馈到客户端.

  • 服务端
public class MessengerService extends Service {

    public static final String TAG = "MessengerService";

    private HandlerThread handlerThread;
    private Handler handler;
    //Messenger
    private Messenger messenger;

    @Override
    public void onCreate() {
        super.onCreate();
        int pid = android.os.Process.myPid();
        Log.d(TAG, "进程Id:" + pid + "");

        //HandlerThread,使用Looper处理队列消息
        handlerThread = new HandlerThread("messenger_server");
        handlerThread.start();
        //获取Looper
        Looper looper = handlerThread.getLooper();
        //让Handler 运行在HandlerThread中

        handler = new Handler(looper) {
            @Override
            public void handleMessage(Message msg) {
                replyToClientMsg(msg);//回复客户端消息
                super.handleMessage(msg);
            }
        };

        messenger = new Messenger(handler);

    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return messenger.getBinder();
    }


    private void replyToClientMsg(Message msg) {
        switch (msg.what) {
            case Constants.MSG_FROM_CLIENT://接受消息处理
                //模拟器服务器响应过程
                Log.d(TAG, "msg what= [" + msg.what + "" + "]");
                Log.d(TAG, "msg arg1= [" + msg.arg1 + "" + "]");
                Log.d(TAG, "msg arg2= [" + msg.arg2 + "" + "]");
                Log.d(TAG,"客户端发送的消息:"+msg.getData().getString(Constants.MESSENGER_KEY));

                Toast.makeText(MessengerService.this, msg.getData().getString(Constants.MESSENGER_KEY), Toast.LENGTH_SHORT).show();
                try {
                    Messenger msgFromClient = msg.replyTo;//客户端回调
                    Message replyMsgToClient = Message.obtain(null, Constants.MSG_FROM_SERVICE);//回复给客户端的消息
                    Bundle bundle = new Bundle();
                    bundle.putString(Constants.MESSENGER_KEY, "我也爱你");
                    replyMsgToClient.setData(bundle);

                    msgFromClient.send(replyMsgToClient);//发送Message消息体给客户端
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
                break;

        }
    }

}

服务端通过Service来回应客户端的请求,通过一个Handler来实例化一个Messenger对象,在onBind中返回底层的Binder对象.

因为客户端可能一次性或者多次发送消息,在服务端需要一个消息队列处理数据信息,因此使用HandlerThread,实现使用Looper来维护消息队列.以此保证不会出现并发的情况。

  • 客户端
public class MessengerActivity extends AppCompatActivity {
    private static final String TAG = MessengerActivity.class.getSimpleName();

    private Button button;

    private Messenger messengerService;//服务端Service

    private Messenger messengerClient = new Messenger(new MessageHandler());//客户端Messenger


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_messenger);
        int pid = android.os.Process.myPid();
        Log.d(TAG, "进程Id:" + pid + "");
        bindMessengerService();


        button = (Button) findViewById(R.id.btn);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        sendMessage();

                    }
                }).start();
            }
        });

    }

    //绑定服务
    public void bindMessengerService() {
        Intent mIntent = new Intent(this, MessengerService.class);
        bindService(mIntent, mServiceConnection, Context.BIND_AUTO_CREATE);
        Log.e(TAG, "bindService invoked !");
    }

    //发送消息
    public void sendMessage() {
        Message msgFromClient = Message.obtain(null, Constants.MSG_FROM_CLIENT, 1, 2);
        Bundle data = new Bundle();
        data.putString(Constants.MESSENGER_KEY, "我爱你,你爱我吗?");
        msgFromClient.setData(data);
        msgFromClient.replyTo = messengerClient;
        try {
            messengerService.send(msgFromClient);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void onDestroy() {
        unbindService(mServiceConnection);
        super.onDestroy();
    }


    private class MessageHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case Constants.MSG_FROM_SERVICE:
                                    Log.d(TAG,"服务端回复消息:"+msg.getData().getString(Constants.MESSENGER_KEY));

                    Toast.makeText(MessengerActivity.this,  msg.getData().getString(Constants.MESSENGER_KEY) , Toast.LENGTH_SHORT).show();
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }


    private ServiceConnection mServiceConnection = new ServiceConnection() {
        /**
         * @param name
         * @param service
         */
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            messengerService = new Messenger(service);

        }

        /**
         * @param name
         */
        @Override
        public void onServiceDisconnected(ComponentName name) {
            messengerService = null;
        }
    };
}
  • 图示
09-08 03:12:19.052 15181-15228/com.allure.study D/MessengerService: 客户端发送的消息:我爱你,你爱我吗?
09-08 03:12:19.056 15181-15181/com.allure.study D/MessengerActivity: 服务端回复消息:我也爱你
android_state_layout.gif

客户端主要用于绑定服务端,绑定之后生成一个Messenger,通过这个Messenger可以实现想服务端的消息发送.

服务端收到消息之后的反馈需要客户端使用Message.reply回调给服务端,服务端在同样使用Message.reply即可实现双向的通信.

特别注意在客户端的2个Messenger,一个是通过绑定服务端的获取的,一个是自身创建给服务端的回调.

AIDL

ADIL可以处理并发和跨进程.
前面的Messenger的底层也是基于AIDL,但是在客户端有多个消息同时发送到客户端,如果按照Messenger的方式只能单独的在消息队列一个个处理,在处理这种场景时就更推荐使用AIDL进行解决.

  • 服务端
QQ20170908-150237@2x.png

创建一个AIDL文件,结尾为.aidl

AIDL的数据类型

  • 基本的数据类型
  • HashMap,ArrayList,实现Parcelable的对象
  • AIDL接口

如果AIDL中使用了自定义的Parcelable,必须建一个同名的AIDL文件

public class AIDLService extends Service {

    private static final String TAG = "AIDLService";


    public AIDLService() {
    }

    @Override
    public void onCreate() {
        Log.e(TAG, "onCreate");
        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.e(TAG, "onStartCommand");

        return super.onStartCommand(intent, flags, startId);

    }


    @Override
    public void onDestroy() {
        Log.e(TAG, "onDestroy");

        super.onDestroy();

    }

    @Override
    public boolean onUnbind(Intent intent) {
        Log.e(TAG, "onUnbind");

        return super.onUnbind(intent);

    }

    @Override
    public void onRebind(Intent intent) {
        super.onRebind(intent);
        Log.e(TAG, "onRebind");

    }

    @Override
    public void onTaskRemoved(Intent rootIntent) {
        super.onTaskRemoved(rootIntent);
        Log.e(TAG, "onTaskRemoved");
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.e(TAG, "onBind");


        return binder;
    }

    private Binder binder = new TestAIDL.Stub() {


        @Override
        public String reply(String s) throws RemoteException {
            if(s.equals("你爱我吗")){
                return  "我爱你";
            }else {
                return "没有你爱我,我怎么爱你";
            }
        }
    };

}
  • 客户端
public class AIDLActivity extends AppCompatActivity {

    private static final String TAG = AIDLActivity.class.getSimpleName();

    private Button button;
    
    private TestAIDL testAIDL;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_aidl);
        //绑定AIDL服务
        bindService();


        button = (Button) findViewById(R.id.btn);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                   String s = testAIDL.reply("你爱我吗");
                    Log.d(TAG,"你爱我吗");
                    Log.d(TAG,s);
                    Toast.makeText(AIDLActivity.this, "你爱我吗", Toast.LENGTH_SHORT).show();
                    Toast.makeText(AIDLActivity.this, s, Toast.LENGTH_SHORT).show();
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unBindServcie();

    }

    private void unBindServcie() {
        unbindService(mServiceConnection);
    }

    private void bindService() {
        Intent mIntent = new Intent(this, AIDLService.class);

        bindService(mIntent, mServiceConnection, Context.BIND_AUTO_CREATE);
        Log.e(TAG, "bindService invoked !");
    }

    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            testAIDL = TestAIDL.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            testAIDL = null;
        }
    };
}
  • 图示
android_state_layout.gif

和Messenger类似,绑定服务,获取aidl对象,接口回调得到相应的服务响应信息.

源码中可以看到AIDL.Stub属于Binder子类,所以AIDL也是基于Binder来完成的

ContentProvider

ContentProvider和Messenger一样底层也是基于AIDL Binder,和Messenger一样在系统都做了一定程度封装,便于上层的调用.最常见的便是我们获取通讯录使用ContentProvider

简单实现一个ContentProvider的联系人增删改查的例子.

  • 数据库
public class DBOpenHelper extends SQLiteOpenHelper {

    private static final String DB_NAME = "contacts.db";
    public static final String CONTACTS_TABLE_NAME = "contacts";

    private static final String CREATE_CONTACTS_TABLE = "CREATE TABLE IF NOT EXISTS "
            + CONTACTS_TABLE_NAME + " (phonenumber CHAR(11) PRIMARY KEY, name TEXT)";

    public static final int DB_VESION = 1;

    public DBOpenHelper(Context context) {
        super(context, DB_NAME, null, DB_VESION);
    }

    @Override
    public void onCreate(SQLiteDatabase sqLiteDatabase) {
        sqLiteDatabase.execSQL(CREATE_CONTACTS_TABLE);
    }

    @Override
    public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {

    }
}

  • ContentProvider
public class TestContentProvider extends ContentProvider {
    private static final String TAG = TestContentProvider.class.getSimpleName();

    private static final String AUTHORITY = "com.allure.study.interprocesscommunication.contentprovider";
    public static final String CONTACTS_URI = "content://" + AUTHORITY + "/" + DBOpenHelper.CONTACTS_TABLE_NAME;

    private static final int CONTACTS_TABLE_CODE = 0;

    private UriMatcher uriMatcher;
    private SQLiteDatabase sqLiteDatabase;

    @Override
    public boolean onCreate() {

        Log.v(TAG, "ContactsProvider进程Id " + android.os.Process.myPid());

        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        uriMatcher.addURI(AUTHORITY, DBOpenHelper.CONTACTS_TABLE_NAME, CONTACTS_TABLE_CODE);
        sqLiteDatabase = new DBOpenHelper(getContext()).getWritableDatabase();
        return false;
    }

    @Nullable
    @Override
    public Cursor query(Uri uri, String[] strings, String s, String[] strings1, String s1) {
        String tableName = getTableName(uri);
        if(tableName == null) {
            throw new IllegalArgumentException("Uri错误: " + uri);
        }
        Cursor cursor = sqLiteDatabase.query(tableName, strings, s, strings1, s1, null, null);
        return cursor;
    }

    /**
     * @param uri
     * @return
     */
    @Nullable
    @Override
    public String getType(Uri uri) {
        return null;
    }

    @Nullable
    @Override
    public Uri insert(Uri uri, ContentValues contentValues) {
        String tableName = getTableName(uri);
        if(tableName == null) {
            throw new IllegalArgumentException("Erro Uri: " + uri);
        }
        sqLiteDatabase.insert(tableName, null, contentValues);
        getContext().getContentResolver().notifyChange(uri, null);
        return null;
    }

    @Override
    public int delete(Uri uri, String s, String[] strings) {
        String tableName = getTableName(uri);
        if(tableName == null) {
            throw new IllegalArgumentException("Erro Uri: " + uri);
        }
        int count = sqLiteDatabase.delete(tableName, s, strings);
        if(count > 0) {
            getContext().getContentResolver().notifyChange(uri, null);
        }
        return 0;
    }

    @Override
    public int update(Uri uri, ContentValues contentValues, String s, String[] strings) {
        return 0;
    }

    
    private String getTableName(Uri uri) {
        String tableName = null;
        switch (uriMatcher.match(uri)) {
            case CONTACTS_TABLE_CODE:
                tableName = DBOpenHelper.CONTACTS_TABLE_NAME;
                break;
        }
        return tableName;
    }
}

由于ContentProvider属于四大组件因此我们在清单文件中需要对其进行注册

  <!--ContentProvider-->
        <provider
            android:authorities="com.allure.study.interprocesscommunication.contentprovider"
            android:name=".interprocesscommunication.contentprovider.TestContentProvider" />

** authorities和TestContentProvider类里的AUTHORITY徐亚保持一致**
** name代表ContentProvider类**

  • Activity 调用
ublic class ContentProviderActivity extends AppCompatActivity {

    private static final String TAG = ContentProviderActivity.class.getSimpleName();
    private Uri uri;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_content_provider);
        Log.v(TAG, "Activity进程Id " + android.os.Process.myPid());
        uri = Uri.parse(TestContentProvider.CONTACTS_URI);
    }

    public void insert(View v) {
        ContentValues values = new ContentValues();
        values.put("phoneNumber", "15000000001");
        values.put("name", "Jacye");
        getContentResolver().insert(uri, values);
        Log.v(TAG, "插入了 " + "Jacye " + "电话号码: 15000000001");
    }

    public void delete(View v) {
        getContentResolver().delete(uri, "name=?", new String[]{"Jacye"});
    }

    public void update(View v) {
    }

    public void query(View v) {
        String[] colum = new String[]{"phoneNumber", "name"};
        Cursor cursor = getContentResolver().query(uri, colum, null, null, null);
        while (cursor.moveToNext()) {
            String phoneNumber = cursor.getString(0);
            String name = cursor.getString(1);
            Log.v(TAG, "获取到联系人 " + phoneNumber + "  " + name);
        }
        cursor.close();
    }
}

显示

09-08 05:04:40.978 31726-31726/com.allure.study V/ContentProviderActivity: 插入了 Jacye 电话号码: 13333333333
09-08 05:04:42.870 31726-31726/com.allure.study V/ContentProviderActivity: 获取到联系人 13333333333  Jacye

总结

到此,Android中常用的IPC方式就已经实现了,当然还有其他的实现方式,比如说文件共享,Scoket通信等,在这里只讲了Android 常用的.

  • Messenger适用于单一无并发的场景,也就是说同一个app的不同进程
  • AIDL多用于并发跨进程的场景(比如说2个app之间的交互),稳定快速高效是他的优点,但是在通信约束上需要客户端服务端都把AIDL加入自身项目。
  • contentprovider多用于跨进程和数据共享方面,相比较于AIDL来说更轻便
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,013评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,205评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,370评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,168评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,153评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,954评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,271评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,916评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,382评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,877评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,989评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,624评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,209评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,199评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,418评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,401评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,700评论 2 345

推荐阅读更多精彩内容