Android跨進程通信IPC之14——其他IPC方式

Android跨進程通信IPC整體內容如下

本片文章的主要內容如下:

1 Bundle
2 文件共享
3 Messager
4 ContentProvider
5 Socket
6 AIDL
7 廣播
8 Binder連接池
9 符合選擇合適的IPC方式

前面幾篇文章,我們介紹了IPC的基礎知識和Binder機制,本篇文章主要講解各種跨進程的通信方式。

一、Bundle

(一)、IPC中的Bundle

我們知道,四大組件中三大組件(Activity,Service,Receiver)都是支持在Intent中傳遞Bundle數據的,由于Bundle中實現了Parcelable接口,所以它方便地在不同的進程間傳輸。基于這一點,當我們在一個進程中啟動了另一個進程的Activity、Service和Receiver,我們就可以在Bundle中附加我們需要傳輸給遠程進程的信息并通過Intent發送出去。當然,我們傳輸的數據必須能夠序列化,比如基本類型、實現了Parcelable接口的對象、實現了Serializable接口的對象以及一些Android支持的特殊對象,具體內容可以看Bundle這個類,就是可以看到所有它支持的類型。Bundle不支持的類型我們無法通過他在進程間傳遞,這是最簡單的進程間通信方式。

除了直接傳遞數據這種典型的使用場景,它還有一種特殊的使用場景。比如進程A正在進行一個計算,計算完成后它要啟動B進程的一個組件并把計算結果傳遞給B進程,可是遺憾的是這個計算結果不支持放入Bundle中,因此無法通過Intent來傳輸,這個時候如果我們用其他IPC方式就會略顯復雜。可以考慮如下方式:我們通過Intent啟動進程B的一個Service組件(比如IntentService),讓Service在后臺進行計算:計算完畢后再啟動B進程中真正要啟動的目標組件,由于Service也運行在B進程中,所以目標組件就可以接獲取計算結果,這樣一來就輕松解決了跨進程的問題。這種方式的核心思想在于將原本需要在A進程的計算任務轉移到B進程的后臺Service中去執行,這樣就成功避免了進程間通信問題,而且只用了很小的代價

(二)、Bundle類簡介

根據google官方文檔 https://developer.android.com/reference/android/os/Bundle.htmlBundle類是一個key-value對,"A mapping from String keys to various Parcelable values."

可以看出,它和Map類型有異曲同工之妙,其實它內部是使用ArrayMap來存儲的,并且實現了Parcelable接口,那么它是支持進程間通信的。所以Bundle可以看做是一個特殊的Map類型,它支持進程間通信,保存了特定的數據。

PS:Bundle繼承自BaseBundle ,而在BaseBundle 中有一個內部變量叫mMap就是ArrayMap<String, Object>類型

(三)、Bundle類的重要方法

  • clear():清楚此Bundle映射中的所有保存的數據
  • clone():克隆當前Bundle
  • containKey(String key):是否包含key
  • getString(String key):返回key對應的String類型的value
  • hasFileDescriptors():指示是否包含任何捆綁打包文件描述
  • isEmpty():判斷是否為空
  • putXxx(String key,Xxx value):插入一個鍵值對
  • writeToParcel(Parcel parcel,int flags),寫入一個parcel
  • readFromParcel(Parcel parcel) 讀取一個parcel
  • remove(String key):移除特定key的值

二、文件共享

共享文件也是一種不錯的進程間通信方式,兩個進程通過讀/寫同一個文件來交換數據,比如A進程把數據寫入文件,B進程通過讀取這個文件來獲取數據。由于Android是基于Linux的,所以并發讀/寫文件可以沒有限制地進行,甚至兩個線程同時對同一份文件進行讀寫操作都是允許的,盡管這可能出現問題。通過文件交換數據很方便使用,除了可以交換一些文本信息外,我們還可以序列化一個對象到文件系統中的同時從另一個進程中恢復這個對象。

PS:反序列化得到的對象只是在內容上和序列化之前的對象是一樣的,但它們本質上還是兩個對象。

