Android 中如何使用 AIDL 详细解析

目录

一、AIDL定义及使用场景

二、AIDL 的使用步骤

1. 先创建一个客户端,在 aidlclient 模块中创建 Activity:

2. 搭建服务端,在 aidlservice 模块中创建 AIDL 文件

3. 在 aidlservice 模块中创建服务

4. 在客户端:aidlclient 模块的 MainActivity 中绑定服务

5. 传输复杂数据(自定义类)

三、总结

1. 服务端 

2. 客户端

四、源码下载地址 


一、AIDL定义及使用场景

AIDL(Android Interface definition language):Android 接口定义语言。它的作用就是主要用于不同进程之间的通信。

我们知道,Android 为每一个应用都分配了一个独立的虚拟机,或者说为每一个进程都分配了一个独立的虚拟机,通常情况下,一个 App 就是一个进程(当然,我们也可以在 AndroidManifest 中通过配置 android:process 属性,使某一个组件单独运行在一个进程中,这样在一个 App 中就会包含多个进程)。运行在同一个进程中的组件是属于同一个虚拟机和同一个 Application 的,同理,运行在不同进程中的组件是属于两个不同的虚拟机和 Application 的。那么,要使这些进程相互通信,就可以使用 AIDL。

Android 推出来了 Messenger,它是用来完成应用之间的通讯的,它的底层其实也是 AIDL。那它和 AIDL 有何区别呢?官方文档介绍 AIDL 中有这么一句话:

Note: Using AIDL is necessary only if you allow clients from different applications to access your service for IPC and want to handle multithreading in your service. If you do not need to perform concurrent IPC across different applications, you should create your interface by implementing a Binder or, if you want to perform IPC, but do not need to handle multithreading, implement your interface using a Messenger. Regardless, be sure that you understand Bound Services before implementing an AIDL.

翻译过来的的意思就是:“只有当你允许来自不同的客户端访问你的服务并且需要处理多线程问题时你才必须使用 AIDL ”,其他情况下你都可以选择其他方法,如使用 Messenger,也能跨进程通讯。可见 AIDL 是处理多线程、多客户端并发访问的。而 Messenger 是单线程处理。此外,Messager 主要是为了传递消息的,如果我们想跨进程调用服务端的方法,Messager 就无法做到了,可以用 AIDL 来解决。

常见的是将 Service 独立运行在一个进程当中,例如我们经常接触的音乐服务、导航服务等。由于不同的虚拟机(不同进程对应不同的虚拟机)在内存分配上有不同的地址空间,所以它可以保证服务不被程序的其他部分所干扰。也正是因为在不同的进程中,要使它们之间进行通信,就要借助 AIDL 了。


二、AIDL 的使用步骤


此处,为了直观一点儿,我们建立一个服务端、一个客户端。让它们运行在两个进程当中。


1. 先创建一个客户端,在 aidlclient 模块中创建 Activity:

 创建的 Activity 及其布局文件如下所示:

 activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:gravity="center_horizontal"
    tools:context=".MainActivity">

        <Button
            android:id="@+id/btn_bind"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="绑定服务-Client" />

        <Button
            android:id="@+id/btn_simple"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="传输基本类型数据" />

        <Button
            android:id="@+id/btn_complicated"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="传输复杂数据" />
</LinearLayout>

Activity 代码:

package com.example.aidl_test;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity implements View.OnClickListener{
    private Button bindBtn,simpleBtn,complicated;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
    }
    @Override
    public void onClick(View v){
        switch (v.getId()){
            case R.id.btn_bind: //绑定服务
                break;
            case R.id.btn_simple:   //传输基本类型的数据,如int、double等
                break;
            case R.id.btn_complicated:  //传输复杂数据类型,如自定义类
                break;
            default:
                break;
        }
    }

    private void initView() {
        bindBtn = (Button)findViewById(R.id.btn_bind);
        simpleBtn = (Button)findViewById(R.id.btn_simple);
        complicated = (Button)findViewById(R.id.btn_complicated);

        bindBtn.setOnClickListener(this);
        simpleBtn.setOnClickListener(this);
        complicated.setOnClickListener(this);
    }
}

2. 搭建服务端,在 aidlservice 模块中创建 AIDL 文件

在包名上选择 File->New->AIDL->AIDL File,如下所示:

这里就用默认的名称了,点击 finish,自动生成的代码如下所示:

// IMyAidlInterface.aidl
package com.example.aidlservice;

// 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 传递 int,long,boolean,float,double,String 这些数据的时候是不需要别的工作的,直接传就好了。

在这个接口内声明的方法,就是服务端可以向外提供的,供客户端调用的方法。这里我们先把系统的这个方法删除,先以传输上述基本数据类型为例,写一个自己的方法。

