Android AIDL 跨进程通信使用详述

时间:2024-05-31 16:40:43

1、AIDL(Android interface definition language)
AIDL是Android进程间通信(IPC)的一种方式。每个进程都有自己独立的内存空间,一个进程不能访问另一个进程的内存空间,两个进程的数据交互需要通过进程间通信。AIDL通过绑定Service的方式,以实现两个App之间的交互。

2、AIDL支持的数据类型
Java的基本数据类型:byte,short,int,long,float,double,boolean,char
String和CharSequence
List和Map:元素必须是AIDL支持的数据类型
实现Parcelable的实体
AIDL生成的接口

3、定向Tag
in:数据只能由客户端流向服务端。使用in定向tag修饰参数,在服务端得到一个和实参值相同的对象(注意:不是同一个对象)。基本数据类型和String,CharSequence默认是in,而且只能是in。
out:数据只能由服务端流向客户端。
Inout:数据的流向是双向的。

4、AIDL文件
AIDL文件有两种类型
一种用于自定义数据类型,用于创建实现Parcelable的实体。
另一种用于定义接口。

5、oneway关键字
方法调用时,如果不想阻塞,而是直接返回,可以在定义方法时使用oneway关键字。例如:
oneway void sendSleep();

6、AIDL实现步骤
AIDL:
创建实现Parcelable的实体类
创建与实体类对应的.aidl文件
创建.aidl接口文件,并编译生成对应的.java文件(包含Binder)

服务端:
创建Service
在Service中创建Binder实例,并在onBind()中返回该实例

客户端:
实现ServiceConnection,获取BpBinder
调用bindService()
调用.aidl文件中已定义的接口,向服务端发送请求。

7、AIDL实例
为了了解AIDL的具体实现,实现了一个简单的例子。在本例当中,服务端进行书籍的管理,客户端可以向服务端查询或添加书籍。
开发环境为:Android Studio 3.2

7.1 创建Book.java
为了能够在客户端和服务端进行传递,需要实现Parcelable。
这里需要注意一下:多了一个readFromParcel方法,如果缺少这个方法,在定义AIDL接口是,Book类型的参数只能使用in定向Tag。

package com.zxd.demo.book;

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

public class Book implements Parcelable {

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

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

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

    public void readFromParcel(Parcel in) {
        this.name = in.readString();
    }

    public Book() {
    }

    protected Book(Parcel in) {
        this.name = 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];
        }
    };
}

7.2 创建Book.aidl文件
创建第一类AIDL文件。在工程中,选中app,点击右键,创建AIDL文件,会自动生成与java目录同一级别的aidl目录,并生成同名package,如下图所示。由于已经有同名的Book.java存在,创建Book.aidl时会报错,先起一个其他的文件名,然后再重命名。
Book.aidl文件内容很简单,如下:

// Book.aidl
package com.zxd.demo.book;

parcelable Book;

Android AIDL 跨进程通信使用详述

7.3 创建IBookService.aidl
创建第二类AIDL文件,也就是客户端与服务器端通信的接口。
可以看到,本例中使用了定向Tag和oneway 关键字。
在这里,我们import了Book和另一个文件IBookCallback,如果不进行import,编译会失败。编译代码之后,我们可以在generatedJava目录下看到自动生成的IBookService.java文件。

// IBookService.aidl
package com.zxd.demo.book;

import com.zxd.demo.book.Book;
import com.zxd.demo.book.IBookCallback;

interface IBookService {

    List<Book> getBooks();

    void addBookIn(in Book book);
    void addBookOut(out Book book);
    void addBookInOut(inout Book book);

    void clearBookList();

    void registerCallback(IBookCallback callback);
    void unRegisterCallback(IBookCallback callback);

    oneway void sendSleep();
}

7.4 服务端实现
在Service中创建IBookService.Stub实例,这就是服务端的BBinder。

package com.zxd.demo.book;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import android.util.Log;

import java.util.ArrayList;
import java.util.List;

public class BookService extends Service {
    private final String TAG = this.getClass().getSimpleName();

    private ArrayList<Book> mBookList = new ArrayList<>();
    private RemoteCallbackList<IBookCallback> mRemoteCallbackList = new RemoteCallbackList<>();

    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d(TAG, "start id:" + startId);

        return super.onStartCommand(intent, flags, startId);
    }

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

