Binder學習(四)利用AIDL、Messenger實現IPC

wustor發表於2017-11-27

概述

在利用Binder進行IPC的時候,會經常需要建立一個Server端,Android中通常的實現是利用Service來實現,所以再進行IPC之前先了解之前先複習一下Service:

啟動方式

startService
   Intent intent = new Intent();
   intent.setClass(this, MyService.class);
   startService(intent);
複製程式碼
bindService
 Intent intent = new Intent();
 intent.setClass(this, MyService.class);
 bindService(intent, new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
//繫結成功的回撥
            }

            @Override
            public void onServiceDisconnected(ComponentName componentName) {
//斷開連線的回撥
            }
        }, Context.BIND_AUTO_CREATE);

複製程式碼

生命週期

Service生命週期

常見方法回撥時機

onCreate()

在每個service的生命週期中這個方法會且僅會呼叫一次,並且它的呼叫在onStartCommand()以及onBind()之前,我們可以在這個方法中進行一些一次性的初始化工作。

onStartCommand()

當其他元件通過startService()方法啟動service時,此方法將會被呼叫。

onBind()

當其他元件通過bindService()方法與service相繫結之後,此方法將會被呼叫。這個方法有一個IBinder的返回值,這意味著在重寫它的時候必須返回一個IBinder物件,它是用來支撐其他元件與service之間的通訊的——另外,如果你不想讓這個service被其他元件所繫結,可以通過在這個方法返回一個null值來實現。

onUnbind()

當呼叫UnBindService的時候 ,此方法會被呼叫

onDestroy

這是service一生中呼叫的最後一個方法,當這個方法被呼叫之後,service就會被銷燬。所以我們應當在這個方法裡面進行一些資源的清理,比如註冊的一些監聽器什麼的。

銷燬方式

startService方式啟動

startService()啟動,stopService()銷燬的生命週期如下: ->onCreate()->onStartCommand()->Service running-> onDestroy()

bindService方式啟動

bindService()啟動,unbindService()銷燬的生命週期如下: ->onCreate()->onStartCommand()->Service running-> onDestroy()

先startService再bindService
  • 如果結束只呼叫unbindService(),那麼只會執行到onUnbind(),將不會執行onDestroy():->onCreate()->onStartCommand()->onBind()->Service running-> onUnbind()。
  • 如果在unbindService後,在呼叫stopService(),那麼:->onCreate()->onStartCommand()->onBind()->Service running-> onUnbind()->onDestroy() 。

正文

startService

其實starService跟Binder機制沒有太大關係,通過此方式雖然可以啟動一個本地或者遠端的Service,但是我們拿不到Binder物件,不能直接通過AIDL的方式進行遠端通訊,只能通過其它的IPC方式進行通訊,即時如此,這種方式啟動的Service還是有一個方法需要注意一下,就是onStartCommand。

onStartCommand
   @Override public int onStartCommand(Intent intent, int flags, int startId) {
      return super.onStartCommand(intent, flags, startId);
    }

複製程式碼

呼叫時機

啟動service的時候,onCreate方法只有第一次會呼叫,onStartCommand每次都被呼叫。onStartCommand會告訴系統如何重啟服務,如判斷是否異常終止後重新啟動,在何種情況下異常終止 啟動服務時依次執行onCreate,onStartCommand;如果在系統顯示呼叫stopService和stopSelf之前終止服務,service再次重啟,onStartCommand會被呼叫,重啟服務時依次執行onStartCommand。

返回值

我們發現onStartCommand這個方法有個int型別返回值,實際上有四種型別,都是定義在Service中的靜態常量:

  public static final int START_NOT_STICKY = 2;
  public static final int START_REDELIVER_INTENT = 3;
  public static final int START_STICKY = 1;
  public static final int START_STICKY_COMPATIBILITY = 0;
複製程式碼

下面依次解釋下這幾種返回值的含義:

  • 1):START_STICKY:如果service程式被kill掉,保留service的狀態為開始狀態,但不保留遞送的intent物件。隨後系統會嘗試重新建立service,由於服務狀態為開始狀態,所以建立服務後一定會呼叫onStartCommand(Intent,int,int)方法。如果在此期間沒有任何啟動命令被傳遞到service,那麼引數Intent將為null。
  • 2):START_NOT_STICKY:“非粘性的”。使用這個返回值時,如果在執行完onStartCommand後,服務被異常kill掉,系統不會自動重啟該服務
  • 3):START_REDELIVER_INTENT:重傳Intent。使用這個返回值時,如果在執行完onStartCommand後,服務被異常kill掉,系統會自動重啟該服務,並將Intent的值傳入。
  • 4):START_STICKY_COMPATIBILITY:START_STICKY的相容版本,但不保證服務被kill後一定能重啟。

flags