通過文件共享這種方式共享數據對文件格式是沒有具體要求的,比如可以是文本文件,也可以是XML文件,只要讀/寫雙方約定數據格式即可。通過文件共享的方式也是有局限的,比如并發讀/寫的問題。如果并發讀/寫,那么我們讀出的內容就有可能不是最新的,如果是并發寫的話,那就更嚴重了。因此我們要盡量避免比規范法寫這種情況發生或者考慮用線程同步來限制多個線程的寫操作。通過上面的分析,我們可以知道,文件共享方式適合在對數據同步要求不高的進程之間進行通信,并且妥善處理并發讀/寫的問題。

說到文件共享又不得不說下SharedPreferences(后面簡稱SP),當然SP是個特例,眾所周知,SP是Android中提供的輕量級存儲方案,它通過鍵值對的方式來存儲數據,在底層實現上它采用XML文件來存儲鍵值對,每個應用的SP文件都可以在當前包所在的data目錄下查看到。一般來說,它的目錄位于/data/data/package name/shared_prefs 目錄下,其中package name表示的是當前應用的包名。從本質上來說,SP也屬于文件的一種,但是由于系統對它的讀/寫有一定緩存策略,即在內存會有一份SP文件的緩存,因此在多進程模式下,系統對它的讀/寫就變的不可靠,當面對高并發的讀/寫訪問,SP有很大幾率會丟失數據,因此,不建議在進程間通信中使用SP。

三、Messenger

(一)、概述

前面Android跨進程通信IPC之11——AIDL講解了AIDL,用于Android進程間的通信。大家知道用編寫AIDL比較麻煩,有沒有比較"好的"AIDL。那我們就來介紹Messenger。

Messenger是一種輕量級的IPC方案,其底層實現原理就是AIDL,它對AIDL做了一次封裝,所以使用方法會比AIDL簡單,由于它的效率比較低,一次只能處理一次請求,所以不存在線程同步的問題。

Messenger的官網地址是https://developer.android.com/reference/android/os/Messenger.html
看下官網描述

Reference to a Handler, which others can use to send messages to it. This allows for the implementation of message-based communication across processes, by creating a Messenger pointing to a Handler in one process, and handing that Messenger to another process.

大概的意思是說,首先Messenger要與一個Handler相關聯,才允許以message為基礎的會話進行跨進程通訊。通訊創建一個messenger指向一個handler在同一個進程內,然后就可以在另一個進程處理messenger了。

這里的重點是

This allows for the implementation of message-based communication across processes

允許實現基于消息的進程間通訊方式。
那么什么是基于消息的進程間通信方式?看圖理解下:

允許實現基于消息的進程間通訊方式.png

可以看到,我們可以在客戶端發送一個Message給服務端,在服務端的handler會接受客戶端的消息,然后進行隊形的處理,處理完成后,在將結果等數據封裝成Message,發送給客戶端,客戶端的handler中會接收到處理的結果。

這樣對比AIDL就方便很多了

  • 基于 Message,詳細大家都容易上手
  • 支持回調的方式,也就是服務端處理完任務可以和客戶端交互
  • 不需要編寫AIDL文件
    此外,還支持,記錄客戶端對象的Messenger,然后可以實現一對多的通信;甚至作為一個轉接處,任意兩個進程都能通過服務端進行通信。

(二)、構造函數

通過我們分析Messenger類的結構,如下圖


Messenger類的結構.png

我們發現Messager有兩個構造函數

1 構造函數1

代碼在Messenger.java) 43行

    /**
     * Create a new Messenger pointing to the given Handler.  Any Message
     * objects sent through this Messenger will appear in the Handler as if
     * {@link Handler#sendMessage(Message) Handler.sendMessage(Message)} had
     * been called directly.
     *
     * @param target The Handler that will receive sent messages.
     */
    public Messenger(Handler target) {
        mTarget = target.getIMessenger();
    }

先翻譯下注釋

傳入一個Handler作為參數創建一個新的Messenger,通過該Messenger發送的任何消息對象將會出現在這個Handler里面,就像Handler直接調用sendMessage(Message)一樣。入參target接收這些已經發送的消息。

1.1 getIMessenger()方法

我們看到這個構造函數很簡單,就是調用了getIMessenger()方法,我們跟蹤一下,代碼在Handler.java 708行

    final IMessenger getIMessenger() {
        synchronized (mQueue) {
            if (mMessenger != null) {
                return mMessenger;
            }
            mMessenger = new MessengerImpl();
            return mMessenger;
        }
    }

