Android的進階學習(五)--Messenger的使用和理解

weixin_34208283發表於2015-12-08

有了上一篇《Android的進階學習(四)--AIDL的使用與理解》的知識後,現在我們看Messenger就會更加容易了。所以,如果你還沒有看《Android的進階學習(四)--AIDL的使用與理解》,推薦看後再看這篇。


Messenger

Messenger就是基於Message的程式間通訊,也就是我們可以向線上程間利用Handler.send(Message)一樣,所以用起來是非常簡單的。

由上篇文章,我們知道我們可以編寫aidl檔案來進行程式間的通訊,而現在,我們用Messnger就不需要顯式使用aidl檔案了,為什麼說不是顯式呢?看完你就懂了。

Messenger的使用

明白一個類的原理前,首先就因該學會使用,畢竟我們的最終目的是搞明白原理,讓我們能夠更好的使用。
這裡我們還是選擇一個遠端的ServiceActivity之間的通訊吧。

Service服務端

首先,我們寫一個服務端Service

public class MyService extends Service {


    public final static String TAG = "MyService";

    public final static int SERVICEID = 0x0001;
    private Messenger messenger = new Messenger(new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (msg.arg1 == SERVICEID) {
              //接受從客戶端傳來的訊息
                Log.d(TAG, "客服端傳來的訊息===>>>>>>");
                String str = (String) msg.getData().get("content");
                Log.d(TAG, str);

                //傳送資料給客戶端
                Message msgTo = Message.obtain();
                msgTo.arg1 = 0X0002;
                Bundle bundle = new Bundle();
                bundle.putString("content", "我是從伺服器來的字串");
                msgTo.setData(bundle);
                try {
                    //注意,這裡把資料從伺服器發出了
                    msg.replyTo.send(msgTo);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }
    });

    @Override
    public void onCreate() {
        super.onCreate();
        Log.i(TAG, "onCreat");
    }

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

看看我們的`Service`服務端,可謂是真的很簡單,我們首先生成的一個`messenger`並個這個`messenger`的建構函式中出入了一個`Handler`物件。然後在`onBind`方法中返回了一個`messenger.getBinder()`,思路也是相當的清晰。我們注意到在`Handler`中我們接受到訊息後又給客戶端傳送了一條訊息`msg.replyTo(msgTo)`,恩,`Messnger`是可以相互傳送訊息的。當然,這也就要求在客戶端和服務端都有自己的`Messnger`。

最後,我們要麼就直接安裝執行`Service`,要麼就註冊`Service`的時候加上`Process`屬性,因為我們要測試的是跨程式通訊了。


######Activity客戶端

 我們的`Activity`也是相當簡單的,所以還是先上程式碼:


public class MainActivity extends AppCompatActivity {

 public final static String TAG = "MainActivity";
 public final static int ACTIVITYID = 0X0002;
 //客戶端的Messnger
 private Messenger aMessenger = new Messenger(new Handler() {
     @Override
     public void handleMessage(Message msg) {
         super.handleMessage(msg);
         if (msg.arg1 == ACTIVITYID) {
          //客戶端接受服務端傳來的訊息
             Log.d(TAG, "服務端傳來了訊息=====>>>>>>>");
             String str = (String) msg.getData().get("content");
             Log.d(TAG, str);
         }
     }
 });

//服務端傳來的Messenger
 Messenger sMessenger;

 ServiceConnection serviceConnection = new ServiceConnection() {
     @Override
     public void onServiceConnected(ComponentName name, IBinder service) {
         sMessenger = new Messenger(service);

         Message message = Message.obtain();
         message.arg1 = 0x0001;
         //注意這裡,把`Activity`的`Messenger`賦值給了`message`中,當然可能你已經發現這個就是`Service`中我們呼叫的`msg.replyTo`了。
         message.replyTo = aMessenger;

         Bundle bundle = new Bundle();
         bundle.putString("content", "我就是Activity傳過來的字串");
         message.setData(bundle);

         try {
           //訊息從客戶端發出
             sMessenger.send(message);
         } catch (RemoteException e) {
             e.printStackTrace();
         }
     }

     @Override
     public void onServiceDisconnected(ComponentName name) {
         Log.e(TAG, "連線Service失敗");
     }
 };

 @Override
 protected void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
     setContentView(R.layout.activity_main);
     startAndBindService();
 }