flags表示啟動服務的方式:通過startService啟動時為0;onStartCommand返回為START_FLAG_REDELIVERY, or START_FLAG_RETRY.

  • START_FLAG_RETRY:表示服務之前被設為START_STICKY,則會被傳入這個標記。
  • START_FLAG_REDELIVERY:如果你實現onStartCommand()來安排非同步工作或者在另一個執行緒中工作, 那麼你可能需要使用START_FLAG_REDELIVERY來讓系統重新傳送一個intent。這樣如果你的服務在處理它的時候被Kill掉, Intent不會丟失.

bindService

前面說了那麼多,bindService才是重點,通過bindService啟動的Service會呼叫onBind方法,我們現在 分析一下如何拿到我們想要的Binder物件,因為不管是Client還是Server,想要通過AIDL進行IPC通訊,就必須拿到一個Binder物件,但是如果通過Messenger的話就不需要了,因為Messenger底層自己封裝了AIDL。 其實通過前面的分析,很容易看出Client在進行bindService的時候傳入了一個ServiceConnection,當跟Server端連線成功的時候會在onServiceConnected中返回一個Binder物件,那麼這個Binder物件是從哪兒來的呢?結合Server端的onBind方法,就很明顯了,這個Binder物件就是服務端傳遞過來的。其實通過之前的AIDL分析,也很容易能夠判斷出來,只要是通過Binder機制進行IPC通訊的,無論是Client還是Server端,都會涉及到Binder,我們只要找到了Binder,整個流程就很清晰了,下面分別描述一下Client跟Server中關於Binder的兩個方法

CLient的bindService

ServiceConnection

 mServiceConnection = new ServiceConnection() {
        @Override
 public void onServiceConnected(ComponentName name, IBinder service) {
          
        }
        @Override
 public void onServiceDisconnected(ComponentName name) {
            
        }
    };
複製程式碼

Client通過onServiceConnected可以拿到服務端返回的Binder物件,因為Binder實現了IBinder介面,返回的是IBinder。

Server的onBind
   @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
複製程式碼

Server通過onBind返回IBinder物件,預設的而實現為null,我們有多種方式可以實現Binder,通過繼承Binder類,AIDL以及Messenger都可以做到,下面簡要說明一下自定義Binder,Messenger跟AIDL下面會重點進行講解:

繼承Binder

  // Binder given to clients
    private final IBinder mBinder = new LocalBinder();
    public class LocalBinder extends Binder {
        LocalService getService() {
            return LocalService.this;
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }
複製程式碼

比較簡單,這種方式一般用在Service跟Activity進行通訊的過程中,進行方法呼叫,大多數是在同一個程式中,我們看到在LocalBinder 中定義了getService方法,可以獲取到Service的例項,比如我在專案中的定位是放在Service中的,但是拿到定位資料之後需要在Activity中顯示地域切換的對話方塊,所以Service就需要跟Activity進行互動,Service可以調Activity的方法,同樣由於Activity在Binder中拿到了Service的引用,也可以呼叫Service的中的方法。

使用AIDL進行IPC

Client端程式碼

ServiceConnection

     private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mPeopleManager = PeopleManager.Stub.asInterface(service);
            mBound = true;
            
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            mBound = false;
        }
    };

複製程式碼

開始連線

   private void attemptToBindService() {
        Intent intent = new Intent();
        intent.setClass(this, AIDLService.class);
        bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
    }
複製程式碼

addPeople

   public void addBook(View view) {
        //如果與服務端的連線處於未連線狀態,則嘗試連線
        if (!mBound) {
            attemptToBindService();
            return;
        }
        if (mPeopleManager == null)
            return;
        People people = new People();
        people.setAge(18);
        people.setGender("male");
        people.setHobby("travel");
        try {
            mPeopleManager.addPeople(people);
            Log.e(getLocalClassName(), people.toString());
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
複製程式碼

getPeople

  public void getPeople(View view) {
        //如果與服務端的連線處於未連線狀態,則嘗試連線
        if (!mBound) {
            attemptToBindService();
            Log.d("client-->", "正在連線Server");
            return;
        }
        if (mPeopleManager == null)
            return;
        try {
            Toast.makeText(this, String.valueOf(mPeopleManager.getPeople().size()), Toast.LENGTH_SHORT).show();
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
複製程式碼
Server端程式碼

建立一個Stub物件

  //由AIDL檔案生成的PeopleManager
    private final PeopleManager.Stub mPeopleManager = new PeopleManager.Stub() {
        @Override
        public List<People> getPeople() throws RemoteException {
            synchronized (this) {
                if (mPeoples != null) {
                    return mPeoples;
                }
                return new ArrayList<>();
            }
        }
        @Override
        public void addPeople(People book) throws RemoteException {
            synchronized (this) {
                if (mPeoples == null) {
                    mPeoples = new ArrayList<>();
                }
                if (book == null) {
                    Log.e(TAG, "People is null in In");
                }
                mPeoples.add(book);
            }
        }
    };

複製程式碼

onBind進行返回

    @Override
    public IBinder onBind(Intent intent) {
        return mPeopleManager;
    }
複製程式碼

開啟遠端執行緒

   <service
            android:name=".AIDLService"
            android:enabled="true"
            android:exported="true"
            android:process=":server">
        </service>
複製程式碼
測試

AIDL進行IPC

使用Messenger進行IPC

Messenger通訊原理

Client端程式碼

建立一個Handler

  public class ClientHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case MESS_FROM_SERVER:
                    //接收伺服器傳過來的值
                    Bundle bundle = msg.getData();
                    int peopleSize = bundle.getInt("server");
                    Toast.makeText(MessengerActivity.this, String.valueOf(peopleSize), Toast.LENGTH_SHORT).show();
                    break;
            }
        }
    }
複製程式碼

建立一個Messenger

    Messenger messenger = new Messenger(new ClientHandler());
複製程式碼

ServiceConnection

    
    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            isBound = true;
            mService = new Messenger(service);

        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            mService = null;
            isBound = false;
        }
    };