這里使用了線程同步的懶漢式單例模式。并且這里面new了一個MessengerImpl()對象,MessengerImpl其實是Handler的內部類

1.2 MessengerImpl類

Handler.java 718行

    private final class MessengerImpl extends IMessenger.Stub {
        public void send(Message msg) {
            msg.sendingUid = Binder.getCallingUid();
            Handler.this.sendMessage(msg);
        }
    }
  • 1、IMessenger,Stub 這樣的結構大家有沒有印象?是不是很想AIDL里面的形式,我們先搜一下IMessenger,發現沒有對應的.java文件,只有一個IMessenger.aidl,所以說Messenger底層是基于AIDL的。
  • 2、當發送消息的時候,調用是Handler.this.sendMessage(msg);,其實就是自己調用的sendMessage(msg)方法而已。
2 構造函數2

代碼在Messenger.java) 145行

   /**
     * Create a Messenger from a raw IBinder, which had previously been
     * retrieved with {@link #getBinder}.
     *
     * @param target The IBinder this Messenger should communicate with.
     */
    public Messenger(IBinder target) {
        mTarget = IMessenger.Stub.asInterface(target);
    }

先來翻譯一下注釋

根據原始的IBinder來創建一個Messenger對象,可以通過getBinder()方法來獲取這個IBinder對象。入參target其實就是這個Messenger與之通信的

這個更簡單了,其中IMessenger.Stub.asInterface(target);這個方法簡直就是AIDL方式里面講到的方法,有沒有?

(三)、其他兩個重要方法

1、getBinder()方法

代碼在Messenger.java66行

    /**
     * Retrieve the IBinder that this Messenger is using to communicate with
     * its associated Handler.
     * 
     * @return Returns the IBinder backing this Messenger.
     */
    public IBinder getBinder() {
        return mTarget.asBinder();
    }

注釋翻譯:返回這個Messenger使用Handler與之通訊的IBinder對象

返回一個IBinder對象,一般在服務端的onBinder()方法里面調用這個方法,返回給客戶端一個IBinder對象。

2、send(Message msg)方法

代碼在Messenger.java56行

    /**
     * Send a Message to this Messenger's Handler.
     * 
     * @param message The Message to send.  Usually retrieved through
     * {@link Message#obtain() Message.obtain()}.
     * 
     * @throws RemoteException Throws DeadObjectException if the target
     * Handler no longer exists.
     */
    public void send(Message message) throws RemoteException {
        mTarget.send(message);
    }

注釋翻譯:發送消息給Messenger的Handler。入參message是要被發送的消息,通常通過Message.obtain()來獲取,如果目標Handler不存在,就拋出RemoteException異常

發送一個message對象到 messagerHandler。這里我們傳遞的參數是一個Message對象

(四)、舉例說明

1 服務端進程

首先我們需要在服務端創建一個Service來處理客戶端的連接請求,同時創建一個Handler并通過它來創建一個Messenger對象,然后在Service的onBinder中返回這個Messenger對象底層的Binder即可。
代碼如下


public class MessageService extends Service {

    private  static class MessaengerHandler extends Handler{
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what){
                case Constants.CLENT_MESSAGE:
                    Log.i("GELAOLITOU","收到消息:"+msg.getData().get(Constants.KEY));
                    break;
                default:
                    break;
            }
        }
    }

    private Messenger messenger=new Messenger(new MessaengerHandler());

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return messenger.getBinder();
    }
}

當然別忘記在AndroidManifest.xml里面注冊

        <service android:name=".MessageService"
                      android:process=".remote" />
2 客戶端進程

客戶端進程中,首先要綁定服務端的Service,綁定成功后用服務端返回的IBinder對象創建一個Messenger,通過這個Messenger就可以向服務端發送消息了,發消息類型為Message對象。如果需要服務端能夠回應客戶端,就和服務端一樣,我們還要創建一個Handler并創建一個新的Messenger,并把這個Messenger對象通過Message的replyTo參數傳遞給服務端,服務端通過這個replyTo參數就可以回應客戶端。這聽起來可能還是有點抽象。那我們就直接上代碼