注意:这个接口内的方法是不能带有权限修饰符的。

interface IMyAidlInterface {
    void transmitSimpleData(int num);
}

随后,我们需要编译一下,只有编译了,系统才会根据这个 AIDL 文件生成对应的 Java 代码,不然是用不了的。编译步骤为:Build->Make Moudle 'aidlservice'。如下图所示:

编译成功后,会在该模块的 “build->generated->source->aidl->debug->包名” 下生成相同名称的 Java 文件。如下图所示:

3. 在 aidlservice 模块中创建服务

有了 AIDL 文件中,我们声明了一个自己的方法,这就等同于服务端对外抛出了承诺,随后客户端就会要求你实现承诺,因此必须实现 AIDL 文件中声明的方法(承诺)。在 aidlservice 的 Java 包中创建一个自定义的 Service。我们利用 IDE 直接生产,如下:

在下一步,注意要勾选 “Exported” 和 “Enabled” 这两个选项属性。“Exported” 属性表示是否允许除了当前程序之外的其它程序访问这个服务,若该属性为 false,就会导致客户端调用不起来;“Enabled” 属性表示是否启用这个服务。

接着,我们修改生成的这个 MyService,有两处需要改动,其一是 AndroidManifest 中,修改如下:

<service
    android:name=".MyService"
    android:enabled="true"
    android:exported="true"
    android:process=":remote">
    <intent-filter>
        <action android:name="com.example.aidlservice.MyService"/>
    </intent-filter>
</service>

我们将这个服务设置为隐式启动的,因此设置了 action 属性。android:process=":remote" 则表明该服务将在一个独立的进程中运行。这里的“:remote”是可以写别的名称的,只是大家都习惯写它了。

这里补充一个知识点,不理解的可以忽略。当开启一个独立的进程时,有两种写法,如下所示:

android:process=":remote"
android:process="com.example.aidlservice.remote"

这两种写法是有区别的, ":” 的含义是要在当前的进程名前面附加上当前的包名,是简写方式,在此其完整的进程名为 “com.example.aidlservice:remote”, 这种进程是属于当前进程的私有进程,其他应用的组件不可以和它跑在同一个进程中;而进程名不以“:”开头的进程属于全局进程,其他应用通过ShareUID方式可以和它跑在同一个进程中。(Android 系统会为每个应用分配一个唯一的 UID,具有相同 UID 的应用才能共享数据。具体要求的话,需要两个应用拥有相同的 ShareUID 并且签名相同才可以。)

MyService 第二处需要改动的地方,就是其自身的实现了。代码如下:

public class MyService extends Service {

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

    IMyAidlInterface.Stub mBinder = new IMyAidlInterface.Stub() {
        @Override
        public void transmitSimpleData(int num) throws RemoteException {
            Log.i("MyService", "MyService 传输的基本类型数据为:" + num);
        }
    };
}

其中,必须实现 onBind() 方法,并在其中返回 IBinder 对象。下方创建了一个 Stub 对象(即 mBinder),在其内部我们就可以重写刚刚在 AIDL 里声明的方法了,这样就对外具备了履行承诺的能力,最后将这个 Stub 对象通过 onBind() 方法返回,这样服务端就写好了。至于 Service 中的其它方法,则可以按需重写。

4. 在客户端:aidlclient 模块的 MainActivity 中绑定服务

首先需要将 aidlservice 模块中的整个 aidl 文件夹拷贝到 aidlclient 的 mian 目录下(客户端的 aidl 包必须和服务端一致,否则会序列化失败,无法 bindservice),然后执行 "Build->Make Moudle 'aidlclient",编译一下,生成 AIDL 对应的 Java 文件(类似第2步)。

接着修改 aidlclient 模块中的 MainActivity,如下所示:

package com.example.lichaoqiang.aidl_test;

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;
import android.view.View;
import android.widget.Button;

import com.example.aidlservice.IMyAidlInterface;

public class MainActivity extends AppCompatActivity implements View.OnClickListener{
    private Button bindBtn,simpleBtn,complicated;

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

        @Override
        public void onServiceDisconnected(ComponentName name){
            mMyAidlInterface = null;
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
    }
    @Override
    public void onClick(View v){
        switch (v.getId()){
            case R.id.btn_bind: //绑定服务
                Intent intent = new Intent("com.example.aidlservice.MyService");
                intent.setPackage("com.example.aidlservice");
                bindService(intent,connection,BIND_AUTO_CREATE);
                break;
            case R.id.btn_simple:   //传输基本类型的数据,如int、double等
                if (mMyAidlInterface != null){
                    try{
                        mMyAidlInterface.transmitSimpleData(1000);
                    }catch (RemoteException e){
                        e.printStackTrace();
                    }
                }
                break;
            case R.id.btn_complicated:  //传输复杂数据类型,如自定义类
                break;
            default:
                break;
        }
    }

