教育部補助資訊軟體人才培育先導計畫 100 年度課程發展專案計畫 實驗課程名稱 : IPC(Inter-Process Communication) 開發教師 : 張晉源老師 開發學生 : 林政揚 (s11639104@stu.edu.tw) 學校系所 : 樹德科技大學資訊工程學系
實驗目的 本實驗的目的在於讓同學們可以了解 Android 系統核心內部的行程通訊的原理, 透過呼叫系統提供的其中一樣服務 (MediaPlayer), 來實作 IPC(Inter-Process Communication) 機制, 讓同學們可以對 IPC 的機制流程有更深刻的理解, 藉此可跟前幾章相做呼應 個人電腦 X 1 需求 : 個人電腦或筆記型電腦一部 目的 : 實作 IPC 機制程式 MP3 2 首 實驗器材 個人電腦 eclipse JDK SDK ADT 實驗所需軟體 實驗步驟簡述 創建專案 (1) 創建專案 MediaIPC 裡面包含兩支程式 MediaIPCActivity.java/ RemoteService.java (2) 創建專案 MediaClient 裡面包含 MediaClientActivity.java 實作 (1) 撰寫 RemoteService.java 內容 (2) main.xml 撰寫 (3) string.xml 撰寫 (4) AndroidManifest.xml 撰寫 (5) 撰寫 MediaIPCActivity.java 內容 (6) 撰寫 MediaClientActivity.java 內容 (7) 測試
Part I 基本知識 Binder 流程
Binder 架構 (MediaPlayer) 舉例 1 系統 Service 實例 Media server framework/base/media/mediaserver/main_mediaserver.cpp 檔案 : 19: int main(int argc, char** argv) 20: { 21: sp<processstate> proc(processstate::self()); 22: sp<iservicemanager> sm = defaultservicemanager(); 23: LOGI("ServiceManager: %p", sm.get()); 24: AudioFlinger::instantiate(); 25: MediaPlayerService::instantiate(); 26: CameraService::instantiate(); 27: AudioPolicyService::instantiate(); 28: ProcessState::self()->startThreadPool(); 29: IPCThreadState::self()->joinThreadPool(); 30: 1. 程式碼第 21 行建立一個 ProcessState 的參照, 但是這個物件後面並沒有被使用到, 那麼為什麼要建立呢? 教學的簡報上有提過, 如果一個行程要使用 Binder 機制, 那麼他的行程中必須要建立一個 ProcessState 物件來負責管理 Service 的代理對象 2. 第 22 行呼叫 defaultservicemanager() 取得一個 Service Manager 代理對象 3. 後面幾行都是實體化 Service 物件, 展開之後發現都是一些調用 Service Manager 的 addservice 進行註冊的函數, 以 AudioFlinger 為例,instantiate 如下 : 1: void AudioFlinger::instantiate() { 2: defaultservicemanager()->addservice( 3: String16("media.audio_flinger"), new AudioFlinger()); 4: 4. 最後呼叫 ProcessState 的 startthreadpool 方法和 IPCThreadState 的 jointhreadpool
使 Media Server 進入等待請求的循環當中 2 系統 Service 的基礎 BBinder 仔細查看一下 Media Server 中定義的四個 Service 將會發現它們都是繼承自 BBinder, 而 BBinder 又繼承自 IBinder 介面, 每個 Service 都覆寫了 BBinder 的 ontransact 函數, 當 Client 發送請求到達 Service 時, 將會呼叫 Service 的 ontransact 函數, 後面將會詳細的介紹這個機制 3 Service 註冊 每個 Service 都需要向 大管家 Service Manager 進行註冊, 呼叫 Service Manager 的 addservice 方法註冊 這樣 Service Manager 將會執行 Client 端查詢和獲取該 Service( 代理對象 ), 然後 Client 端就可以通過該 Service 的代理對象請求該 Service 的服務 4 Service 進入等待請求的循環 每個 Service 必須要進入死循環, 等待 Client 端請求的到達, 本例中最後兩句就是使 Service 進行等待請求的循環之中 ProcessState 的 startthreadpool 方法最終調用的也是 IPCThreadState 的 jointhreadpool 方法, 具體請查看程式碼 IPCThreadState 的 jointhreadpool 方法的程式碼如下 : 1: void IPCThreadState::joinThreadPool(bool ismain) 2: { 3:... 4: do { 5: int32_t cmd; 6: 7:... 8: 9: // now get the next command to be processed, waiting if necessary 10: result = talkwithdriver(); 11: if (result >= NO_ERROR) { 12:... 13: 14: result = executecommand(cmd); 15: 16: 17:... 18: while (result!= -ECONNREFUSED && result!= -EBADF); 19: 20:... 21: Service 在 IPCThreadState 的 jointhreadpool 方法中, 呼叫 talkwithdriver 方法和 Binder 驅動進行溝通, 讀取 Client 端的請求 當 Client 端請求到達之後呼叫 executecommand 方法進行處理
Service 怎樣處理客戶端的請求? 看一下 executecommand 方法的程式碼 : 1: status_t IPCThreadState::executeCommand(int32_t cmd) 2: { 3: BBinder* obj; 4: RefBase::weakref_type* refs; 5: status_t result = NO_ERROR; 6: 7: switch (cmd) { 8:... 9: case BR_TRANSACTION: 10: { 11:... 12: if (tr.target.ptr) { 13: sp<bbinder> b((bbinder*)tr.cookie); 14: const status_t error = b->transact(tr.code, buffer, &reply, 0); 15: if (error < NO_ERROR) reply.seterror(error); 16: 17: 18:... 19: 20: 21: 22:... 23: 24: 25: if (result!= NO_ERROR) { 26: mlasterror = result; 27: 28: 29: return result; 30: 可以看到 IPCThreadState 將會直接呼叫 BBinder 的 transact 方法來處理客戶端請求, 再看一下 BBinder 的 transact 方法 : 1: status_t BBinder::transact( 2: uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) 3: { 4: data.setdataposition(0); 5: 6: status_t err = NO_ERROR; 7: switch (code) { 8: case PING_TRANSACTION:
9: reply->writeint32(pingbinder()); 10: 11: default: 12: err = ontransact(code, data, reply, flags); 13: 14: 15: 16: if (reply!= NULL) { 17: reply->setdataposition(0); 18: 19: 20: return err; 21: 發現它將會叫用自己的方法 ontransact 前面提到所有的 Service 都繼承自 BBinder, 並且都覆寫了 ontransact 虛函數 那麼 IPCThreadState 將會呼叫 Service 定義 ontransact 方法來處理客戶請求 Part II 放置音樂檔案及創建專案 Step 1 SDcard 放入所需檔案 放入兩個 mp3 檔案, 名稱分別為 music.mp3 music.mp3
Step 2 新增 MediaIPC 專案 File > New > Android Project( 新增一個 Android 專案 ) Project name : 為您的 Android 專案命名 Build Target : 選擇您剛剛所選的 Android 版本 (2.2) 設定方式 : Application Name: 請自行設定欲使用的名稱, 建議與本實習取相同名稱 MediaIPC Project Name: 預設會與 Application Name 相同 Package Name: 請不要使用預設的名稱, 建意將前述取名為各自學校的名稱 ( 可參考本範例 ) SDK 版本 : 請挑選 Android2.2 版 之後一直下一步, 直到 Finish, 無須再進行任何設定
Step 3 新增 RomteService 於左側欄中的 src -> stu.edu.mediaipc 按右鍵 new -> Class 並設定 class 名稱為 RomteService, 之後按下 Finish 即可 Step 4 新增 MediaClient 專案 依照前面的步驟再新增另一個專案, 叫 MediaClient 設定方式 : Application Name: 請自行設定欲使用的名稱, 建議與本實習取相同名稱 MediaClient Project Name: 預設會與 Application Name 相同 Package Name: 請不要使用預設的名稱, 建意將前述取名為各自學校的名稱 ( 可參考本範例 ) SDK 版本 : 請挑選 Android2.2 版 之後一直下一步, 直到 Finish, 無須再進行任何設定
Step 5 刪除兩者專案中不必要的 Menu 資料夾 此步驟請將 MediaIPC 及 MediaClient 專案中位於 res 內的 Menu 資料夾刪除, 因為此資 料夾內的檔案會與後續我們要增加的檔案有所衝突 Part III MediaIPC Project 撰寫 Step 1 string.xml 撰寫 string.xml 在創建專案時就一並會被創建出來, 在 res ->values 資料夾底下, 在這邊新增一些字串 <?xml version="1.0" encoding="utf-8"?> <resources> <string name="hello">hello World, MediaIPCActivity!</string> <string name="app_name">mediaipc</string> <string name="remote_service_started">remoteservice started!!</string> <string name="remote_service_label">remoteservice label</string> <string name="remote_service_stopped">remoteservice stopped!!</string> </resources>
Step 2 activity_main.xml 撰寫 activity_main.xml 在創建專案時就一並會被創建出來, 在 res ->layout 資料夾底下, 在 這邊新增三個按鈕, 做播放 暫停及停止動作 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="horizontal" > <Button android:id="@+id/start" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="start" /> <Button android:id="@+id/pause" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="pause" /> <Button android:id="@+id/stop" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="stop" /> </LinearLayout> Step 3 RomteService.java MediaIPCActivity.java 檔先不動, 因為需要呼叫 RomteService.java 檔, 若先寫 MediaIPCActivity.java 則在撰寫的過程中會出現大量錯誤, 所以我們撰寫的順序稍微改變一下 宣告全域變數 (Global variables) 並將 RomteService 繼承 Service, 且實作 onbind package stu.edu.mediaipc; import java.io.ioexception; import android.app.notification; import android.app.notificationmanager; import android.app.pendingintent;
import android.app.service; import android.content.intent; import android.media.mediaplayer; 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.widget.toast; public class RomteService extends Service { private String path1 = "/sdcard/music1.mp3"; private String path2 = "/sdcard/music2.mp3"; private static final int SOUNG1 = 1; private static final int SOUNG2 = 2; private static final int PAUSE = 3; private static final int STOP = 4; /** For showing and hiding our notification. */ NotificationManager mnm; Messenger mclients ;// 紀錄 client 的 Messnger int mvalue = 0; MediaPlayer mp; /* * 下面是如何使用 Messenger 的概要 : * 1. Service 實作一個接收從 Client 端的每次請求時產生回應的 Handler * 2. Handler 被用來建立一個 Messenger 物件 ( 它是 Handler 的一個參照 ) * 3. Messenger 建立一個從 service 的 onbind() 返回給 Client 端的 IBinder * 4. 客戶端使用 IBinder 來實體化這個 Messenger( 它參照到 service 的 Handler) *,Client 端用它來向 service 發送 Message * 5. service 在它的 Handler 中接收每個消息 具體是在 handlemessage() 方法中 */ Messenger mmessenger = null;// 讓 client 可以對 service 溝通的 Messenger /** * When binding to the service, we return an interface to our messenger * for sending messages to the service. */ @Override public IBinder onbind(intent intent) {
Log.i("RemoteService", "onbind()"); return mmessenger.getbinder(); 撰寫一個方法 shownotification() 這方法是當 service 被創建時會做提醒的動作, 提醒 client 已經連線成功 /** * Show a notification while this service is running. */ private void shownotification() { CharSequence text = gettext(r.string.remote_service_started); // Set the icon, scrolling text and timestamp Notification notification = new Notification(R.drawable.ic_launcher, text, System.currentTimeMillis()); PendingIntent contentintent = PendingIntent.getActivity(this, 0, new Intent(this, MediaIPCActivity.class), 0); // Set the info for the views that show in the notification panel. notification.setlatesteventinfo(this, gettext(r.string.remote_service_label), text, contentintent); // Send the notification. // We use a string id because it is a unique number. We use it later to cancel. mnm.notify(r.string.remote_service_started, notification); 建立一個內隱類別 (inner class ), 繼承 Handler 覆寫 handlemessage () 方法, 將從 Client 端傳來的 Message 做處理 class IncomingHandler extends Handler { @Override public void handlemessage(message msg) { Message message = null; mclients = msg.replyto; try { switch (msg.what) { case SOUNG1: message = Message.obtain(null, SOUNG1); mp.reset(); mp.setdatasource(path1); mp.prepare(); mclients.send(message); mp.start();
case SOUNG2: message = Message.obtain(null, SOUNG2); mp.reset(); mp.setdatasource(path2); mp.prepare(); mp.start(); mclients.send(message); case STOP: message = Message.obtain(null, STOP); mp.stop(); mclients.send(message); case PAUSE: message = Message.obtain(null, PAUSE); mp.pause(); mclients.send(message); catch (IllegalArgumentException e) { e.printstacktrace(); catch (IllegalStateException e) { e.printstacktrace(); catch (IOException e) { e.printstacktrace(); catch (RemoteException e) { e.printstacktrace(); Service 生命週期 : oncreate() -> onstart() -> ondestroy(), 由於本範例沒用到 onstart() 因此沒有做覆寫動作, 這邊做的是將提醒框以及 Media Player 做關閉動作 覆寫 oncrate() @Override public void oncreate() { Log.i("RemoteService", "oncreate()"); mnm = (NotificationManager)getSystemService(NOTIFICATION_SERVICE); mp = new MediaPlayer();
shownotification(); // Display a notification 覆寫 ondestroy() @Override public void ondestroy() { Log.i("RemoteService", "ondestroy()"); // Cancel the persistent notification. mnm.cancel(r.string.remote_service_started); mp.reset(); mp.release(); // Tell the user we stopped. Toast.makeText(this, R.string.remote_service_stopped, Toast.LENGTH_SHORT).show(); 附註 : RomteService.java 檔完成之 source code 如下所示 : package stu.edu.mediaipc; import java.io.ioexception; import android.app.notification; import android.app.notificationmanager; import android.app.pendingintent; import android.app.service; import android.content.intent; import android.media.mediaplayer; 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.widget.toast; public class RomteService extends Service { private String path1 = "/sdcard/music1.mp3"; private String path2 = "/sdcard/music2.mp3"; private static final int SOUNG1 = 1; private static final int SOUNG2 = 2;
private static final int PAUSE = 3; private static final int STOP = 4; /** For showing and hiding our notification. */ NotificationManager mnm; Messenger mclients ;// 紀錄 client 的 Messnger int mvalue = 0; MediaPlayer mp; Messenger mmessenger = null;// 讓 client 可以對 service 溝通的 Messenger /** * When binding to the service, we return an interface to our messenger * for sending messages to the service. */ @Override public IBinder onbind(intent intent) { Log.i("RemoteService", "onbind()"); return mmessenger.getbinder(); /** * Show a notification while this service is running. */ private void shownotification() { CharSequence text = gettext(r.string.remote_service_started); // Set the icon, scrolling text and timestamp Notification notification = new Notification(R.drawable.ic_launcher, text, System.currentTimeMillis()); PendingIntent contentintent = PendingIntent.getActivity(this, 0, new Intent(this, MediaIPCActivity.class), 0); // Set the info for the views that show in the notification panel. notification.setlatesteventinfo(this, gettext(r.string.remote_service_label), text, contentintent); // Send the notification. // We use a string id because it is a unique number. We use it later to cancel. mnm.notify(r.string.remote_service_started, notification);
@Override public void oncreate() { if (mmessenger == null) { mmessenger = new Messenger(new IncomingHandler()); Log.i("RemoteService", "oncreate()"); mnm = (NotificationManager)getSystemService(NOTIFICATION_SERVICE); mp = new MediaPlayer(); shownotification(); // Display a notification @Override public void ondestroy() { Log.i("RemoteService", "ondestroy()"); // Cancel the persistent notification. mnm.cancel(r.string.remote_service_started); mp.reset(); mp.release(); // Tell the user we stopped. Toast.makeText(this, R.string.remote_service_stopped, Toast.LENGTH_SHORT).show(); class IncomingHandler extends Handler { @Override public void handlemessage(message msg) { Message message = null; mclients = msg.replyto; try { switch (msg.what) { case SOUNG1: message = Message.obtain(null, SOUNG1); mp.reset(); mp.setdatasource(path1); mp.prepare(); mclients.send(message); mp.start();
case SOUNG2: message = Message.obtain(null, SOUNG2); mp.reset(); mp.setdatasource(path2); mp.prepare(); mp.start(); mclients.send(message); case STOP: message = Message.obtain(null, STOP); mp.stop(); mclients.send(message); case PAUSE: message = Message.obtain(null, PAUSE); mp.pause(); mclients.send(message); catch (IllegalArgumentException e) { e.printstacktrace(); catch (IllegalStateException e) { e.printstacktrace(); catch (IOException e) { e.printstacktrace(); catch (RemoteException e) { e.printstacktrace(); Step 4 AndroidManifest.xml 撰寫這邊是在做權限設定以及 service 的相關設定, 如果沒做這些設定程式將會無法執行 <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="stu.edu.mediaipc"
android:versioncode="1" android:versionname="1.0" > <uses-sdk android:minsdkversion="8" /> <application android:icon="@drawable/ic_launcher" android:label="@string/app_name" > <activity android:label="@string/app_name" android:name=".mediaipcactivity" > <intent-filter > <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> <!-- 以下為新增片段 --> <service android:enabled="true" android:name=".romteservice" android:process=":remote" > <intent-filter > <action android:name="stu.edu.mediaipc.romteservicetransact" /> </intent-filter> </service> <!-- 以上為新增片段 --> </application> </manifest> Slep5 MediaIPCActivity.java 撰寫 宣告全域變數 (Global variables) package stu.edu.mediaipc; import android.app.activity; 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.view.view.onclicklistener; import android.widget.button; public class MediaIPCActivity extends Activity { private Button start, pause, stop; private Messenger locms = new Messenger(new handler()); private Messenger romms; private static final int SOUNG1 = 1; private static final int PAUSE = 3; private static final int STOP = 4; private static final String tag = "MediaIPCActivity"; protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); /* 以下為新增程式片段 */ start = (Button) findviewbyid(r.id.start); pause = (Button) findviewbyid(r.id.pause); stop = (Button) findviewbyid(r.id.stop); start.setonclicklistener(this); pause.setonclicklistener(this); stop.setonclicklistener(this); bindservice(new Intent(this,RomteService.class), serverconnection, Context.BIND_AUTO_CREATE); /* 以上為新增程式片段 */ 新增 ServiceConnection 變數 這類別是用來與 Service 做連線用的, 當連線成功後, 會呼叫 onserviceconnected, 這時候再用 剛剛上面定義好的 Messager roms 去參照從 service 回傳過來的 Ibinder
private ServiceConnection serverconnection = new ServiceConnection() { public void onserviceconnected(componentname name, IBinder service) { // TODO Auto-generated method stub Log.i("ServiceConnection", "onserviceconnected"); romms = new Messenger(service); public void onservicedisconnected(componentname name) { // TODO Auto-generated method stub romms = null; ; 新增內隱 Class handler 這類別是內隱類別 (inner class), 繼承 Handler 覆寫 handlemessage() 方法, 將從 Service 端傳來的 Message 做處理 class handler extends Handler { @Override public void handlemessage(message msg) { switch (msg.what) { case SOUNG1: settitle("play song1"); case PAUSE: settitle("meida Pause"); case STOP: settitle("media Stop"); 實做 OnClickListener 為方便處理按鈕事件, 因此覆寫監聽事件 public void onclick(view v) { Message message = null; try { if (v == start) message = Message.obtain(null, SOUNG1);
else if (v == pause) message = Message.obtain(null, PAUSE); else if (v == stop) message = Message.obtain(null, STOP); message.replyto = locms; if (message!= null) romms.send(message); catch (RemoteException e) { e.printstacktrace(); 附註 : MediaIPCActivity java 檔完成之 source code 如下所示 : package stu.edu.mediaipc; import android.app.activity; 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.view.view.onclicklistener; import android.widget.button; public class MediaIPCActivity extends Activity implements OnClickListener { private Button start, pause, stop; private Messenger locms = new Messenger(new handler()); private Messenger romms; private static final int SOUNG1 = 1; private static final int PAUSE = 3; private static final int STOP = 4; private static final String tag = "MediaIPCActivity";
class handler extends Handler { @Override public void handlemessage(message msg) { switch (msg.what) { case SOUNG1: settitle("play song1"); case PAUSE: settitle("meida Pause"); case STOP: settitle("media Stop"); protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); start = (Button) findviewbyid(r.id.start); pause = (Button) findviewbyid(r.id.pause); stop = (Button) findviewbyid(r.id.stop); start.setonclicklistener(this); pause.setonclicklistener(this); stop.setonclicklistener(this); bindservice(new Intent(this,RomteService.class), serverconnection, Context.BIND_AUTO_CREATE); Log.i("MediaIPCActivity", "oncreate()"); private ServiceConnection serverconnection = new ServiceConnection() { public void onserviceconnected(componentname name, IBinder service) { // TODO Auto-generated method stub Log.i("ServiceConnection", "onserviceconnected"); romms = new Messenger(service);
; public void onservicedisconnected(componentname name) { // TODO Auto-generated method stub romms = null; public void onclick(view v) { Message message = null; try { if (v == start) message = Message.obtain(null, SOUNG1); else if (v == pause) message = Message.obtain(null, PAUSE); else if (v == stop) message = Message.obtain(null, STOP); message.replyto = locms; if (message!= null) romms.send(message); catch (RemoteException e) { e.printstacktrace();
Part IV MediaClient Project 撰寫 Step 1 將 MediaIPC 中的 main.xml 覆蓋 MediaClient 中的 main.xml 將 MediaIPC 的 res -> layout -> main.xml 複製到 MediaClient 的 res -> layout 因內容相同, 所以直接複製過來使用即可 Step 2 撰寫 MediaClientActivity.java 內容 與 MediaIPCActivity.java 大同小異, 下列需要更改之處將會用紅框標示 package stu.edu.mediaclient; import android.app.activity; 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.view.view.onclicklistener; import android.widget.button; public class MediaClientActivity extends Activity implements OnClickListener { private Button start, pause, stop; private Messenger locms = new Messenger(new Handler()); private Messenger romms; private static final int SONG2 = 2; private static final int PAUSE = 3; private static final int STOP = 4; private static final String tag = "MediaIPCActivity"; private ServiceConnection serviceconnection = new ServiceConnection() { public void onservicedisconnected(componentname name) { Log.i(tag, "onservicedisconnected"); romms = null; ; public void onserviceconnected(componentname name, IBinder service) { Log.i(tag, "onserviceconnected"); romms = new Messenger(service); class handler extends Handler { @Override public void handlemessage(message msg) { switch (msg.what) { case SONG2: settitle("play song2"); case PAUSE: settitle("meida Pause"); case STOP: settitle("media Stop");
/** Called when the activity is first created. */ @Override public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); start = (Button) findviewbyid(r.id.start); pause = (Button) findviewbyid(r.id.pause); stop = (Button) findviewbyid(r.id.stop); start.setonclicklistener(this); pause.setonclicklistener(this); stop.setonclicklistener(this); // 呼叫名為 " stu.edu.mediaipc.romteservicetransact" 的 Service bindservice(new Intent("stu.edu.mediaipc.RomteServiceTransact"), serviceconnection, Context.BIND_AUTO_CREATE); @Override public void onclick(view v) { Message message = null; try { if (v == start) message = Message.obtain(null, SONG2); else if (v == pause) message = Message.obtain(null, PAUSE); else if (v == stop) message = Message.obtain(null, STOP); message.replyto = locms; romms.send(message); catch (RemoteException e) { e.printstacktrace();
Part IV 測試 Step 1 執行應用程式並進行撥放音樂 啟動 MeidaIPC 按播放 按 home 鍵啟動 MediaClient 按播放