AIDL使用

Android的IPC(跨进程通信)方式有Binder、AIDL、Messenger、文件共享等,AIDL是最常用的方案。但是由于项目中用的不多,所以不熟悉,特意将AIDL的使用过程记录一下,供日后查看。

AIDL接口支持的格式

AIDL的语法与Java接口的语法是一致的,但是不支持定义接口常量 ,并且对于数据的类型是有要求的,仅支持以下几种格式:

  • Java的原语类型:intlongcharbolean等。
  • String
  • CharSequence
  • 实现Parcelable接口的数据类型
  • List List 中的所有元素必须是以上列表中支持的数据类型,或者您所声明的由 AIDL 生成的其他接口或Parcelable 类型。您可选择将 List 用作“泛型”类(例如,List)。尽管生成的方法旨在使用 List 接口,但另一方实际接收的具体类始终是 ArrayList。
  • Map Map 中的所有元素必须是以上列表中支持的数据类型,或者您所声明的由 AIDL 生成的其他接口或 Parcelable 类型。不支持泛型 Map(如 Map<String,Integer> 形式的 Map)。尽管生成的方法旨在使用 Map 接口,但另一方实际接收的具体类始终是 HashMap。

AIDL使用方法

AIDL实现需要3步,分别是:

  1. 创建 .aidl 文件
  2. 创建Service并实现aidl生成的Sub类
  3. 客户端bindService调用Service

下面以书店的例子举例说明这三个步骤。

实现一个书店的aidl,完成添加图书与查询图书信息的功能。

1.创建.aidl文件

使用AndroidStudio可以很方便的创建AIDL文件,将鼠标放在文件夹目录上面,然后右键选择创建AIDL,如下图所示。

IBookManager.aidl

根据我们的需求,需要设计两个方法,分别是void addBook(Book book)Book getBook(String name),下面让我们创建一个IBookManager.aidl的文件,如下:

//IBookManager.aidl
package cn.bearever.android.book;

interface IBookManager {

    void addBook(in Book book);

    Book getBook(String name);
}

注意 在aidl里面,非原语参数需要用inout或者inout标记,例如上面的void addBook (in Book book)

Book.aidl

如果只是这样的话,编译会失败,提示Book找不到:

cn.bearever.android.book.Book: couldn't find import for class cn.bearever.android.book.Book

接着我们在IBookManager.aidl的文件夹下创建Book.aidl文件,如下:

// Book.aidl
package cn.bearever.android.book;

parcelable Book;

这里的关键在于parcelable Book;,它指向了一个实现了Parcelable的Book类,所以我们还需要实现一个Book.java的类。需要注意的是
Book.java的代码位置不能放在aidl文件夹,需要放在java代码的文件夹下
,例如src\main\java\cn\bearever\android\book\Book.java

Book.java

//Book.java
public class Book implements Parcelable {
    public String name;
    public int money;

    public Book() {
    }

    public Book(String name, int money) {
        this.name = name;
        this.money = money;
    }

    protected Book(Parcel in) {
        name = in.readString();
        money = in.readInt();
    }

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

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

    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];
        }
    };

    @NonNull
    @Override
    public String toString() {
        return "书名:" + name + ",价格:" + money;
    }
}

注意
以为这样就可以了?还没有的!前面我们虽然使用Book.aidl指定了需要用到Book.java类,但是这个类的具体路径还没有确定,需要在IBookManager.aidl里面通过import指定,例如:

// IBookManager.aidl
package cn.bearever.android.book;

import cn.bearever.android.book.Book; //这一句话就是指定Book的路径!!!!

interface IBookManager {
    void addBook(in Book book);
    Book getBook(String name);
}

创建Service并实现aidl生成的Sub类

实现了aidl接口之后AndroidStudio会自动生成一个与aidl同名的java文件,具体生成的代码就不贴上来了,下面我们来实现aidl的接口定义的功能。在java代码文件夹里面创建一个BookService.java的Service,并实现将IBookManager.Sub的实现作为Binder返回。

BookService.java

//BookService.java
public class BookService extends Service {
    private BookBinder mBinder;
    private HashMap<String, Book> mBookMap;

    @Override
    public void onCreate() {
        super.onCreate();
        mBinder = new BookBinder();
        mBookMap = new HashMap<>();
    }

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

    public class BookBinder extends IBookManager.Stub {
        @Override
        public void addBook(Book book) throws RemoteException {
            if (book == null) {
                return;
            }
            mBookMap.put(book.name, book);
        }

        @Override
        public Book getBook(String name) throws RemoteException {
            return mBookMap.get(name);
        }
    }
}

Service实现之后我们就可以将其注册到AndroidManifest.xml里面了,并为其设置运行的进程,例如:

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

注意 接口的访问是多线程进行的,一定要注意线程安全问题!HashMap是线程不安全的,需要根据业务场景使用线程安全的方案。

前面两个步骤创建的aidl完整代码文件结构如下:

3.客户端bindService调用Service

下面我们在主进程里面来访问remote进程的数据,创建一个MainActivity.java,绑定BookService,通过其返回的service转成IBookManager的接口,然后调用其方法试试。

//MainActivity
public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    private IBookManager mBookManager;
    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mBookManager = IBookManager.Stub.asInterface(service); //注意这里是通过Sub.asInterface转成接口的
            try {
                //添加图书
                mBookManager.addBook(new Book("哈利波特", 100));
                Log.d(TAG, "添加一本图书");
                //获取图书信息
                Book book = mBookManager.getBook("哈利波特");
                Log.d(TAG, "图书信息:" + book);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Intent intent = new Intent();
        intent.setClass(this, BookService.class);
        bindService(intent, connection, BIND_AUTO_CREATE);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(connection);
    }
}

运行程序打印如下:

2020-04-23 11:53:04.789 10288-10288/cn.bearever.android.test D/MainActivity: 添加一本图书
2020-04-23 11:53:04.790 10288-10288/cn.bearever.android.test D/MainActivity: 图书信息:书名:哈利波特,价格:100

再看一下运行的进程数量,确实是两个,说明我们的跨进程通信成功了。

结语

AIDL是Android里面最常用的IPC方案,虽然对于单进程的项目使用不到,但还是要掌握的。对于Android而言,一个进程就是一个应用,跨进程通信其实也是跨应用通信的解决方案。一搬情况下我们并不希望自己的进程被别人访问,可以加入权限校验,这个部分就不展开了。对于AIDL本身需要注意的要点有:

  • AIDL支持的数据类型及Parcelable的实现
  • 主动import需要的类路径
  • 方法调用是多线程环境的,要注意线程安全问题。
Search by:GoogleBingBaidu