    private void initView() {
        bindBtn = (Button)findViewById(R.id.btn_bind);
        simpleBtn = (Button)findViewById(R.id.btn_simple);
        complicated = (Button)findViewById(R.id.btn_complicated);

        bindBtn.setOnClickListener(this);
        simpleBtn.setOnClickListener(this);
        complicated.setOnClickListener(this);
    }

    @Override
    protected void onDestroy () {
        super.onDestroy();
        if (connection != null) {
            unbindService(connection);
        }
    }
}

首先,我们创建一个 AIDL 对象(此处指 mMyAidlInterface),然后在 ServiceConnection 的连接成功方法里,将 IBinder 通过 Stub 的 asInterface 来给 AIDL 赋值,然后在连接服务的按钮里建立一个 Intent 对象,因为是利用隐式 Intent 启动的,所以需要给它 action 标签里的内容,另外需要注意的是,Android5.0 之后 Intent 还必须加上目标的包名,不然会报错的。最后通过 bindService 方法将 Activity 也连接上。再在  simpleBtn 按钮的回调里,调用 AIDL 对象里  transmitSimpleData 方法,就可以调用服务,并将值传给 Service。最后,不用的时候解除绑定。

小功告成,现在可以测试一下了。注意查看日志时,绑定服务后,进程选择为 “com.example.aidlservice:remote”。接下来我们介绍复杂数据(自定义类)的传输。


在此需要补充说明:因为我们在此处是客户端和服务端分别处于两个模块中,所以需要将服务端的整个 aidl 文件夹拷贝到 客户端,但如果客户端和服务端在一个包中时,例如,我们可以在 aildservice 模块中创建一个 MainActivity,代码和 aidlclient 模块中的 MainActivity 一样。由于我们已经配置 MyService 运行在独立进程中了(android:process属性),因此,此时 aidlservice 模块中的 MainActivity 和 MyService 也是运行在两个进程中的。这时,我们就不需要执行复制 aidl 文件夹的操作了,因为它们已经在一个包中了。我会在后面给出源代码。

5. 传输复杂数据(自定义类)

为了方便介绍,我们在此处在 aidlservice 模块的 MainActivity 中编写客户端的相关代码,当然,你也可以在 aidlclient 的 MainActivity 中实现同样的功能(我在源码中都会给出),它们的区别也只在于是否需要复制上述提到的那个 aidl 文件夹。

首先,我们创建一个自定义类 Book.java,这个类需要实现 Parcelable 接口(以实现序列化,AIDL 默认只能传递基本类型, 如果想传递自己的对象, 需要利用 Parcelable)。如果熟悉 Parcelable 的书写方法,那当然可以直接写,但 Android Studio 为我们提供了一个更加简便的插件:Android parcelable code generator,它可以自动生成序列化 Parcelable 对应的代码。在 Settings->Plugins 中搜索、安装这个插件即可。安装完成后,重启  Android Studio,接着在 aidlservice 模块的 aidl 文件夹下,创建 Book 类,并实现 Parcelable 接口,至于为什么在这个文件夹下创建,我会在后面说明,如图:

现在标红是因为我们还没实现 Parcelable 的方法。接下来定义 Book 类的变量,代码如下:

package com.example.aidlservice;

import android.os.Parcelable;

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

定义了一个 int 型的 bookId 和 String 类型的 bookName,接着就可以使用我们刚才提到的插件了,在该类窗口点击鼠标右键,选择 Generate...,再选择 Parcelable,

在接下来的窗口中,选择需要包含进 Parcelable 的变量,此处我们都选(按住 ctr 键,全部选择),点击 OK,就可以看到插件自动为我们生产的代码了,是不是很简单呢。

此外,为了方便我们打印传输 Book 对象,我们还重写了 toString() 方法,并添加了一个带参数的构造方法,详细代码如下:

package com.example.aidlservice;

import android.os.Parcel;
import android.os.Parcelable;

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

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

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

    public Book() {
    }

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

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

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

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

    @Override
    public String toString() {
        return String.format("[bookId:%s, bookName:%s]", bookId, bookName);
    }
}

 接下来,我们还需要在 aidlservice 模块的 aidl 包中,创建一个 Book.aidl 类(注意:必须和自定义的 Parcelable 类同名)。为什么要有这个 Book.aidl 类呢?因为只有将自定义类在 aidl 中声明时候,AIDL 才能调用这个 自定义的 Book 类。还是在 aidlservice 模块的 aidl 包中创建 Book.aidl 文件,创建方法同上。创建完成后,目录结构如下:

修改 Book.aidl 为如下内容,这样就声明了 Book 类。

package com.example.aidlservice;

parcelable Book;

这里有一个坑需要说明一下,倘若你将 Book.java 类创建在了 java 包中(即和 MainActivity 在一个包中),那么在创建 Book.aidl 文件,将名称填写为 Book 时,就会提示不让创建,这时可以任意先写一个名称,待创建完成后,再将其 Rename 成 Book即可,这也是为什么在 aidl 文件夹中创建 Book.java 的原因之一。

声明了 Book 类,那下一步就可以使用了,修改 IMyAidlInterface.aidl,有两处修改,一是在其内部需要导入 Book 类,尽管 Book 类已经和 IMyAidlInterface 位于相同的包中了,这是 AIDL 的特殊之处;二是在接口中定义一个新的方法,用于传输复杂数据(和 transmitSimpleData() 方法地位相同)。代码如下,注意比较两个方法的不同之处:

package com.example.aidlservice;

import com.example.aidlservice.Book;

interface IMyAidlInterface {
    void transmitSimpleData(int num);
    void transmitComplicatedData(in Book book);
}

在新加的方法中,其参数列表中有一个 in 符号,因为 AIDL 除了基本数据类型外,其他类型的参数都必须标上这三个方向中的一个:in、out、inout。其中,in 表示输入型参数,out 表示输出型参数,inout 表示输入输出型参数。此外,AIDL 接口中只支持方法,不支持声明静态常量。

接着在 MyService 中去实现这个新添加的方法(注意,参数列表中没有那个 in 了啊):

public class MyService extends Service {

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

    IMyAidlInterface.Stub mBinder = new IMyAidlInterface.Stub() {
        @Override
        public void transmitSimpleData(int num) throws RemoteException {
            Log.i("MyService", "MyService 传输的基本类型数据为:" + num);
        }

        @Override
        public void transmitComplicatedData(Book book) throws RemoteException {
            Log.i("MyService", "MyService 传输的复杂数据为:" + book.toString());
        }
    };
}

最后,还要在对应模块的 build.gradle 文件中,声明我们的 AIDL 文件夹为资源文件夹。此处即为 aidlservice 的 build.gradle,在 android 闭包中,添加如下代码:


android {

    //其它无关代码省略,不粘贴了

    sourceSets {
        main {
            manifest.srcFile 'src/main/AndroidManifest.xml'
            java.srcDirs = ['src/main/java', 'src/main/aidl']
            resources.srcDirs = ['src/main/java', 'src/main/aidl']
            aidl.srcDirs = ['src/main/aidl']
            res.srcDirs = ['src/main/res']
            assets.srcDirs = ['src/main/assets']
        }
    }

}

大功告成,在 MainActivity 中编写代码测试一下吧,注意查看日志时,绑定服务后,进程选择为 “com.example.aidlservice:remote”。MainActivity 中代码为:

case R.id.btn_complicated:  //传输复杂数据类型,如自定义类
                Book book1 = new Book();
                Book book2 = new Book(100,"我的书籍!");
                try{
                    mMyAidlInterface.transmitComplicatedData(book1);
                    mMyAidlInterface.transmitComplicatedData(book2);
                }catch (RemoteException e){
                    e.printStackTrace();
                }
                break;

当然,AIDL 的使用还有很多坑,例如:如何在 AIDL 中定义接口,结合观察者模式使用、跨进程删除监听者、服务端和客户端线程阻塞问题、Binder 死亡监听及重连方法、AIDL 权限验证等等,这就留给读者继续深入学习吧。

三、总结

1. 服务端 

服务端首先要创建一个 Service 用来监听客户端的连接请求,然后创建一个 AIDL 文件,将暴露给客户端的接口在这个 AIDL 文件中声明,最后在 Service 中实现这个 AIDL 接口即可。

2. 客户端

客户端首先要绑定服务端的 Service,绑定成功后,将服务端返回的 Binder 对象转成 AIDL 接口所属的类型,接着就可以调用 AIDL 中的方法了。

四、源码下载地址 

本文介绍了 AIDL 的使用方法及一些注意事项,源代码下载地址为:

https://download.csdn.net/download/chaoqiangscu/10715472 (最低只能设置1分,麻烦有积分的就用这个下载吧,没有积分的可以用下面的另一个链接。谢谢!)

https://github.com/chaoqiangscu/AIDL_Test

猜你喜欢

转载自blog.csdn.net/chaoqiangscu/article/details/83013010