android IPC通信(下)-AIDL

  android IPC通信(上)-sharedUserId&&Messenger
  android IPC通信(中)-ContentProvider&&Socket
  這篇我們將會着重介紹AIDL的使用方式和原理,要介紹AIDL先要簡單介紹一下 Binder,而且 Messenger,ContentProvider 和 AIDL 的最底層都是使用的 Binder。
  相關博客介紹:
  android 不能在子線程中更新ui的討論和分析:Activity 打開的過程分析;
  java/android 設計模式學習筆記(9)—代理模式:AMS 的相關類圖和介紹;
  android WindowManager解析與騙取QQ密碼案例分析:界面 window 的創建過程;
  java/android 設計模式學習筆記(8)—橋接模式:WMS 的相關類圖和介紹;
  android IPC通信(下)-AIDL:AIDL 以及 Binder 的相關介紹;
  Android 動態代理以及利用動態代理實現 ServiceHook:ServiceHook 的相關介紹;
  Android TransactionTooLargeException 解析,思考與監控方案:TransactionTooLargeException 的解析以及監控方案。  

Binder

  直觀來說,Binder 是 Android 中的一個類,它實現了 IBinder 接口。從 IPC 角度來說,Binder 是 Android 中的一種跨進程通信方式,Binder 還可以理解爲一種虛擬的物理設備,它的設備驅動是 /dev/binder,該通信方式在Linux中沒有;從Android Framework 角度來說,Binder 是 ServiceManager 連接各種Manager (ActivityManager,WindowManager,等等)和相應 ManagerService 的橋樑;從Android 應用層來說,Binder 是客戶端和服務端進行通信的媒介,當 bindService 的時候,服務端會返回一個包含了服務端業務調用的 IBinder 對象(注意這個地方不能換成 Binder 對象,只能是 IBinder 對象,因爲 IBinder 的實現類有兩個,具體的區別我下面會講到),通過這個 IBinder 對象,客戶端就可以獲取服務端提供的服務或者數據,這裏的服務包括普通服務和基於AIDL的服務。
  還有兩點需要提到,第一點就是當客戶端發起遠程請求時,由於當前線程會被掛起直至服務端進程返回數據,所以如果一個遠程方法是耗時的,那麼不能在UI線程中發起此遠程請求;其次,由於服務端的Binder方法運行在Binder的線程池中,所以Binder方法不管是否耗時都應該採用同步的方法去實現,因爲他已經運行在一個線程中了。下圖爲Binder的工作機制圖:
  這裏寫圖片描述
  可以看到Client客戶端會block直到方法返回,從圖中我們可以看到 Binder 的調用中有四個角色:Client, Proxy, Binder Driver 和 Server ,他們的關係如下圖所示:
  這裏寫圖片描述
這四者的關係類似於網絡訪問,

  • Binder Client 只需要知道自己要使用的 Binder 的名字及其在 ServiceManager 中的引用即可獲取該 Binder 的引用,得到引用後就可以像普通方法調用一樣調用 Binder 實體的方法;
  • Binder Server 在生成一個 IBinder 實體時會爲其綁定一個名稱並傳遞給 Binder Driver,Binder Driver 會在內核空間中創建相應的 Binder 實體節點和節點引用,並將引用傳遞給 ServiceManager。ServiceManager 會將該 Binder 的名字和引用插入一張數據表中,這樣 Binder Client 就能夠獲取該 Binder 實體的引用,並調用上面的方法;
  • ServiceManager 相當於 DNS服務器,負責映射 Binder 名稱及其引用,其本質同樣是一個標準的 Binder Server;
  • Binder Driver 則相當於一個路由器。