public class MainActivity extends AppCompatActivity {


    private Messenger mService;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Intent intent=new Intent(this,MessageService.class);
        bindService(intent,connection, Context.BIND_AUTO_CREATE);

    }


    private ServiceConnection connection=new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mService=new Messenger(service);
            //創建一個what值為Constants.CLENT_MESSAGE的message
            Message message=Message.obtain(null,Constants.CLENT_MESSAGE);
            Bundle bundle=new Bundle();
            bundle.putString(Constants.KEY,"我是來自客戶端的信息");
            message.setData(bundle);
            try{
                mService.send(message);
            }catch (Exception e){
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    @Override
    protected void onDestroy() {
        unbindService(connection);
        super.onDestroy();
    }
}

運行app,得出下面的截圖


結果.png

(五)、服務端響應客戶端請求

上面的例子演示了如何在服務端接收客戶端中發送的消息,但是有時候我們還需要能回應客戶端,下面就介紹如何實現這種效果。還是采用上面的例子,但是稍微做一下修改,每當客戶端發送來一條消息,服務端就會回復一條"你的信息我已經收到,現在就回復你"。

1、首先服務端的修改

服務端只修改MessengerHandler,當收到消息后,會立即回復一條消息給客戶端。

public class MessageService extends Service {


    private  static class MessaengerHandler extends Handler{
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what){
                case Constants.CLENT_MESSAGE:
                    Log.i("GELAOLITOU","收到消息:"+msg.getData().get(Constants.KEY));
                    Messenger msgr_client= msg.replyTo;
                    Message mes_reply=Message.obtain(null,Constants.SERVER_MESSAGE);
                    Bundle bundle=new Bundle();
                    bundle.putString(Constants.REPLY_KEY,"你的信息我已經收到,現在就回復你");
                    mes_reply.setData(bundle);

                    try {
                        msgr_client.send(mes_reply);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    break;
                default:
                    break;
            }
        }
    }

    private Messenger messenger=new Messenger(new MessaengerHandler());

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return messenger.getBinder();
    }
}
2、其次客戶端的修改

為了接收服務端的回復,客戶端也需要準備一個接收消息的Messenger和Handler。代碼如下:

public class MainActivity extends AppCompatActivity {


    private Messenger mService;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Intent intent=new Intent(this,MessageService.class);
        bindService(intent,connection, Context.BIND_AUTO_CREATE);

    }


    private ServiceConnection connection=new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mService=new Messenger(service);
            //創建一個what值為Constants.CLENT_MESSAGE的message
            Message message=Message.obtain(null,Constants.CLENT_MESSAGE);
            Bundle bundle=new Bundle();
            bundle.putString(Constants.KEY,"我是來自客戶端的信息");
            message.setData(bundle);
            try{
                mService.send(message);
            }catch (Exception e){
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };


    private Messenger mGetReplyMessenger=new Messenger(new MessengerClientHandler());

    private static class MessengerClientHandler extends Handler{

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case Constants.SERVER_MESSAGE:
                    Log.i("GEBILAOLITOU","收到 服務端的消息,消息內容是"+msg.getData().getString(Constants.REPLY_KEY));
                    break;
            }

        }

    }


    @Override
    protected void onDestroy() {
        unbindService(connection);
        super.onDestroy();
    }
}

