Android进程间通信(IPC)之Binder/AIDL

我们常常听说IPC(Inter Process Communication)、线程、Binder、AIDL这次词,似懂非懂,在从事Android应用开发初期,往往按照规则调用SDK的接口即可实现绝大部分功能没有深究这些名词,随着岁月流逝,终于不得不在某个时刻开始反反复复的问自己:

  1. 到底什么是进程间通信?
  2. 线程是什么东西,和进程有什么关系?
  3. 我们为什么要了解进程间通信,在Android开发中有哪些用途?
  4. Binder机制究竟是什么样子的机制?
  5. AIDL是什么,和Binder之间有着什么样子的关系?

笔者读过很多书籍和相关博文,总是会在某个关键点感觉到模糊不清,本文就用浅显易懂的文字,按照我认为最合理的顺序和方式,带着大家揭开Android进程间通信的神秘面纱,以更好的为Android应用开发服务,需要注意的是,本文不打算对每一个概念进行最深层次的细节分析,而是重在思路上的理解,以助于达成我们理解Android进程间通信(IPC)机制的目的,笔者后续会出一系列文章对提到的每一个概念进行深入的分析,敬请读者朋友期待。

  • IPC(进程间通信)的基本概念

    • 进程的概念

    • 进程和线程之间的区别和联系

    • 进程间通信的传统方式

  • Binder机制的实现原理
    • Binder的通信模型
    • Binder在Android中的使用场景
    • Binder和AIDL之间的关系

IPC(进程间通信)的基本概念

进程

要理解进程间通信,必须得先理解进程,众所周知,进程是操作系统中一个核心概念,但是却非常抽象,内容繁多,甚至涉及计算机发展史,这里用一个例子来帮助理解进程的概念即可,不做深入研究,相信很多人都用过打印机,假设一条街上有10家公司,而且这条街上只有一台打印机,如果需要打印,每次都要排着长长的队伍,假设前面的公司打印的材料要打印一整天,那后面的人也只能跟着等一整天(即使只需要几分钟就可以打印完成也需要等待如此长的时间),毫无疑问,这是一种低效的工作方式,后来人们就对打印机做了改进,设定10个输入纸张的入口,10家公司都可以同时把需要打印的材料放在对应的入口处,打印机公平对待,打印一家A公司的纸,然后再打印一张B公司的纸,依此顺序循环往复,无疑这会缩短大家等待的时间。此例,如果我们把这条街道比喻做计算机,把这台打印机比作处理器,那么这街上的10家公司就是进程

进程和线程之间的区别和联系

那么什么是线程呢?这得对照着我们刚才的例子,计算机(街道)为每个进程(公司)开辟了固定的内存空间(办公场所),CPU(打印机)负责不断切换进程(公司)进行工作,但是我们也知道,一个公司有多个部门,如果公司内部有部门甲和部门乙同时都需要去打印呢,那么公司内部(进程内部)就要自己协调好先后顺序,这里的各个部门,我们可以理解为线程。根据这个例子我们可以大概看出,进程和线程之间有如下关系:

  1. 每个进程有计算机分配的独立的内存空间,而同一进程的内存空间是共享的
  2. 线程由进程产生,在进程中工作
  3. 线程比进程更小,不由系统直接创建维护,而是进程内部调度,因此线程的开销要比进程小得多

进程间通信的传统方式

理解了进程,已经进程与线程的关系,那么什么是进程间通信呢?接着上文的例子,每家公司是一个进程,那公司之间通信就很容易让人理解的,虽然各个公司相互独立,通常情况下,我们是不可以私自进入别人的内部空间的,那难免有业务的往来,总得联系吧,要么打电话,要么找街道大妈传话等等...

那么在计算机中我们通常有哪些方式进行通信呢?

  1. 管道
  2. FIFO
  3. 消息队列
  4. 信号量
  5. 共享内存区
  6. ……

以上五种通信方式我就不逐一展开了,大家在这里知道即可。下文中,我会阐述为什么有了如此多的进程间通信方式,还会有Binder。

Binder机制的实现原理

Binder产生的原因

