Android程式間通訊(複習筆記)

chenToV發表於2018-03-22

1、程式和執行緒的概念:

在我的理解中,程式是一段被作業系統執行的指令集,作業系統在對資源>進行分配和排程時,程式是基本單位,程式其實就是一個程式。而執行緒是作業系統排程的最小單元。程式可以包含多個執行緒,多個執行緒可共享程式中的資源,另外執行緒的建立代價比執行緒要小

2、多程式的應用場景:

通常需要用到多程式的是,有常駐後臺的應用,例如音樂播放器的後臺播放服務,健身跑步的計算步數和跑步路徑模組等,這些模組需要脫離介面執行,而且需要長時間的在系統中執行,所以放在獨立的程式中執行是比較好的選擇。另外一種就是,如果一些應用很大,需要拆分模組,為了增加應用的記憶體用量,就需要開啟多個程式來擴大應用的記憶體用量,同時,將應用模組化,也更有利於解耦

3、實現多程式的方式:

在android中實現多程式有兩種方式,一種是在AndroidManifest檔案中給元件設定android:process屬性,這樣,這個元件就執行在設定的程式中了,另外一種就是利用jni在native層fork一個新的程式出來

4、android:process屬性的設定方式:

設定方式有兩種,一種是以“:xxx”的方式設定,這種方式代表該程式是應用的私有程式,其他應用的元件不可在該程式執行,另外一種就是以“xxx.xx.xx”類似包名的方式來設定,這種方式設定的程式是全域性的程式,其他應用的元件也可以在該程式上面執行,但是shareUID必須一樣才可以

5、在多程式開發中會遇到的問題:

1、靜態成員和單例模式完全失效:因為系統會為每個程式分配獨立的虛擬機器,不同的虛擬機器會有不同的記憶體空間,這會導致不同的虛擬機器訪問同一個類的物件會產生多個副本,我們修改其中一個副本只會影響當前程式,因此,靜態成員和單例模式就會完全失效

2、執行緒同步機制會完全失效:既然是不同的記憶體空間,那鎖的就不是同一個物件,因此無法保證執行緒同步

3、Sharedpreferences的可靠性下降:因為SharedPreferences不支援兩個程式同時執行寫操作

3、Application會被多次建立:不同的程式有不同的虛擬機器,當啟動一個新的程式時,相當於一個應用的啟動過程,那麼當然,就會建立一個新的Application

6、使用Serializable時需要注意的問題:

在實現Serializable的過程中,雖然serialVersionUID不寫是可以,但是這樣可能會造成在反序列化的過程失敗,因為這個serialVersionUID在序列化的過程中會被儲存進去,在反序列化的時候會先比較物件中的serialVersionUID是否與類中的serialVersionUID相同,如果在類中沒有設定這個值,那麼系統會自動計算hash值,而如果這個類的結構發生了改變,那麼這個自動計算的hash值就會重新計算,就會造成反序列化的失敗。因此,設定這個值的意義在於儘量讓反序列化可以成功,就算有時候這個類已經發生了部分改變。另外一個就是靜態成員不參與序列化,使用transient關鍵字標記的成員不參與序列化

7、Serializable與Parcelable的區別:

Parcelable是為了Android平臺而定製的,它的操作稍微複雜,但是效率很高,因此在記憶體的序列化上推薦使用Parcelable,而Serializable因為在序列化時需要進行大量的IO操作,因此雖然操作簡單,但是開銷很大,因此,在將物件序列化到儲存裝置中或者在網路中傳輸就推薦使用Serializable

8、實現多程式間通訊的方式

實現多程式間的通訊有很多種方式,1、利用Bundle進行資料的傳遞,它簡單易用,但是隻支援Bundle支援的資料型別,推薦在四大元件的程式通訊可以使用它。2、檔案共享,這種方式也比較簡單易用,但是這種方式不適合多併發的場景,而且無法進行程式間的即時通訊。3、Messenger。這種方式的底層是利用AIDL實現的,支援一對多的序列通訊,支援低併發的即時通訊。3、ContentProvider,ContentProvider的底層利用Binder實現,天生支援跨程式通訊,但是它只適合用來作為資料共享。4、Socket,這種方式通過網路傳輸位元組流也可以實現跨程式通訊,但實現稍微負責,適合網路間的資料交換。5、AIDL,最後一種,也是最常用的一種方式,功能強大,下面會詳細介紹

9、AIDL的一些理解

AIDL(Android Interface Difinition Language)是一種Android種獨有的定義語言,它的主要作用是為了簡化跨程式通訊程式碼的編寫,在編譯階段,我們編寫的AIDL檔案會生成相應的跨程式程式碼,簡單來說,它就是一種簡化的工具,沒有AIDL,我們一樣可以編寫出跨程式程式碼,只是會稍微繁瑣一點

AIDL只會接受基本的資料型別(通過實驗short還不行),String和CharSequence型別,然後如果是自定義的物件,需要實現Parcelable介面,並且需要在AIDL檔案中宣告出來,這樣才可以使用,當然也可以用集合(ArrayList,HashMap)作為引數,但是集合中的元素也必須是AIDL支援的型別。另外需要注意的就是AIDL檔案中的方法引數,是有一個資料流向的,通過in,out,inout三個標識來確定,如果引數設定為in,那麼資料的流向就是客戶端->伺服器(意思就是客戶端傳遞給伺服器的資訊,伺服器可以收到,但是伺服器如果改動了這個傳遞的物件,客戶端中的原物件是不會跟著發生改變的),如果引數設定為out,那麼資料的流向就是服務端->客戶端(意思就是客戶端傳遞給服務端的引數,服務端是無法收到的,但是服務端改動了這個物件的內容,在客戶端的原物件是會跟著改變的),如果引數設定為inout,那就代表著雙向流動,但是一般情況不提倡這樣設定,因為會增加開銷

