Android跨进程通信Binder机制与AIDL实例

Posted Tr0e

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android跨进程通信Binder机制与AIDL实例相关的知识,希望对你有一定的参考价值。

在操作系统中,进程与进程间的内存和数据都是不共享的。这样做的目的,是为了避免进程间相互操作数据的现象发生,从而引起各自的安全问题。为了实现进程隔离,采用了虚拟地址空间,两个进程各自的虚拟地址不同,从逻辑上来实现彼此间的隔离。两个进程之间要进行通信,就需要采用特殊的通信机制:进程间通信(IPC:Inter-Process Communication,即进程间通信或跨进程通信,简称 IPC)。

进程通信

1.1 进程空间划分

一个进程空间分为 用户空间 & 内核空间(Kernel),即把进程内 用户 & 内核 隔离开来。二者的区别:

  1. 进程间用户空间的数据不可共享,所以用户空间 = 不可共享空间
  2. 进程间内核空间的数据可共享,所以内核空间 = 可共享空间,所有进程共用1个内核空间

进程内 用户空间 & 内核空间 进行交互 需通过 系统调用,主要通过函数:

  1. copy_from_user():将用户空间的数据拷贝到内核空间
  2. copy_to_user():将内核空间的数据拷贝到用户空间

1.2 跨进程通信IPC

  • 进程隔离:为了保证 安全性 & 独立性,一个进程 不能直接操作或者访问另一个进程,即 android 的进程是相互独立、隔离的
  • 跨进程通信(IPC:Inter-Process Communication):即进程间需进行数据交互、通信。

跨进程通信的基本原理:

1.3 Linux跨进程通信

我们知道,Android系统就是基于Linux内核实现的,咱们先简单了解一下Linux系统的IPC方式。虽然不同的资料对各种方式的名称和种类说法不完全相同,但是主要来说有如下6种方式:(1)管道 Pipe;(2)信号Signal;(3)信号量Semaphore;(4)消息队列Message Queue;(5)共享内存Shared Memmory;(6)套接字Socket。读者若想深入了解这些方式,可以自行查阅,这里不展开介绍,只需要知道有这六种方式即可。

1.4 Android进程通信

Android IPC的方式在不同的资料中有不同的说法,但从大方向上看,可以归纳为如下四种(这里仅对各种方式做简单介绍和优劣对比,对具体如何使用,不做讲解):

1、Activity方式

Activity是四大组件中使用最频繁的,咱们先从它说起。使用Activity方式实现,就是使用startActivity()来启动另外一个进程的Activity。我们在使用App的使用,往往会遇到如下几种情形:(1)浏览器中看到一篇比较不错的文章,分享到微信朋友圈或者微博;(2)在某个App中点击某个网址,然后界面跳转到浏览器中进行阅读;(3)使用美团外卖app,看到店家的电话,点击联系商家时,跳转到了电话拨打界面…这样的操作再频繁不过了。这些就是通过startActivity的方式从一个App,跳转到了另外一个App的Activity,从而实现了跨进程通信。

我们知道,在调用startActivity(Intent intent)的时候,intent有两个类型:显式Intent和隐式Intent。显式Intent的使用方式如下,用于进程内组件间通信:

Intent intent = new Intent(this,OtherActivity.class);
startActivity(intent);

隐式intent的使用方式如下,用于IPC:

Intent intent = new Intent();
intent.setAction(Intent.ACTION_CALL);
startActivity(intent); //startActivityForResult()同样,这里不赘述

Intent.ACTION_CALL就是字符串常量“android.intent.action.CALL”,这种方式通过setAction的方式来启动目标app的Activity,上述代码就是启动电话app的拨号界面,有时候还可以带上电话号码等参数。由上可知,Activity实现跨进程通信的方式,适合于不同App之间功能界面的跳转。

2、Content provider(后面简称CP)方式

当我们开发App需要用到联系人,多媒体信息等数据的时候,往往会通过系统提供Uri,采用CP的方式去获取。Android系统中,数据主要存储在自带的SqlLite数据库中。应用要共享SqlLite中的数据给其他App操作(增、删、改、查),就要用到CP,也就是说,CP主要用于跨进程数据库共享。Android系统提供了很多的CP来供其它App使用,如多媒体信息、联系人、日历等。如下图显示了Android系统提供的CP,包名都是以"com.android.providers“开头的:

这些用于共享的数据其实都是存储在系统数据库中的,如下显示了meida CP中的数据库:

App开发者也可以自定义CP,把自己的数据提供给其它app使用,也可以自己定义操作权限,如只允许其它app读取自己的数据,而不允许修改等。CP的使用场景,是提供数据共享。CP本质上还是在操作数据库,数据存储在sdcard中,所以建立连接和操作数据都是耗时操作,所以注意开辟子线程去操作。当数据库中数据有变化时,Content Observer监听到数据库变化也是有一定的滞后。

3、Broadcase方式

Broadcast使用非常简单,注册好广播,添加上action,就可以等着接收其他进程发出的广播。发送和接收广播时,还可以借助Intent来携带数据。但是广播的使用存在很多问题,被很多程序员吐槽,甚至鄙夷,所以选择用广播进行跨进程通信,是下下策。下面盘点一下Broadcast的槽点:

  1. Broadcast是一种单向的通信方式。当一个程序发送广播后,其他应用只能被动地接收,无法向发送者反馈。
  2. Broadcast非常消耗系统资源,会导致系统性能下降。
  3. 速度慢,容易造成系统ANR。且除了Parall Broadcast外,无法保证接收到的时间,甚至不一定能收得到。
  4. 如果使用Ordered Broadcast,一个Receiver执行时间过长,会影响后面接收者的接收时间,甚至还有可能被中间某个Receiver拦截,导致后面Receiver无法接收到。
  5. 发送者无法确定谁会接收该广播,而接收者也无发确认是谁发来的广播。
  6. 如果是静态注册的广播,一个没有开启的进程,都有可能被该广播激活。

总而言之,言而总之,使用Broadcast来实现跨进程通信,是下下之策!

4、Service方式

启动Service的方式有多种,有的用于跨进程通信,有的用于进程内部模块之间的通信,下面仅简单介绍一下跨进程通信的方式。

(1)startService()方式

Intent startIntent = new Intent ();
ComponentName componentName = new ComponentName(string packageName,string serviceClassName);
startIntent.setComponent(componentName );
startService( startIntent);

该方式启动远程Service实现跨进程通信,耦合度比较低,功能及代码结构清晰,但是存在以下缺点:

  • 没有好的机制立即返回执行结果,往往Service完成任务后,还需要其他方式向Client端反馈。

  • Service端无法识别Client端是谁,只知道有启动命令,但无法知道是谁下的命令。

  • 在已经创建好Service情况下,每次调用startService,就会执行onStartCommand()生命周期方法,相比于bindService,效率低下。

  • 如果Client端忘记调用stopService()了,那么该Service会一直运行下去,这也是一个隐患。

    所以,针对上述缺点,往往建议startService()方式用于同一个App内,跨进程的方式用后面将要讲到的AIDL来实现。

(2)bindService、Handler、Messager结合

这种方式也可以实现跨进程通信,据说和AIDL具有相同的功效,但比AIDL使用简单,详细可以阅读博文【Android总结篇系列:Android Service】。

(3)AIDL

这种方式也是 bindService() 启动方式的一种使用情况,也是广受程序员们推崇的方式。前面说 startService() 和 Broadcast 如何不好,就是为了衬托 AIDL 如何的好!这是本文的主角,本文后面会专门详细讲解,这里不赘述。

【总结】从如上的介绍来看,其实 Android 中跨进程通信的实现,就是利用四大组件来实现的。对方式的选择,我们总结一下:

  • 如果跨进程需要界面上的交互操作,用隐式startActivity()方式实现。
  • 如果需要共享数据,用Content Provider方式实现。
  • 排除前两种情形,就用AIDL。
  • 仅仅为了完成功能,又确实不会用AIDL的,就用Broadcast吧!!!虽然很low,但比实现不了功能还是强多了。

Binder跨进程通信

传统的跨进程通信需拷贝数据2次,但 Binder 机制只需1次,主要是使用到了内存映射,具体下面会详细说明。

跨进程通信的核心原理:内存映射,具体请看文章:操作系统:图文详解 内存映射

对比 Linux (Android基于Linux)上的其他进程通信方式(管道、消息队列、共享内存、信号量、Socket),Binder 机制的优点有:

2.1 Binder简介

Binder 跨进程通信机制 模型 基于 Client - Server 模式:

此处重点讲解 Binder 驱动的作用和 原理:

2.2 Binder驱动

2.3 Binder原理

