参考

轻松理解 Android Binder,只需要读这一篇

图文详解 Android Binder跨进程通信的原理

Android中的Parcel是什么

Android Binder IPC通信机制对比

 

前言

Android中有很多种IPC通信机制,如共享内存、管道、socket等。

Linux中的内存管理中存在虚拟内存和物理内存两种概念;每个进程都拥有自己独立的虚拟内存空间,这样用户进程可以认为自己所有用的是一段独立、连续的内存空间,而不必管这些数据具体存在于物理内存的哪个段、哪个页上,虚拟内存的映射统一交给kernel去管理。 

Android中每个进程可以访问到的虚拟内存空间分为用户空间和内核空间。其中用户空间为每个进程独占,一个进程无法访问到其他进程的用户空间;而内核空间是公用的。所以大多数传统IPC采用的都是以公用的内核空间为数据传递的中间站,通过

copy_from_user():将用户空间的数据拷贝到内核空间;

copy_to_user():将内核空间的数据拷贝到用户空间;

完成数据由进程A用户空间到进程B用户空间的传递。

也就是说,在传统的IPC中,数据需要两次的拷贝过程才可以完成进程间通信。

再看下共享内存,简单来说,共享内存是依赖与内存映射来完成的。A和B两个进程使用共享内存进行通信,A和B将自己虚拟空间与共享对象进行内存映射。共享内存无需数据拷贝就可以完成数据通信,但同时,由于共享内存方法在使用上比较繁琐,再加上在一些使用场景不是很适用(比如A进程想使用B进程提供的某个服务,并获得这个服务的执行结果),所以此时就需要引入Android原生的IPC通信方式Binder,但共享内存在某些使用场景如大量数据的传输中,契合度还是很高的。

先看下Android几种IPC方式,数据拷贝次数的对比:

IPC

数据拷贝次数

共享内存

0

Binder

1

Socket/管道/消息队列

2

 

白话浅析Binder

Binder由Client进程、Server进程、Binder驱动、ServerManager组成。

举个例子,甲和乙两个人使用QQ聊天,甲问乙一句明天天气怎么样,乙回了一句明天下午,甲接受到了这条回复。甲和乙就相当于两个进程,他们因为不在一个地方所以无法直接进行通信,所以要借助IM工具、硬件、网络来完成这一系列交互。首先甲和乙要通过QQ聊天,那么乙一定是要注册QQ并在线的,那么此时甲相当于client进程,乙相当于server进程,乙注册QQ上线后,相当于告诉了QQ服务端自己的用户名、用户id、ip和端口号,并建立连接。那么这时候甲将信息发到QQ服务端后,QQ后端服务会根据数据库中存放的表,通过甲发送过来的乙的用户id来找到和乙建立的链接,然后将消息发送给乙。在这个过程中,QQ后端服务分管注册、用户查询、路由的这部分功能就相当于ServerManager,而双方的PC、QQ软件、网路、QQ后端服务的其他功能就相当于Binder Driver。

当然,拿QQ聊天举例子只是为了对Binder到底是个什么东西有个初步的认识,其中的实现细节会有很大的不同。

结合之前所说的,传统IPC通信是通过数据由用户空间拷贝到内核空间,再拷贝到用户空间完成的。共享内存是通过用户空间对共享对象的映射完成的。那么Binder呢?

下面盗图一张,图片出处 图文详解 Android Binder跨进程通信的原理

可以看到,client进程首先将要发送的数据通过Binder驱动,拷贝到内核空间,然后这个内核空间其实是与Binder创建的接收缓存区以及Server进程的用户空间是映射关系的,这样Client向内核空间copy数据后,相当于直接写到了server进程的用户空间,而server进程在接受到数据后并进行一些列的逻辑处理后,将结果写入到自己的用户空间,由于内存映射,也同时相当于写入到了Client进程的内核空间,然后再将返回的结果从内核空间拷贝到用户空间就可以了。也就是说单向的数据传递,在建立好了内存映射后,只需要发生一次数据拷贝即可。

