Android 进程间通信 Binder IPC

Android 整体架构

image

从下往上依次为

  • 内核层:Linux 内核和各类硬件设备的驱动,这里需要注意的是,Binder IPC 驱动也是在这一层实现
  • 硬件抽象层:封装「内核层」硬件驱动,提供可供「系统服务层」调用的统一硬件接口
  • 系统服务层:提供核心服务,并且提供可供「应用程序框架层」调用的接口
  • Binder IPC 层:作为「系统服务层」与「应用程序框架层」的 IPC 桥梁,互相传递接口调用的数据,实现跨进层的通讯
  • 应用程序框架层:这一层可以理解为 Android SDK,提供四大组件,View 绘制体系等平时开发中用到的基础部件

内核层与硬件抽象层均用 C/C++ 实现,系统服务层是以 Java 实现,硬件抽象层编译为 so 文件,以 JNI 的形式供系统服务层使用。

系统服务层中的服务随系统的启动而启动,每一个服务均运行在一个独立进程中,所以本质上来说就是运行在一个独立进程的 Dalvik 虚拟机中。开发者的 APP 运行在一个新的独立进程空间,如何调用到系统服务层中的接口呢?IPC(Inter-Process Communication),进程间通讯。
每一个系统服务在应用层序框架层都有一个 Manager 与之对应,方便开发者调用其相关的功能。关系如下:


image

进程间通信

进程间通信的几种方式:管道、消息队列、共享内存、socket套接字、Binder等。

  • 其中管道和消息队列采用存储-转发方式,即数据线通发送方缓存区拷贝到内核开辟的缓存区中,然后再从内核缓存区拷贝到接收方缓存区,至少有两次拷贝过程。

  • 共享内存虽然无需拷贝,但控制复杂,难以使用。
    socket作为一款通用接口,其传输效率低,开销大,主要用在跨网络的进程通信和本机进程间的低速通信。

  • Binder通过内存映射的方式,使数据只需在内存进行一次读写过程。

内存映射,就是讲用户控件的一段内存区域映射到内核空间,映射成功后,用户对这段内存区域的修改可以直接反映到内核空间,相反内核空间对这段区域的修改也可以直接反映在用户空间。对于内核空间<->用户空间两者之间需要大量数据传输灯操作的话,效率是非常高的。

利用Binder 进行IPC 进程通信

image
  • Binder IPC 属于 C/S 架构,包括 Client、Driver、Server 三个部分

  • Client 可以手动调用 Driver 的 transact 接口,也可以通过 AIDL 生成的 Proxy 调用

  • Server 中会启动一个「线程池」来处理 Client 的调用请求,处理完成后将结果返回给 Driver,Driver 再返回给 Client

实现 Binder IPC 有两种方式:手动实现 和 利用AIDL实现。

1、 继承 IBinder,手动实现 IPC

Service

public class NoAidlService extends Service {

    public static final int TRANSATION_ADD = 0x001;
    public static final String DESCRIPTOR = "NoAidlService";
    private Binder mNoAidlBinder = new NoAidlBinder();
    public NoAidlService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        return  mNoAidlBinder;
    }



    private class NoAidlBinder extends Binder{
        @Override
        protected boolean onTransact(int code, Parcel data,  Parcel reply, int flags) throws RemoteException {
            switch (code){
                case TRANSATION_ADD:
                    data.enforceInterface(DESCRIPTOR);
                    int _arg0;
                    _arg0 = data.readInt();

                    int _arg1 = data.readInt();
                    int _result = _arg0+_arg1;
                    reply.writeNoException();
                    reply.writeInt(_result);

                    return true;
            }

            return super.onTransact(code, data, reply, flags);
        }
    }
}

client :

   
    private ServiceConnection mNoAidlConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            android.os.Parcel _data = android.os.Parcel.obtain();
            android.os.Parcel _reply = android.os.Parcel.obtain();

            try {
                int _result;
                _data.writeInterfaceToken("NoAidlService");
                _data.writeInt(100);
                _data.writeInt(200);
                service.transact(NoAidlService.TRANSATION_ADD,_data,_reply,0);
                _reply.readException();
                _result = _reply.readInt();
                Log.d(TAG,"_result:"+_result);

            }catch (Exception e){

            }finally {
                _reply.recycle();
                _data.recycle();
            }

        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };
    
     /**
     * 调用 NoAIDL
     */
    public void testNoADILService(){
        Intent intent = new Intent(this,NoAidlService.class);
        bindService(intent,mNoAidlConnection, Context.BIND_AUTO_CREATE);
    }

manifest.xml

    <service
            android:name=".noaidlservice.NoAidlService"
            android:enabled="true"
            android:exported="true"
            android:process=":remote_noadil">

        </service>

2、利用AIDL 实现IPC

AIDL 是不是过时 实现了对 ”手动实现IPC“ 的一种封装,简化了程序员的操作。

image
(1)无自定义类型的 IPC

在main目录下 与java同级建立aidl文件夹,在相同的包名下建立aidl文件

PlusAidlInterface.aidl