複製程式碼

開始連線

  private void attemptToBindService() {
        Intent intent = new Intent();
        intent.setClass(this, MessengerService.class);
        bindService(intent, connection, Context.BIND_AUTO_CREATE);
    }
複製程式碼

addPeople

      public void addPeople(View view) {
        //如果與服務端的連線處於未連線狀態,則嘗試連線
        if (!isBound) {
            attemptToBindService();
            return;
        }
        People people = new People();
        people.setAge(18);
        people.setGender("male");
        people.setHobby("travel");
        Message message = Message.obtain(null, MESS_ADD_PEOPLE);
        Bundle bundle = new Bundle();
        bundle.putParcelable("people", people);
        message.setData(bundle);
        //Messenger用來接收服務端發來的資訊
        message.replyTo = messenger;
        try {
            //將Message傳送到服務端
            mService.send(message);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

複製程式碼

getPeople

    public void getPeople(View view) {
        //如果與服務端的連線處於未連線狀態,則嘗試連線
        if (!isBound) {
            attemptToBindService();
            Log.d("client-->", "正在連線Server");
            return;
        }
        Message message = Message.obtain(null, MESS_GET_PEOPLE);
        //Messenger用來接收服務端發來的資訊
        message.replyTo = messenger;
        try {
            //將Message傳送到服務端
            mService.send(message);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
複製程式碼
Server端程式碼

建立一個Handler 用來收發訊息,由於我們是在一個應用裡面的開啟的多程式,所以這邊接收到Client的請求,不能直接將其轉化為People物件,因為People物件屬於應用程式,而MessengerService屬於另外一個程式,是不能共享這個資料的,這裡採用了收到訊息後,用一個int 型別的資料來模擬集合的數量

          mHandler = new Handler() {
            @Override
            public void handleMessage(Message msgFromClient) {
                super.handleMessage(msgFromClient);
                //獲取一個新訊息
                Message replyToClient = Message.obtain(msgFromClient);
                switch (msgFromClient.what) {
                    //根據Message.what來判斷執行服務端的哪段程式碼
                    case MESS_ADD_PEOPLE:
                        size += 2;
                        break;
                    case MESS_GET_PEOPLE:
                        replyToClient.what = MESS_FROM_SERVER;
                        Bundle serverBundle = new Bundle();
                        serverBundle.putInt("server", size);
                        replyToClient.setData(serverBundle);
                        try {
                            msgFromClient.replyTo.send(replyToClient);
                        } catch (RemoteException e) {
                            e.printStackTrace();
                        }
                        break;
                }
            }
        };
  

複製程式碼

建立一個Messenger

        messenger = new Messenger(mHandler);

複製程式碼

onBind進行返回

   @Override
    public IBinder onBind(Intent intent) {
        return messenger.getBinder();
    }
複製程式碼

開啟遠端執行緒

    <service
            android:process=":server"
            android:name=".MessengerService"/>
複製程式碼

測試結果

使用Messenger進行IPC

###總結

通過利用AIDL跟Messenger來實現Android應用層的IPC,可以更加方便的幫助我們理解Binder機制,當然Android系統還可以利用其它的方式來進行IPC,例如通過檔案共享,intent傳值等,下面簡單就這些方式做一個對比:

程式間通訊的方式

名稱 優點 缺點 適用場景
Intent 簡單易用 只能傳輸Bundle所支援的資料型別 四大元件間的程式間通訊
檔案共享 簡單易用 不適合高併發 簡單的資料共享,無高併發場景
AIDL 功能強大,支援一對多併發實時通訊 使用稍微複雜,需要注意執行緒同步 複雜的程式間呼叫,Android中最常用
Messenger 比AIDL稍微簡單易用些 比AIDL功能弱,只支援一對多序列實時通訊 簡單的程式間通訊
ContentProvider 強大的資料共享能力,可通過call方法擴充套件 受約束的AIDL,主要對外提供資料線的CRUD操作 程式間的大量資料共享
RemoteViews 在跨程式訪問UI方面有奇效 比較小眾的通訊方式 某些特殊的場景
Socket 跨主機,通訊範圍廣 只能傳輸原始的位元組流 常用於網路通訊中

原始碼下載地址

參考資料

Android開發藝術探索

相關文章