Android跨进程通信Binder、Messenger、AIDL汇总

 前言

提到的进程间通信(IPC:Inter-Process Communication),在Android系统中,一个进程是不能直接访问另一个进程的内存的,需要提供一些机制在不同的进程之间进行通信,Android官方推出了AIDL(Android Interface Definition Language),它是基于Binder机制的,具体Binder机制的东西就很多了,网上很多资料,我们就不在这里说了,推荐文章

跨进程通信方式

  • 实现IBinder
  • Messenger
  • AIDL

后面两种可以跨进程通信,是基于Binder机制的通信方式。第一种我们多用于service直接的通信,但是当sevice被设为远程服务时(设为:remote),我们就要用后面两种方式来进行通信了。

概念

  • IPC:Inter-Process Communication,进程间的通信或跨进程通信。简单点理解,一个应用可以存在多个进程,但需要数据交换就必须用IPC;或者是二个应用之间的数据交换。
  • Binder:Binder是Android的一个类,它实现了IBinder接口。从IPC角度来说,Binder是Android中的一种跨进程通信方式。通过这个Binder对象,客户端就可以获取服务端提供的服务或数据,这里的服务包括普通服务和基于AIDL的服务。
  • AIDL:Android Interface Definition language,它是一种Android内部进程通信接口的描述语言。

区别

  •  Messenger是对AIDL的一种封装,开发的时候不用再写.aidl文件。结合我自身的使用,因为不用去写.aidl文件,相比起来,Messenger使用起来十分简单。但前面也说了,Messenger本质上也是AIDL,故在底层进程间通信这一块,两者的效率应该是一样的。
  • service端,Messenger处理client端的请求是单线程的,而AIDL是多线程的。使用AIDL的时候,service端每收到一个client端的请求时,就会启动一个线程去执行相应的操作。而Messenger,service收到的请求是放在Handler的MessageQueue里面,Handler大家都用过,它需要绑定一个Thread,然后通过Looper不断循环取出message执行相关操作,这个过程是同步执行的。但是你也可以不直接处理请求,而是开启线程或者使用线程池,这样也可以实现异步。
  •  client端,使用AIDL获取返回值是同步的,而Messenger是异步的。Messenger只提供了一个方法进行进程间通信,就是send(Message msg)方法,发送的是一个Message,没有返回值,要拿到返回值,需要把client的Messenger作为msg.replyTo参数传递过去,service端处理完之后,在调用客户端的Messenger的send(Message msg)方法把返回值传递回client,这个过程是异步的,而AIDL你可以自己指定方法,指定返回值,它获取返回值是同步的。

使用场景

在确定使用什么机制之前,首先了解应用场景。Android系统中,如果组件与服务通信是在同一进程,就使用第一种方式;如果是跨进程通信,使用第二种和第三种,两者不同在于,Messenger不能处理多线程并发请求。

代码实现 

1、实现IBinder

  对用于service中,一般使用bindService()启动服务。这是一种比startService更复杂的启动方式,同时使用这种方式启动的service也能完成更多的事情,比如其他组件可向其发送请求,接受来自它的响应,甚至通过它来进行IPC等等。我们通常将绑定它的组件称为客户端,而称它为服务端。 如果要创建一个支持绑定的service,我们必须要重写它的onBind()方法。这个方法会返回一个IBinder对象,它是客户端用来和服务器进行交互的接口。而要得到IBinder接口,我们通常有三种方式:继承Binder类,使用Messenger类,使用AIDL。

服务端配置

public class BinderService extends Service {


    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return new MyBinder();
    }

    public class MyBinder extends Binder{

        public void setMessage(String s,BinderActivity.CallBack callBack){
            Log.i("Binder_Service", s );
            callBack.sendMsg("从服务端发来的Binder响应");
        }
    }

}

客户端配置