//服务端的BBinder
    private IBookService.Stub mBookBinder = new IBookService.Stub() {

        @Override
        public void registerCallback(IBookCallback callback) throws RemoteException {
            mRemoteCallbackList.register(callback);
        }

        @Override
        public void unRegisterCallback(IBookCallback callback) throws RemoteException {
            mRemoteCallbackList.unregister(callback);
        }

        @Override
        public List<Book> getBooks() throws RemoteException {
            synchronized (this) {
                return mBookList;
            }
        }

        @Override
        public void addBookIn(Book book) throws RemoteException {
            synchronized (this) {
                if (book != null) {
                    addBook(book);
                }
            }
        }

        @Override
        public void addBookOut(Book book) throws RemoteException {
            synchronized (this) {
                if (book != null) {
                    book.setName("Little Prince");
                } else {
                    book = new Book();
                    book.setName("Little Prince create");
                }
                addBook(book);
            }
        }

        @Override
        public void addBookInOut(Book book) throws RemoteException {
            synchronized (this) {
                if (book != null) {
                    book.setName("Little Prince");
                } else {
                    book = new Book();
                    book.setName("Little Prince create");
                }
                addBook(book);
            }
        }

        @Override
        public void clearBookList() throws RemoteException {
            synchronized (this) {
                mBookList.clear();
                broadcast();
            }
        }

        @Override
        public void sendSleep() throws RemoteException {
            synchronized (this) {
                try {
                    wait(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
	      broadcast();
            }
        }

        private void addBook(Book book) {
            mBookList.add(book);
            broadcast();
        }

        private void broadcast() {
            mRemoteCallbackList.beginBroadcast();
            for (int i = 0; i < mRemoteCallbackList.getRegisteredCallbackCount(); i++) {
                IBookCallback callback = mRemoteCallbackList.getBroadcastItem(i);
                try {
                    callback.onBookChanged(mBookList);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
            mRemoteCallbackList.finishBroadcast();
        }
    };
}

7.5 客户端实现

private ServiceConnection mServiceConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        mBookService = IBookService.Stub.asInterface(service);
        try {
            mBookService.registerCallback(mBookCallbackBinder);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        try {
            mBookService.unRegisterCallback(mBookCallbackBinder);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        mBookService = null;
    }
};

//绑定BookService,flag设置为BIND_AUTO_CREATE,如果service尚未启动,会启动service。

Intent service = new Intent();
service.setAction("com.zxd.demo.book.SERVICE");
service.setPackage("com.zxd.demo.book");
bindService(service, mServiceConnection, BIND_AUTO_CREATE);

当客户端bindService并且连接成功后,在mServiceConnection中可以获得客户端的BpBinder。mBookService是IBookService.Stub.Proxy的实例,实现了接口IBookService,在IBookService.java中可以查看到这部分代码。
通过mBookService就可以调用AIDL接口定义的方法,实现与服务端的通信。

private void addBookIn() {
    Book book = new Book();
    book.setName("Black and Red " + mCount++);
    try {
        mBookService.addBookIn(book);
    } catch (RemoteException e) {
        e.printStackTrace();
    }
}

7.6 Callback回调
试想这样一种情况:客户端调用接口,发起一个请求让服务端做一项耗时的任务,客户端不想阻塞等待服务端返回,但还想在此项任务完成时得到通知。这种异步情况在AIDL机制中怎么实现呢?这样就需要用到oneway关键字和RemoteCallbackList

7.6.1 定义CallBack接口
在图7-2中,我们看到有一个接口文件IBookCallback.aidl。客户端想得到通知就要实现这个接口并进行远程注册,当服务端返回时就能收到通知了。

// IBookCallback.aidl
package com.zxd.demo.book;

import com.zxd.demo.book.Book;

interface IBookCallback {

    void onBookChanged(in List<Book> bookList);
    void onWakeup();
}

7.6.2 定义注册Callback的方法和oneway方法
在本例中我们定义了如下方法:

void registerCallback(IBookCallback callback);
void unRegisterCallback(IBookCallback callback);
oneway void sendSleep();

这些方法定义在IBookService.aidl中,sendSleep方法因为使用了oneway关键字,当客户端调用时不会被阻塞。这就是服务端要做的耗时任务。前两个方法是用来注册和注销callback的。

7.6.3 服务端的实现
在服务端我们需要实现7.4.2定义的三个方法。
回顾BookService.java中的实现:RemoteCallbackList用于进行远程Callback的注册和注销。
在sendSleep()方法中,在执行结束后,进行回调处理。

private RemoteCallbackList<IBookCallback> mRemoteCallbackList = new RemoteCallbackList<>();

//服务端的BBinder
private IBookService.Stub mBookBinder = new IBookService.Stub() {

    @Override
    public void registerCallback(IBookCallback callback) throws RemoteException {
        synchronized (this) {
            mRemoteCallbackList.register(callback);
        }
    }

    @Override
    public void unRegisterCallback(IBookCallback callback) throws RemoteException {
        synchronized (this) {
            mRemoteCallbackList.unregister(callback);
        }
    }

    @Override
    public void sendSleep() throws RemoteException {
        synchronized (this) {
            try {
                wait(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        mRemoteCallbackList.beginBroadcast();
        for (int i = 0; i < mRemoteCallbackList.getRegisteredCallbackCount(); i++) {
            IBookCallback callback = mRemoteCallbackList.getBroadcastItem(i);
            try {
                //调用客户端的方法
                callback.onWakeup();
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        mRemoteCallbackList.finishBroadcast();
    }
};

7.6.4 客户端的实现
在客户端实现Callback接口,并调用AIDL接口注册和注销CallBack。
当服务端发起回调时,客户端的mBookCallbackBinder的onWakeup()方法将被执行。

private IBookCallback.Stub mBookCallbackBinder = new IBookCallback.Stub() {

    @Override
    public void onWakeup() throws RemoteException {
        Log.d(TAG, "server wake up");
    }
};

当与服务端建立连接时,注册Callback。连接断开后,注销Callback。

private ServiceConnection mServiceConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        mBookService = IBookService.Stub.asInterface(service);
        try {
            mBookService.registerCallback(mBookCallbackBinder);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        try {
            mBookService.unRegisterCallback(mBookCallbackBinder);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        mBookService = null;
    }
};

8 小结
AIDL是进程间通信的一种方法,它是Android Binder机制的运用,客户端和服务端每一次的通信都要通过底层的Binder驱动,在同一个App内部不建议使用。