Client进程、Server进程 & Service Manager 进程之间的交互 都必须通过Binder驱动(使用 open 和 ioctl文件操作函数),而非直接交互。具体原因:

  • Client进程、Server进程 & Service Manager进程属于进程空间的用户空间,不可进行进程间交互;
  • Binder驱动 属于 进程空间的 内核空间,可进行进程间 & 进程内交互。

所以,原理图可表示为以下(虚线表示并非直接交互—):

Binder驱动 & Service Manager进程 属于 Android基础架构(即系统已经实现好了);而Client 进程 和 Server 进程 属于Android应用层(需要开发者自己实现)。所以,在进行跨进程通信时,开发者只需自定义Client & Server 进程并显式使用上述3个步骤,最终借助 Android的基本架构功能就可完成进程间通信:

AIDL编程Demo

前面我们讲到,为了克服 Linux 中 IPC 各种方式的缺点,在Android中引入了Binder机制。但是当说起Binder在Android中的使用时,几乎所有的资料都是在说 AIDL 的使用。AIDL 的全称是 Android Interface Definition Language,即Android接口定义语言,是 Binder 机制实现 Android IPC 时使用比较广泛的工具。

本节将以一个 Demo 演示一下 AIDL 的基本实现步骤。源码地址:https://pan.baidu.com/s/1CyE_8-T9TDQLVQ1TDAEX2A 提取码:4auk 。如下两个图展示了该 Demo 的结构图和 AIDL 关键文件:

建立两个App,分别为Client端和Server端。

​ 这个比较 好理解,Server端就是包含了Service真正干活的那一端;Client端就是通过远程操控指挥的那一端,分别在不同的App中。如下图所示:

3.1 服务端

1、在Server端main目录下建立aidl文件夹以及.aidl文件,并copy一份到Client端,如图6.1中②处所示结构。注意,Client端和Server端②处是一模一样的。另外,AS中提供了快捷方式创建aidl文件,在main处点击右键 > New > AIDL > AIDL File文件,按照提示给aidl文件命名即可自动创建完成,可以看到文件路径也是该项目的包名。

这里给aidl命名为IDemoService.aidl,这里需要注意的是命名规范,一般都是以“I”开头,表示是一个接口,其内容如下:

//========== IDemoService.aidl========
package com.songwei.aidldemoserver;
// Declare any non-default types here with import statements
interface IDemoService 
    void setName(String name);
    String getName();

2、Server端创建Service文件 AidlService.java,如图6.1中③处所示,代码如下:

package com.songwei.aidldemoserver;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;

public class AidlService extends Service 
    private final static String TAG = "aidlDemo";

    public AidlService() 
    

    @Override
    public void onCreate() 
        super.onCreate();
        Log.i(TAG, "server:[onCreate]");
    

    @Override
    public IBinder onBind(Intent intent) 
        // TODO: Return the communication channel to the service.
        Log.i(TAG, "server:[onBind]");
        return new MyBinder();
    

    @Override
    public boolean onUnbind(Intent intent) 
        Log.i(TAG, "server:[onUnbind]");
        return super.onUnbind(intent);
    

    @Override
    public void onDestroy() 
        super.onDestroy();
        Log.i(TAG, "server:[onDestroy]");
    

    class MyBinder extends IDemoService.Stub 
        private String mName = "";

        public void setName(String name) throws RemoteException
            Log.i(TAG, "server:[setName]");
            mName = name;
        

        @Override
        public String getName() throws RemoteException 
            Log.i(TAG, "server:[getName]");
            return mName;
        
    

为了下文分析流程及生命周期,在其中各个方法中都添加了Log。同时,在Server端的AndroidManifest.xml文件中添加该Service的注册信息(注意exported属性值,如果有“intent-filter”,则默认值为true,否则为false。所以这里其实可以去掉,因为有“intent-filter”,其默认值就是true):

<service
    android:name=".AidlService"
    android:exported="true">
    <intent-filter>
         <action android:name="com.songwei.aidl" />
    </intent-filter>
</service>	

3、编译Sever端和Client端App,生成IDemoService.java文件。

​ 当编译的时候,AS会自动为我们生成IDemoService.java文件,如图6.1和图6.2中④处所示。当你打开该文件的时候,是不是看到了如下场景?

惊不惊喜?意不意外?是不是一脸懵逼,大惊,卧槽,这尼玛啥玩意啊?