其中 Binder Driver 實現在內核空間中,而其餘的 3 者 Binder Client, Binder Server, ServiceManager 實現在用戶空間中。Binder Driver 在內核空間中,其以字符設備中的 misc 類型註冊,用戶可以從 /dev/binder 設備文件節點上,通過 open 和 ioctl 文件操作函數與 Binder Driver 進行通信,其主要負責 Binder 通信的建立,以及其在進程間的傳遞和 Binder 引用計數管理/數據包的傳輸等。而 Binder Client 與 Binder Server 之間的跨進程通信則統一通過 Binder Driver 處理轉發,對於 Binder Client 來說,其只需要知道自己要使用的 Binder 的名字以及該 Binder 實體在 ServiceManager 中的 0 號引用即可,訪問的原理也很簡單,Binder Client 先是通過 0 號引用去訪問 ServiceManager 獲取該 Binder Server 的引用,得到引用後就可以像普通方法調用那樣調用 Binder 實體的方法。最後我們的 ServiceManager 則用來管理 Binder Server,Binder Client 可以通過它來查詢 Binder Server 接口,剛纔我們說到 Binder Client 可以通過 ServiceManager 來獲取 Binder Server 的引用,這個 Binder Server 的引用就是由 ServiceManager 來轉換的,其實不如說映射更直接,Binder Server 在生成一個 Binder 實體的同時會爲其綁定一個名字並將這個名字封裝成一個數據包傳遞給 Binder Driver,Binder Driver 接收到這個數據包後,如果發現這個 Binder 是新傳遞來的,那麼就會爲其在內核空間中創建對應的 Binder 實體節點和一個對該實體節點的引用,這個實體節點在相應的源碼中叫做 Binder_node 而其引用則叫做 Binder_ref,創建完畢後,Binder Driver 就會將該引用傳遞給 ServiceManager ,ServiceManager 收到後就會從中取出該 Binder 的名字和引用插入一張數據表中,這跟 DNS 中存儲的域名到 IP 地址的映射原理類似,而對於網絡訪問來說,DNS 服務器也並不一定對每一個 IP 地址都有域名映射的記錄,我們常常也會碰到直接通過 IP 地址訪問服務器的情況,而 Binder 也一樣並非一定要在 ServiceManager 中有記錄,很多時候 Binder Server 會將一個 Binder 實體封裝進數據包傳遞給 Binder Client,而此時 Binder Server 會在該數據包中標註 Binder 實體的位置,Binder Driver 則會爲該匿名的 Binder 生成實體節點和實體引用,並將該引用傳遞給 Binder Client。從大的角度來說, ServiceManager 其實也是一個標準的 Binder Server,並且在 Android 中約定其在 Binder 通信的過程中唯一標識永遠是 0 ,這個標識就是進程號,只不過它在 Binder Driver 中是最先被註冊的。
  ServiceManager 既然是一個標準的 Binder Server,那麼它應該對外公佈其可用的接口方法,這裏你可以將它看作一個聯想服務器,既然能讓客戶端訪問,總得給客戶端可訪問的接口和數據吧,以博客 java/android 設計模式學習筆記(9)—代理模式 中的 ActivityManagerNative 爲例來分析(強烈推薦看一下博客: Android 利用 ServiceHook 實現特殊功能,針對 hook ClipboardService 可以對 ServiceManager 的使用有更全面的瞭解),在 ActivityManagerNative 的 gDefault 對象中,有這樣一行代碼:

IBinder b = ServiceManager.getService("activity");

通過 ServiceManager 去獲取 AMS 的 IBinder 對象,ServiceManager 類的代碼很簡單:

public final class ServiceManager {
    private static final String TAG = "ServiceManager";

    private static IServiceManager sServiceManager;
    private static HashMap<String, IBinder> sCache = new HashMap<String, IBinder>();

    private static IServiceManager getIServiceManager() {
        if (sServiceManager != null) {
            return sServiceManager;
        }

        // Find the service manager
        sServiceManager = ServiceManagerNative.asInterface(BinderInternal.getContextObject());
        return sServiceManager;
    }

    /**
     * Returns a reference to a service with the given name.
     * 
     * @param name the name of the service to get
     * @return a reference to the service, or <code>null</code> if the service doesn't exist
     */
    public static IBinder getService(String name) {
        try {
            IBinder service = sCache.get(name);
            if (service != null) {
                return service;
            } else {
                return getIServiceManager().getService(name);
            }
        } catch (RemoteException e) {
            Log.e(TAG, "error in getService", e);
        }
        return null;
    }

    public static void addService(String name, IBinder service) {
    ...
    }
    ....
}

  • 每次通過 ServiceManager 的 getService() 方法取得一個 SystemService 的引用,實際上只是通過 getIServiceManager() 取回一個 Proxy 對象,然後再調用這個 Proxy對象的 getService() 方法;
  • getIServiceManager(),實際上在跨進程通信當中是返回一個 Proxy 對象,而這個 Proxy 其實是 ServiceManagerProxy ,訪問到一個 ServiceManagerNative 對象。
IServiceManager 爲一個接口,它所承擔的就是 “DNS 服務器” 的角色,即 ServiceManager 角色:

public interface IServiceManager extends IInterface
{
    /**
     * Retrieve an existing service called @a name from the
     * service manager.  Blocks for a few seconds waiting for it to be
     * published if it does not already exist.
     */
    public IBinder getService(String name) throws RemoteException;

    /**
     * Retrieve an existing service called @a name from the
     * service manager.  Non-blocking.
     */
    public IBinder checkService(String name) throws RemoteException;

    /**
     * Place a new @a service called @a name into the service
     * manager.
     */
    public void addService(String name, IBinder service, boolean allowIsolated)
                throws RemoteException;

    /**
     * Return a list of all currently running services.
     */
    public String[] listServices() throws RemoteException;

    /**
     * Assign a permission controller to the service manager.  After set, this
     * interface is checked before any services are added.
     */
    public void setPermissionController(IPermissionController controller)
            throws RemoteException;

    static final String descriptor = "android.os.IServiceManager";

    int GET_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION;
    int CHECK_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+1;
    int ADD_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+2;
    int LIST_SERVICES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+3;
    int CHECK_SERVICES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+4;
    int SET_PERMISSION_CONTROLLER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+5;
}

