Android通信机制分析-Binder(1)

概述

Android四大组件分别是 ActivityServiceBroadcastReceiverContentProvider,我们平时开发的 App 都是由四大组件中的一个或者多个组合而成;这四大组件所涉及的多进程间通信底层实现都是基于 Binder 的 IPC 机制。

我们平时开发过程中用到很多跨进程的通信,比如:

  1. App 中的 ActivityA 调用系统的 ActivityB

  2. App 中的 ActivityA 调用另一个 App 的 Service

Binder 作为 Android 系统提供的一种的 IPC 机制,整个 Android 系统架构中大量采用了 Binder 机制的 IPC 方案。

IPC

说到 IPC,我们来稍微看一下 IPC 机制的原理:进程间通信(IPC, Inter-Process Communication) 指至少两个进程或线程减传送数据或者信号的技术或方法。每个进程都有自己的一部分独立的系统资源,彼此之间是隔离的。为了能使不同的进程相互访问资源并进行协调工作,才有了进程间通信。IPC 的方法有多种,包括文件、Socket、管道、信号量、共享内存 等,我们今天主要来说说 Android 中的 Binde r。

Binder

概念

每个 Android 在各自独立的 Dalvik 虚拟机中运行,每个进程拥有独立的地址空间和资源,对应的地址空间有一部分是用户空间,另一部分是内核空间。对于用户空间,不同进程之间彼此是不能共享的,而内核空间则是可以共享的,Client 进程同 Server 进程通信,就是利用进程间可共享的内核空间(内核驱动)来完成底层通信工作的。

IPC 机制

Android 中 Binder 通信采用的是 C/S 架构,Binder 框架包括 Client、Server、ServiceManager 以及 Binder 驱动,其中 ServiceManager 用来管理系统中各种服务。其中 Server,Client,ServiceManager 运行于用户空间,Binder 驱动运行于内核空间。这四个角色的关系和互联网类似:Server 是服务器,Client 是客户终端,ServiceManager 是域名服务器(DNS),Binder 驱动是路由器。如图:

Binder

Binder使用实例

Binder 是 Android 中一个类,它实现 IBinder 接口,从 IPC 方面来看 Binder 是一种跨进程通信的方式,从 Framework 方面来看 Binder 是 ServiceManager 连接各种 Manager (WindowManager, ActivityManager 等等)和相应的 ManagerService (WindowManagerService, ActivityManagerService 等等)的桥梁;


AMS 架构

对客户端应用来说,Binder 是客户端和服务端通信的媒介,当bindService的时候,服务端会返回一个包含服务端业务调用的 Binder 对象,通过这个 Binder 对象客户端可以获取服务端提供的服务、数据等。

public class LocalService extends Service {
    // Binder given to clients
    private final IBinder mBinder = new LocalBinder();
    // Random number generator
    private final Random mGenerator = new Random();

    /**
     * Class used for the client Binder.  Because we know this service always
     * runs in the same process as its clients, we don't need to deal with IPC.
     */
    public class LocalBinder extends Binder {
        LocalService getService() {
            // Return this instance of LocalService so clients can call public methods
            return LocalService.this;
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder; //通过 onBind 返回一个服务端的 Binder 对象
    }

    /** method for clients */
    public int getRandomNumber() {
      return mGenerator.nextInt(100);
    }
}

上面这段代码是 Service 的一种实现方式,这里的 Binder 不涉及跨进程通信,所以比较简单,跨进程的 Binder 使用在 Android 中主要是 Messager 和 AIDL,Messager 的底层实现也是 AIDL,所以我们先来聊聊 AIDL。

AIDL

新建一个 AIDL 的文件</br>


新建 AIDL File

新建后会自动生成 aidl 的目录,并将新建的 AIDL 文件放到该目录下</br>


生成的aidl目录

Book.aidl 文件内容是自动生成的,我们可以根据自己的需求进行修改, 自动生成的代码标记了 import 语句的地方和可以用做 AIDL 参数和返回值的一些基本类型

// Book.aidl
package me.jifengzhang.aidldemo;

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

interface Book {
    /**
     * 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);
}

这个demo主要是提供 Book 的两个操作,我们新建的文件包括 Book.java 、 IBook.aidl 、 IBookManager.aidl,其中 Book 对象需要跨进程通过Binder传输,所以需要实现 Parcelable 接口。

Book.java

import android.os.Parcelable;

public class Book implements Parcelable{
    public int bookId;
    public String bookName;

    public Book(int bookId, String bookName) {
        this.bookId = bookId;
        this.bookName = bookName;
    }

    protected Book(Parcel in) {
        bookId = in.readInt();
        bookName = in.readString();
    }

    public static final Creator<Book> CREATOR = new Creator<Book>() {
        @Override
        public Book createFromParcel(Parcel in) {
            return new Book(in);
        }

        @Override
        public Book[] newArray(int size) {
            return new Book[size];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(bookId);
        dest.writeString(bookName);
    }
}

Book.aidl

package me.jifengzhang.aidldemo;

parcelable Book;

IBookManager.aidl

package me.jifengzhang.aidldemo;

import me.jifengzhang.aidldemo.Book;

interface IBookManager {
    List<Book> getBookList();
    void addBook(in Book book);
}

编译后,会自动生成 IBookManager.aidl 对应的 java 文件(app/build/generated/source/aidl/debug/me/jifengzhang/aidldemo/IBookManager.java),格式调整后如下:

package me.jifengzhang.aidldemo;

public interface IBookManager extends android.os.IInterface {
    /**
    * Local-side IPC implementation stub class.
    */
   public static abstract class Stub extends android.os.Binder implements me.jifengzhang.aidldemo.IBookManager {

