2.3 使用 AIDL 进行进程间通信 - TomeOkin/Learning-Notes GitHub Wiki

使用 AIDL 进行进程间通信

当 Service 与客户端处于不同的进程时,它们运行在不同的虚拟机中,内存空间也是各自独立的,因此它们之间不能直接进行通信。Android 中,使用 Binder 作为进程间通信的桥梁。使用 Binder 进行数据传输时,需要将数据类型分解为原始的数据类型,为了简化 Binder 的使用,提供了 AIDL 文件。

编写 AIDL 文件

在 Android Studio 中,AIDL 文件存放于 src/main/aidl 文件夹(相应的,如 test/main/aidl 等)中,文件夹内的 Package 命名与一般 java Package 相同,文件扩展名为 .aidl,编译后存放在 generated/source/aidl 文件夹中。

aidl 文件用于定义客户端与 Service 通信的接口,由于

  • 原子类型(int、long 等)
  • String、CharSequence
  • List:本质上都会被转换为 ArrayList。
  • Map:不支持通用的 Map(如 Map<String,Integer>),本质上都会被转换为 HashMap。
  • Parelable:所有实现了 Parelable 的自定义类型都必须在 aidl 文件中声明和显示导入(即使位于同一个 Package 中)。
  • aidl 文件中定义的 aidl 接口。

aidl 接口只支持定义方法,该方法使用到的所有非原子类型的参数(不包含 aidl 接口)都必须声明参数的传递方向,可以是 in, out 或者 inout。方法如果包括接口做参数使用,需要使用的是 aidl 接口而不是普通的 java 接口。

aidl 中不能使用静态域。

// Book.aidl
package com.ryg.chapter_2.aidl;

parcelable Book;

// IOnNewBookArrivedListener.aidl
package com.ryg.chapter_2.aidl;

import com.ryg.chapter_2.aidl.Book;

interface IOnNewBookArrivedListener {
    void onNewBookArrived(in Book newBook);
}

// IBookManager.aidl
package com.ryg.chapter_2.aidl;

import com.ryg.chapter_2.aidl.Book;
import com.ryg.chapter_2.aidl.IOnNewBookArrivedListener;

interface IBookManager {
     List<Book> getBookList();
     void addBook(in Book book);
     void registerListener(IOnNewBookArrivedListener listener);
     void unregisterListener(IOnNewBookArrivedListener listener);
}

以上代码编译后,可以看到两个文件:

// IOnNewBookArrivedListener.java
package com.ryg.chapter_2.aidl;

public interface IOnNewBookArrivedListener extends android.os.IInterface {

    /** Local-side IPC implementation stub class. */
    // 客户端使用的 Binder
    public static abstract class Stub extends android.os.Binder
            implements com.ryg.chapter_2.aidl.IOnNewBookArrivedListener {
        private static final java.lang.String DESCRIPTOR =
                "com.ryg.chapter_2.aidl.IOnNewBookArrivedListener";

        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        // 如果是 Service 与客户端处于同个进程,则返回原始的对象,否则构造一个 Proxy 对象,并返回
        public static com.ryg.chapter_2.aidl.IOnNewBookArrivedListener asInterface(
                        android.os.IBinder obj) {...}

        @Override
        public android.os.IBinder asBinder() {
            return this;
        }

        // 每个方法都会定义一个对应的代码,用于下文与 code 比较
        static final int TRANSACTION_onNewBookArrived =
                (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);

        // 根据 code 选择找到对应的方法,接着对 Service 执行响应的方法
        @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply,
                int flags) throws android.os.RemoteException {...}

        // 代理类
        private static class Proxy implements com.ryg.chapter_2.aidl.IOnNewBookArrivedListener {
            private android.os.IBinder mRemote;

            Proxy(android.os.IBinder remote) {
                mRemote = remote;
            }

            @Override
            public android.os.IBinder asBinder() {
                return mRemote;
            }

            public java.lang.String getInterfaceDescriptor() {
                return DESCRIPTOR;
            }

            // 代理对象提供的与服务器接口具有相同签名的方法,在该方法中,会对数据进行序列化传输;
            // 同时,还会调用 mRemote.transact() 进行数据传输等
            @Override
            public void onNewBookArrived(com.ryg.chapter_2.aidl.Book newBook)
                    throws android.os.RemoteException {...}
        }
    }

    public void onNewBookArrived(com.ryg.chapter_2.aidl.Book newBook)
            throws android.os.RemoteException;
}