public class BinderActivity extends AppCompatActivity {
    private BinderService.MyBinder mMyBinder;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_binder);
        setTitle("客户端");
        //绑定服务
        Intent intent = new Intent(BinderActivity.this,BinderService.class);
        bindService(intent, conn, BIND_AUTO_CREATE);
        findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mMyBinder.setMessage("从客服端发来的Binder请求",new CallBack() {
                    @Override
                    public void sendMsg(String s) {
                        Log.i("Binder_Client", s );
                    }
                });
            }
        });
    }
    /**
     * 服务回调方法
     */
    private ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mMyBinder = (BinderService.MyBinder) service;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            mMyBinder = null;
        }
    };
    @Override
    protected void onDestroy() {
        super.onDestroy();
        //解绑服务,回收资源
        unbindService(conn);
    }

   public interface CallBack{
        void sendMsg(String s);
    }
}
manifest注册
 <service
            android:name="com.veer.aidl.BinderService">
        <!--android:process=":remote"-->
        </service>

注意不要使用远程服务,如果一定要使用远程服务就要用后面的两种跨进程方式。

结果

服务端打印日志:

08-28 15:57:21.009 2199-2199/com.veer.aidl I/Binder_Service: 从客服端发来的Binder请求

客户端打印日志:

08-28 15:57:21.009 2199-2199/com.veer.aidl I/Binder_Client: 从服务端发来的Binder响应

Messenger

以前讲到跨进程通信,我们总是第一时间想到AIDL(Android接口定义语言),实际上,使用Messenger在很多情况下是比使用AIDL简单得多的,两种比较就能看出来。

大家看到Messenger可能会很轻易的联想到Message,然后很自然的进一步联想到Handler——没错,Messenger的核心其实就是Message以及Handler来进行线程间的通信。下面讲一下通过这种方式实现IPC的步骤:

  • 服务端实现一个Handler,由其接受来自客户端的每个调用的回调
  • 使用实现的Handler创建Messenger对象
  • 通过Messenger得到一个IBinder对象,并将其通过onBind()返回给客户端
  • 客户端使用 IBinder 将 Messenger(引用服务的 Handler)实例化,然后使用后者将 Message 对象发送给服务

服务端在其 Handler 中(具体地讲,是在 handleMessage() 方法中)接收每个 Message

服务端代码

public class MessengerService extends Service {

    private Messenger mMessenger = new Messenger(new MessengerHandler());

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mMessenger.getBinder();
    }
    private class MessengerHandler extends Handler{
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case 0:
                    Log.i("Binder_Service", "从客户端发来的Messenger请求");
                    Message message = Message.obtain(null,1);
                    try {
                        msg.replyTo.send(message);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    break;
                default:
                    super.handleMessage(msg);
            }

        }
    }

}

客户端代码

public class MessengerActivity extends AppCompatActivity {
    private Messenger mMessenger;
    private Messenger mMyClientMessenger = new Messenger(new MyClientHandler());

    @SuppressLint("WrongConstant")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_messenger);
        setTitle("客服端");
        Intent intent = new Intent();
        intent.setAction("com.veer.aidl.MessengerService");
        intent.setPackage("com.veer.aidl");
        bindService(intent, mCoon, BIND_AUTO_CREATE);
        findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                try {
                    Message message = Message.obtain(null,0);
                    message.replyTo = mMyClientMessenger;
                    mMessenger.send(message);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });


    }

    private  class  MyClientHandler extends Handler{
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case 1:
                    Log.i("Binder_client", "从服务端发来的Messenger请求,已经接受到通知");
                    break;
                default:

                    super.handleMessage(msg);
            }

        }
    }
    /**
     * 服务回调方法
     */
    private ServiceConnection mCoon = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mMessenger = new Messenger(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };
    @Override
    protected void onDestroy() {
        super.onDestroy();
        //解绑服务,回收资源
        unbindService(mCoon);
    }

}
manifest注册
 <service
            android:exported="true"
            android:enabled="true"
            android:name="com.veer.aidl.MessengerService">
            <intent-filter>
                <action android:name="com.veer.aidl.MessengerService"/>
            </intent-filter>
        </service>

运行结果

服务端

客户端

用Messenger来进行IPC的话整体的流程是非常清晰的,Message在其中起到了一个信使的作用,通过它客户端与服务端的信息得以互通。

AIDL

AIDL,即Android Interface Definition Language,Android接口定义语言。它是一种IDL语言,可以拿来生成用于IPC的代码。在我看来,它其实就是一个模板。为什么这样说呢?在我们的使用中,实际上起作用的并不是我们写的AIDL代码,而是系统根据它生成的一个IInterface实例的代码。而如果大家多生成几个这样的实例,然后把它们拿来比较,你会发现它们都是有套路的——都是一样的流程,一样的结构,只是根据具体的AIDL文件的不同有细微的变动。所以其实AIDL就是为了避免我们一遍遍的写一些千篇一律的代码而出现的一个模板。