它的具體實現類是 ServiceManagerNative 和 ServiceManagerProxy 類(和 ActivityManagerNative, ActivityManagerProxy 與 IActivityManager 的關係一樣: java/android 設計模式學習筆記(9)—代理模式):
這裏寫圖片描述
ServiceManagerNative 和 ServiceManagerProxy 類都屬於 Binder Client 端, ServiceManager 和 Binder Driver 在 Android 平臺中已經實現。這裏要提到的一點是 ServiceManager 在 Java 和 Native 環境裏各有其實現,但在 Java 端實際上只有 Proxy端,而 Native 環境裏實現的 Servicemanager 才具有完整的 Proxy 與 Stub 實現,這也就是爲什麼 ServiceManagerNative 和 ServiceManagerProxy 相對於 ServiceManager 這個 BinderServer 角色來說都是屬於 BinderClient 了。
  繼續來介紹一下上面提到的 Binder 對象與 IBinder 對象的區別,爲什麼上面不能說是 Binder 對象,因爲 IBinder 有兩個實現的子類,第一個是我們熟知的 Binder.class,第二個是 BindlerProxy.class,這兩個都是繼承自 IBinder 接口的(其實還有一個 ApplicationTest.NullBinder,這個子類什麼都沒實現,應該是使用 NullPattern 來進行相關的單元測試的類),上面我們提到的 Binder Client 通過 ServiceManager 獲取到的 IBinder 對象其實是 BinderProxy 對象,我們來跟蹤一下 ServiceManager 的 getService 方法,上面貼出的 ServiceManager 的源碼中,我們主要看的是 getIServiceManager 函數:

private static IServiceManager getIServiceManager() {
    if (sServiceManager != null) {
        return sServiceManager;
    }

    // Find the service manager
    sServiceManager = ServiceManagerNative.asInterface(BinderInternal.getContextObject());
    return sServiceManager;
}

asInterface 函數的作用我在博客java/android 設計模式學習筆記(9)—代理模式中已經介紹到了,這裏就不介紹了,簡單來說,跨進程通信當中,asInterface 方法會將一個 IBinder 對象轉換成 Proxy 對象,所以我們繼續跟蹤一下 BinderInternal.getContextObject() 這句話,看看這個 getContextObject 這個函數:

/**
 * Return the global "context object" of the system.  This is usually
 * an implementation of IServiceManager, which you can use to find
 * other services.
 */
public static final native IBinder getContextObject();

是一個 native 的函數,我們接着根據名字 android_os_BinderInternal_getContextObject 去全局搜索,最終實現是在 android_util_Binder.cpp 文件中,我們看一下這個函數:

static jobject android_os_BinderInternal_getContextObject(JNIEnv* env, jobject clazz)
{
    sp<IBinder> b = ProcessState::self()->getContextObject(NULL);
    return javaObjectForIBinder(env, b);
}
......
jobject javaObjectForIBinder(JNIEnv* env, const sp<IBinder>& val)
{
    if (val == NULL) return NULL;

    if (val->checkSubclass(&gBinderOffsets)) {
        // One of our own!
        jobject object = static_cast<JavaBBinder*>(val.get())->object();
        LOGDEATH("objectForBinder %p: it's our own %p!\n", val.get(), object);
        return object;
    }

    // For the rest of the function we will hold this lock, to serialize
    // looking/creation/destruction of Java proxies for native Binder proxies.
    AutoMutex _l(mProxyLock);

    // Someone else's...  do we know about it?
    jobject object = (jobject)val->findObject(&gBinderProxyOffsets);
    if (object != NULL) {
        jobject res = jniGetReferent(env, object);
        if (res != NULL) {
            ALOGV("objectForBinder %p: found existing %p!\n", val.get(), res);
            return res;
        }
        LOGDEATH("Proxy object %p of IBinder %p no longer in working set!!!", object, val.get());
        android_atomic_dec(&gNumProxyRefs);
        val->detachObject(&gBinderProxyOffsets);
        env->DeleteGlobalRef(object);
    }

    object = env->NewObject(gBinderProxyOffsets.mClass, gBinderProxyOffsets.mConstructor);
    if (object != NULL) {
        LOGDEATH("objectForBinder %p: created new proxy %p !\n", val.get(), object);
        // The proxy holds a reference to the native object.
        env->SetLongField(object, gBinderProxyOffsets.mObject, (jlong)val.get());
        val->incStrong((void*)javaObjectForIBinder);

        // The native object needs to hold a weak reference back to the
        // proxy, so we can retrieve the same proxy if it is still active.
        jobject refObject = env->NewGlobalRef(
                env->GetObjectField(object, gBinderProxyOffsets.mSelf));
        val->attachObject(&gBinderProxyOffsets, refObject,
                jnienv_to_javavm(env), proxy_cleanup);

        // Also remember the death recipients registered on this proxy
        sp<DeathRecipientList> drl = new DeathRecipientList;
        drl->incStrong((void*)javaObjectForIBinder);
        env->SetLongField(object, gBinderProxyOffsets.mOrgue, reinterpret_cast<jlong>(drl.get()));

        // Note that a new object reference has been created.
        android_atomic_inc(&gNumProxyRefs);
        incRefsCreated(env);
    }

    return object;
}