从这可以看出,Binder对比与传统的IPC方式,数据传输效率是有提升的,而由于管道和socket等方式需要将自己的管道id和端口号等信息暴漏出来,而Binder则将所有注册和路由的方式交给ServerManager和Binder驱动来做,所以在安全性上,binder对比传统IPC方式也有很大提升。

从代码角度来看如何使用Binder

Binder Driver和ServerManager是由Android 的FW和HAL来实现的,那么做为应用开发人员使用Binder的重点就是就是实现client和server进程。

还是以上面甲询问乙天气怎么样为例,首先乙作为server进程,要定义好自己可以提供的服务,也就是说现在乙现在只能回答甲天气情况,而没回答吃饭了没~然后要将自己注册到ServerManager上。

那Server进程如何将自己注册到ServerManager上,并实现接受和发送的能力呢?这些都已经被封装好了,Server进程只需要继承Binder这个类,并且重写其中的

onTransact(int code, Parcel data, Parcel reply, int flags)

这个方法,并在实现onBind这个抽象方法,并将刚才继承Binder的那个类的对象通过onBind方法返回就可以了。

上代码

package com.qyy.remotemonitor.ui.service;import android.app.Service;import android.content.Intent;import android.os.Binder;import android.os.IBinder;import android.os.Parcel;import android.os.RemoteException;import android.support.annotation.Nullable;import java.util.Date;/** * Created by qinyy on 1/23/2019. */public class MyServerService extends Service{    public static final int WEATHER_INFO = 999;    private MyServer mMyServer = new MyServer();    private class MyServer extends Binder implements IMyServer    {        @Override protected boolean onTransact(int code, Parcel data, Parcel reply, int flags)                throws RemoteException        {            switch (code)            {                case WEATHER_INFO:                    data.enforceInterface("MyServer");                    long timeStamp = data.readLong();                    String result = getWeatherInfo(com.blankj.utilcode.util.TimeUtils.millis2Date(timeStamp));                    reply.writeInterfaceToken("MyClient");                    reply.writeString(result);                    return true;            }            return super.onTransact(code, data, reply, flags);        }        @Override public String getWeatherInfo(Date date)        {            String weather = "";            //根据date查询天气情况            // mPresenter.getWeather(date);            return weather;        }    }    @Nullable @Override public IBinder onBind(Intent intent)    {        return mMyServer;    }    public interface IMyServer    {        String getWeatherInfo(Date date);    }}

总结一下编写Server进程的要点:

1. 编写一个继承Binder类的类。

2. 重写Binder类中的onTransact方法,这个方法是为了接受Client传来的参数并在这里实现业务处理,并将结果返回给Client。这个方法是运行在Server进程利用进程池创建的线程中的,这个线程池的最大容量是16.

3. 通信使用了Parcel进行数据的封装与序列化,如果只传递简单的参数直接read就可以,如果需要传递复杂的参数,如何封装Parcel和读取,请参考Android中的Parcel是什么。

4. enforceInterface和writeInterfaceToken是配套出现的,用来在通信双端进行一个校验,writeInterfaceToken()方法标注远程服务名称,理论上讲,这个名称不是必要的,因为客户端既然已将获取指定远程服务的Binder引用,那么就不会调用其他远程服务,该名称作为Binder驱动确保客户端的确像调用指定的服务端,所以要在服务端在接受的时候调用enforeInterface来校验,至于校验的值,只要通信双方约定好即可。

5. 在Server进程的服务中,实现Service的onBind抽象方法,并将上面集成Binder的类实例化的对象返回。那这个Binder对象就相当于注册在Binder驱动中的一个remote代理,可以通过这个对象实现对Server进程的通信。

 

再看一下client进程如何通过Binder调用Server进程提供的服务,先看代码:

public class MyClientActivity extends Activity{    IBinder mRemoteService;    ServiceConnection mServiceConnection = new ServiceConnection()    {        @Override public void onServiceConnected(ComponentName componentName, IBinder iBinder)        {            mRemoteService = iBinder;            getWeatherRemote();        }        @Override public void onServiceDisconnected(ComponentName componentName)        {            mRemoteService = null;        }    };    private void getWeatherRemote()    {        Parcel data = Parcel.obtain();        Parcel reply = Parcel.obtain();        data.writeInterfaceToken("MyServer");        //查询今天的天气情况        data.writeLong(TimeUtils.getNowMills());        try        {            mRemoteService.transact(MyServerService.WEATHER_INFO, data, reply, 0);            reply.enforceInterface("MyClient");            String result = reply.readString();            ToastUtils.showLong("今天天气:" + result);        }        catch (RemoteException e)        {            e.printStackTrace();        }        finally        {            data.recycle();            reply.recycle();        }    }    @Override protected void onCreate(@Nullable Bundle savedInstanceState)    {        super.onCreate(savedInstanceState);        Intent intent = new Intent(this,MyServerService.class);        bindService(intent,mServiceConnection,BIND_AUTO_CREATE);    }}

1. 首先,声明一个IBinder对象,这个对象在client来看就是server的代理,可以通过它与server通信。

2. 定义一个ServiceConnection对象,onServiceConnected和onServiceDisconnected两个方法,并在这两个方法中对刚才声明的IBinder对象进行赋值和置空。

3.使用BindService方法绑定Server进程的Service,并在参数中传入上一步定义的ServiceConnection对象,这样就可以完成由客户端到服务端的链接了。

4. client向server发送消息调用Server提供的服务,其实就是对parcel对象进行封装并传入约定值的过程,和server端接受数据并返回很像,可以直接看下代码。

5. client向server发送信息后,其实在Binder驱动中,会将client进程中执行发送数据的这个线程挂起来,然后在线程池中启动一个线程处理server端的接受逻辑,当Server端返回数据后,会notify被阻塞的client线程。

 

这样一个基本的Binder通信就完成了,但是如果是一些复杂数据传递起来,我们在封装parcel和解析的时候会很麻烦,还好Android对Binder的使用进行了一次封装,既AIDL。

 

使用AIDL实现两个应用的通信

AIDL最常用的使用场景就是两个应用之间的通信。要使用AIDL,首先要在两个应用的工程中分别建立aidl文件,注意这两个文件的包名和内容要相同,使用as建立aidl的方法如下:

创建完成后aidl文件所在的目录,这个包名是可以更改的。 

看下现在里面是啥都没有的,只有一个自动生成的方法:

// IMyAidlInterface.aidlpackage com.qyy.myaidl;// Declare any non-default types here with import statementsinterface IMyAidlInterface {    /**     * Demonstrates some basic types that you can use as parameters     * and return values in AIDL.     */    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,            double aDouble, String aString);}

先不用管这个方法,我们可以修改这个aidl的接口文件,实现一些自定义的方法,比如上面说过的查询天气:

interface IMyAidlInterface {    /**     * Demonstrates some basic types that you can use as parameters     * and return values in AIDL.     */    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,            double aDouble, String aString);    String getWeatherInfo(long timestamp);}

这样AIDL文件就定义好了,如果是应用间通信,别忘了这两个应用每个都要有一份这个文件。

使用AIDL通信同样有server端和client端的区别,老规矩,还是先看server端的实现。

package com.qyy.remotemonitor.ui.service;import android.app.Service;import android.content.Intent;import android.os.IBinder;import android.os.RemoteException;import android.support.annotation.Nullable;import com.qyy.myaidl.IMyAidlInterface;/** * Created by qinyy on 1/23/2019. */public class MyAIDLService extends Service{    private IMyAidlInterface.Stub mIMyAidlInterface = new IMyAidlInterface.Stub()    {        @Override public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,                                         double aDouble, String aString) throws RemoteException        {        }        @Override public String getWeatherInfo(long timestamp) throws RemoteException        {            String weather = "";            //根据date查询天气情况            // mPresenter.getWeather( com.blankj.utilcode.util.TimeUtils.millis2Date(timestamp));            return weather;        }    };    @Nullable @Override public IBinder onBind(Intent intent)    {        return mIMyAidlInterface;    }}

 对比一下,和上面使用Binder写的server端是不是很像,但是更加的简洁?

可以看到,我们只要创建一个AIDL自动生成的stub对象,并将我们在aidl中定义的接口给实现,再把这个stub对象在onBind中返回就好了。而onTransact的重写、parcel的解析什么的,aidl都帮我们做好了~ 通过查看源码可以发现,这个stub对象,就是AIDL根据我们写的AIDL文件自动生成的、继承了IBinder的一个类,它已经重写了onTransact并在里面实现了parcel的解析、服务请求码的分发、向client返回结果等等操作,使用起来非常的简单。

 

回头看下客户端如何实现,client是存在另外一个应用中的,那这个应用中有可能有很多地方都需要server提供的服务,上面写Binder客户端的时候,我们是在bindService的时候传入一个ServiceConnection对象,并重写其中的监听方法,为了可以在client应用全局使用Server提供的服务,可以把上面的步骤写在client应用的application类中。

 private IMyAidlInterface mRemoteService;    private ServiceConnection mServiceConnection = new ServiceConnection()    {        @Override public void onServiceConnected(ComponentName name, IBinder service)        {            mRemoteService = IMyAidlInterface.Stub.asInterface(service);        }        @Override public void onServiceDisconnected(ComponentName name)        {            mRemoteService = null;        }    };

声明一个AIDL文件接口的对象,然后和binder的使用一样,定义一个ServiceConnection,并在serviceconnect和disconnect的时候对remoteService进行赋值和置空

然后在合适的时机bind service

Intent remoteIntent = new Intent(                "服务端Service的包名加服务名");        bindService(createExplicitFromImplicitIntent(MyApplication.this, remoteIntent),                mServiceConnection, BIND_AUTO_CREATE);

createExplicitFromImplicitIntent方法是为了防止Service Intent must be explicit这个异常,具体代码如下:

 public static Intent createExplicitFromImplicitIntent(Context context, Intent implicitIntent) {        // Retrieve all services that can match the given intent        PackageManager pm = context.getPackageManager();        List resolveInfo = pm.queryIntentServices(implicitIntent, 0);        // Make sure only one match was found        if (resolveInfo == null || resolveInfo.size() != 1) {            return null;        }        // Get component info and create ComponentName        ResolveInfo serviceInfo = resolveInfo.get(0);        String packageName = serviceInfo.serviceInfo.packageName;        String className = serviceInfo.serviceInfo.name;        ComponentName component = new ComponentName(packageName, className);        // Create a new intent. Use the old one for extras and such reuse        Intent explicitIntent = new Intent(implicitIntent);        // Set the component to be explicit        explicitIntent.setComponent(component);        return explicitIntent;    }

然后可以在使用的时候就可以直接通过IMyAidlInterface 的实例调用对应Server端提供的方法了

        mRemoteService.getWeatherInfo(System.currentTimeMillis());

 

这篇文章只是从一种浅析的、便于使用的角度来分析了Binder和AIDL,如果有兴趣的话可以继续研究Binder驱动和ServerManager的源码。

更多相关文章

  1. mybatisplus的坑 insert标签insert into select无参数问题的解决
  2. python起点网月票榜字体反爬案例
  3. 浅析android通过jni控制service服务程序的简易流程
  4. 《Android开发从零开始》——25.数据存储(4)
  5. Android系统配置数据库注释(settings.db)
  6. Android中不同应用间实现SharedPreferences数据共享
  7. android图表ichartjs
  8. Android内容提供者源码
  9. android SharedPreferences

随机推荐

  1. Android开发工具——Android studio1.0正
  2. 让Ubuntu和Android同时运行(Ubuntu on And
  3. Android(安卓)T9搜索
  4. Xamarin Mono Android实现“再按一次退出
  5. 一起学android之EditText的各种使用(15)
  6. Android -- 设置textview文字居中或者控
  7. 在Android模拟器安装应用
  8. Android开发常用代码片段(三)
  9. Kotlin Anko Layout+MVP(Glide,Retrofit,
  10. APIDEMO GRIDVIEW