Android Binder机制浅析及AIDL的使用

参考

轻松理解 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跨进程通信的原理

Android Binder机制浅析及AIDL的使用_第1张图片

可以看到,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的方法如下:

Android Binder机制浅析及AIDL的使用_第2张图片

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

Android Binder机制浅析及AIDL的使用_第3张图片

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

// IMyAidlInterface.aidl
package com.qyy.myaidl;

// Declare any non-default types here with import statements

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);
}

先不用管这个方法,我们可以修改这个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的源码。

你可能感兴趣的:(Android)