這個函數其實最後返回的是一個 BinderProxy 類的對象,通過 ServiceManager 獲取到的仍然是 BinderProxy 對象,真正的 Binder 類其實是 Stub 根類的父類,這也就是爲什麼上面要說 IBinder 對象,而不是 Binder 對象。
  Binder 的介紹就到此爲止了,畢竟太複雜,需要更加詳細瞭解 Binder 的可以看看老羅的文章:http://blog.csdn.net/luoshengyang/article/details/6642463
或者這篇博客也講的很清楚:http://blog.csdn.net/21cnbao/article/details/8087304

AIDL

  AIDL的全稱是Android Interface definition language,一看就明白,它是一種android內部進程通信接口的描述語言,通過它我們可以定義進程間的通信接口,用處當然就是用來進程間的通信和方法調用了(我在IPC通信上篇中介紹過也可以使用Messenger加上反射機制來進行跨應用的方法調用,但是前提是讓兩個應用在一個進程中,侷限性比AIDL大)。先介紹一下 AIDL 進程間通信的流程:

  1. AIDL接口的創建
  2. AIDL文件中,並不是所有的數據類型都是可以使用的,它支持的數據類型有:
  • 基本數據類型(int,long,char,boolean,double等)
  • String和CharSequence
  • List:只支持ArrayList,而且list中的元素也必須是 AIDL 支持的類型
  • Map:只支持HashMap,裏面的key和value也必須是AIDL支持的類型
  • Parceable:所有實現了 Parceable 接口的對象
  • AIDL:所有的 AIDL 接口本身也可以在 AIDL 文件中使用,所以 IBinder 類型也是支持的。
  • 服務端
  • 服務端首先要創建一個 Service 用來監聽客戶端的請求,然後將在對應AIDL文件中聲明的接口實現,並且通過onbind函數返回相應 IBinder 對象即可。
  • 客戶端
  • 客戶端所要做的事情就稍微簡單一些,首先需要綁定服務端的Service,綁定成功後,將服務端返回的 IBinder 對象轉成AIDL接口所屬的類型,接着就可以調用AIDL中的方法了。  更多內容也可以去看google文檔,介紹完流程之後,緊接着來介紹一下demo中的相關代碼。

    aidl文件

      第一步創建幾個相關的AIDL文件,特別需要注意的是在AS中,先要在app_name/src/main/文件夾下創建一個aidl文件夾,接下來在該文件夾下去創建相關的package用來放置這些AIDL文件,基本結構如下圖所示:
      這裏寫圖片描述
    不這麼做是無法使用的。接着我們就來仔細分析這幾個AIDL文件:

    // IWeatherManager.aidl
    package com.android.aidl;
    import com.android.aidl.Weather;
    import com.android.aidl.listener.IWeatherChangeListener;
    
    interface IWeatherManager {
        List<Weather> getWeather();
        void addWeather(in Weather weather);
        void addListener(in IWeatherChangeListener listener);
        void removeListener(in IWeatherChangeListener listener);
    }

      這個IWeatherManager.aidl文件是連接客戶端和服務端的核心文件,我們可以看到這個aidl文件中需要引用兩個類:Weather和IWeatherChangeListener,看看這兩個aidl文件的代碼:

    //Weather.aidl
    package com.android.aidl;
    parcelable Weather;
    // IWeatherChangeListener.aidl
    package com.android.aidl.listener;
    import com.android.aidl.Weather;
    
    interface IWeatherChangeListener {
        void onWeatherChange(in Weather newWeather);
    }

      詳細介紹一下這幾個文件的要點:第一點是如果AIDL文件中用到了自定義的Parcelable對象,那麼必須新建一個和它同名的AIDL文件,並在其中聲明它爲Parcelable類型。在IWeatherManager.aidl文件中用到了Weather這個Parcelable類,所以我們必須要創建Weather.aidl文件,要不然只有一個Weather.java文件是無法識別的,並且非常重要的是Weather.aidl和Weather.java兩個文件的包名必須要一致,比如demo中的都爲com.android.aidl,不一致也會導致Weather類無法識別;第二點是AIDL中除了基本數據類型,其他類型的參數必須標上方向:in,out或者inout, in表示輸入型參數,out表示輸出型參數,inout表示輸入輸出型參數。我們要根據實際需要去指定參數類型,不能一概使用out或者inout,因爲這在底層實現是有開銷的;第三點是AIDL接口中只支持方法,不支持聲明靜態常量,這一點區別於傳統的接口。
      在這個demo中,我們仍然是在一個應用中創建兩個進程進行通信,和在兩個應用中的兩個進程之間進行通信是很類似的,差異方面就以這個demo來說第一個需要在兩個應用中的app_name/src/main/文件夾下都創建一個aidl文件夾,然後將三個aidl文件整體拷貝進來,當然要保證兩個應用的package名字com.android.aidl一樣;第二個還有Weather.java文件也必須在兩個應用中的com.android.aidl(就是要和Weather.aidl的package名字一致)包下面,做到這兩點就可以了。一個工程和兩個工程的多進程本質是一樣的,有興趣的可以自己試試。

    java文件

    Parcelable實體類

      我們來看看demo中Weather.java類的代碼:

    public class Weather implements Parcelable{
        public String cityName;
        public double temperature;
        public double humidity;
        public AllWeather weather;
    
        protected Weather(Parcel in) {
            temperature = in.readDouble();
            humidity = in.readDouble();
            //使用該方式來寫入枚舉
            weather = AllWeather.values()[in.readInt()];
            cityName = in.readString();
        }
    
        public Weather() {
    
        }
    
        public static final Creator<Weather> CREATOR = new Creator<Weather>() {
            @Override
            public Weather createFromParcel(Parcel in) {
                return new Weather(in);
            }
    
            @Override
            public Weather[] newArray(int size) {
                return new Weather[size];
            }
        };
    
        @Override
        public int describeContents() {
            return 0;
        }
    
        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeDouble(temperature);
            dest.writeDouble(humidity);
            dest.writeInt(weather.ordinal());
            dest.writeString(cityName);
        }
    
        public enum AllWeather{
            sunny,cloudy,rain,snowy
        }
    }

      代碼很簡單,就是實現Parcelable接口即可,唯一的難點就是enum枚舉在多客戶端之間的處理了,處理方法就是使用ordinal()函數將枚舉轉換成int類型,通着這個int值,就可以找到枚舉變量在枚舉類中的位置,也就可以知道原始值了。

    服務端

      接着就是服務端的代碼實現了:

    public class WeatherManagerService extends Service{
    
        //支持併發讀寫的list
        public CopyOnWriteArrayList<Weather> weathers = new CopyOnWriteArrayList<>();
        public RemoteCallbackList<IWeatherChangeListener> listeners = new RemoteCallbackList<>();
    
        @Override
        public void onCreate() {
            super.onCreate();
            Weather nanshan = new Weather();
            nanshan.cityName = "南山";
            nanshan.temperature = 20.5;
            nanshan.humidity = 45;
            nanshan.weather = Weather.AllWeather.cloudy;
    
            Weather futian = new Weather();
            futian.cityName = "福田";
            futian.temperature = 21.5;
            futian.humidity = 48;
            futian.weather = Weather.AllWeather.rain;
    
            weathers.add(nanshan);
            weathers.add(futian);
        }
    
        private Binder mBinder = new IWeatherManager.Stub() {
            @Override
            public List<Weather> getWeather() throws RemoteException {
                L.i("server returns all of the weathers");
                return weathers;
            }
    
            @Override
            public void addWeather(Weather weather) throws RemoteException {
                weathers.add(weather);
                L.i("server add new Weather:" + weather.cityName);
    
                int N = listeners.beginBroadcast();
                for (int i=0; i<N; i++){
                    IWeatherChangeListener listener = listeners.getBroadcastItem(i);
                    listener.onWeatherChange(weather);
                }
                L.i("server notify the listener that weathers have been changed");
                listeners.finishBroadcast();
            }
    
            @Override
            public void addListener(IWeatherChangeListener listener) throws RemoteException {
                L.i("server adding listener");
                listeners.register(listener);
            }
    
            @Override
            public void removeListener(IWeatherChangeListener listener) throws RemoteException {
                L.i("server removing listener");
                listeners.unregister(listener);
            }
    
            @Override
            public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
                int permission = checkCallingPermission("com.android.permission.WRITEWEATHERPERMISSION");
                //檢測客戶端是否聲明權限
                if (permission == PackageManager.PERMISSION_DENIED){
                    L.e("permission denied");
                    return false;
                }
                L.i("permission granted");
    
                String[] packages = getPackageManager().getPackagesForUid(getCallingUid());
                if (packages != null && packages.length > 0){
                    String packageName = packages[0];
                    if (!packageName.startsWith("com.android")){
                        L.e("package name not accept");
                        return false;
                    }
                    L.i("package name accept");
                }
                return super.onTransact(code, data, reply, flags);
            }
        };
    
        @Override
        public IBinder onBind(Intent intent) {
    //        int permission = checkCallingPermission("com.android.permission.WRITEWEATHERPERMISSION");
    //        //檢測客戶端是否聲明權限
    //        if (permission == PackageManager.PERMISSION_DENIED){
    //            L.e("permission denied");
    //            return null;
    //        }
            return mBinder;
        }
    }

      服務端的實現比較複雜,我們一步步來分析:

    1. 服務端當然是個 Service,在該 Service 中我們需要新建一個 IBinder 對象,這個 IBinder 對象是一個由 IWeatherManager.aidl 生成的 IWeatherManager 接口中的內部 Stub 類的對象,該對象需要實現 4 個接口中的方法。然後在 onBind 函數返回這個 IBinder 對象即可。
    2. 爲了支持多進程的併發讀寫,我們需要使用CopyOnWriteArrayList而不是普通list,類似的還有ConcurrentHashMap。
    3. 如果需要爲這個Service增加訪問的權限,有三個方法來實現:
      • 先使用permission標籤定義一個permission(詳情看博客 android permission權限與安全機制解析(上)),然後在manifest文件中的服務端 service 標籤中添加 android:permission=”yourPermissionName” 即可。
      • 同樣的先聲明一個permission,接着在Service的onBind函數中,通過 checkCallingPermission 函數檢測調用者是否使用了該聲明的權限,如果沒有就直接返回null。
      • 在onTransact函數中進行檢測,和onBind中的檢測一樣,不通過返回 false,而且在該函數中還可以檢測調用者的 package name,在 demo 中如果調用者應用的包名不是以com.android開頭,就會拒絕訪問。簡單介紹一下onTransact函數,該函數運行在服務端中的 Binder 線程池中,當客戶端發起跨進程請求時,遠程請求會通過系統底層封裝後交由此方法來處理,如果此方法返回false,那麼客戶端的請求會失敗,所以在這個方法中檢測權限也是可以的。
    4. 如何爲服務端添加監聽器?我們知道客戶端的listener對象經過parcelable之後到服務端的對象並不是同一個對象,所以如果客戶端想要解註冊一個listener,調用服務端removeListener函數並傳入一個listener參數,但是這個listener對象經過parcelable之後並不是原來的那個對象,服務端無法處理,所以爲了應對這種情況,系統專門提供了用於跨進程刪除listener的接口RemoteCallbackList。RemoteCallbackList是一個泛型,因爲繼承自IInterface接口,所以支持管理任意的AIDL接口。爲什麼RemoteCallbackList類就可以識別parcelable之後的對象呢?先來看看RemoteCallbackList的實現,在它的內部有一個Map結構專門用來保存所有的AIDL回調,這個 Map 的 key 是 IBinder 類型,value是Callback類型:

    ArrayMap<IBinder, Callback> mCallbacks = new ArrayMap<IBinder, Callback>()

    其中Callback中封裝了真正的遠程listener。當客戶端註冊listener的時候,他會把這個listener的信息存入mCallbacks中,其中的key和value分別通過下面的方式獲得:

    IBinder binder = callback.asBinder();
    Callback cb = new Callback(callback, cookie);

    雖然說多次跨進程傳輸客戶端的同一個對象在服務端生成不同的對象,但是這些新生成的對象有一個共同點,那就是它們底層的 IBinder 對象是同一個,所以使用RemoteCallbackList就能夠成功的刪除指定listener。

    客戶端

      看看客戶端的代碼:

    public class ClientActivity extends BaseActivity implements View.OnClickListener{
    
        private ServiceConnection serviceConnection = null;
        private IBinder.DeathRecipient deathRecipient = null;
        private IWeatherChangeListener listener = null;
        private IWeatherManager weatherManager;
        private TextView tv_content;
        private TextView tv_add;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_client);
            findViewById(R.id.btn_add).setOnClickListener(this);
            findViewById(R.id.btn_query).setOnClickListener(this);
            findViewById(R.id.btn_remove_listener).setOnClickListener(this);
            tv_content = (TextView) findViewById(R.id.tv_content);
            tv_add = (TextView) findViewById(R.id.tv_add);
            listener = new IWeatherChangeListener.Stub(){
    
                @Override
                public void onWeatherChange(Weather newWeather) throws RemoteException {
                    L.i("client has been notified that "+newWeather.cityName+" has been added");
                    tv_add.setText(newWeather.cityName + "has been added");
                }
            };
            serviceConnection = new ServiceConnection() {
                @Override
                public void onServiceConnected(ComponentName name, IBinder service) {
                    weatherManager = IWeatherManager.Stub.asInterface(service);
                    try {
                        weatherManager.asBinder().linkToDeath(deathRecipient, 0);
                        weatherManager.addListener(listener);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
    
                @Override
                public void onServiceDisconnected(ComponentName name) {
                    weatherManager = null;
                }
            };
    
            deathRecipient = new IBinder.DeathRecipient() {
                @Override
                public void binderDied() {
                    //移出之前的死亡容器
                    weatherManager.asBinder().unlinkToDeath(deathRecipient, 0);
                    weatherManager = null;
    
                    //重新連接
                    bindServer();
                }
            };
            bindServer();
        }
    
        private void bindServer(){
            Intent intent = new Intent(this, WeatherManagerService.class);
            bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
        }
    
        @Override
        public void onClick(View v) {
            if (v.getId() == R.id.btn_query){
                try {
                    //調用遠程服務端接口時,客戶端進程會掛起,勿在主線程中調用耗時遠程操作
                    L.i("client is getting weather");
                    List<Weather> weathers = weatherManager.getWeather();
                    L.i("client has gotten weather");
                    StringBuilder sb = new StringBuilder();
                    for (Weather weather : weathers){
                        sb.append(weather.cityName).append("\n");
                        sb.append("humidity:").append(weather.humidity)
                            .append("temperature").append(weather.temperature)
                            .append("weather").append(weather.weather).append("\n");
                    }
                    tv_content.setText(sb);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }else if (v.getId() == R.id.btn_add){
                Weather weather = new Weather();
                weather.weather = Weather.AllWeather.cloudy;
                weather.humidity = 25.5;
                weather.temperature = 19.5;
                weather.cityName = "羅湖";
                try {
                    //調用遠程服務端接口時,客戶端進程會掛起,勿在主線程中調用耗時遠程操作
                    L.i("client is adding weather " + weather.cityName);
                    weatherManager.addWeather(weather);
                    L.i("client has added weather " + weather.cityName);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }else if (v.getId() == R.id.btn_remove_listener){
                try {
                    weatherManager.removeListener(listener);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            unbindService(serviceConnection);
            try {
                weatherManager.asBinder().linkToDeath(deathRecipient, 0);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

      客戶端邏輯很簡單,bindService綁定服務端之後,將服務端傳過來的 IBinder 對象通過 asInterface 方法轉換成 AIDL 接口,然後就能通過這個接口去調用服務端的遠程方法了。而且在客戶端還能夠註冊死亡代理,新建一個 DeathRecipient 對象,並且使用Binder的linkToDeath註冊該對象,當 Binder 死亡時,我們就會收到通知,unlinkToDeath 函數可以解註冊該死亡代理。
      還有非常重要的幾點需要說明:客戶端調用服務端方法,被調用的方法運行在服務端的 Binder 線程池中,同時客戶端線程會被掛起,這個時候如果服務端方法執行比較耗時,就會導致客戶端線程長時間阻塞,而如果這個客戶端線程是UI線程的話,就會導致客戶端 ANR ,所以如果知道服務端的一個方法是耗時的,就要避免在客戶端的UI線程中去調用該遠程方法。由於 onServiceConnected 和 onServiceDisconnected 方法都運行在 UI 線程中,所以也不可以在這兩個函數中調用耗時方法。另外,由於服務端的方法本身就運行在服務端的 Binder 線程池中,所以服務端方法可以執行大量的耗時操作,這個時候切記不要在服務端方法中開線程去進行異步任務,除非你明確知道自己在幹什麼,否則不建議這麼做。但是有一種方法可以發起非阻塞式的遠程調用,Messager 就是採用的非阻塞式的方式通訊,其關鍵就在於 IMessager.aidl 的實現:
    這裏寫圖片描述
    相比平常自定義的 aidl,多了 oneway 的關鍵字,聲明和不聲明 oneway 關鍵字的在於生成 Java 類中一個參數:
    這裏寫圖片描述
    這裏寫圖片描述
    不聲明 oneway 時,mRemote.transact 傳入的最後一個參數是 0;聲明 oneway 時,mRemote.transact 傳入的最後一個參數是 android.os.IBinder.FLAG_ONEWAY 。
    這裏寫圖片描述
    這裏寫圖片描述
    查看 API 文檔即可以看到 FLAG_ONEWAY 的作用就是讓客戶端能夠非阻塞的調用遠程方法,至此真相大白,如果我們自定義的 aidl 也想實現非阻塞的調用,只需聲明 oneway 關鍵字即可。
      關於服務端和客戶端的方法分別執行在那個進程和線程中以及它們執行的先後順序,我們先來看看log日誌的輸出:

    I/[PID:28533](28533): [TID:7035] 1.onTransact(line:90): permission granted
    I/[PID:28533](28533): [TID:7035] 1.onTransact(line:99): package name accept
    I/[PID:28533](28533): [TID:7035] 1.addListener(line:72): server adding listener
    
    //***client add click
    I/[PID:28502](28502): [TID:1] ClientActivity.onClick(line:115): client is adding weather 羅湖
    I/[PID:28533](28533): [TID:7036] 1.onTransact(line:90): permission granted
    I/[PID:28533](28533): [TID:7036] 1.onTransact(line:99): package name accept
    I/[PID:28533](28533): [TID:7036] 1.addWeather(line:59): server add new Weather:羅湖
    I/[PID:28502](28502): [TID:1] 1.onWeatherChange(line:47): client has been notified that 羅湖 has been added
    I/[PID:28533](28533): [TID:7036] 1.addWeather(line:66): server has notified the listener that weathers have been changed
    I/[PID:28502](28502): [TID:1] ClientActivity.onClick(line:117): client has added weather 羅湖
    
    //***client remove listener click
    I/[PID:28533](28533): [TID:7035] 1.onTransact(line:90): permission granted
    I/[PID:28533](28533): [TID:7035] 1.onTransact(line:99): package name accept
    I/[PID:28533](28533): [TID:7035] 1.removeListener(line:78): server removing listener
    
    //***client get click
    I/[PID:28502](28502): [TID:1] ClientActivity.onClick(line:93): client is getting weather
    I/[PID:28533](28533): [TID:7036] 1.onTransact(line:90): permission granted
    I/[PID:28533](28533): [TID:7036] 1.onTransact(line:99): package name accept
    I/[PID:28533](28533): [TID:7036] 1.getWeather(line:52): server returns all of the weathers
    I/[PID:28502](28502): [TID:1] ClientActivity.onClick(line:95): client has gotten weather

      PID:28502爲客戶端進程,PID:28533爲服務端進程,TID:1爲UI主線程,TID:7036爲 Binder 線程。看看log打印的順序基本就能夠明白方法的執行進程,線程和客戶端的阻塞情況了。
      源碼下載:https://github.com/zhaozepeng/IPC-demo/tree/master/AIDL

    BinderPool

      上面差不多就把AIDL的用法詳細介紹完了,但是有的時候我們可能需要不止一個業務模塊,也就是不單單需要一個天氣模塊,我們還需要一個計算溫度平均值的模塊(雖然可以寫在一個模塊中,但是我們還是假設要用兩個模塊吧~),是不是需要爲每個模塊都單獨建立一個Service呢?當然不是,會很耗資源的好嗎,解決方法就是先爲每一個模塊建立一個單獨的aidl文件,最後再建立一個整體的aidl文件用來管理這些單獨的aidl。
      看看這三個文件,IWeatherManager.aidl,IComputerManager.aidl和IBinderPoolManager.aidl:

    // IWeatherManager.aidl
    package com.android.binderpool;
    import com.android.binderpool.Weather;
    
    interface IWeatherManager {
        List<Weather> getWeather();
        void addWeather(in Weather weather);
    }
    // IComputerManager.aidl
    package com.android.binderpool;
    import com.android.binderpool.Weather;
    
    interface IComputerManager {
        double computeAverageTemperature(in List<Weather> weathers);
    }
    // IBinderPoolManager.aidl
    package com.android.binderpool;
    
    interface IBinderPoolManager {
        IBinder queryCode(int code);
    }

    IBinderPoolManager.aidl文件用來統一管理所有的AIDL接口,queryCode函數通過code值來確定需要返回給客戶端的IBinder對象。
      來看看服務端的代碼的變動:

    public class BinderPoolService extends Service{
        public static final int CODE_WEATHER = 1;
        public static final int CODE_COMPUTER = 2;
    
        private IBinderPoolManager iBinderPoolManager;
    
        //支持併發讀寫的list
        public CopyOnWriteArrayList<Weather> weathers = new CopyOnWriteArrayList<>();
    
        @Override
        public void onCreate() {
            super.onCreate();
            Weather nanshan = new Weather();
            nanshan.cityName = "南山";
            nanshan.temperature = 20.5;
            nanshan.humidity = 45;
            nanshan.weather = Weather.AllWeather.cloudy;
    
            Weather futian = new Weather();
            futian.cityName = "福田";
            futian.temperature = 21.5;
            futian.humidity = 48;
            futian.weather = Weather.AllWeather.rain;
    
            weathers.add(nanshan);
            weathers.add(futian);
            iBinderPoolManager = new IBinderPoolManager.Stub(){
                @Override
                public IBinder queryCode(int code) throws RemoteException {
                    switch (code){
                        case CODE_WEATHER:
                            return new IWeatherManager.Stub(){
    
                                @Override
                                public List<Weather> getWeather() throws RemoteException {
                                    return weathers;
                                }
    
                                @Override
                                public void addWeather(Weather weather) throws RemoteException {
                                    weathers.add(weather);
                                }
                            };
                        case CODE_COMPUTER:
                            return new IComputerManager.Stub() {
                                @Override
                                public double computeAverageTemperature(List<Weather> weathers) throws RemoteException {
                                    double sum = 0;
                                    for (int i=0; i<weathers.size(); i++){
                                        sum += weathers.get(i).temperature;
                                    }
                                    return sum/weathers.size();
                                }
                            };
                        default:
                            return null;
                    }
                }
            };
        }
    
        @Override
        public IBinder onBind(Intent intent) {
            return iBinderPoolManager.asBinder();
        }
    }

      根據code的不同返回不同的 IBinder 對象,這樣在客戶端中就能夠獲取對應 AIDL 接口的 IBinder 對象,最終就能在客戶端調用不同 AIDL 模塊中的方法。客戶端代碼很簡單,在這裏就不介紹了,感興趣的可以去看看源碼:
      https://github.com/zhaozepeng/IPC-demo/tree/master/BinderPool
      關於IPC相關知識的介紹就到這了,如果有什麼疑問,大家可以多多交流啊,謝謝~

    發佈了92 篇原創文章 · 獲贊 412 · 訪問量 70萬+
    發表評論
    所有評論
    還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
    相關文章