另一个文件是 IBookManager.java,由于结构相同,就不再列出。

可以看出,当客户端与 Service 处于不同进程时,客户端需要通过代理对象执行操作,而代理对象与 Service 之间的交互,则由系统来完成,此处不深入分析。

默认情况下,当客户端调用 Service 端的接口时,客户端进程会被挂起直到 Service 端执行完毕,如果 Service 中对应的方法执行时间过长,客户端会阻塞。解决方法有两种,一种是客户端开线程去操作服务器接口,另一种方法是使用关键字 oneway 对 aidl 接口进行声明,第二种方式下,当客户端完成数据传输后就会立刻返回,而不会等待 Service 的执行,这种情况下,如果需要获得 Service 端的执行结果,需要用其他方式。

Service 端的编写

服务器端需要实现生成的 Stub 接口,也即实现一个 Binder,以响应客户端的请求。

public class BookManagerService extends Service {
    private static final String TAG = "BMS";

    private AtomicBoolean mIsServiceDestoryed = new AtomicBoolean(false);

    private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<Book>();
    private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList = new RemoteCallbackList<IOnNewBookArrivedListener>();

    private Binder mBinder = new IBookManager.Stub() {
        @Override
        public List<Book> getBookList() throws RemoteException {
            SystemClock.sleep(5000);
            return mBookList;
        }

        @Override
        public void addBook(Book book) throws RemoteException {
            mBookList.add(book);
        }

        @Override
        public void registerListener(IOnNewBookArrivedListener listener)
                throws RemoteException {
            mListenerList.register(listener);

            final int N = mListenerList.beginBroadcast();
            mListenerList.finishBroadcast();
            Log.d(TAG, "registerListener, current size:" + N);
        }

        @Override
        public void unregisterListener(IOnNewBookArrivedListener listener)
                throws RemoteException {
            boolean success = mListenerList.unregister(listener);

            if (success) {
                Log.d(TAG, "unregister success.");
            } else {
                Log.d(TAG, "not found, can not unregister.");
            }
            final int N = mListenerList.beginBroadcast();
            mListenerList.finishBroadcast();
            Log.d(TAG, "unregisterListener, current size:" + N);
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();
        mBookList.add(new Book(1, "Android"));
        mBookList.add(new Book(2, "Ios"));
    }

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

    @Override
    public void onDestroy() {
        mIsServiceDestoryed.set(true);
        super.onDestroy();
    }

    private void onNewBookArrived(Book book) throws RemoteException {
        mBookList.add(book);
        final int N = mListenerList.beginBroadcast();
        for (int i = 0; i < N; i++) {
            IOnNewBookArrivedListener l = mListenerList.getBroadcastItem(i);
            if (l != null) {
                try {
                    l.onNewBookArrived(book);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }
        mListenerList.finishBroadcast();
    }
}

有几点需要注意:
(1)Service 端会从 Binder 连接池中获得一个常规的远程调用,该调用以同步方式执行,如果 Service 端执行时间过长,会影响 Service 端对其他请求的响应。当然,如果 Service 不需要返回结果给客户端,就可以直接开启线程进行操作,如果 Service 需要返回结果给客户端,则还需要提供一个回调接口(see Binding Callback)。一般情况下,连接池提供的线程数可以满足要求,可以不用担心服务器端被阻塞的问题。
(2)Service 支持并发访问,因此,Service 端需要保证线程安全,因此,上面代码使用了 CopyOnWriteArrayList 以支持并发读写,类似支持并发读写的,还有 ConcurrentHashMap
(3)由于数据传输到 Service 端的时候,数据对象已经是重新构造出来的了,因此,如果有注册监听器、注销监听器之类的需求,则不应该使用普通的 List 来管理。如上,使用了 RemoteCallbackList 来处理接口。

当我们往 RemoteCallbackList 中注册一个 callback 时,RemoteCallbackList 通过 callback.asBinder() 获得 Binder,并将其作为 key,callback 作为 value,放入 ArrayMap<IBinder, Callback>。因为 Binder 能够唯一标识一个当前绑定的客户端,因此可以借助 Binder 来区分 callback。RemoteCallbackListbeginBroadcast()finishBroadcast() 需要成对使用,因此,获取已注册的监听器个数时,需要使用:

final int N = mListenerList.beginBroadcast();
mListenerList.finishBroadcast();

另外,当客户端进程终结后,RemoteCallbackList 能够自动移除客户端注册的监听器。

客户端的编写

public class BookManagerActivity extends Activity {
    private static final String TAG = "BookManagerActivity";
    private static final int MESSAGE_NEW_BOOK_ARRIVED = 1;

    private IBookManager mRemoteBookManager;

    @SuppressLint("HandlerLeak") private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MESSAGE_NEW_BOOK_ARRIVED:
                    Log.d(TAG, "receive new book :" + msg.obj);
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    };

    private ServiceConnection mConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className, IBinder service) {
            IBookManager bookManager = IBookManager.Stub.asInterface(service);
            mRemoteBookManager = bookManager;
            try {
                List<Book> list = bookManager.getBookList();
                Log.i(TAG, "query book list, list type:" + list.getClass().getCanonicalName());
                Log.i(TAG, "query book list:" + list.toString());
                Book newBook = new Book(3, "Android进阶");
                bookManager.addBook(newBook);
                Log.i(TAG, "add book:" + newBook);
                List<Book> newList = bookManager.getBookList();
                Log.i(TAG, "query book list:" + newList.toString());
                bookManager.registerListener(mOnNewBookArrivedListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        public void onServiceDisconnected(ComponentName className) {
            mRemoteBookManager = null;
            Log.d(TAG, "onServiceDisconnected. tname:" + Thread.currentThread().getName());
        }
    };

    private IOnNewBookArrivedListener mOnNewBookArrivedListener =
            new IOnNewBookArrivedListener.Stub() {
                @Override
                public void onNewBookArrived(Book newBook) throws RemoteException {
                    mHandler.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED, newBook).sendToTarget();
                }
            };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_book_manager);
        Intent intent = new Intent(this, BookManagerService.class);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }

    public void onButton1Click(View view) {
        Toast.makeText(this, "click button1", Toast.LENGTH_SHORT).show();
        new Thread(new Runnable() {
            @Override
            public void run() {
                if (mRemoteBookManager != null) {
                    try {
                        List<Book> newList = mRemoteBookManager.getBookList();
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }

    @Override
    protected void onDestroy() {
        if (mRemoteBookManager != null && mRemoteBookManager.asBinder().isBinderAlive()) {
            try {
                Log.i(TAG, "unregister listener:" + mOnNewBookArrivedListener);
                mRemoteBookManager.unregisterListener(mOnNewBookArrivedListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        unbindService(mConnection);
        super.onDestroy();
    }
}

这里,同样也有一些需要注意的地方:
(1)通过 IInterface.Stub.asInterface(IBinder) 获得了 aidl 接口实例,在操作对应的方法的时候,要注意避免主线程被阻塞,最好开线程进行操作,如 onButton1Click() 里做的。
(2)当 Service 回调监听器接口或者其他接口(IInterface.Stub)时,不能保证其一定是在主线程,因此,这里借助 Handler 来传递消息,保证是在主线程进行操作,也可以使用 EventBus.getDefault().post(new SomeEvent()) 来实现。
(3)可以通过 IInterface.asBinder().isBinderAlive() 判断 Binder 是否还存活,不过返回 true时,也有可能是在返回查询结果的时候,Binder 已经结束了。因此,需要防止抛出 RemoteException 异常。另外,也可以通过 IInterface.asBinder().pingBinder() 来判断。
(4)当我们的 Service 和 客户端是在同个应用内时,如上,可以直接使用显示意图去绑定。但如果不是同个应用呢?

在 Android 中,隐式意图有如下几种情况:

  • 安装一个新应用时,如果系统中已经有其它已安装的应用与新应用具有相同 的 ContentProvider,那么新应用会安装失败(INSTALL_FAILED_CONFLICTING_PROVIDER)。
  • 安装一个新应用时,如果系统中已经有其它已安装的应用与新应用具有相同 的 Service,Android 5.0 起,新应用会安装失败(INSTALL_FAILED_CONFLICTING_PROVIDER),其他的可以正常安装,android:priority 优先级高的会接收到访问的请求;如果优先级相同,先安装的应用会接收到访问的请求。

在 Android 5.0 起,不能再使用隐式意图绑定 Service。

因此,当 Service 和 客户端不是在同个应用内时,最好的方式是通过 PackageManager 获得显式的意图后再启动:

<service android:name="BookManagerService">
    <intent-filter>
        <action android:name="com.ryg.chapter_2.aidl.IBookManagerService" />
    </intent-filter>
</service>
Intent implicit = new Intent(IBookManager.class.getName());
List<ResolveInfo> matches = getActivity().getPackageManager()
        .queryIntentServices(implicit, 0);

if (matches.size() == 0) {
    // Cannot find a matching service!
} else if (matches.size() > 1) {
    // Found multiple matching services!
} else {
    Intent explicit = new Intent(implicit);
    ServiceInfo svcInfo = matches.get(0).serviceInfo;
    ComponentName cn = new ComponentName(svcInfo.applicationInfo.packageName, svcInfo.name);

    explicit.setComponent(cn);
    bindService(explicit, this, Context.BIND_AUTO_CREATE);
}

监听与 Service 之间的连接

一般情况下,Service 不会轻易被系统结束,但如果真的发生,服务器端的 Binder 连接会断裂(Binder 死亡),那么客户端的功能就不能正常进行了。在 Android 中,Binder 提供了一种称为 link-to-death 的机制,当通过 Binder 连接的提供端,也即我们这里的 Service,不存在时,客户端进程可以接收到通知,过程如下:

首先,我们需要实现 IBinder.DeathRecipient 接口,在 Service 死亡的的时候,我们会接收到回调;接着通过 IInterface.asBinder().linkToDeath(mDeathRecipient, 0); 注册该回调。在 Service 结束的时候,我们会通过 DeathRecipient 里的 binderDied() 获得通知,在这个时候,onServiceDisconnected() 也是会接收到通知的,区别在于,binderDied() 是在客户端的 Binder 线程池中被回调的,而 onServiceDisconnected() 是在 UI 线程被回调的。

private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
    @Override
    public void binderDied() {
        Log.d(TAG, "binder died. tname:" + Thread.currentThread().getName());
        if (mRemoteBookManager == null) return;
        mRemoteBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);
        mRemoteBookManager = null;
        // TODO:这里重新绑定远程Service
    }
};

private ServiceConnection mConnection = new ServiceConnection() {
    public void onServiceConnected(ComponentName className, IBinder service) {
        IBookManager bookManager = IBookManager.Stub.asInterface(service);
        mRemoteBookManager = bookManager;
        try {
            mRemoteBookManager.asBinder().linkToDeath(mDeathRecipient, 0);
            // other
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    public void onServiceDisconnected(ComponentName className) {
        mRemoteBookManager = null;
        Log.d(TAG, "onServiceDisconnected. tname:" + Thread.currentThread().getName());
    }
};

Service 安全

我们可以通过 permission 来添加访问权限,在 onTransact 中进行认证:

<permission
    android:name="com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE"
    android:protectionLevel="normal" />
private Binder mBinder = new IBookManager.Stub() {

    @Override
    public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
            throws RemoteException {
        // 权限认证
        int check = checkCallingOrSelfPermission("com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE");
        Log.d(TAG, "check=" + check);
        if (check == PackageManager.PERMISSION_DENIED) {
            return false;
        }

        // 包名或者签名进行认证
        // 签名方式参见 https://github.com/commonsguy/cw-omnibus/blob/master/Binding/SigCheck/Service/app/src/main/java/com/commonsware/android/advservice/remotebinding/sig/DownloadService.java
        String packageName = null;
        String[] packages = getPackageManager().getPackagesForUid(
                getCallingUid());
        if (packages != null && packages.length > 0) {
            packageName = packages[0];
        }
        Log.d(TAG, "onTransact: " + packageName);
        if (!packageName.startsWith("com.ryg")) {
            return false;
        }

        // must do it
        return super.onTransact(code, data, reply, flags);
    }

    // other is hided
};

客户端使用时,需要添加访问的权限:

<uses-permission android:name="com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE" />

参考

《Android 开发艺术探索》 本文中的例子大部分都来自这里
《The Busy Coders Guide to Android Development》
Binders & Death Recipients
Android Interface Definition Language (AIDL)

⚠️ **GitHub.com Fallback** ⚠️