10、使用AIDL需要注意的地方:

在使用AIDL的過程中,會有一種情形,就是當服務端處理了一些業務,需要主動通知客戶端,而不是等待客戶端發起請求才去響應,那麼這個時候就需要觀察者模式了,這個時候我們需要註冊監聽服務端的狀態,這個時候服務端需要儲存客戶端的監聽,因此,可能會有併發的情形,因此推薦使用RemoteCallbackList進行儲存。另外就是有時候服務端程式被kill掉,這時候會造成連結失敗,因此我們需要通過linkToDeath來繫結服務端,如果服務端程式殺死,客戶端可以收到響應,可以進行重連操作。

11、AIDL的實際操作:

在實際開發過程中,我們如果有很多模組都需要用到AIDL的話,那麼不可能每個模組開啟一個服務程式來處理,這樣造成的系統資源損耗是巨大的,因此我們需要利用Binder連線池來作為中間媒介,來連結各個AIDL的處理,下面的例子來演示相關實現

11.1 第一步

首先,建立兩個AIDL檔案IPlayMedia.aidl和IMonitorDevice.aidl,程式碼如下:

interface IPlayMedia {
    void play(in String path);
    void puase();
    void stop();
    String getCurrentMusicName();
}

interface IMonitorDevice {
    void monitor(int a);
}
複製程式碼

11.2 第二步

實現AIDL介面,程式碼如下:

// IPlayMedia介面實現
public class PlayMediaImpl extends IPlayMedia.Stub{

    @Override
    public void play(String path) throws RemoteException {
        Log.e("TAG","播放音樂:"+path);
    }

    @Override
    public void puase() throws RemoteException {
        Log.e("TAG","暫停播放");
    }

    @Override
    public void stop() throws RemoteException {
        Log.e("TAG","停止播放");
    }

    @Override
    public String getCurrentMusicName() throws RemoteException {
        Log.e("TAG","獲取當前歌曲名稱");
        return "七里香";
    }
}

// IMonitorDevice的實現
public class MonitorDeviceImpl extends IMonitorDevice.Stub {

    @Override
    public void monitor(int a) throws RemoteException {
        Log.e("TAG","監控方法實現");
    }
}
複製程式碼

11.3 第三步

接下來需要建立一個BinderPool連線池AIDL介面,這個介面是直接與服務端程式的服務互動的

interface BinderPool {
    IBinder queryBinder(int binderCode);
}
複製程式碼

11.4 第四步

接下來建立一個服務AIDLService,設定android:process屬性,讓其可以在獨立的程式執行,先不做任何的實現,接著,建立BinderPoolUtils工具類,程式碼如下:

public class BinderPoolUtils {

    public static final int BINDER_PLAY_MEDIA = 1;
    public static final int BINDER_MONITOR_DEVICE = 2;
    public static volatile BinderPoolUtils mInstance = null;

    private Context mContext;
    private test.com.testpoj.BinderPool mBinderPool;
    private CountDownLatch mConnectBinderPoolDownLatch;

    /**
     * 單例模式
     */
    public static BinderPoolUtils getInstance(Context context) {
        if (mInstance == null) {
            synchronized (BinderPoolUtils.class) {
                if (mInstance == null) {
                    mInstance = new BinderPoolUtils(context);
                }
            }
        }
        return mInstance;
    }

    /**
     * 查詢對應的Binder物件
     */
    public IBinder queryBinder(int binderCode) {
        IBinder binder = null;
        try {
            if (mBinderPool != null) {
                binder = mBinderPool.queryBinder(binderCode);
            }
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        return binder;
    }

    /**
     * 構造方法私有化
     */
    private BinderPoolUtils(Context context) {
        mContext = context;
        connectBinderPoolService();
    }

    /**
     * 繫結服務
     */
    private synchronized void connectBinderPoolService() {
        mConnectBinderPoolDownLatch = new CountDownLatch(1);
        Intent intent = new Intent(mContext, AIDLService.class);
        mContext.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
        try {
            mConnectBinderPoolDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mBinderPool = test.com.testpoj.BinderPool.Stub.asInterface(service);
            try {
                mBinderPool.asBinder().linkToDeath(mBinderPoolDeathRecipient, 0);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            mConnectBinderPoolDownLatch.countDown();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    private IBinder.DeathRecipient mBinderPoolDeathRecipient = new IBinder.DeathRecipient() {
        @Override
        public void binderDied() {
            mBinderPool.asBinder().unlinkToDeath(mBinderPoolDeathRecipient, 0);
            mBinderPool = null;
            connectBinderPoolService();
        }
    };

    /**
     * BinderPool連結池的實現
     */
    public static class BinderPoolImpl extends test.com.testpoj.BinderPool.Stub {

        @Override
        public IBinder queryBinder(int binderCode) throws RemoteException {
            IBinder binder = null;
            switch (binderCode) {
                case BINDER_PLAY_MEDIA:
                    binder = new PlayMediaImpl();
                    break;
                case BINDER_MONITOR_DEVICE:
                    binder = new MonitorDeviceImpl();
                    break;
                default:
                    break;
            }
            return binder;
        }
    }
}
複製程式碼

有了上面的工具類,我們就可以在AIDLService中的onBinder方法中返回BinderPoolImpl的實現,然後我們呼叫的時候就可以這樣來呼叫:

  BinderPoolUtils utils = BinderPoolUtils.getInstance(this);
  IBinder playBinder = utils.queryBinder(BinderPoolUtils.BINDER_PLAY_MEDIA)
  mPlayManager = IPlayMedia.Stub.asInterface(playBinder);
複製程式碼

這樣我們就可以呼叫IPlayMedia介面中的方法了

相關文章