如题,为什么有了那么多进程间通信的方式,还需要Binder这种新兴的进程间通信机制呢?一种新事物的产生必然是为了解决某些问题产生的,Binder可以优化传统进程间通信方式以符合手持设备的需要。

首先,Binder的传输性能更好

Socket是一个通用接口,其传输效率低,开销大;共享内存的方式虽然在传输时不需要拷贝数据,但其控制机制复杂;管道和消息队列采用存储转发方式,至少需要拷贝两次数据,效率低下;而Binder机制对复杂数据类型进行传递的时候可以复用内存,仅需要拷贝1次数据。

其次,Binder的安全性更高

传统的进程间通信方式对于通信双方没有做出严格的验证,只在上层协议进行架设;而Binder机制从协议本身就支持对通信双方的身份校验,提升了安全性。

Binder的通信模型

在讲解通信模型之前,先记住Binder通信机制中几个重要的组成部分:

Client进程:跨进程通信的客户端

Server进程:跨进程通信的服务端

Binder驱动:跨进程通信的媒介也是基础

ServerManager:帮助实现进程间通信的一个服务商

对照下图,初步理解这这个重要组成之间的关系:

在讲正事之前,我先讲个故事:

长栓想娶隔壁村的小翠姑娘,到了隔壁村的媒婆张大妈家,小王问:“大妈啊,听说咱们村有个小翠姑娘很漂亮,我想娶她当媳妇,你看行吗”,大妈翻了翻自己苦心经营的未婚女青年名单,嘿嘿,还真有,找到了小翠的号码,长栓迫不及待接通了电话。

Service Manager

Service Manager是一个Linux进程,它是一个信息登记查询中心。任何Service进程在使用之前,均要向Service Manager注册;同时,Client进程要想访问某个Server进程,要向Service Manager查询是否存在该服务,并获得这个Server(备注:并非真的获得,这个过程中Binder驱动做了许多工作)。对照上面的例子,张大妈就是Service Manager,小翠把自己的信息登记到了张大妈的本本上,小王从张大妈处取得了联系方式。

Binder驱动

从上面的例子中我们可以发现,虽然张大妈把电话号码给了小王,但是张大妈不可能把小翠真人直接交给小王,因为他们每个人都是独立的,相当于进程间是完全独立的,这时候真正的通信是用电话实现的,这里的电话就相当于Binder驱动。电话是怎么工作的呢,就相当于Binder驱动式如何工作的,这涉及到大量的C层代码,作为一个应用层开发者,不了解Binder驱动的具体实现细节也不影响开发出高质量优秀的应用程序。

Server进程/Client进程

一句话,Server进程就是故事中的小翠,这样一来,我们可以发现,Client进程和Server进程是相对的,因为小王也可以把电话留在张大妈处,由小翠来查询,谁发起请求谁就是Client进程,谁被请求谁就是Server进程。Server通过函数获得Service Manager远程接口,然后将自己的Service添加到Service Manager中,接着启动自己,等待着Client的请求。

Binder在Android中的使用场景

笔者即将在另外一篇文章 《AMS(Activity Manager Service)到底做了啥?》进行阐述Binder在Android中的具体应用案例,敬请读者朋友们期待。

AIDL和Binder之间的关系

其实一句话即可阐述明白:AIDL是一种快速使用Binder机制的便捷形式。AIDL是Android Interface definition language的缩写,本质上,是IDE工具用aidl文件生成相应可以正确使用Binder机制的模板代码。下面我们实现一个利用AIDL进行进程间通信的小案例,然后再集中进行分析其中的一些核心点。

AIDL小案例

案例场景:app_client进程想要从app_server进程中获取一个字符串,如何通过AIDL来实现。

GitHub地址:https://github.com/xcdebw123/AIDLTest

本案例在Android Studio中的实现步骤如下:

既然要实现进程间通信,则需要创建两个APP,这里我们创建了两个modual,一个是app_client,一个是app_server

然后我们先在app_server中创建一个AIDL文件,MyAIDL,在app_server 的module目录右键,输入名字,如下图示

随后会发现app_server module下多了一个aidl目录并且自动生成了MyAIDL.aidl文件

会发现自动生成了一些方法,将下图中红框中的多余代码删除即可