 private void startAndBindService() {
     Intent service = new Intent(MainActivity.this, MyService.class);
     startService(service);
     bindService(service, serviceConnection, Context.BIND_AUTO_CREATE);
 }

} ```

這裡和上一篇的程式碼佈局都幾乎是一樣的,只是這裡我們把serviceConnection成功返回的IBinder轉換成的是Messnger而已,接著就呼叫而messenger.send(message);,就這樣,我們的訊息就傳送出了,和同一執行緒裡面的訊息傳送幾乎一樣,只是這裡的handmessage(Message message)是在不同的程式。
當然,為了接受從服務端發來的訊息,我們在Activity中定義了一個Messenger,並且在客戶端發給服務端的訊息(Message)中把客戶端的Messenger給賦值進Message中。

就這樣,最簡單的Messenger就完成了。

Demo執行結果

客戶端:

295205-510efd006eaed18e.png
Paste_Image.png

服務端:

295205-6ad57dcaf0091faa.png
Paste_Image.png

可見,當我們的服務端接收到訊息後,就向客戶端發出了一條訊息。這種情況在我們的專案中也是經常可見的,例如,你下載一個很大的東西時,就直接開一個程式去下,讓後下載完成後再將檔案路徑給傳送給客戶端......當然,這個demo中我們什麼都沒有去處理就直接返回了,在服務端是會有邏輯的,這裡就沒有過多的演示了。


Messenger的理解

知其所以然,我們就接著看看Messenger到底是何方神聖?

首先,我們還是從Service中看起,還記得我們在Servicenew了一個Messenger並且傳入了一個Handler吧,然後在onBind()方法中就返回了一個messenger.getBinder()

public IBinder getBinder() {
    return mTarget.asBinder();
}

一看,裡面返回的就是mTarget.asBinder(),然後我們就找找mTarget

public Messenger(Handler target) {
      mTarget = target.getIMessenger();
  }

結果在Messenger的建構函式中發現了target.getIMessenger(),繼續,我們看看Handler中的這個方法:

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

  private final class MessengerImpl extends IMessenger.Stub {
      public void send(Message msg) {
          msg.sendingUid = Binder.getCallingUid();
          Handler.this.sendMessage(msg);
      }
  }

getIMessenger()中就簡單的判斷一下mMessenger是否存,不存在的話就new一個返回,存在的話就直接返回,還是很簡單的。接著我們就看看這個MessengerImpl到底是個什麼東西?
由原始碼我們發現MessengerImpl就是Hnadler的一個內部類,然後繼承於IMessenger.Stub。看到這裡,或許就已經發現了,這和aidl中生成的類不是一樣的嗎?的確,這裡就是實現的一個aidl檔案。在這個類中,實現了以一個send方法,這個方法中呼叫了Handler.sendMessage()方法,這也就是為什麼,我們的訊息來得時候會出現在handlerMessage()中。這裡,我們在理一下,客戶端在呼叫send方法的時候,由於在不同的程式,傳送的資料先回被序列化,然後進行跨程式傳送,最後到了服務端進行解析,對應呼叫send方法執行。

不知道是否還記得,我們在用aidl通訊的時候,我們在Service中首先要生成一個XXXX.Stub的實現類,然後再在onBind()中返回一個XXXX.Stub的引用。
現在我們在回首看一下,我們在Service中返回的mTarget.asBinder()其實就是MessengerImpl,當然asBinder()返回的就是自己本身。

當然,還有個問題我們得注意,就是服務端給客戶端傳送的訊息。從demo中我們知道,我們在Serivice中使用msg.replyTo.send(msgTo);進行向客戶端傳送訊息。而msgreplyTo這個屬性在客戶端中就是由客戶端生成的Messenger。其實這裡也是好理解的,首先,我們這裡的跨程式主要就是通過Binder的,即我們在客戶端和服務端傳遞Binder來進行通訊。而這裡的Messnger也就是一個包裝了的Binder

295205-38731edd1d756b09.png
Messenger.png

圖畫得很簡單,但是也足以說明問題了。這裡我們有兩個程式,一個程式A,一個程式B,其中每個程式中我們都生成了一個相應的程式。接著就該注意一下了,當我們從A到B傳送訊息時,我們使用的是MessengerB.send(Message),B到A的時候是MessengerA.send(Message),這時候你可能就納悶了,為什麼不是用相應的傳送?程式A中的MessengerB又是怎樣來的?

聯絡上面的demo這就很說明問題了,我們假設程式A是客戶端,B是服務端。那麼程式A(Activity)中通過ServiceConnection就得到了程式B(Service)中Messenger中包裝的Binder,在自己包裝一下,就是MessengerB了。這也和上一篇中的在客戶端呼叫aidl檔案介面方法聯絡起來。

然後,我們需要知道的就是程式B(Service)中是怎樣有程式A(Activity)的MessengerA的了?其實我想你已經明白了,就是在程式A給程式B傳送的Message中,我們把MessengerA給傳了過去。回想上面的程式碼,我們在客戶端進行的message.replyTo = aMessenger;和在服務端進行的msg.replyTo.send(msgTo);就是把MessengerA給進行寫入和讀取。現在是不是有一種大徹大悟的感覺?如果沒有,那就慢慢再理一下吧!


aidl實現雙向通訊

看了Messenger的實現方式,我們想一下用aidl來實現客戶端與服務端的雙向通訊也是很容易的,那就在上一篇文章的基礎上理一下思路吧。

1.我們在服務端aidl定義一個方法,接受一個Binder。
2.在客戶端中我們也通過aidl生成一個Binder,然後當我們通過ServicConnection獲取到服務端的Binder後,再呼叫接受Bindler的那個方法,並把客戶端中生成的Binder給傳進去。
3.服務端相應客戶端請求的方法時,在呼叫客戶端Binder傳送訊息即可。

基本思路就是這樣,當然肯定沒有谷歌的Messenger完美,但是可以加強我們對aidlMessenger的理解了。

總結

搞什麼嘛,最後還不就是aidl,就是封裝了一下而已。這也就是文章開頭所說的不顯式使用aidl


最後

我一個苦逼的大學生,而且專業還不對口,希望寫寫東西,加強理解與記憶,同時也希望自己的理解能幫到更多的人。所以,有什麼錯誤的,寫得不好的,希望指出,共同進步。

還有,這些是我參考《Android開發藝術探索》的,對,就是任大大的。

相關文章