学习AIDL,这一篇差不多了

AIDL

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


参考Blog

目录


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.aidl
package com.example.marti.aidlstudy.aidl;

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

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

// BookManager.aidl
package 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 == in

public com.martin.aidlblog.Book inBook(com.martin.aidlblog.Book book) throws android.os.RemoteException;
// 定向tag == out

public com.martin.aidlblog.Book outBook(com.martin.aidlblog.Book book) throws android.os.RemoteException;
// 定向 inout

public 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分,如果有需要可以留下邮箱,我看到会即使回的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值