Android:IPC,Messenger,AIDL

简介

IPC,即进程间通信。常见的IPC场景有两种,一种是单个应用开启多个进程,这些进程间需要通信;另外一种是不同应用间的进程间通信。

单个应用开启多个进程并不复杂,只需要为四大组件声明一个android:process属性,这个组件便会运行在该声明的进程上。而这个属性的声明方式有两种:

  1. 以:号开头,比如android:process=":remote",这时这个组件便运行在package_name:remote这个进程上。这种方式称为私有进程,虽然名为私有进程,但是可以为其再声明一个android:exported="true"属性,然后增加intent-filter属性(如果没有intent-filter属性,只能在本应用内使用),其他应用也可以关联到这个进程。

  2. 以单个.号分割,比如android:process="com.leelit",这时这个组件便会运行在com.leelit这个进程上。这种方式称为全局进程,假设有多个应用都有声明了com.leelit全局进程的组件,也会产生多个名字相同但PID并不相同的进程,所以全局进程并非全局唯一的意思。全局进程的作用是,其他应用可以通过ShareUID的方式和这个全局进程跑在同一个进程上,从而共享资源。

IPC的基础是数据结构的序列化与反序列化。Java平台简单地实现Serializable,通过ObjectOutputStream以及ObjectInputStream即可完成对象的序列与反序列化。而Android平台上新增了一种效率更高的方式,Parcelable,其实现方式比较固定,所以也有一些插件可以直接生成代码。如果说Serializable和Parcelable是实现IPC的“原料”,那么Binder则可以视为“桥梁”,Binder是客户端和服务端进行通信的媒介,当bindService时,客户端可以得到一个可以“操纵”服务端的Binder对象。

Android平台上实现IPC的方式有很多种,比如常见的:

  • Intent和bundle
  • 文件共享
  • Messenger
  • AIDL
  • ContentProvider
  • Socket

这篇文章主要关注:Messenger和AIDL


Messenger

Messenger是系统为我们封装好的一套IPC方案,它的底层是基于AIDL与Handler。如果了解Handler,Looper那一套东西就会知道,Handler每次只会处理一个Message,使用Messenger是不需要也无法考虑并发的情况的。

Messenger的构造方法有两个:

    public Messenger(Handler target) {
        mTarget = target.getIMessenger();
    }

    public Messenger(IBinder target) {
        mTarget = IMessenger.Stub.asInterface(target);
    }

由构造方法也能看出它基于AIDL和Handler。

使用套路:

1、Service服务端实例一个Messenger来接收客户端Messenger发来的message,onBind方法返回这个Messenger的Binder。如果有需要还可以通过客户端发来的message携带的客户端Messenger接收对象,返回message给客户端。

2、客户端bind到服务端的Service后,实例一个Messenger对象,便可以通过这个Messenger对象发送message到服务端。如果有需要还可以实例另外一个Messenger对象来接收服务端返回的message。

按照这个套路一共需要实例三个Messenger,客户端两个,一个用于发送给服务端,一个用于接收服务端;服务端一个用于接收客户端,而返回给客户端的Messenger在客户端发来的message中携带。

服务端进程:

public class MessengerService extends Service {

    private static final String Messenger_TAG = "Messenger";

    private static class ServerMessengerHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case 0:
                    // 接收到客户端的信息
                    Log.i(Messenger_TAG, msg.getData().getString("client-data") + " " + Thread.currentThread().toString());

                    // 接收服务端返回信息的客户端Messenger
                    Messenger replyMessenger = msg.replyTo;

                    // 返回信息给客户端
                    Message message = Message.obtain();
                    message.what = 1;
                    Bundle data = new Bundle();
                    data.putString("server-data", "hello client");
                    message.setData(data);
                    try {
                        replyMessenger.send(message);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }

    // 服务端Messenger
    private Messenger serverMessenger = new Messenger(new ServerMessengerHandler());

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        // 返回两个Messenger沟通的Binder桥梁
        return serverMessenger.getBinder();
    }

}

