Android程序間通訊(3)-Messenger實現
前面兩篇文章中已經介紹了兩種實現程序間通訊的方式,那是不是隻有這兩種方式實現程序間通訊呢?當然不是,還有更好的實現方式,那就是Messenger。本篇文章將帶領大家一起來學習下Messenger.
沒有看我寫的上面兩篇文章的同學,建議先去看下上面兩篇文章,以更好的理解。
什麼是基於訊息的程序通訊?
該圖片是引用自弘揚大神的部落格
其實基於訊息的程序通訊就是客戶端通過Handler傳送一條Message,服務端收到這條Message然後獲取到客戶端的值,服務端進行處理,再將結果封裝成Message傳遞給客戶端。通過這種方式實現aidl有什麼好處呢?
- 不用再寫aidl檔案了(很多同學一提到aidl就頭疼)
- 基於Message,這個大家已經很熟悉了
接下來我們通過程式碼詳細的介紹下。
1,服務端,我們定義一個MessengerService.java的Service,程式碼如下:
package com.wms.github.aidl.server;
import android.app.Service;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.util.Log;
/**
* Created by 王夢思 on 2017/5/25.
*/
public class MessengerService extends Service {
private static final String TAG = "MessengerService";
private Messenger messenger = new Messenger(new Handler() {
@Override
public void handleMessage(Message clientMsg) {
if (clientMsg.what == 0x001) {
//注意這個裡clientMsg是客戶端傳送過來的訊息
Bundle bundle = (Bundle) clientMsg.obj;
String str = bundle.getString("str");
str = str.toUpperCase();
//服務端處理完邏輯後,將資料通過Messeage的方式傳遞給客戶端
Message message = Message.obtain();
Bundle sendBundle = new Bundle();
sendBundle.putString("str", str);
message.obj = sendBundle;
message.what = 0x001;
try {
//將訊息傳送到客戶端
clientMsg.replyTo.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
}
super.handleMessage(clientMsg);
}
});
@Override
public IBinder onBind(Intent intent) {
Log.e(TAG, "onBind...");
//這裡不能返回null,必須要返回我們建立的Binder物件
return messenger.getBinder();
}
@Override
public boolean onUnbind(Intent intent) {
Log.e(TAG, "onUnbind...");
return super.onUnbind(intent);
}
@Override
public void onStart(Intent intent, int startId) {
Log.e(TAG, "onStart...");
super.onStart(intent, startId);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.e(TAG, "onStartCommand...");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onCreate() {
Log.e(TAG, "onCreate...");
super.onCreate();
}
@Override
public void onDestroy() {
Log.e(TAG, "onDestroy...");
super.onDestroy();
}
}
服務端就一個Service,可以看到程式碼相當的簡單,只需要去宣告一個Messenger物件,然後onBind方法返回messenger.getBinder();
然後坐等客戶端將訊息傳送到handleMessage想法,根據message.what去判斷進行什麼操作,然後做對應的操作,最終將結果通過 clientMsg.replyTo.send(),這裡注意一定要呼叫clientMsg.replyTo.send()去傳送訊息,不然客戶端接收不到。
註冊MessengerService
<service
android:name="com.wms.github.aidl.server.MessengerService"
android:exported="true">
<intent-filter>
<action android:name="com.wms.github.aidl.server.MessengerService"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</service>
2,客戶端。在客戶端我新建一個MessengerActivity.java,程式碼如下:
package com.wms.github.aidl.client;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.util.Log;
import android.view.View;
import android.widget.EditText;
/**
* Created by 王夢思 on 2017/5/25.
*/
public class MessengerActivity extends MainActivity {
private EditText mEditText;
private Messenger mService;
private Messenger clientMessenger = new Messenger(new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == 0x001) {
Bundle bundle = (Bundle) msg.obj;
mEditText.setText(bundle.getString("str"));
}
super.handleMessage(msg);
}
});
private ServiceConnection mServiceConn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//當繫結成功後呼叫
Log.e("MainActivity", "onServiceConnected...");
mService = new Messenger(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.e("MainActivity", "onServiceDisconnected...");
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mEditText = (EditText) findViewById(R.id.id_edittext);
findViewById(R.id.bind).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
bindService();
}
});
findViewById(R.id.unbind).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
unBindService();
}
});
findViewById(R.id.invokeServer).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
invokeServer();
}
});
}
/**
* 繫結服務
*/
public void bindService() {
Intent intent = new Intent();
intent.setAction("com.wms.github.aidl.server.MessengerService");
//Android 5.0以上必須要加這句程式碼,不然報錯
intent.setPackage("com.wms.github.aidl.server");
bindService(intent, mServiceConn, Context.BIND_AUTO_CREATE);
}
public void unBindService() {
unbindService(mServiceConn);
}
public void invokeServer() {
String inputStr = mEditText.getText().toString().trim();
try {
Message message = Message.obtain();
message.what = 0x001;
Bundle bundle = new Bundle();
bundle.putString("str",inputStr);
message.obj = bundle;
//將訊息的迴應設定為clientMessenger,這樣客戶端傳送訊息就能在客戶端收到了。
message.replyTo = clientMessenger;
mService.send(message);
} catch (RemoteException e) {
//這裡會丟擲遠端異常
e.printStackTrace();
}
}
}
佈局檔案和上篇文章中一樣。
MessengerActivity.java中程式碼也很簡單,就是繫結一個服務,然後再onServiceConnect中例項化一個Messenger,這個Messenger和MessengerActivity中屬性clientMessenger不一樣,因為這個Messenger例項化的時候把Binder驅動傳遞進來了。所以當客戶端呼叫mService.send(message);
後,服務端將會收到客戶端傳遞過來的資料,服務端處理完之後,返回到Client端的clientMessenger中的Handler的handleMessage方法中,這樣就完成了客戶端和服務端的通訊。
效果圖:
當我們點選呼叫服務端轉換後,服務端就把大寫字母回傳給客戶端。以上就是簡單的Messenger的使用,肯定很多人有疑問,為什麼這樣可以實現程序間的通訊呢?下面我將帶領大家一起來從原始碼的角度來看看。
1,先分析服務端,服務端中onBind()方法裡面我們返回了messenger.getBinder();
很簡單,就一行程式碼,我們進入Messenger的內部看下getBinder內部到底做了什麼事情
/**
* 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();
}
getBinder方法也很簡單,也就一行程式碼。這裡mTarget是什麼呢?檢視下原始碼,private final IMessenger mTarget;
可以看出mTarget是一個IMessenger物件,mTarget賦值是在我們例項化clientMessenger物件的時候Messenger構造方法裡面。我們再來看看Messenger的構造方法
/**
* 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內部的getImessenger方法
final IMessenger getIMessenger() {
synchronized (mQueue) {
if (mMessenger != null) {
return mMessenger;
}
mMessenger = new MessengerImpl();
return mMessenger;
}
}
恍然大悟,原來mTarget其實是一個MessengerImpl物件。再回到Messenger中的getBinder方法,其實就是呼叫了MessengerImpl內部的asBinder方法,我們進入到MessengerImpl中的asBinder中看看其內部做了什麼事情。
MessengerImpl原來是繼承自IMessenger.Stub,是不是很熟悉?這不又回到了我們前面的文章中通過aidl實現程序通訊的了麼?原來Android內部已經幫我們實現了一個IMessenger.aidl檔案,這個檔案位於framework中的platform_frameworks_base/core/java/android/os/IMessenger.aidl,大家可以下載framework原始碼去檢視下。由於aidl程式碼較少,我下面貼出IMessenger.aidl內部程式碼如下:
package android.os;
import android.os.Message;
/** @hide */
oneway interface IMessenger {
void send(in Message msg);
}
哇,好簡答,就一個send方法,send方法裡面傳遞的是一個Message物件。所以這一切又回到了我們前面所說的aidl進行程序通訊的內容了。如果不明白的可以看我之前寫過的一篇文章 基於aidl實現的程序通訊
2,接下來我們分析下客戶端原始碼
當客戶端呼叫繫結服務的時候,會呼叫onServiceConnect方法
private ServiceConnection mServiceConn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//當繫結成功後呼叫
Log.e("MainActivity", "onServiceConnected...");
mService = new Messenger(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.e("MainActivity", "onServiceDisconnected...");
}
};
在onServiceConnected中我們例項化了一個Messenger,並且傳遞了一個IBinder物件,這個IBinder物件就是一直說的Binder驅動。我們進入Messenger這個建構函式中看看:
/**
* 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);
}
是不是又很熟悉?這不就是我們在 基於aidl實現的程序通訊 中onSeviceConnect方法實現的一樣麼?
綜上分析客戶端和服務端的原始碼,其實和我們寫aidl完全一樣,沒有任何區別,Messenger底層其實就是基於aidl來實現的程序通訊,只是Android內部已經給我們寫好了一個IMessnger.aidl檔案,不需要我們手動實現了。
到此,我們就已經分析完了原始碼了,如果還是不明白的同學建議去翻看下Messenger的原始碼,並不複雜,到此Android中實現程序通訊的常用方式已經介紹完了。