AIDL 是 Android 接口定义语言,IDemoService.java 是一个java中的interface(接口),现在是不是若有所思了呢?AIDL 正是定义了 IDemoService.java 这个接口!!! 这个接口文件就是 AIDL 帮助咱们生成的 Binder 相关代码,这些代码就是用来帮助实现 Client 端和 Server 端通信的。前面第2步中提到的IDemoService.aidl文件,其作用就是作为原料通过AIDL来生成这些你貌似看不懂的代码的,第3步中的 AidlService.java 和后续在 Client 端App连接Server端App的时候,其实这个aidl文件就从来没有出现过,也就是说,它已经没有什么价值了。所以说,AIDL 的作用就是用来自动生成 Binder 相关接口代码的,而不需要开发者手动编写。有些教程中说,可以不使用AIDL而手动编写这份Binder代码,AIDL不是Binder实现通信所必需的,笔者也没有尝试过手动编写,如果读者您想挑战,可以尝试一下!

咱们继续!打开IDemoService.java文件后,点击主菜单兰Code > Reformat Code (或 Ctrl + Alt +L快捷健),你会发现画面变成了下面这个样子:

/*
 * This file is auto-generated.  DO NOT MODIFY.
 * Original file: D:\\\\ASWorkspace\\\\testDemo\\\\aidldemoclient\\\\src\\\\main\\\\aidl\\\\com\\\\songwei\\\\aidldemoserver\\\\IDemoService.aidl
 */
package com.songwei.aidldemoserver;

public interface IDemoService extends android.os.IInterface 
    /**
     * Local-side IPC implementation stub class.
     */
    public static abstract class Stub extends android.os.Binder implements com.songwei.aidldemoserver.IDemoService 
        private static final java.lang.String DESCRIPTOR = "com.songwei.aidldemoserver.IDemoService";

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

        /**
         * Cast an IBinder object into an com.songwei.aidldemoserver.IDemoService interface,
         * generating a proxy if needed.
         */
        public static com.songwei.aidldemoserver.IDemoService asInterface(android.os.IBinder obj) 
            if ((obj == null)) 
                return null;
            
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.songwei.aidldemoserver.IDemoService))) 
                return ((com.songwei.aidldemoserver.IDemoService) iin);
            
            return new com.songwei.aidldemoserver.IDemoService.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 
            switch (code) 
                case INTERFACE_TRANSACTION: 
                    reply.writeString(DESCRIPTOR);
                    return true;
                
                case TRANSACTION_setName: 
                    data.enforceInterface(DESCRIPTOR);
                    java.lang.String _arg0;
                    _arg0 = data.readString();
                    this.setName(_arg0);
                    reply.writeNoException();
                    return true;
                
                case TRANSACTION_getName: 
                    data.enforceInterface(DESCRIPTOR);
                    java.lang.String _result = this.getName();
                    reply.writeNoException();
                    reply.writeString(_result);
                    return true;
                
            
            return super.onTransact(code, data, reply, flags);
        

        private static class Proxy implements com.songwei.aidldemoserver.IDemoService 
            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 void setName(java.lang.String name) throws android.os.RemoteException 
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try 
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeString(name);
                    mRemote.transact(Stub.TRANSACTION_setName, _data, _reply, 0);
                    _reply.readException();
                 finally 
                    _reply.recycle();
                    _data.recycle();
                
            

            @Override
            public java.lang.String getName() 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_getName, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.readString();
                 finally 
                    _reply.recycle();
                    _data.recycle();
                
                return _result;
            
        

        static final int TRANSACTION_setName = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_getName = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    

    public void setName(java.lang.String name) throws android.os.RemoteException;

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


惊不惊喜?意不意外?这下是不是有种似曾相识的赶脚?这就是一个很普通的java中的接口文件而已,结构也非常简单。

神秘的面纱揭开一层了吧!后面在讲完Client端和Server端的连接及通信后,还会继续深入剖析这个文件。

3.2 客户端

Client端通过ClientActivity活动类来连接Server端AidlService并通信。ClientActivity.java 的内容如下,布局文件在此省略,比较简单,就两个按钮,一个用于绑定,一个用于解绑,看Button命名也很容易分辨:

package com.songwei.aidldemoclient;

import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle以上是关于Android跨进程通信Binder机制与AIDL实例的主要内容,如果未能解决你的问题,请参考以下文章

Android Binder,AIDL跨进程通讯详解与实现,看一遍就懂

Android Studio创建AIDL文件并实现进程间通讯

Android 进阶8:进程通信之 Binder 机制浅析

基于binder的跨进程通讯之使用AIDL实现Demo

Android 跨进程通信-从源码分析AIDL跨进程通信实现

Binder的使用(跨进程——AIDL,非跨进程)