Android進程間通信之AIDL

一、 摘要

本文介紹Android中的IPC方式之一——AIDL。


二、 關於AIDL

AIDL:Android Interface Definition Language,即Android接口定義語言。

Android系統中的進程之間不能共享內存,因此,需要提供一些機制在不同進程之間進行數據通信。

爲了使其他的應用程序也可以訪問本應用程序提供的服務,Android系統採用了遠程過程調用(Remote Procedure Call,RPC)方式來實現。與很多其他的基於RPC的解決方案一樣,Android使用一種接口定義語言(Interface Definition Language,IDL)來公開服務的接口。我們知道4個Android應用程序組件中的3個(Activity、BroadcastReceiver和ContentProvider)都可以進行跨進程訪問,另外一個Android應用程序組件Service同樣可以。因此,可以將這種可以跨進程訪問的服務稱爲AIDL(Android Interface Definition Language)服務。


三、 編寫一個Demo

1. AIDL接口

以Android Studio爲例,鼠標在module上點擊右鍵 - new - AIDL - AIDL File,之後,會在該module下自動創建一個名叫aidl的和src同級的文件夾,其中包含一個和src下同包名的aidl文件,這就是我們的服務接口。

我們編寫完接口後,需要對module重新build一次,然後會自動生成和該aidl接口同名的java接口文件。關於aidl中接口的規範和約束,建議讀者學習這篇文章:你真的理解AIDL中的in,out,inout麼?

這個自動生成的同名java文件,請不要修改它,爲了便於觀察,我將其格式化,然後在註釋中講解:

package com.zengyu.aidldemo;

public interface IDemo extends android.os.IInterface {
    /**
     * 繼承Binder,這個很關鍵,Android中的IPC,底層都是基於Binder實現,
     * 這個靜態內部類,實現了外部類的接口,但是由於是抽象類,所以沒有真正實現,
     * 因此後面當我們使用這個Stub時,需要自己去實現外部類的接口,也就是aidl文件中的服務接口
     */
    public static abstract class Stub extends android.os.Binder implements com.zengyu.aidldemo.IDemo {
        private static final java.lang.String DESCRIPTOR = "com.zengyu.aidldemo.IDemo";

        /**
         * Stub主要做三件事:
         * 1. 將自身與Binder進行關聯;
         * 2. 將方法名以字符串形式進行映射(因此aidl不支持方法的不同入參的重載);
         * 3. 實現每個服務接口包裹化的讀寫
         * 這些都在自動生成時替我們完成了,我們無需關注具體內容
         */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        /**
         * 從Binder到aidl接口的轉換
         */
        public static com.zengyu.aidldemo.IDemo asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.zengyu.aidldemo.IDemo))) {
                return ((com.zengyu.aidldemo.IDemo) iin);
            }
            return new com.zengyu.aidldemo.IDemo.Stub.Proxy(obj);
        }

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

        /**
         * 服務接口方法映射
         */
        @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
            java.lang.String descriptor = DESCRIPTOR;
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(descriptor);
                    return true;
                }
                case TRANSACTION_seyHello: {
                    data.enforceInterface(descriptor);
                    java.lang.String _arg0;
                    _arg0 = data.readString();
                    java.lang.String _result = this.seyHello(_arg0);
                    reply.writeNoException();
                    reply.writeString(_result);
                    return true;
                }
                default: {
                    return super.onTransact(code, data, reply, flags);
                }
            }
        }

        /**
         * 服務接口包裹化讀寫實現
         */
        private static class Proxy implements com.zengyu.aidldemo.IDemo {
            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;
            }

            @Override
            public java.lang.String seyHello(java.lang.String from) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                java.lang.String _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeString(from);
                    mRemote.transact(Stub.TRANSACTION_seyHello, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.readString();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
        }

        static final int TRANSACTION_seyHello = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    }

    /**
     * 需要我們之後使用時去實現的服務接口
     */
    public java.lang.String seyHello(java.lang.String from) throws android.os.RemoteException;
}

2. 服務端

我們在Service中實例化一個Stub(兩種啓動方式的重載方法由讀者自己實現,此處不展示):

public class AIDLService extends Service {
    private final IDemo.Stub mBinder = new IDemo.Stub() {
        @Override
        public String seyHello(String from) throws RemoteException {
            return "Hello client, I receive your msg: " + from;
        }
    };

    public AIDLService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        /**
         * Binder是IPC的基礎,我們可以在各處見到它
         */
        return mBinder;
    }
}

別忘了在AndroidManifest中註冊:

<service
    android:name=".AIDLService"
     <!--表示允許其他應用調用我們這個Service-->
    android:exported="true">
</service>

3. 客戶端

客戶端需要包含相同的aidl文件,也就是說,在客戶端的module中,同樣需要創建一個aidl文件,其路徑、文件名、內容,應該與使用到的服務端的aidl保持一致。

然後我們在一個Activity中綁定遠程的服務端Service:

public class MainActivity extends AppCompatActivity {
    private IDemo iDemo = null;

    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            /**
             * 將服務端的Binder實例轉換爲客戶端的aidl接口
             */
            iDemo = IDemo.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            iDemo = null;
        }
    };

    /**
     * 我們需要在Activity創建時綁定Service,通常在onCreate中
     */
    private void bindService() {
        Intent intent = new Intent();
        // 如果我們給Service添加了用於啓動的filter:
        intent.setAction("啓動的filter");
        // TODO 使用setComponent或者setPackage來指定Service所在的應用
        bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
    }

    private void testSending() {
        try {
            iDemo.seyHello("MainActivity");
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
}

同樣,別忘了檢查我們的Activity是否在AndroidManifest中註冊了。


四、 參考文獻

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章