除了上述修改,還有很多關鍵的一點,當客戶端發送消息的時候,需要把接收服務端的Messenger通過Message的replyTo參數傳遞給服務端,如下所示:

    private ServiceConnection connection=new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mService=new Messenger(service);
            //創建一個what值為Constants.CLENT_MESSAGE的message
            Message message=Message.obtain(null,Constants.CLENT_MESSAGE);
            Bundle bundle=new Bundle();
            bundle.putString(Constants.KEY,"我是來自客戶端的信息");
            message.setData(bundle);
            
            //新增,這是重點
            message.replyTo=mReplyMessenger;
            try{
                mService.send(message);
            }catch (Exception e){
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

通過上述修改,我們再運行程序,然后看一下log,很顯然,客戶端收到了服務端的回復。請看截圖:

結果截圖.png

(六)、總結

通過上面的例子可以看出,在Messenger中進行數據傳遞必須將數據放入Message中,而Messenger和Message都實現了Parcelable接口,因此可以跨進程傳輸。簡單來說,Message中所支持的數據類型就是Messenger所支持的傳輸類型。實際上,通過Messenger來傳輸Message,Message中只能使用的載體只有what、arg1、arg2、Bunder以及replyTo。Messag中的另一個字段object在同一個進程中是很實用的,但是在進程間通信的時候,在Android2.2以前object字段不支持跨進程傳輸。即便是2.2以后,也僅僅是系統提供的實現了Parcelable接口的對象才能通過它來傳輸。這就意味著我們自定義的Parcelable對象是無法通過object字段來傳輸的,不過所幸我們還有Bundle,Bundle中可以支持大量的數據類型。

下面給出一張Messenger的工作原理圖,以便大家更好地理解Meseenger.


Messenger的工作原理.png

四、ContentProvider

ContentProvider是Android中提供的專門用于不同應用間進行數據共享的方式,從這一點來看,它天生就適合進程間通信。和Messenger一樣,ContentProvider的底層同樣也是Binder,由此可見Binder在Android系統是何等重要。雖然Content Provider的底層實現是Binder,但是它的使用過程要比AIDL簡單許多,這是因為系統已經為我們做了封裝,使得我們無須關心底層細節即可輕松實現IPC。ContentProvider雖然使用起來很簡單,包括自己創建一個ContentProvider也不是什么難事,盡管如此,它的細節還是很多的。比如CRUD操作等。

系統預置了許多ContentProvider,比如通訊錄信息、日程表信息等,要跨進程訪問這些信息,只需要通過ContentResolver的query、update、insert和delete方法即可。受篇幅的限制,我這里就不粘貼ContentProvicder的代碼了,這塊代碼網上很多,大家可以自行去搜索

PS:這里需要注意的是query、update、insert、delete四大方法是存在多線程并發訪問的,因此方法內部要做好線程同步。而大多數android手機上采用的是SQLite,并且只有一個SQLiteDatabase的時候,要正確應對多線程的情況。因為SQLiteDatabase內部對數據庫的操作有同步處理。如果ContentProvider的底層數據是一塊內存的話,比如是List,在這種情況下同List遍歷、插入、刪除操作就需要進行線程同步,否則會一起并發錯誤。這點要特別注意。

五、Socket

(一) Socket 簡述

我們也可以通過socket來實現進程間通信。Socket也稱為"套接字",是網絡通信中的概念。它分為流式套接字和用戶數據報套接字兩種。分別對應網絡傳輸控制層的TCP和UDP協議。TCP協議是面向連接的協議,提供穩定的雙向通信功能,TCP連接的建立需要經過"三次握手"才能完成,為了提供穩定的數據傳輸功能,其本身提供的超時重傳機制,因此具有很高的穩定性;而UDP是無連接的,提供不穩定的單向通信功能,當然UDP也可以實現雙向通信功能。在性能上,UDP具有更好的效率,其確定啊是不保證數據一定能夠正確傳輸,尤其是在網絡擁塞的情況下。關于TCP和UDP的介紹就這么多,更詳細的資料請查看相關網絡資料。

PS:在使用Socket進行跨進程通信的時候,有兩點需要注意

  • 1 首先要在AndroidManifest上聲明權限
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
  • 2 其次不能在主線程中訪問網絡,這回導致在我們在Android 4.0及其以上設備,會拋出異常NetworkOnMainThreadException異常。而且進行網絡操作很可能是耗時的,如果放在主線程中,會影響響應效率,這方面來說也不應該在主線程中訪問網絡。

(二) 舉例說明

這塊的例子很多,大家上網搜一下,推薦這邊博客[[Android IPC機制(五)用Socket實現跨進程聊天程序] Android IPC機制(五)用Socket實現跨進程聊天程序

六、AIDL:

具體請參考Android跨進程通信IPC之11——AIDL

七、使用廣播(Broadcast)

廣播是一種被動跨進程的通訊方式。當某個程序向系統發送廣播時,其他的應用程序只能被動的接受廣播數據。就像電臺進行廣播一樣,聽眾只能被動地收聽,而不能主動的與電臺進行溝通。

BroadcastReceiver本質上是一種系統界別的監聽器,他專門監聽各種程序發出的Broadcast,因此他擁有自己的進程,只要存在與之匹配的Intent被廣播出來,BroadcastReceiver總會被激發。我們知道,只有先注冊了某個廣播之后,廣播接收者才能收到該廣播。廣播注冊的一個行為將是自己感興趣的IntentFliter注冊到Android系統的AMS(ActivityManagerService)中,里面保存了一個IntentFilter列表。廣播發送者將自己的IntentFilter的action行為發送到AMS中,然后遍歷AMS中的IntentFilter列表,看誰訂閱了該廣播,然后將消息遍歷發送到注冊了相應的IntentFilter的Activity或者Service中中,也就是會調用抽象方法onReceive()方法。其中AMS起到了中間橋梁的作用。

程序啟動的BroadcastReceiver只需要兩步:

  • 1 創建需需啟動的BroadcastReceive的Intent
  • 2 調用Context的sendBroadcast()或sendOrderBroadcast()方法來啟動指定的BroadcastReceiver。

每當Broadcast事件發生后,系統會創建對應的BroadcastReceiver實例,并自動觸發onReceiver()方法。onReceiver()方法執行完后,BroadcastReceiver實例就會被銷毀。

注意:onReceiver()方法中盡量不要做耗時操作,如果onReceiver()方法不能在10s內完成事件處理,Android會認為該程序無響應,也就彈出我們熟悉的ANR對話框。如果我們需要在接收到廣播小猴進行一些耗時的操作,我們可以考慮通過Intent啟動一個Server來完成操作,不應該啟動一個新的線程來完成操作,因為BroadcastReceiver生命周期很短,可能新建線程還沒有執行完,BroadcastReceiver已經銷毀了,而如果BroadcastReceiver結束了,它所在的進程中雖然還有啟動的新線程執行任務,可是由于該進程中已經沒有任何組件,因此系統會在內存緊張的情況下回收該進程,這就導致BroadcastReceiver啟動的子線程不能執行完成。

八、Binder連接池

上面我們介紹了不同的IPC方式,我們知道不同的IPC方式有不同特點和使用場景,這里還是要在說一下AIDL,因為AIDL是一種常見的進程間通信方式,是日常開發中設計進程通信時的首選。

如何使用AIDL在Android跨進程通信IPC之11——AIDL中已經詳細介紹了,現在回顧一下大致流程:首先創建一個Service和AIDL接口,接著創建一個類繼承自AIDL接口中的Stub類并實現Stub中的抽象方法,在Service的onBinder方法中返回這類的對象,然后客戶端就可以綁定服務端的Service,建立連接后,就可以訪問遠程服務端的方法了。

上面的過程就是典型的AIDL的使用流程。這本來也沒什么問題,但是現在考慮一種情況:公司項目越來越大了,現在有10個不同的業務模塊都需要使用AIDL來進行進程間通信,那我們該怎么處理?或許有人會說按照AIDL昂視一個一個來吧,如果有100個地方需要用到AIDL,難道就要創建100個Service?大家應該明白隨AIDL數量的增加,我們不能無限制的增加Service,Service是四大組件之一,本身就是一種系統資源。而且太多的Service會使得我們的應用看起來很重量級,因此正在運行的Service可以在應用詳情頁看到,而且讓用戶看到有10個服務正在運行,也很坑,針對上面的問題,我們需要減少Service的數量,將所有的AIDL放到同一個Service中去管理。

在這種某事下,整個工作機制是這樣的:每個業務模塊都創建自己的AIDL接口并實現此接口,這時候不同的業務模塊之間是不能耦合的,所有實現細節我們都要單獨來看,然后向服務端提供自己的唯一標示和對應的Binder對象;對于服務端來說,只需要一個Service就可以了,服務端提供一個queryBinder接口,這個接口能夠根據業務模塊的特征來返回相應的Binder對象給它們,不同的業務模塊拿到所需的Bidner對象后就可以進行遠程方法調用了。由此可見,Binder連接池的主要作用就是將每個業務模塊的Binder請求轉發到遠程Service中去執行,從而避免了重復創建Service的過程。原理如下圖:

Binder連接池.png

上面是理論,也許不是很好理解,現在我們用代碼的形式給大家弄一個demo。既然是demo,我們就不弄100個接口了,就弄3個好了,一個是登錄的用戶模塊的IUser,一個是訂單模塊的IOrder,一個是給交易模塊的IPay,其中IUser提供登錄,和登出功能,IOrder提供下單和取消訂單功能,IPay提供支付和取消支付功能,具體代碼如下:

1、編寫相應的AIDL文件

編寫 IOrder.aidl文件

interface IOrder {
    void doOrder(long orderId);
    void cancelOrder(long orderId);
}

編寫IPay.aidl文件

interface IPay {
    void doPay(long payId,int amount);
    void cancelPay(long payId);
}

編寫IUser.aidl文件

interface IUser {


    void login(String userName,String passwork);
    void logout(String username);
}
2、編寫具體的的AIDL中Stub的具體實現類

當然在具體寫實現類的時候,別忘記同步項目,否則你是找不到相應的Stub類的

(1) 編寫IPayImpl.java類
/**
 * Created by gebilaolitou on 2017/8/26.
 * 支付模塊的實現
 */

public class IPayImpl extends IPay.Stub  {
    @Override
    public void doPay(long payId, int amount) throws RemoteException {
        Log.i("GEBILAOLITOU","IPayImpl  doPay, payId="+payId+",amount="+amount);
    }

    @Override
    public void cancelPay(long payId) throws RemoteException {
        Log.i("GEBILAOLITOU","IPayImpl  doPay, payId="+payId);
    }
}
(2) 編寫IUserImpl.java類
/**
 * Created by gebilaolitou on 2017/8/26.
 * 用戶模塊的實現
 */

public class IUserImpl extends IUser.Stub {


    @Override
    public void login(String userName, String password) throws RemoteException {
        Log.i("GEBILAOLITOU","IUserImpl  login, userName="+userName+",password="+password);
    }

    @Override
    public void logout(String username) throws RemoteException {
        Log.i("GEBILAOLITOU","IUserImpl  logout, userName="+username);
    }
}
(3) 編寫IUserImpl.java類
/**
 * Created by gebilaolitou on 2017/8/26.
 * 訂單模塊的實現
 */

public class IOrderImpl extends IOrder.Stub {
    @Override
    public void doOrder(long orderId) throws RemoteException {
        Log.i("GEBILAOLITOU","IOrderImpl  doOrder, oderId="+orderId);
    }

    @Override
    public void cancelOrder(long orderId) throws RemoteException {
        Log.i("GEBILAOLITOU","IOrderImpl  cancelOrder, oderId="+orderId);
    }
}
3、定義IBinderPool接口并實現之。
(1) 編寫IBinderPool.aidl
interface IBinderPool {
    IBinder queryBinder(int binderCode);
}
(2) 編寫IBinderPool的具體實現類
/**
 * Created by gebilaolitou on 2017/8/26.
 * 連接池
 */

public class BinderPoolImpl extends IBinderPool.Stub {

    /**
     * query  方法就是根據請求功能模塊的代碼,返回相應模塊的實現
     * @param binderCode
     * @return
     * @throws RemoteException
     */
    @Override
    public IBinder queryBinder(int binderCode) throws RemoteException {
        Log.d("GEBILAOLITOU", "BinderPoolImpl queryBinder  binderCode="+binderCode);
        switch (binderCode){
            case Constans.TYPE_ORDER:
                return new IOrderImpl();
            case Constans.TYPE_PAY:
                return new IPayImpl();
            case Constans.TYPE_USER:
                return new IUserImpl();
        }
        return null;
    }
}
4、編寫相應的Service。
public class BinderPoolService extends Service {

    private BinderPoolImpl mBinderPoolImp = new BinderPoolImpl();
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mBinderPoolImp;
    }
}

在AndroidManifest.xml作相應的配置

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.gebilaolitou.binderpooldemo">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <service android:name=".BinderPoolService"
            android:process=".binderpoolservice"
            />
    </application>

</manifest>
5、客戶端的實現
(1)實現一個BinderPool的管理器

為了操作方便,將所有的BinderPool操作放在同一個類里面。這個類的大體結構和標準的AIDL客戶端基本相同。首先要有一個IBinderPool類型的私有變量,然后定義一個ServiceConnection類型的私有變量,并在它的onServiceConnected 方法里面給IBinderPool類型的變量賦值,然后定義一個bindBinderPoolService函數,在里面bindService。完整的代碼如下:

public class BinderPoolMananger {


    private Context mContext;
    private static BinderPoolMananger sInstance;
    private IBinderPool mBinderPool;
    private CountDownLatch mCountDownLatch;

    private BinderPoolMananger(Context context){
        this.mContext = context;
        bindBinderPoolService();
    }

    public static BinderPoolMananger getInstance(Context context){
        if(sInstance == null){
            sInstance = new BinderPoolMananger(context);
        }
        return sInstance;
    }


    private ServiceConnection mServiceConnection = new ServiceConnection(){
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.d("GEBILAOLITOU", "onServiceConnected() 被調用了");
            mBinderPool = IBinderPool.Stub.asInterface(service);
            try {
                //死亡通知
                mBinderPool.asBinder().linkToDeath(new IBinder.DeathRecipient() {
                    @Override
                    public void binderDied() {
                        mBinderPool.asBinder().unlinkToDeath(this, 0);
                        mBinderPool = null;
                        bindBinderPoolService();
                    }
                }, 0);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            mCountDownLatch.countDown();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    private void bindBinderPoolService(){
        Log.d("GEBILAOLITOU", "bindService()  被調用");
        mCountDownLatch = new CountDownLatch(1);
        Intent intent = new Intent(mContext,BinderPoolService.class);
        Log.d("GEBILAOLITOU", "開始 bind service");
        mContext.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
        try {
            mCountDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Log.d("GEBILAOLITOU", "finish to bind service");
    }


    public IBinder query(int code){
        Log.d("GEBILAOLITOU", "query() 被調用, code = " + code);
        IBinder binder = null;
        try {
            binder = mBinderPool.queryBinder(code);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        return binder;
    }
}
(2) 調用BinderPool管理器
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new Thread(new Runnable() {
            @Override
            public void run() {
                doBinder();
            }
        }).start();
    }

    private void doBinder() {
        BinderPoolMananger bm = BinderPoolMananger.getInstance(MainActivity.this);
        
        IOrder order = IOrder.Stub.asInterface(bm.query(Constans.TYPE_ORDER));
        try {
             order.doOrder(123L);
             order.cancelOrder(123L);
        } catch (RemoteException e) {
            e.printStackTrace();
            Log.d("GEBILAOLITOU", "order 模塊出現異常,e="+e.getMessage());
        }

        IPay pay = IPay.Stub.asInterface(bm.query(Constans.TYPE_PAY));

        try {
            pay.doPay(123L,1000);
            pay.cancelPay(123L);
        } catch (RemoteException e) {
            e.printStackTrace();
            Log.d("GEBILAOLITOU", "pay 模塊出現異常,e="+e.getMessage());
        }


        IUser user = IUser.Stub.asInterface(bm.query(Constans.TYPE_USER));

        try {
            user.login("gebilaolitou","123456");
            user.logout("gebilaolitou");
        } catch (RemoteException e) {
            e.printStackTrace();
            Log.d("GEBILAOLITOU", "user 模塊出現異常,e="+e.getMessage());
        }
    }
}
6、其他代碼

public class Constans {

    public static final int TYPE_USER=1;  //用戶模塊
    public static final int TYPE_PAY=2;   //支付模塊
    public static final int TYPE_ORDER=3;  //訂單模塊
}
7、項目結構

如下圖:


項目結構.png
8、結果輸出
結果輸出.png

七、選擇合適的IPC方式

receiver其實本質是用的Bundle來實現的,Binder連接池其實本質是AIDL,所以我們大概可以分為 Bundle、AIDL、Messenger、ContentProvider、Socket。

合適IPC方案.png
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,428評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,024評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,285評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,548評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,328評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,878評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,971評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,098評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,616評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,554評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,725評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,243評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,971評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,361評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,613評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,339評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,695評論 2 370

推薦閱讀更多精彩內容