// PlusAidlInterface.aidl
package com.example.mybinder;

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

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

     int plus(int a,int b);

    String evalate(in User user);
}

建立aidl文件后,点击AS“同步”按钮,会在build/generated/source/aidl/ 目录下自动生成aidl对应的代码。

建立AIDLservice.java

public class AIDLService extends Service {

    public AIDLService(){

    }

    private final PlusAidlInterface.Stub mBinder = new PlusAidlInterface.Stub() {
        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {

        }

        @Override
        public int plus(int a, int b) throws RemoteException {
            return (a+b);
        }

    };

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


}

manifest.xml

 <service
            android:name=".aidlservice.AIDLService"
            android:enabled="true"
            android:exported="true"
            android:process=":remote_adil">

        </service>

client 调用代码


    private PlusAidlInterface mPlus;
    private ServiceConnection mAIDLConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
                mPlus = PlusAidlInterface.Stub.asInterface(service);
                try {
                    int result = mPlus.plus(100,200);
                    Log.d(TAG,"_result 100+200 = "+result);

                    User user = new User();
                    user.name = "nancy";
                    user.age = "19";
                    String evalate = mPlus.evalate(user);
                    Log.d(TAG,"evalate:"+evalate);

                }catch (Exception e){

                }finally {

                }

        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };
    
    
    public void testAIDL(){
        Intent intent = new Intent(this,AIDLService.class);
        bindService(intent,mAIDLConnection, Context.BIND_AUTO_CREATE);
    }


(2)有自定义数据类型的 Binder IPC
  • 自定义数据类型必须实现Parcelable 接口,可序列化。

User.java

public class User implements Parcelable{


    public String name = "feifei";
    public String age = "18";
    public String value ="";

    public User(){

    }
    protected User(Parcel in) {
        name = in.readString();
        age = in.readString();
        value = in.readString();
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(name);
        dest.writeString(age);
        dest.writeString(value);
    }

//    @Override
//    public void readFromParcel(Parcel dest) {
//        //注意,此处的读值顺序应当是和writeToParcel()方法中一致的
//        value = dest.readString();
//        age = dest.readString();
//        name = dest.readString();
//    }

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

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

        @Override
        public User[] newArray(int size) {
            return new User[size];
        }
    };
}
  • 每个自定义类型必须 创建一个同名的aidl文件与之对应。用来生命该自定义数据类型

User.aidl

// User.aidl
package com.example.mybinder;

parcelable User
  • 自定义数据类型 User.java 最好和aidl放到同一个目录下。同时修改build.gradle的sourceset 是AS 能够找到User.java
apply plugin: 'com.android.application'

android {
    compileSdkVersion 27


    sourceSets {
        main {
            java.srcDirs = ['src/main/java', 'src/main/aidl']
        }
    }

    defaultConfig {
        applicationId "com.example.mybinder"
        minSdkVersion 15
        targetSdkVersion 27
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

}
  • PlusAidlInterface.aidl 中补充 String evalate(in User user) 接口声明。
// PlusAidlInterface.aidl
package com.example.mybinder;

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

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

     int plus(int a,int b);

    String evalate(in User user);
}
  • AIDLService 中补充 public String evalate(User user) 接口的实现
public class AIDLService extends Service {

    public AIDLService(){

    }

    private final PlusAidlInterface.Stub mBinder = new PlusAidlInterface.Stub() {
        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {

        }

        @Override
        public int plus(int a, int b) throws RemoteException {
            return (a+b);
        }

        @Override
        public String evalate(User user){
            return  user.name+",age:"+user.age+":good";
        }
    };

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


}
  • 调用 evalate()方法
    public void testAIDL(){
        Intent intent = new Intent(this,AIDLService.class);
        bindService(intent,mAIDLConnection, Context.BIND_AUTO_CREATE);
    }


    private PlusAidlInterface mPlus;
    private ServiceConnection mAIDLConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
                mPlus = PlusAidlInterface.Stub.asInterface(service);
                try {
                 
                    String evalate = mPlus.evalate(user);
                    Log.d(TAG,"evalate:"+evalate);

                }catch (Exception e){

                }finally {

                }

        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };
注意AIDL中自定数据类型 有数据流向的概念

-in tag 表示数据只能由客户端流向服务端
服务端将会接收到一个那个对象的完整数据,但是客户端的那个对象不会因为服务端对传参的修改而发生变动

  • out tag 表示数据只能由服务端流向客户端
    服务端将会接收到那个对象的的空对象,但是在服务端对接收到的空对象有任何修改之后客户端将会同步变动;

-inout tag 则表示数据可在服务端与客户端之间双向流通。

服务端将会接收到客户端传来对象的完整信息,并且客户端将会同步服务端对该对象的任何变动.

    void setBookPrice(in Book book , int price)
    void setBookName(in Book book , String name)
    void addBookIn(in Book book);
    void addBookOut(out Book book);
    void addBookInout(inout Book book);

注意:
-java 中的基本类型和 String ,CharSequence 的定向 tag 默认且只能是 in 。

-要想定义out 或 inout的数据类型,还必须实现readFromParcel 接口才行。

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

推荐阅读更多精彩内容