客户端进程:

public class MainActivity extends AppCompatActivity {
    private static final String Messenger_TAG = "Messenger";

    private Messenger clientMessenger;

    private Button button;

    // 接收服务端返回信息的Messenger
    private Messenger replyMessenger = new Messenger(new ClientMessengerHandler());

    private static class ClientMessengerHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case 1:
                    // 接收到服务端的信息
                    Log.i(Messenger_TAG, msg.getData().getString("server-data") + " " + Thread.currentThread().toString());
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }

    private ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // 客户端Messenger
            clientMessenger = new Messenger(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        button = (Button) findViewById(R.id.messenger);

        // bind服务端Service
        Intent intent = new Intent(this, MessengerService.class);
        bindService(intent, serviceConnection, BIND_AUTO_CREATE);

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 发送消息到服务端
                Message message = Message.obtain();
                message.what = 0;
                Bundle data = new Bundle();
                data.putString("client-data", "hello server");
                message.setData(data);

                // 指定接收服务端返回信息的Messenger
                message.replyTo = replyMessenger;

                try {
                    clientMessenger.send(message);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });

    }


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

点击Button后打印的信息如下:

10-21 21:44:28.557 29650-29650/com.example.kenjxli.ipc:messenger I/Messenger: hello server Thread[main,5,main]
10-21 21:44:28.567 29615-29615/com.example.kenjxli.ipc I/Messenger: hello client Thread[main,5,main]

处理的线程是Handler实例时所在的线程,所以两个进程的Message处理都是在主线程。

这里补充两点bindService相关的内容:

  1. bindService()是异步调用,会立即返回;
  2. 系统会在与服务连接上时回调onServiceConnected()方法,并传递服务的onBind() 方法返回的 IBinder;与服务的连接意外中断时(例如当服务崩溃或被终止时)回调onServiceDisconnected()方法。当客户端主动取消绑定时,系统“绝对不会”调用该方法。

AIDL

根据官方文档,可以看出,底层AIDL和经过封装的Messenger相比,最大的不同就是,AIDL是具备多线程处理能力的。

Messenger 会在单一线程中创建包含所有客户端请求的队列,以便服务一次接收一个请求。不过,如果您想让服务同时处理多个请求,则可直接使用 AIDL。 在此情况下,您的服务必须具备多线程处理能力,并采用线程安全式设计。

使用AIDL的基本步骤:
1、确定服务端提供的服务,定义AIDL接口;
2、编写服务端Service,onBind方法返回Binder;
3、客户端bindService,在回调处将Binder转化为AIDL接口,后续即可使用这个接口调用服务端的方法。
可以参考之前的一篇文章,链接

这里再补充几个内容。

  1. AIDL传递对象;
  2. 服务端回调客户端;
  3. 调用时同步异步状态及所在线程;

AIDL传递对象

如果使用AIDL传递基本数据类型,就比较简单,只需要服务端定义好AIDL接口文件,并在Service中实现AIDL.Stub接口,返回binder。客户端bindService后将binder重新转化为AIDL接口,即可IPC调用。但是要传递对象,就需要额外的功夫。

需要用AIDL传递对象时,除了实际使用的AIDL接口之外,还需要该对象的类实现Parcelable接口,在同一个包内声明该类的AIDL文件,并且AIDL接口中必须import该类,即便处在同一个包。

比如服务端AIDL接口,需要返回一个User对象。

interface MyAidl {
    User getUser();
}

首先需要创建一个实现Parcelable的User类;
然后在同一个包,新建一个AIDL文件;

// User.aidl
package com.example.kenjxli.ipc.aidl;

parcelable User;

最后需要在AIDL接口处import该类,就算处在同一个包也必须import。

// MyAidl.aidl
package com.example.kenjxli.ipc.aidl;