接下里我们在AIDL文件中自己定义一个我们要提供给app_client使用的接口方法 getText()

然后Make  Module "app_server"

此时可以找到自动生成的MyAIDL.java文件,且该文件中有刚才在MyAIDL中定义好的getText方法

至于MyAIDL.java文件中的内容,我们在后面单独拆开来讲,目前我们先把案例中的实现步骤讲完。

随后我们新建一个Service,JCMyService.java,并且在Service中创建一个内部类MyBinder,继承我们刚才生成的MyAIDL.java中的Stub类,并实现接口方法,在onBind返回我们刚才创建的内部类MyBinder的实例。

接下来,我们把app_server中的这个MyAIDL.aidl文件拷贝到app_client中的aidl目录下,需要注意的是,MyAIDL.aidl必须位于xyz.jiaci.app_server包名下,也就是说,即使是将app_server中的aidl文件拷贝到了app_client中,也需要保证aidl的包名和在app_server中保持一致。

Make modual app_client 会发现和app_server一样生成了MyAIDL.java文件

此时,我们在app_client中的Activity中进行调用

package xyz.jiaci.app_client;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Toast;

import java.util.List;

import xyz.jiaci.app_server.MyAIDL;

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    private MyAIDL myAIDL;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ServiceConnection connection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                myAIDL = MyAIDL.Stub.asInterface(service);
            }

            @Override
            public void onServiceDisconnected(ComponentName name) {

            }
        };
        Intent intentPre = new Intent();
        intentPre.setAction("xyz.jiaci.app_server.JCMYSERVICE");
        final Intent intent = new Intent(createExplicitFromImplicitIntent(this,intentPre));
        bindService(intent,connection,BIND_AUTO_CREATE);
    }
    //点击事件
    public void showToast(View v){
        try {
            Toast.makeText(MainActivity.this, myAIDL.getText(), Toast.LENGTH_LONG).show();
        } catch (RemoteException e) {
            Log.e(TAG,e.toString());
        }
    }


    /***
     * Android L (lollipop, API 21) introduced a new problem when trying to invoke implicit intent,
     * "java.lang.IllegalArgumentException: Service Intent must be explicit"
     *
     * If you are using an implicit intent, and know only 1 target would answer this intent,
     * This method will help you turn the implicit intent into the explicit form.
     *
     * Inspired from SO answer: http://stackoverflow.com/a/26318757/1446466
     * @param context
     * @param implicitIntent - The original implicit intent
     * @return Explicit Intent created from the implicit original intent
     */
    public static Intent createExplicitFromImplicitIntent(Context context, Intent implicitIntent) {
        // Retrieve all services that can match the given intent
        PackageManager pm = context.getPackageManager();
        List<ResolveInfo> 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;
    }
}

此时我们同时运行app_server和app_client,在app_client中点击显示文字按钮

好了,至此小案例的功能已经实现,但此时我们不得不问自己:

1.为什么我们可以通过调用MyAIDL.java实现这个案例中的跨进程通信呢?

2.新生成的类MyAIDL.java里面那么复杂,层层嵌套的几个类,到底啥关系?

首先我们来回答第一个问题,前面我们说过,所谓AIDL机制,其实就是利用Binder实现进行进程间通信的一种方式。那么问题来了,既然我们是利用Binder机制实现,那具体怎么实现呢?当我们回答了第二个大家就知道了。

下面我们来看看这个自动生成的MyAIDL.java文件

package xyz.jiaci.app_server;

public interface MyAIDL extends android.os.IInterface {
    /**
     * Local-side IPC implementation stub class.
     */
    public static abstract class Stub extends android.os.Binder implements xyz.jiaci.app_server.MyAIDL {
        private static final java.lang.String DESCRIPTOR = "xyz.jiaci.app_server.MyAIDL";

