学习AIDL,这一篇差不多了

来源:互联网 发布:重庆知秋凤凰怎么样 编辑:程序博客网 时间:2024/05/17 01:30

AIDL

标签(空格分隔): 跨进程通讯


参考Blog

目录

  • AIDL
    • 为什么要设计这门语言
    • 可传递的数据类型
    • 定向Tag
    • 实现步骤
      • 服务端配置
        • 1 创建aidl文件
        • 2 创建数据Model
        • 3 书写aidl文件内容
        • 4 创建服务端Service
        • 5 修改配置信息
      • 客户端配置
        • 1 文件拷贝
        • 2 创建连接
        • 3 展示 定向Tag
    • 源码地址


AIDL(Android Interface Definition Language),接口定义语言

为什么要设计这门语言

实现进程间通讯(特定规格和特定方法下)

可传递的数据类型

  • 8种基本类型:byte,boolean,char,short,int,long,float,double
  • String类型
  • CharSequence
  • List (支持泛型)
  • Map (不支持泛型)
  • 实现Parcelable接口的对象

定向Tag

AIDL的定向Tag表示在跨进程通讯中,信息的流向

Tag符 作用 表现 in 数据只能从客户端流向服务端 服务端可以接收到一个完整的对象,且客户端不会因为服务端对该对象的操作而改变 out 数据只能从服务端流向客户端 服务端会接收到该对象的空对象,但服务端对接收到的空对象有任何修改之后,客户端将会同步变动 inout 数据可以在服务端和客户端之间双向流通 服务端可以接收到客户端发过来的完整对象,并且客户端会同步服务端对该对象的所有操作

下面做个简单的图来展示:

Tag符 服务端是否接收到完整对象 客户端是否同步服务端对Object的操作 in √ × out × √ inout √ √

注意: Java中的八种基本类型和String、CharSequence默认并且只能使用in

实现步骤

1 服务端配置

1.1 创建*.aidl文件

创建AIDL文件的界面

我们以 Book.aidl 为例,创建完成之后,Android Studio会自动给我们创建aidljava包同级

创建完成之后,编辑器自动为我们创建的包

创建后,里面的初始化代码为:

    // Book.aidl    package com.martin.aidlblog;    // Declare any non-default types here with import statements    interface Book {    /**     * 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);    }

1.2 创建数据Model

这时候,我们创建Book.java文件,在Book.aidl相同包下。写入成员变量

    private String name;    private int price;    private boolean nowRead;    private int readPage;

并且创建成员变量的 set/get 方法,空构造方法以及方便创建对象的全有属性构造方法。
这是一个Model类的基本创建过程。但AIDL通讯中,数据对象必须是Parcelable对象,所以我们将Book.java实现Parcelable接口,最终代码如下:

public class Book implements Parcelable{    private String name;    private int price;    private boolean nowRead;    private int readPage;    public Book() {    }    public Book(String name, int price, boolean nowRead, int readPage) {        this.name = name;        this.price = price;        this.nowRead = nowRead;        this.readPage = readPage;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public int getPrice() {        return price;    }    public void setPrice(int price) {        this.price = price;    }    public boolean isNowRead() {        return nowRead;    }    public void setNowRead(boolean nowRead) {        this.nowRead = nowRead;    }    public int getReadPage() {        return readPage;    }    public void setReadPage(int readPage) {        this.readPage = readPage;    }    protected Book(Parcel in) {        name = in.readString();        price = in.readInt();        nowRead = in.readByte() != 0;        readPage = in.readInt();    }    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];        }    };    @Override    public int describeContents() {        return 0;    }    @Override    public void writeToParcel(Parcel dest, int flags) {        dest.writeString(name);        dest.writeInt(price);        dest.writeByte((byte) (nowRead ? 1 : 0));        dest.writeInt(readPage);    }}