import com.example.kenjxli.ipc.aidl.User; //必须import

interface MyAidl {
    User getUser();
}

以上步骤缺一不可,否则编译都将失败。

另外,AIDL接口中的方法有一些非基本类型的参数,需要指定其“方向”。

All non-primitive parameters require a directional tag indicating which way the data goes. Either in, out, or inout (see the example below).
Primitives are in by default, and cannot be otherwise.

假设有一个AIDL接口如下:

interface MyAidl {
    void inUser(in User user);
    void outUser(out User user);
    void inOutUser(inout User user);
}

则三个参数的含义如下:

in:客户端输入参数,此时服务端可以得到这个参数对象的内容,但是修改后无法同步回给客户端。
out:客户端输入参数,即便这个对象是有内容的,到了服务端也会变成空值,服务端修改后能同步回给客户端。
inout:则是上面两者的集合,服务端既能得到客户端对象的内容,修改后也能同步回给客户端。

服务端回调

AIDL一般是客户端进程调用服务端进程,但是有些时候是可能需要服务端回调客户端,这时需要使用一个系统接口,RemoteCallbackList<E extends IInterface>

具体使用步骤如下:
1、定义一个客户端AIDL回调接口
2、在原有的AIDL接口增添注册回调接口的方法
3、客户端注册时,将该接口添加到callbackList中;
4、回调时使用固定的代码格式。

部分代码如下:
1、定义一个AIDL回调接口

// OnServerCallBack.aidl
package com.example.kenjxli.ipc.aidl;

import com.example.kenjxli.ipc.aidl.User;
// Declare any non-default types here with import statements

interface OnServerCallBack {
    void onServerCallBack(in User user); // 此时我们的客户端相当于是服务端的服务端了!
}

2、原有AIDL接口增添注册回调的方法

// MyAidl.aidl
package com.example.kenjxli.ipc.aidl;

import com.example.kenjxli.ipc.aidl.User;
import com.example.kenjxli.ipc.aidl.OnServerCallBack;
// Declare any non-default types here with import statements

interface MyAidl {
    User getUser();
    void inUser(in User user);
    void outUser(out User user);
    void inOutUser(inout User user);
    void registerCallBack(in OnServerCallBack callbcak);
}

并在服务端Service中的AIDL.Stub中实现该方法

@Override
        public void registerCallBack(final OnServerCallBack callbcak) throws RemoteException {
        // 这里可以添加一个cookie对象,方便回调时识别是哪个回调对象
            callbackList.register(callbcak, "cookie1");
        }

3、客户端注册

  myAidl.registerCallBack(new OnServerCallBack.Stub() {
                        @Override
                        public void onServerCallBack(User user) throws RemoteException {
                            Log.e("tag", "call back " + user.toString());
                        }

                    });

4、服务端回调代码

private void callback() {
        int size = callbackList.beginBroadcast();
        for (int i = 0; i < size; i++) {
            OnServerCallBack callBack = callbackList.getBroadcastItem(i);
            if (callBack != null) {
                try {
                    Log.e("tag", (String) callbackList.getBroadcastCookie(i));
                    callBack.onServerCallBack(new User("callback user", 10000));
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }
        callbackList.finishBroadcast();
    }

这样就是一个完整的服务端回调客户端的流程了。上面没有涉及到反注册,其实也是大同小异的!值得一提的是,如果客户端进程退出后,服务端会自动移除这个回调对象。


调用时同步异步状态及所在线程

这一小节直接说明结论:

1、ServiceConnection的回调线程始终是在主线程;
2、AIDL调用是在Binder线程池,不管是客户端调用服务端,还是服务端回调客户端,方法体执行的地方都是在Binder线程池中;
3、AIDL调用是同步调用,不管是客户端调用服务端,还是服务端回调客户端。当调用某个远程方法后,本地进程当前的线程会挂起,直到远程方法返回后,本地进程的线程才能唤醒。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值