        /**
         * Construct the stub at attach it to the interface.
         */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        /**
         * Cast an IBinder object into an xyz.jiaci.app_server.MyAIDL interface,
         * generating a proxy if needed.
         */
        public static xyz.jiaci.app_server.MyAIDL asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof xyz.jiaci.app_server.MyAIDL))) {
                return ((xyz.jiaci.app_server.MyAIDL) iin);
            }
            return new xyz.jiaci.app_server.MyAIDL.Stub.Proxy(obj);
        }

        @Override
        public android.os.IBinder asBinder() {
            return this;
        }

        @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
            java.lang.String descriptor = DESCRIPTOR;
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(descriptor);
                    return true;
                }
                case TRANSACTION_getText: {
                    data.enforceInterface(descriptor);
                    java.lang.String _result = this.getText();
                    reply.writeNoException();
                    reply.writeString(_result);
                    return true;
                }
                default: {
                    return super.onTransact(code, data, reply, flags);
                }
            }
        }

        private static class Proxy implements xyz.jiaci.app_server.MyAIDL {
            private android.os.IBinder mRemote;

            Proxy(android.os.IBinder remote) {
                mRemote = remote;
            }

            @Override
            public android.os.IBinder asBinder() {
                return mRemote;
            }

            public java.lang.String getInterfaceDescriptor() {
                return DESCRIPTOR;
            }

            @Override
            public java.lang.String getText() throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                java.lang.String _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_getText, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.readString();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
        }

        static final int TRANSACTION_getText = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    }

    public java.lang.String getText() throws android.os.RemoteException;
}

我们发现这个文件,涉及一个接口(MyAIDL),一个MyAIDL的静态抽象内部类Stub,还有一个Stub的静态内部类Proxy。这三个类其实分开写效果也一样,但是Google工程师考虑到一个项目中往往存在多个aidl文件,这样生成自动生成的java文件难免会类名重复,通过这种内部类的方式就不会造成这种冲突了,赞!那么这三个类,分别承担了什么样的职责呢?而且这里面这么多方法各自有什么作用呢?

我们先来分析本文小案例的调用过程:

app_client端调用

MyAIDL.Stub.asInterface(service).getText();//这里的参数service是一个IBinder对象

Stub的asInterface方法其实就是判断这里的IBinder对象service是不是与自己处于同一个进程中,如果是,则直接使用,接下来与Binder跨进程通信无关,如果不是,则把IBinder参数包装成一个Proxy对象,此时调用Stub的getText方法,实际上是调用Proxy的getText方法(这里如果有疑惑,则请参阅笔者将要写的新文章《设计模式之代理模式》,敬请期待)。Proxy类在自己的getText方法中会使用Parcelable来准备数据,把函数名称、函数参数都写入_data,让_reply接收函数返回值。最后使用IBinder的transact方法,就可以把数据传给Binder的Server端了。

/**
 *mRemote是app_client从app_server获得的IBinder对象,当然并不是直接持有原始binder对象的引用
 *当然,底层具体怎么实现的通信,这就是Binder驱动的功劳了,反正我们知道,看起来app_client端
 *似乎真的拿到了app_server端的对象,可以操作对象就行了
 *Stub.TRANSACTION_getText代表方法名称标识,_data代表参数,_reply代表返回结果所在地方
 */
mRemote.transact(Stub.TRANSACTION_getText, _data, _reply, 0);

如果一切顺利,app_server所处进程则的相对应的Binder对象(此例中即为存在于app_server中继承自MyAIDL的类MyBinder的实例)会通过onTransact方法接收Client进程传过来的数据,包括函数名称、函数参数,找到对应的函数(此处是getText()),传入来自app_client的参数,得到结果并赋值给_reply并返回。

public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
            java.lang.String descriptor = DESCRIPTOR;
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(descriptor);
                    return true;
                }
                case TRANSACTION_getText: {
                    data.enforceInterface(descriptor);
                    java.lang.String _result = this.getText();
                    reply.writeNoException();
                    reply.writeString(_result);
                    return true;
                }
                default: {
                    return super.onTransact(code, data, reply, flags);
                }
            }
        }

总之,所谓的AIDL其实就是对Binder的简单封装。

MyAIDL.Stub.asInterface(service).getText();//这里的参数service是一个IBinder对象

本文代码地址:https://github.com/xcdebw123/AIDLTest

交流QQ群:821357266

欢迎关注微信公众号:甲辞

猜你喜欢

转载自blog.csdn.net/strongthinker/article/details/89157903