那么如何使用AIDL来通过bindService()进行线程间通信呢?基本上有下面这些步骤:

  • 服务端创建一个AIDL文件,将暴露给客户端的接口在里面声明
  • 在service中实现这些接口
  • 客户端绑定服务端,并将onServiceConnected()得到的IBinder转为AIDL生成的IInterface实例

通过得到的实例调用其暴露的方法

1、服务端

创建aidl接口

在面下面创建aidl文件夹,然后创建我们的aidl接口文件,创建完了、之后一定要重新编译一下项目,因为Androidstudio会自动帮我创建一下文件。

这里是binder机制应用层的原理,感兴趣的同学可以了解下。

然后开始写服务端代码

public class AidlService extends Service {

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return new MyBinder();
    }

    public class MyBinder extends IMyAidlInterface.Stub {
        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {

        }

        @Override
        public int add(int num1, int num2) throws RemoteException {
            Log.i("Aidl_Service", "从客户端发来的AIDL请求:num1->" + num1 + "::num2->" + num2);
            return num1+num2;
        }
    };
}

 

这里也比较简单,和service中binder类似,但不是集成binder  ,我们定义的aidl接口。

服务端manifest注册

<service
            android:exported="true"
            android:enabled="true"
            android:name="com.veer.aidl.AidlService">
            <intent-filter>
                <action android:name="com.veer.aidl.AidlService"/>
            </intent-filter>
        </service>

客户端配置

客户端需要把服务端的aidl文件copy到客服端上来,放置的位置同服务端。然后一样,重新编译项目。

客户端代码

public class AidlActivity extends AppCompatActivity {

    IMyAidlInterface iMyAidlInterface;
    private TextView mTvService;

    @SuppressLint("WrongConstant")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_aidl);
        setTitle("客户端");
        mTvService = (TextView) findViewById(R.id.tv_service);
        //绑定服务
        Intent intent = new Intent();
        intent.setAction("com.veer.aidl.AidlService");
        intent.setPackage("com.veer.aidl");
        bindService(intent, conn, BIND_AUTO_CREATE);
        findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                add();
            }
        });

    }
    /**
     * 点击“AIDL”按钮事件
     */
    public void add() {
        try {
            if(iMyAidlInterface!=null){
                int res = iMyAidlInterface.add(1, 2);
                mTvService.setText("从服务端调用成功的结果:" + res);
                Log.i("Aidl_Client", "从服务端调用成功的结果:" + res);
            }
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    /**
     * 服务回调方法
     */
    private ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            iMyAidlInterface = null;
        }
    };

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //解绑服务,回收资源
        unbindService(conn);
    }

}

这里和binder方式,获取ibinder的方式不同。

服务端日志

客户端日志

这样我们就完成了两个应用间的通信了。

Messenger与AIDL的比较

首先,在实现的难度上,肯定是Messenger要简单的多——至少不需要写AIDL文件了(虽然如果认真的究其本质,会发现它的底层实现还是AIDL)。另外,使用Messenger还有一个显著的好处是它会把所有的请求排入队列,因此你几乎可以不用担心多线程可能会带来的问题。

但是这样说来,难道AIDL进行IPC就一无是处了么?当然不是,如果是那样的话它早就被淘汰了。一方面是如果项目中有并发处理问题的需求,或者会有大量的并发请求,这个时候Messenger就不适用了——它的特性让它只能串行的解决请求。另外,我们在使用Messenger的时候只能通过Message来传递信息实现交互,但是在有些时候也许我们需要直接跨进程调用服务端的方法,这个时候又怎么办呢?只能使用AIDL。

所以,这两种IPC方式各有各的优点和缺点,具体使用哪种就看具体的需要了——当然,能使用简单的就尽量使用简单的吧。

总结

Android跨进程通信的知识点其实很多,我们这里只是简单介绍了用法,还需要我们更加深入的了解。我也一直会更新博客,项目我会放到GitHub上,一直维护,增加更加的功能。GitHub地址:https://github.com/fuweiwei/VAidl

源码下载

源码下载地址

GitHub地址

 

 

 

 

 

 

 

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值