注意:这里默认生成的模板类,只能支持 in的定向Tag,因为生成的方法里面,只有writeToParcel(),如果想要支持outinout定向Tag的话,还需要实现readFromParcel()方法,但这个方法并不在Parcelable里面,需要我们手写。万幸,有writeToParcel()方法作为参考:

   @Override    public void writeToParcel(Parcel dest, int flags) {        dest.writeString(name);        dest.writeInt(price);        dest.writeByte((byte) (nowRead ? 1 : 0));        dest.writeInt(readPage);    }    public void readFromParcel(Parcel dest) {        //注意,此处的读值顺序应当是和writeToParcel()方法中一致的        name = dest.readString();        price = dest.readInt();        nowRead = dest.readByte() == 1;        readPage = dest.readInt();    }

再生成 toString() 方法,方便后面展示日志

@Override    public String toString() {        return "Book{" +                "name='" + name + '\'' +                ", price=" + price +                ", nowRead=" + nowRead +                ", readPage=" + readPage +                '}';    }

1.3 书写.aidl文件内容

Book.aidl和Book.Java文件之所以同名,是因为我们要将两个文件进行关联,所以这两个文件的包名需要一致,并且,我们将Book.aidl的内容改写成:

// Book.aidlpackage com.example.marti.aidlstudy.aidl;//第一类AIDL文件//这个文件的作用是引入了一个序列化对象 Book 供其他的AIDL文件使用// 注意:Book.aidl 与 Book.java 包名需要一致parcelable Book;

接下来创建一个AIDL接口,并且定义几个方法:

// BookManager.aidlpackage com.martin.aidlblog;// 第二类AIDL文件// 在同一包下,也需要导包import com.martin.aidlblog.Book;interface BookManager {         // 定向tag == in        Book inBook(in Book book);        // 定向tag == out        Book outBook(out Book book);        // 定向 inout        Book inoutBook(inout Book book);        // 获取书籍集合        List<Book> getBooks();}

这两个.aidl文件创建完成,我们将项目 builder 一下,如果没有报错,那么AIDL的操作就成功了。
编辑器会自动将我们的第二类AIDL文件编译成.java文件,存放在以下目录:
.aidl文件被编辑器编译存放位置
我们定义的方法被编译后的样子:

// 定向tag == inpublic com.martin.aidlblog.Book inBook(com.martin.aidlblog.Book book) throws android.os.RemoteException;// 定向tag == outpublic com.martin.aidlblog.Book outBook(com.martin.aidlblog.Book book) throws android.os.RemoteException;// 定向 inoutpublic com.martin.aidlblog.Book inoutBook(com.martin.aidlblog.Book book) throws android.os.RemoteException;// 获取书籍集合public java.util.List<com.martin.aidlblog.Book> getBooks() throws android.os.RemoteException;

1.4 创建服务端Service

现在创建的Service就没有路径限制了,我们在java包下,随便位置创建一个AIDLService文件。
我们怎么让ServiceBookManager产生联系呢?
现在看BookManager.java文件,我们发现文件中的内部类 Stub继承了android.os.Binder

编译后的内容

我们立刻联想到Service里面的onBind()方法,返回值是IBinder,而IBinderBinder的父类!
所以,Service内部逻辑:

public class AIDLService extends Service {    // 维护一个 书籍 集合    private List<Book> books = new ArrayList<>();    public AIDLService() {    }    private BookManager.Stub manager = new BookManager.Stub() {        @Override        public Book inBook(Book book) throws RemoteException {            resetBook(book);            return book;        }        @Override        public List<Book> getBooks() throws RemoteException {            return books;        }        @Override        public Book outBook(Book book) throws RemoteException {            resetBook(book);            return book;        }        @Override        public Book inoutBook(Book book) throws RemoteException {            resetBook(book);            book.setNowRead(true);            return book;        }    };    // 重设 书籍 内容    private void resetBook(Book book) {        book.setPrice(91);        book.setReadPage(23);        books.add(book);    }    @Override    public IBinder onBind(Intent intent) {        return manager;    }    @Override    public void onCreate() {        super.onCreate();    }}

Service的java文件内容写完,我们再到AndroidManifest.xml文件中,给Service添加意图过滤器:

<service    android:name=".AIDLService"    android:enabled="true"    android:exported="true">    <intent-filter>        <action android:name="com.martin.aidlblog" />        <category android:name="android.intent.category.DEFAULT" />    </intent-filter></service>

1.5 修改配置信息

大家都知道,Android Studio 是默认使用 Gradle 来构建 Android 项目的,而 Gradle 在构建项目的时候会通过 sourceSets来配置不同文件的访问路径,从而加快查找速度——问题就出在这里。Gradle 默认是将 java 代码的访问路径设置在 java 包下的,这样一来,如果 java 文件是放在 aidl 包下的话那么理所当然系统是找不到这个 java 文件的。那应该怎么办呢?

  • 修改app的build.gradle文件里android内添加以下内容:
sourceSets {        main {            java.srcDirs = ['src/main/java', 'src/main/aidl']        }    }

这样Gradle在构建项目时,也会自动遍历aidl包。

2 客户端配置

2.1 文件拷贝

我们将服务端的aidl包直接copy到客户端项目中,位置要一样,与java包平级。

2.2 创建连接

所谓创建连接,就是打开服务端的服务:

    private void bindService() {        Intent intent = new Intent();        intent.setAction("com.martin.aidlblog");        intent.setPackage("com.martin.aidlblog");        bindService(intent, connection, BIND_AUTO_CREATE);    }    private ServiceConnection connection = new ServiceConnection() {        @Override        public void onServiceConnected(ComponentName name, IBinder service) {            manager = BookManager.Stub.asInterface(service);        }        @Override        public void onServiceDisconnected(ComponentName name) {            connection = null;        }    };

到这里,我们已经实现了进程间通讯的功能。但是前面说的定向Tag好像并没有什么体现。

2.3 展示 定向Tag

我们在客户端设置四个Button,分别调用我们在 BookManager.aidl里面设置的四个方法,整体代码如下:

public class MainActivity extends AppCompatActivity {    private static final String TAG = "客户端";    private BookManager manager;    private TextView txtIn;    private TextView txtGet;    private TextView txtInout;    private TextView txtOut;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        init();    }    private void init() {        txtIn = findViewById(R.id.txt_in);        txtGet = findViewById(R.id.txt_get);        txtInout = findViewById(R.id.txt_inout);        txtOut = findViewById(R.id.txt_out);    }    private void bindService() {        Intent intent = new Intent();        intent.setAction("com.martin.aidlblog");        intent.setPackage("com.martin.aidlblog");        bindService(intent, connection, BIND_AUTO_CREATE);    }    private ServiceConnection connection = new ServiceConnection() {        @Override        public void onServiceConnected(ComponentName name, IBinder service) {            manager = BookManager.Stub.asInterface(service);        }        @Override        public void onServiceDisconnected(ComponentName name) {            connection = null;        }    };    public void inBook(View view) {        try {            Book sendBook = getBook();            Book book = manager.inBook(sendBook);            txtIn.setText(sendBook.toString());            Log.e(TAG, "add:  客户端 书籍信息 :" + sendBook);            Log.e(TAG, "add: 服务端 返回书籍信息:" + book);        } catch (RemoteException e) {            e.printStackTrace();        }    }    public void outBook(View view) {        try {            Book sendBook = getBook();            Book book = manager.outBook(sendBook);            txtOut.setText(sendBook.toString());            Log.e(TAG, "read:  客户端 书籍信息 :" + sendBook);            Log.e(TAG, "read: 服务端 返回书籍信息:" + book);        } catch (RemoteException e) {            e.printStackTrace();        }    }    public void inoutBook(View view) {        try {            Book sendBook = getBook();            Book book = manager.inoutBook(sendBook);            txtInout.setText(sendBook.toString());            Log.e(TAG, "now:  客户端 书籍信息 :" + sendBook);            Log.e(TAG, "now: 服务端 返回书籍信息:" + book);        } catch (RemoteException e) {            e.printStackTrace();        }    }    public void get(View view) {        try {            List<Book> books = manager.getBooks();            txtGet.setText(books.toString());            Log.e(TAG, "get:  书籍数量 " + books.size());        } catch (RemoteException e) {            e.printStackTrace();        }    }    public Book getBook() {        return new Book("自动获取的名字", 66, false, 35);    }    @Override    protected void onResume() {        super.onResume();        bindService();    }    @Override    protected void onPause() {        super.onPause();        unbindService(connection);    }}

先运行服务端,再运行客户端,分别点击按钮,查看获取的内容:

  • inBook()
12-01 17:38:36.516 7536-7536/com.martin.aidlclientblog E/客户端: add:  客户端 书籍信息 :Book{name='自动获取的名字', price=66, nowRead=false, readPage=35}12-01 17:38:36.516 7536-7536/com.martin.aidlclientblog E/客户端: add: 服务端 返回书籍信息:Book{name='自动获取的名字', price=91, nowRead=false, readPage=23}
  • outBook()
12-01 17:43:04.444 7536-7536/com.martin.aidlclientblog E/客户端: read:  客户端 书籍信息 :Book{name='null', price=91, nowRead=false, readPage=23}12-01 17:43:04.444 7536-7536/com.martin.aidlclientblog E/客户端: read: 服务端 返回书籍信息:Book{name='null', price=91, nowRead=false, readPage=23}
  • inoutBook()
12-01 17:43:54.412 7536-7536/com.martin.aidlclientblog E/客户端: now:  客户端 书籍信息 :Book{name='自动获取的名字', price=91, nowRead=true, readPage=23}12-01 17:43:54.412 7536-7536/com.martin.aidlclientblog E/客户端: now: 服务端 返回书籍信息:Book{name='自动获取的名字', price=91, nowRead=true, readPage=23}
  • get()
12-01 17:47:27.979 12139-12139/com.martin.aidlclientblog E/客户端: get:  书籍数量 3

我们打印一下获取的书籍信息:

12-01 17:47:27.976 12139-12139/com.martin.aidlclientblog E/客户端: get:  这是原本的书籍 Book{name='自动获取的名字', price=66, nowRead=false, readPage=35}

现在结合上面写过的,定向Tag的特点以及图标,三个定向符的含义就更加清晰了。

源码地址

CSDN源码最低设置2分,如果有需要可以留下邮箱,我看到会即使回的。