       ...

   }

   public java.util.List<me.jifengzhang.aidldemo.Book> getBookList() throws android.os.RemoteException;

   public void addBook(me.jifengzhang.aidldemo.Book book) throws android.os.RemoteException;

这个类结构其实很简单,它继承了 IInterface 接口(所有可以在 Binder 中传输的接口都需要继承 IInterface 接口)同时它自己也是一个接口类,可以看到其实它的内部结构就是声明了两个方法 getBookList 和 addBook (显然这两个方法是 IBookManager.aidl 中声明的),同时还有一个内部类 Stub,这个类就是一个 Binder 类(很明显 Stub 继承 Binder)。但我们应该认识到 IBookManager.java 的核心实现是 Stub 以及 Stub 的内部代理类 Proxy。

下面详细介绍下 Stub 以及内部代理类 Proxy 中各个方法的含义:

** DESCRIPTOR **

Binder 的唯一标识符,一般使用当前 Binder 的类名表示, 比如上述例子中的 *** "me.jifengzhang.aidldemo.IBookManager" ***

** asInterface **

转换服务端传递过来的 IBinder 对象为客户端需要的 AIDL 接口类型的对象,这个转换过程是区分进程的,如果客户端和服务端是同一个进程,那么该方法返回的就是服务端的 Stub 对象本身,当然如果不是同一个进程则返回的是封装后的 Stub.Proxy 对象。

public static me.jifengzhang.aidldemo.IBookManager asInterface(android.os.IBinder obj) {
    if ((obj == null)) {
        return null;
    }
    android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
    if (((iin != null) && (iin instanceof me.jifengzhang.aidldemo.IBookManager))) { //同一个进程
        return ((me.jifengzhang.aidldemo.IBookManager) iin);
    }
    //不同进程
    return new me.jifengzhang.aidldemo.IBookManager.Stub.Proxy(obj);
}

** asBinder **

该方法用于返回当前 Binder 对象

** onTransact **

该方法运行在服务端的 Binder 线程池中, 当客户端发起跨进程请求时,远程请求会通过系统底层封装后交给该方法处理。服务端通过 code 来确定客户端请求的方法是什么,从 data:Parcel 中取出方法所需要的参数,然后执行目标方法,最后将方法执行的结果(返回值)存到 replay: Parcel 中。

** Proxy#getBookList **

该方法运行在客户端,当客户端调用该方法时,首先创建该方法所需要的输入 Parcel 对象 _data, 输出 Parcel 对象 _reply 以及返回值 List,然后将参数信息写入 _data 中,接着调用 transact 方法发起远程调用 RPC ,此时客户端当前线程挂起,服务端的 onTransact 方法会被调用执行,直到 RPC 过程返回后,客户端当前线程继续执行,并从 _reply 中取出 RPC 过程的返回结果。最后将 _reply 转换成 List 返回。

** Proxy#addBook

执行过程和 getBookList 一样。

通过简单的分析描述,其实 AIDL 的实现其实很简单,方法调用的流程大概如下:


AIDL 流程

服务端(Service)实现

上面简单说明了 AIDL 中接口的定义,这里我们来看看服务端也就是作为服务提供者的 Service 的实现,我们可以想到的是 Service 肯定是要实现 IBookManager 定义的两个接口,剩余的应该有 Service 的基本实现。那我们来看代码:

public class BookManagerService extends Service {
    private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();

    private Binder mBinder = new IBookManager.Stub() {

        @Override
        public List<Book> getBookList() throws RemoteException {
            return mBookList;
        }

        @Override
        public void addBook(Book book) throws RemoteException {
            mBookList.add(book);
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();
        mBookList.add(new Book(1, "Android"));
        mBookList.add(new Book(2, "IOS"));
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }
}

果然,代码和我们想的是一样的,这里通过 new 一个 Stub (IBookManager 的内部类,继承自 Binder)来实现 getBookList 和 addBook 两个接口方法。

客户端的实现

客户端也就是访问端的实现也比较简单,首先是绑定服务(BinderService),绑定成功后将服务端返回的 Binder 对象转换为 AIDL 接口,然后就可以使用这个接口来调用 Server 端的远程方法。

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    //绑定服务
    Intent intent = new Intent(this, BookManagerService.class);
    bindService(intent, mServiceConnect, Context.BIND_AUTO_CREATE);
}

@Override
protected void onDestroy() {
    super.onDestroy();
    //解绑
    unbindService(mServiceConnect);
}

private ServiceConnection mServiceConnect = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        //将服务端传递过来的Binder对象转换为AIDL接口
        IBookManager manager = IBookManager.Stub.asInterface(service);
        try {
            List<Book> list = manager.getBookList();
            Log.i("MainActivity", "queue book list : " + list.toString());
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {

    }
};

运行结果

我们在 Manifest 中给 BookManagerService 设置 process 属性,这样 BookManagerService 和 MainActivity 就是不同的进程。

<service android:name=".BookManagerService" android:process=":remote"/>

运行结果:

05-04 10:10:28.622 17622-17622/me.jifengzhang.aidldemo I/MainActivity: queue book list : [[1:Android], [2:IOS]]

参考

Gityuan

** Android 开发艺术探索 **

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

推荐阅读更多精彩内容