《Android》『呼叫外部 App』- 透過 Messager 與 Service 執行外部 App 並互相溝通(IPC)的基本方法
《簡單介紹》
IPC 的全名是 Inter-Process Communication,中文叫做行程間通訊,在 android 中,不同的 App 可以看成不同的行程,每個行程都有自己一部分獨立的系統資源,彼此是隔離的。為了能使不同的行程互相存取資源並進行協調工作,才有了行程間通訊。
《用法介紹》
在 android 中,行程間通訊的實作有幾種方式,一種是透過 AIDL 的方式,另一種則是透過 Messager,也就是這篇文章所要介紹的,假設現在有兩個 App,我們姑且把這兩個 App 當作用戶端與伺服端,在伺服端我們定義了回應不同類型 Message 物件的 Handler,這個 Handler 是 Messenger 的基礎,之後便可以與用戶端分享 IBinder,讓用戶端使用 Message 物件傳送命令給伺服端。
以下直接透過程式碼片段,說明實作的方式。
1. 在專案中建立兩個 App
首先我們在 Android Studio 專案中同時建置兩個應用程式,先建立一個程式專案,在此程式專案點右鍵選 Module,接著選 Phone & Tablet Module ,就可以在此專案中建立另一個應用程式。
2. 先實作伺服端的程式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
package com.example.serverapp; import ...; import com.example.itembeanlibrary.ItemBean; public class MessengerService extends Service { //自訂訊息分類 public static final int MSG_ADD_SUM = 0; @Override public void onCreate() {...} @Override public IBinder onBind(Intent intent){ //在建立 Service 時,透過 onBind 返回 mMessenger.getBinder() return mMessenger.getBinder(); } @Override public boolean onUnbind(Intent intent) { return false; } //自訂一個名為 mMessenger 的 Messenger 物件 private Messenger mMessenger = new Messenger(new Handler() { @Override public void handleMessage(Message msgFromClient) { //建立用來返回資訊給客戶端的 Message Message msgToClient = Message.obtain(msgFromClient); //分析由客戶端傳來的 Message switch (msgFromClient.what) { case MSG_ADD_SUM: msgToClient.what = MSG_ADD_SUM; try { msgToClient.arg2 = msgFromClient.arg1 + msgFromClient.arg2; //也可以透過 bundle 塞入所要傳回給客戶端的資料 // Bundle bundle = new Bundle(); // bundle.putInt("PID", pid); // msgToClient.setData(bundle); msgFromClient.replyTo.send(msgToClient); } catch (InterruptedException e){ e.printStackTrace(); } catch (RemoteException e){ e.printStackTrace(); } break; } super.handleMessage(msgfromClient); } }); } |
MessengerService.java
在伺服端程式中,我們建立了一個繼承自 Service 的自訂類別 MessengerService,並在裡面實作了一個Messenger 的物件 mMessenger,在 mMessenger 中處理由用戶端程式所傳來的訊息,最後透過 onBind() 將此物件的 Binder 回傳。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<?xml version="1.0" encoding="utf-8"?> <manifest ...> <application ...> <activity android:name=".MainActivity"> ... </activity> <service android:name=".MessengerService" android:enabled="true" android:exported="true"> <intent-filter> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </service> </application> </manifest> |
AndroidManifest.xml
最後記得要在此伺服端程式的 AndroidManifest.xml 中註冊此 MessengerService。
3. 再來實作用戶端的程式
現在我們已經建立好伺服端的程式,那麼在用戶端該如何去與之互動呢?我們已經知道在伺服端的程式中,定義了一個 Service 服務,因此在用戶端的程式中,便可以透過 bindService 的方式讓兩個程式聯繫起來。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
package com.example.xylonchen.messagerserivcesample; import ...; public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; public static final int MSG_ADD_SUM = 0; private Button btnAdd; private LinearLayout llContainer; private TextView mTvState; private Messenger messenger; private boolean isConnect; private int mA; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //UI宣告 mTvState = (TextView) findViewById(R.id.txt_callback); btnAdd = (Button) findViewById(R.id.btn_add); llContainer = (LinearLayout) findViewById(R.id.ll_container); //開始綁定服務 bindService(); btnAdd.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { try { int a = mA++; int b = (int) (Math.random() * 100); TextView tv = new TextView(MainActivity.this); tv.setText(a + " + " + b + " = caculating ..."); tv.setId(a); llContainer.addView(tv); //定義將要發送給伺服端的 Message Message msgToServer = Message.obtain(null, MSG_ADD_SUM, a, b); //將定義好的 mMessenger 透過 msgToServer 送給伺服端 msgToServer.replyTo = mMessenger; if (isConnect) { //發訊息給 Server 端 messenger.send(msgToServer); } } catch (RemoteException e) { e.printStackTrace(); } } }); } //綁定服務(透過伺服端程式的 packageName 與其 MessengerService 的 ClassName) private void bindService() { Intent intent = new Intent(); intent.setComponent(new ComponentName("com.example.serverapp","com.example.serverapp.MessengerService" )); bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE); Log.e(TAG, "bindService invoked !"); } //定義要傳入伺服端程式的 Messager private Messenger mMessenger = new Messenger(new Handler() { @Override public void handleMessage(Message msgFromServer) { switch (msgFromServer.what) { case MSG_ADD_SUM: TextView tv = (TextView) llContainer.findViewById(msgFromServer.arg1); tv.setText(tv.getText() + "=>" + msgFromServer.arg2); // Bundle bundle = msgFromServer.getData(); // pid = bundle.getInt("PID"); break; } super.handleMessage(msgFromServer); } }); private ServiceConnection mServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { //messenger 接收由伺服端程式傳來的 messenger 物件 messenger = new Messenger(service); isConnect = true; mTvState.setText("connected!"); } @Override public void onServiceDisconnected(ComponentName name) { messenger = null; isConnect = false; mTvState.setText("disconnected!"); } }; @Override protected void onDestroy() { super.onDestroy(); unbindService(mServiceConnection); } } |
MainActivity.java
在伺服端程式的 onBind() 方法,會在 Service 綁定成功後回傳定義在伺服端程式的 mMessenger 物件,因此我們在用戶端程式,可以透過 繼承自 ServiceConnection 的物件 mServiceConnection 取得此 messager,透過這個 messager 的 send(Message) 方法,我們可以將想要傳遞的資訊傳給伺服端程式,要注意的是,我們必須在要傳遞給伺服器程式的 Messager 的 replyTo 參數中,塞入在用戶端程式所定義的 Messenger 物件 mMessenger,這麼一來,兩個程式之間就建立起了一個雙向溝通的管道。
以此範例來說,我們分解程式中的流程如下 –
Step1.
當用戶端程式啟動後,會先透過 bindService 啟動並綁定位於伺服端程式的 MessengerService,並在綁定成功後取得其中所定義的 Messenger 物件 mMessenger。
Step2.
在用戶端程式中,也定義了一個 Messenger 物件 mMessenger,用來處理當收到從伺服端程式傳來的 Message 時,所該做的事情。
Step3.
接著當使用者透過用戶端程式按下 btnAdd 按鈕後,將想要傳遞給伺服端程式的資料包成名為 msgToServer 的 Message 物件,並在 msgToServer 的 replyTo 參數中塞入用戶端所定義的 mMessenger,最後透過在步驟一所取得的 mMessenger(伺服端傳來的) 之 send(Message) 方法,將 msgToServer 發送給伺服端程式。
Step4.
在伺服端程式中,因為用戶端程式是透過伺服端本身所定義的 Messager 發出訊息,所以會直接觸發在其中定義好的 mMessenger, 此 mMessenger 亦是用來處理當收到從用戶端程式傳來的 Message 時,所該做的事情的地方。
Step5.
在伺服端程式中,因為在步驟三時,我們有將在用戶端定義好的 mMessager 塞入 replyTo 參數傳回來,所以我們可以透過 msgFromClient.replyTo 的方法,取得 mMessenger(用戶端傳來的),同樣利用其 send(Message) 方法,將 msgToClient 發回給用戶端程式。
臉書留言
一般留言