Android組件系列----Android Service組件深入解析
【聲明】
歡迎轉載,但請保留文章原始出處→_→
生命壹號:http://www.cnblogs.com/smyhvae/
文章來源:http://www.cnblogs.com/smyhvae/p/4070518.html
【前言】
花了週末兩天的時間,整理了一下作爲Android四大組件之一的Service的基礎知識,通過這篇文章,應該可以明白:對Service的理解、在什麼地方使用、怎麼使用、要注意哪些問題等。
【本文主要內容】
一、Service的基本概念(四大組件之一)
二、定義(啓動)一個Service
- 1、如何定義(啓動)一個Service:
- 2、停止一個started服務有兩種方法
- 3、onStartCommand方法的返回值
三、IntentService
- 1、IntentService的引入
- 2、IntentService的作用
- 3、IntentService的用法
- 4、Service和Thread的關係
四、使用Bind Service完成Service和Activity之間的通信
- 1、Bind Service的介紹
- 2、實現Service和Activity之間通信步驟
- 3、started服務與bind服務的區別
- 4、Service的生命週期
五、使用Bind Service完成IPC進程間通信:(在同一個APP內模擬)
- 1、Bind Service的介紹
- 2、在客戶端綁定一個服務的步驟
- 3、IPC(Inter-Process Communication)進程間通信機制
- 4、AIDL(Android Interface Definition Language)Android接口定義語言
- 5、IPC(進程間通訊)具體的步驟如下
- 6、讓Activity與一個遠程Service建立關聯的步驟:(在同一個APP內模擬)
- 7、AIDL支持的自定義數據類型
六、使用Bind Service完成IPC進程間通信:(兩個APP之間)
七、Messenger的使用
【正文】
一、Service的基本概念(四大組件之一)
Service是Android中實現程序後臺運行的解決方案,非常適合用於去執行哪些不需要和用戶交互而且還要求長期運行的任務。不能運行在一個獨立的進程當中,而是依賴與創建服務時所在的應用程序進程。只能在後臺運行,並且可以和其他組件進行交互。
Service可以在很多場合使用,比如播放多媒體的時候用戶啓動了其他Activity,此時要在後臺繼續播放;比如檢測SD卡上文件的變化;比如在後臺記錄你的地理信息位置的改變等等,總之服務是藏在後臺的。
服務不會自動開啓線程,我們需要在服務的內部手動創建子線程,並在這裏執行具體的任務。關於多線程的知識:可以參考另外一篇文章:Android多線程----異步消息處理機制之Handler詳解
二、定義(啓動)一個Service:
1、如何定義(啓動)一個Service:
核心步驟如下:
- 創建一個類繼承android.app.Service類,實現抽象方法onBind(),重寫onCreate()、onStartCommand()、onDestry();
- 在清單文件中配置Service。
新建一個Android項目ServiceTest,具體步驟如下:
(1)新建一個MyService類,繼承自Service,並重寫父類的onCreate()、onStartCommand()和onDestroy()方法,代碼如下:
1 package com.example.servicetest; 2 3 import android.app.Service; 4 import android.content.Intent; 5 import android.os.IBinder; 6 import android.util.Log; 7 8 public class MyService extends Service { 9 10 public static final String TAG = "MyService"; 11 12 //創建服務時調用 13 @Override 14 public void onCreate() { 15 super.onCreate(); 16 Log.d(TAG, "onCreate"); 17 } 18 19 //服務執行的操作 20 @Override 21 public int onStartCommand(Intent intent, int flags, int startId) { 22 Log.d(TAG, "onStartCommand"); 23 return super.onStartCommand(intent, flags, startId); 24 } 25 26 //銷燬服務時調用 27 @Override 28 public void onDestroy() { 29 super.onDestroy(); 30 Log.d(TAG, "onDestroy"); 31 } 32 33 @Override 34 public IBinder onBind(Intent intent) { 35 return null; 36 } 37 }
可以看到,我們只是在onCreate()、onStartCommand()和onDestroy()方法中分別打印了一句話,並沒有進行其它任何的操作,注意代碼註釋中這三個方法的作用。
onBind()方法是Service中唯一的一個抽象方法,所以必須要在子類裏實現。我們知道,Service可以有兩種啓動方式:一種是startService(),另一種是bindService()。第二種啓動方式纔會用到onBind()方法。我們這先用第一種方式啓動Service,所以暫時忽略onBind()方法。
(2)在清單文件中聲明:(和Activity標籤並列)
<service android:name=".MyService"> </service>
(3)修改activity_main.xml代碼,如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" ><span style="color: #0000ff;"><</span><span style="color: #800000;">Button </span><span style="color: #ff0000;">android:id</span><span style="color: #0000ff;">="@+id/button1_start_service"</span><span style="color: #ff0000;"> android:layout_width</span><span style="color: #0000ff;">="match_parent"</span><span style="color: #ff0000;"> android:layout_height</span><span style="color: #0000ff;">="wrap_content"</span><span style="color: #ff0000;"> android:text</span><span style="color: #0000ff;">="Start Service"</span> <span style="color: #0000ff;">/></span> <span style="color: #0000ff;"><</span><span style="color: #800000;">Button </span><span style="color: #ff0000;">android:id</span><span style="color: #0000ff;">="@+id/button2_stop_service"</span><span style="color: #ff0000;"> android:layout_width</span><span style="color: #0000ff;">="match_parent"</span><span style="color: #ff0000;"> android:layout_height</span><span style="color: #0000ff;">="wrap_content"</span><span style="color: #ff0000;"> android:text</span><span style="color: #0000ff;">="Stop Service"</span> <span style="color: #0000ff;">/></span>
</LinearLayout>
我們在佈局文件中加入了兩個按鈕,一個用於啓動Service,一個用於停止Service。
(4)在MainActivity作爲程序的主Activity,在裏面加入啓動Service和停止Service的邏輯,代碼如下:
1 package com.example.servicetest; 2 3 import android.app.Activity; 4 import android.content.Intent; 5 import android.os.Bundle; 6 import android.view.View; 7 import android.view.View.OnClickListener; 8 import android.widget.Button; 9 10 11 public class MainActivity extends Activity implements OnClickListener { 12 13 private Button button1_start_service; 14 15 private Button button2_stop_service; 16 17 @Override 18 protected void onCreate(Bundle savedInstanceState) { 19 super.onCreate(savedInstanceState); 20 setContentView(R.layout.activity_main); 21 button1_start_service = (Button) findViewById(R.id.button1_start_service); 22 button2_stop_service = (Button) findViewById(R.id.button2_stop_service); 23 button1_start_service.setOnClickListener(this); 24 button2_stop_service.setOnClickListener(this); 25 } 26 27 @Override 28 public void onClick(View v) { 29 switch (v.getId()) { 30 case R.id.button1_start_service: 31 Intent startIntent = new Intent(this, MyService.class); 32 startService(startIntent); 33 break; 34 case R.id.button2_stop_service: 35 Intent stopIntent = new Intent(this, MyService.class); 36 stopService(stopIntent); 37 break; 38 default: 39 break; 40 } 41 } 42 43 }
核心代碼:31行至32行、35行至36行。
可以看到,在Start Service按鈕的點擊事件裏,我們構建出了一個Intent對象,並調用startService()方法來啓動MyService。然後在Stop Serivce按鈕的點擊事件裏,我們同樣構建出了一個Intent對象,並調用stopService()方法來停止MyService。代碼的邏輯非常簡單。
這樣的話,一個簡單的帶有Service功能的程序就寫好了。
啓動和停止服務:
定義好服務之後,接下來看一下如何啓動和停止一個服務,這主要是藉助Intent來實現的。注意startService()和stopService()方法都是定義在Context類當中的,所以可以在MainActivity中直接調用這兩個方法。
運行上面的程序,點擊button1_start_service按鈕,啓動服務,後臺打印日誌如下:
說明服務啓動成功。
那麼如果我再連續點三次button1_start_service按鈕,後臺增加的日誌如下:
事實上,onCreate()方法只會在Service第一次被創建的時候調用,而onStartCommand()方法在每次啓動服務的時候都會調用。
我們還可以在正在“設置--應用---運行”中找到這個服務,如下圖所示:
點開上圖中的紅框部分,可以看到:
如果我們再點擊button2_stop_service按鈕或者點擊上圖中的“Stop”,MyService服務就停止掉了:
需要注意的是:
- 服務對象同時只會有一個
- 默認情況下,一個started的Service與啓動他的組件在同一個線程中。上面的實例中,服務就是在主線程中運行的,如果是在服務中完成耗時操作的話,容易造成主線程阻塞。
2、停止一個started服務有兩種方法:
(1)在外部使用stopService()
(2)在服務內部(onStartCommand方法內部)使用stopSelf()方法。
3、onStartCommand方法的返回值:
onStartCommand方法執行時,返回的是一個int型。這個整型可以有三個返回值:START_NOT_STICKY、START_STICKY、START_REDELIVER_INTENT
- START_NOT_STICKY:“非粘性的”。使用這個返回值時,如果在執行完onStartCommand方法後,服務被異常kill掉,系統不會自動重啓該服務。
- START_STICKY:如果Service進程被kill掉,保留Service的狀態爲開始狀態,但不保留遞送的intent對象。隨後系統會嘗試重新創建Service,由於服務狀態爲開始狀態,所以創建服務後一定會調用onStartCommand(Intent,int,int)方法。如果在此期間沒有任何啓動命令被傳遞到Service,那麼參數Intent將爲null。
- START_REDELIVER_INTENT:重傳Intent。使用這個返回值時,系統會自動重啓該服務,並將Intent的值傳入。
三、IntentService
1、IntentService的引入:
我們在第一段中就已經說了,服務中的代碼默認運行在主線程中,如果直接在服務裏執行一些耗時操作,容易造成ANR(Application Not Responding)異常,所以就需要用到多線程的知識了。
因此一個比較標準的服務可以這樣寫:
1 package com.example.servicetest; 2 3 import android.app.Service; 4 import android.content.Intent; 5 import android.os.IBinder; 6 7 public class MyService extends Service { 8 9 public static final String TAG = "MyService"; 10 11 //服務執行的操作 12 @Override 13 public int onStartCommand(Intent intent, int flags, int startId) { 14 new Thread(new Runnable() { 15 public void run() { 16 //處理具體的邏輯 17 stopSelf(); //服務執行完畢後自動停止 18 } 19 }).start(); 20 return super.onStartCommand(intent, flags, startId); 21 } 22 23 @Override 24 public IBinder onBind(Intent intent) { 25 // TODO Auto-generated method stub 26 return null; 27 } 28 29 }
核心代碼:14至19行,在子線程中處理具體的邏輯。
需要注意的是,如果沒有第17行的stopSelf(),服務一旦啓動後,就會一直處於運行狀態,必須調用stopService()或者stopSelf()方法才能讓服務停止下來;所以我們添加了17行的stopSelf(),服務執行完畢後會自動停止。
雖說上面的這種寫法並不複雜,但總會有一些程序猿忘記開啓線程,或者忘記調用stopSelf()方法。爲了可以簡單地創建一個異步的、會自動停止的服務,Android專門提供了一個IntentService類,這個類就很好的解決了上面所提到的兩種尷尬。另外,可以啓動IntentService多次,而每一個耗時操作會以工作隊列的方式在IntentService的onHandleIntent()回調方法中執行,並且每次只會執行一個工作線程,執行完第一個後,再執行第二個,以此類推。
2、IntentService的作用:
當我們需要這樣一次性完成的任務時,就可以使用IntentService來完成。
3、IntentService的用法:
我們在上面的項目ServiceTest基礎上進行修改,步驟如下:
(1)新建一個MyIntentService類,繼承自IntentService,並重寫父類的onHandleIntent()方法,代碼如下:
1 package com.example.servicetest; 2 3 import android.app.IntentService; 4 import android.content.Intent; 5 import android.util.Log; 6 7 public class MyIntentService extends IntentService{ 8 9 public MyIntentService() { 10 super("MyIntentService");//調用父類有參構造函數。這裏我們手動給服務起個名字爲:MyIntentService 11 // TODO Auto-generated constructor stub 12 } 13 14 //該方法在會在一個單獨的線程中執行,來完成工作任務。任務結束後,該Service自動停止 15 @Override 16 protected void onHandleIntent(Intent intent) { 17 // TODO Auto-generated method stub 18 for(int i = 0;i<3;i++) { 19 //打印當前線程的id 20 Log.d("MyIntentService","IntentService線程的id是:"+Thread.currentThread().getId()); 21 try { 22 Thread.sleep(1000); 23 } catch (InterruptedException e) { 24 // TODO Auto-generated catch block 25 e.printStackTrace(); 26 } 27 } 28 } 29 30 @Override 31 public void onDestroy() { 32 // TODO Auto-generated method stub 33 super.onDestroy(); 34 Log.d("MyIntentService","onDestroy"); 35 } 36 }
這裏首先要提供一個無參的構造方法,並且必須在其內部調用父類的有參構造方法(9至12行),我們在第10行手動將服務的名字改爲“MyIntentService”。
然後在子類中實現onHandleIntent()這個抽象方法,可以在這個方法裏去處理一些具體的邏輯,我們就用三次for循環,打印當前線程的id,每次延時1秒。
因爲這個服務在運行結束後會自動停止,所以我們在onDestroy()方法中打印日誌驗證一下。
(2)在清單文件中對服務進行註冊服務:
<service android:name=".MyIntentService"> </service>
(3)在activity_main.xml中添加一個按鈕button3_stop_intentservice,用於啓動MyIntentService服務,代碼略。
(4)在MainActivity裏面加入啓動IntentService的邏輯,核心代碼如下:
1 case R.id.button3_stop_intentservice: 2 Log.d("MainActivity","主線程的id是:"+Thread.currentThread().getId()); 3 Intent intentService = new Intent(this,MyIntentService.class); 4 startService(intentService); 5 default:
我們在第02行中,打印主線程的id。
運行程序,點擊按鈕button3_stop_intentservice,顯示如下:
由此可見,啓動一個IntentService和啓動一個普通的Service,步驟是一樣的。
4、Service和Thread的關係:
不少Android初學者都可能會有這樣的疑惑,Service和Thread到底有什麼關係呢?什麼時候應該用Service,什麼時候又應該用Thread?答案可能會有點讓你吃驚,因爲Service和Thread之間沒有任何關係!
之所以有不少人會把它們聯繫起來,主要就是因爲Service的後臺概念。Thread我們大家都知道,是用於開啓一個子線程,在這裏去執行一些耗時操作就不會阻塞主線程的運行。而Service我們最初理解的時候,總會覺得它是用來處理一些後臺任務的,一些比較耗時的操作也可以放在這裏運行,這就會讓人產生混淆了。但是,如果我告訴你Service其實是運行在主線程裏的,你還會覺得它和Thread有什麼關係嗎?
其實,後臺和子線程是兩個完全不同的概念:
Android的後臺就是指,它的運行是完全不依賴UI的。即使Activity被銷燬,或者程序被關閉,只要進程還在,Service就可以繼續運行。比如說一些應用程序,始終需要與服務器之間始終保持着心跳連接,就可以使用Service來實現。你可能又會問,Service既然是運行在主線程裏,在這裏一直執行着心跳連接,難道就不會阻塞主線程的運行嗎?當然會,但是我們可以在Service中再創建一個子線程,然後在這裏去處理耗時邏輯就沒問題了。
既然在Service裏也要創建一個子線程,那爲什麼不直接在Activity裏創建呢?這是因爲Activity很難對Thread進行控制,當Activity被銷燬之後,就沒有任何其它的辦法可以再重新獲取到之前創建的子線程的實例;而且在一個Activity中創建的子線程,另一個Activity無法對其進行操作。但是Service就不同了,所有的Activity都可以與Service進行關聯,然後可以很方便地操作其中的方法,即使Activity被銷燬了,之後只要重新與Service建立關聯,就又能夠獲取到原有的Service中Binder的實例。因此,使用Service來處理後臺任務,Activity就可以放心地finish,完全不需要擔心無法對後臺任務進行控制的情況。
所以說,一個比較標準的Service,就可以寫成本段中第1節的樣子。
四、使用Bind Service完成Service和Activity之間的通信
有沒有什麼辦法能讓它們倆的關聯更多一些呢?比如說在Activity中指揮Service去幹什麼,Service就去幹什麼。當然可以,只需要讓Activity和Service建立關聯就好了。
1、Bind Service的介紹:
應用程序組件(客戶端)通過調用bindService()方法能夠綁定服務,然後Android系統會調用服務的onBind()回調方法,則個方法會返回一個跟服務器端交互的Binder對象。
這個綁定是異步的,bindService()方法立即返回,並且不給客戶端返回IBinder對象。要接收IBinder對象,客戶端必須創建一個ServiceConnection類的實例,並且把這個實例傳遞給bindService()方法。ServiceConnection對象包含了一個系統調用的傳遞IBinder對象的回調方法。
注意:只有Activity、Service、Content Provider能夠綁定服務;BroadcastReceiver廣播接收器不能綁定服務。
2、實現Service和Activity之間通信步驟:
我們依然在第二段中的項目ServiceTest基礎上進行修改。
觀察上面第二段中MyService中的代碼,你會發現一直有一個onBind()方法我們都沒有使用到,這個方法其實就是用於和Activity建立關聯的,修改MyService中的代碼,如下所示:
1 package com.example.servicetest; 2 3 import android.app.Service; 4 import android.content.Intent; 5 import android.os.Binder; 6 import android.os.IBinder; 7 import android.util.Log; 8 9 public class MyService extends Service { 10 11 public static final String TAG = "MyService"; 12 13 private MyBinder mBinder = new MyBinder(); 14 15 @Override 16 public void onCreate() { 17 super.onCreate(); 18 Log.d(TAG, "onCreate"); 19 } 20 21 @Override 22 public int onStartCommand(Intent intent, int flags, int startId) { 23 Log.d(TAG, "onStartCommand"); 24 return super.onStartCommand(intent, flags, startId); 25 } 26 27 @Override 28 public void onDestroy() { 29 super.onDestroy(); 30 Log.d(TAG, "onDestroy"); 31 } 32 33 @Override 34 public IBinder onBind(Intent intent) { 35 return mBinder; //在這裏返回新建的MyBinder類 36 } 37 38 //MyBinder類,繼承Binder:讓裏面的方法執行下載任務,並獲取下載進度 39 class MyBinder extends Binder { 40 41 public void startDownload() { 42 Log.d("TAG", "startDownload() executed"); 43 // 執行具體的下載任務 44 } 45 public int getProgress(){ 46 Log.d("TAG", "getProgress() executed"); 47 return 0; 48 } 49 50 } 51 52 }
38至50行:新建一個MyBinder類,繼承Binder:讓裏面的方法執行下載任務,並獲取下載進度。當然,這裏只是兩個模擬方法,並沒有實現真正的功能,我們通過打印日誌的形式來體現。
接着創建MyBinder的實例(13行),然後在onBind()方法裏返回這個實例(35行)。
核心代碼是35行,返回這個mBinder,是一個IBinder類型,就可以把這個IBinder類型傳遞到MainActivity中,從而調用Service裏面的方法。下面就要看一看,在MainActivity是如何調用Service裏面的兩個方法的。
(2)檢查清單文件,是否已經對Service進行註冊:
<service android:name=".MyService" > </service>
(3)在activity_main.xml中繼續添加兩個按鈕button3_bind_service和button4_unbind_service,用於綁定服務和取消綁定服務。最終,activity_main.xml的完整代碼如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" ><span style="color: #0000ff;"><</span><span style="color: #800000;">Button </span><span style="color: #ff0000;">android:id</span><span style="color: #0000ff;">="@+id/button1_start_service"</span><span style="color: #ff0000;"> android:layout_width</span><span style="color: #0000ff;">="match_parent"</span><span style="color: #ff0000;"> android:layout_height</span><span style="color: #0000ff;">="wrap_content"</span><span style="color: #ff0000;"> android:text</span><span style="color: #0000ff;">="Start Service"</span> <span style="color: #0000ff;">/></span> <span style="color: #0000ff;"><</span><span style="color: #800000;">Button </span><span style="color: #ff0000;">android:id</span><span style="color: #0000ff;">="@+id/button2_stop_service"</span><span style="color: #ff0000;"> android:layout_width</span><span style="color: #0000ff;">="match_parent"</span><span style="color: #ff0000;"> android:layout_height</span><span style="color: #0000ff;">="wrap_content"</span><span style="color: #ff0000;"> android:text</span><span style="color: #0000ff;">="Stop Service"</span> <span style="color: #0000ff;">/></span> <span style="color: #0000ff;"><</span><span style="color: #800000;">Button </span><span style="color: #ff0000;">android:id</span><span style="color: #0000ff;">="@+id/button3_bind_service"</span><span style="color: #ff0000;"> android:layout_width</span><span style="color: #0000ff;">="match_parent"</span><span style="color: #ff0000;"> android:layout_height</span><span style="color: #0000ff;">="wrap_content"</span><span style="color: #ff0000;"> android:text</span><span style="color: #0000ff;">="bind Service"</span> <span style="color: #0000ff;">/></span> <span style="color: #0000ff;"><</span><span style="color: #800000;">Button </span><span style="color: #ff0000;">android:id</span><span style="color: #0000ff;">="@+id/button4_unbind_service"</span><span style="color: #ff0000;"> android:layout_width</span><span style="color: #0000ff;">="match_parent"</span><span style="color: #ff0000;"> android:layout_height</span><span style="color: #0000ff;">="wrap_content"</span><span style="color: #ff0000;"> android:text</span><span style="color: #0000ff;">="unbind Service"</span> <span style="color: #0000ff;">/></span>
</LinearLayout>
(4)接下來再修改MainActivity中的代碼,讓MainActivity和MyService之間建立關聯,代碼如下所示:
1 package com.example.servicetest; 2 3 import android.app.Activity; 4 import android.content.ComponentName; 5 import android.content.Intent; 6 import android.content.ServiceConnection; 7 import android.os.Bundle; 8 import android.os.IBinder; 9 import android.util.Log; 10 import android.view.View; 11 import android.view.View.OnClickListener; 12 import android.widget.Button; 13 14 public class MainActivity extends Activity implements OnClickListener { 15 16 private Button button1_start_service; 17 private Button button2_stop_service; 18 private Button button3_bind_service; 19 private Button button4_unbind_service; 20 21 private MyService.MyBinder myBinder; 22 23 //匿名內部類:服務連接對象 24 private ServiceConnection connection = new ServiceConnection() { 25 26 //當服務異常終止時會調用。注意,解除綁定服務時不會調用 27 @Override 28 public void onServiceDisconnected(ComponentName name) { 29 } 30 31 //和服務綁定成功後,服務會回調該方法 32 @Override 33 public void onServiceConnected(ComponentName name, IBinder service) { 34 myBinder = (MyService.MyBinder) service; 35 //在Activity中調用Service裏面的方法 36 myBinder.startDownload(); 37 myBinder.getProgress(); 38 } 39 }; 40 41 @Override 42 protected void onCreate(Bundle savedInstanceState) { 43 super.onCreate(savedInstanceState); 44 setContentView(R.layout.activity_main); 45 button1_start_service = (Button) findViewById(R.id.button1_start_service); 46 button2_stop_service = (Button) findViewById(R.id.button2_stop_service); 47 button3_bind_service = (Button) findViewById(R.id.button3_bind_service); 48 button4_unbind_service = (Button) findViewById(R.id.button4_unbind_service); 49 50 button1_start_service.setOnClickListener(this); 51 button2_stop_service.setOnClickListener(this); 52 button3_bind_service.setOnClickListener(this); 53 button4_unbind_service.setOnClickListener(this); 54 } 55 56 @Override 57 public void onClick(View v) { 58 switch (v.getId()) { 59 case R.id.button1_start_service: 60 Intent startIntent = new Intent(this, MyService.class); 61 startService(startIntent); 62 break; 63 case R.id.button2_stop_service: 64 Intent stopIntent = new Intent(this, MyService.class); 65 stopService(stopIntent); 66 break; 67 case R.id.button3_bind_service: 68 Intent bindIntent = new Intent(this, MyService.class); 69 bindService(bindIntent, connection, BIND_AUTO_CREATE); 70 break; 71 case R.id.button4_unbind_service: 72 unbindService(connection); 73 break; 74 75 default: 76 break; 77 } 78 } 79 80 }
可以看到,這裏我們首先創建了一個ServiceConnection的匿名類(24行),在裏面重寫了onServiceConnected()方法和onServiceDisconnected()方法,如果當前Activity與服務連接成功後,服務會回調onServiceConnected()方法,
在onServiceConnected()方法中,我們又通過向下轉型得到了MyBinder的實例(34行),有了這個實例,Activity和Service之間的關係就變得非常緊密了。現在我們可以在Activity中根據具體的場景來調用MyBinder中的任何public方法(36、37行),即實現了Activity指揮Service幹什麼Service就去幹什麼的功能。
當然,現在Activity和Service其實還沒關聯起來了呢,這個功能是在Bind Service按鈕的點擊事件裏完成的。可以看到,這裏我們仍然是構建出了一個Intent對象,然後調用bindService()方法將Activity和Service進行綁定。bindService()方法接收三個參數,第一個參數就是剛剛構建出的Intent對象,第二個參數是前面創建出的ServiceConnection的實例,第三個參數是一個標誌位,這裏傳入BIND_AUTO_CREATE表示在Activity和Service建立關聯後會自動創建Service(即使之前沒有創建Service也沒有關係),這會使得MyService中的onCreate()方法得到執行,但onStartCommand()方法不會執行。
然後如何我們想解除Activity和Service之間的關聯怎麼辦呢?調用一下unbindService()方法就可以了,這也是Unbind Service按鈕的點擊事件裏實現的邏輯。
現在讓我們重新運行一下程序吧,在MainActivity中點擊一下Bind Service按鈕,LogCat裏的打印日誌如下圖所示:
可以看到,只點擊了Bind Service按鈕,但是oncreate()方法得到了執行,而onStartCommand()方法不會執行。
另外需要注意,任何一個Service在整個應用程序範圍內都是通用的,即MyService不僅可以和MainActivity建立關聯,還可以和任何一個Activity建立關聯,而且在建立關聯時它們都可以獲取到相同的MyBinder實例。
如何銷燬Service:
根據上面第一段的知識,我們介紹了銷燬Service最簡單的一種情況:現在卸載程序,重新運行程序,點擊Start Service按鈕啓動Service,再點擊Stop Service按鈕停止Service,這樣MyService就被銷燬了:
現在回到本段內容。卸載程序,重新開始。那麼如果我們只點擊的Bind Service按鈕呢?由於在綁定Service的時候指定的標誌位是BIND_AUTO_CREATE,說明點擊Bind Service按鈕的時候Service也會被創建,這時應該怎麼銷燬Service呢?其實也很簡單,點擊一下Unbind Service按鈕,將Activity和Service的關聯解除就可以了:
以上這兩種銷燬的方式都很好理解。那麼如果我們既點擊了Start Service按鈕,又點擊了Bind Service按鈕會怎麼樣呢?這個時候你會發現,不管你是單獨點擊Stop Service按鈕還是Unbind Service按鈕,Service都不會被銷燬,必要將Unbind Service按鈕和Stop Service按鈕都點擊一下(沒有先後順序),Service纔會被銷燬。也就是說,點擊Stop Service按鈕只會讓Service停止,點擊Unbind Service按鈕只會讓Service和Activity解除關聯,一個Service必須要在既沒有和任何Activity關聯又處理停止狀態的時候纔會被銷燬。
點擊Unbind Service按鈕後,再次點擊Unbind Service按鈕按鈕引發的問題:
假設現在Service和Activity已經相關聯了,點擊Unbind Service按鈕能夠解除綁定,如果繼續點擊Unbind Service按鈕,程序會異常退出,這說明代碼不夠完善,我們需要在代碼中加一個判斷是否綁定的標記mBound。在改MainActivity中增加一部分代碼,最終改MainActivity的完整代碼如下:(加粗字體是添加的內容)
1 package com.example.servicetest02; 2 3 import android.app.Activity; 4 import android.content.ComponentName; 5 import android.content.Intent; 6 import android.content.ServiceConnection; 7 import android.os.Bundle; 8 import android.os.IBinder; 9 import android.util.Log; 10 import android.view.View; 11 import android.view.View.OnClickListener; 12 import android.widget.Button; 13 14 public class MainActivity extends Activity implements OnClickListener { 15 private Button button1_start_service; 16 private Button button2_stop_service; 17 private Button button3_bind_service; 18 private Button button4_unbind_service; 19 private MyService.MyBinder myBinder; 20 21 boolean mBound = false; //一開始,並沒有和Service綁定.這個參數是用來顯示綁定狀態 22 23 //匿名內部類:服務連接對象 24 private ServiceConnection connection = new ServiceConnection() { 25 26 //當服務異常終止時會調用。注意,解除綁定服務時不會調用 27 @Override 28 public void onServiceDisconnected(ComponentName name) { 29 mBound = false; //服務異常終止時,狀態爲未綁定 30 } 31 32 //和服務綁定成功後,服務會回調該方法 33 @Override 34 public void onServiceConnected(ComponentName name, IBinder service) { 35 myBinder = (MyService.MyBinder) service; 36 //在Activity中調用Service裏面的方法 37 myBinder.startDownload(); 38 myBinder.getProgress(); 39 mBound = true; //true說明是綁定狀態 40 } 41 }; 42 @Override 43 protected void onCreate(Bundle savedInstanceState) { 44 super.onCreate(savedInstanceState); 45 setContentView(R.layout.activity_main); 46 button1_start_service = (Button) findViewById(R.id.button1_start_service); 47 button2_stop_service = (Button) findViewById(R.id.button2_stop_service); 48 button3_bind_service = (Button) findViewById(R.id.button3_bind_service); 49 button4_unbind_service = (Button) findViewById(R.id.button4_unbind_service); 50 button1_start_service.setOnClickListener(this); 51 button2_stop_service.setOnClickListener(this); 52 button3_bind_service.setOnClickListener(this); 53 button4_unbind_service.setOnClickListener(this); 54 } 55 @Override 56 public void onClick(View v) { 57 switch (v.getId()) { 58 case R.id.button1_start_service: 59 Intent startIntent = new Intent(this, MyService.class); 60 startService(startIntent); 61 break; 62 case R.id.button2_stop_service: 63 Intent stopIntent = new Intent(this, MyService.class); 64 stopService(stopIntent); 65 break; 66 case R.id.button3_bind_service: 67 Intent bindIntent = new Intent(this, MyService.class); 68 bindService(bindIntent, connection, BIND_AUTO_CREATE); 69 break; 70 case R.id.button4_unbind_service: 71 //如果和Service是綁定的狀態,就解除綁定。 72 if(mBound){ 73 unbindService(connection); 74 mBound=false; 75 } 76 break; 77 78 default: 79 break; 80 } 81 } 82 }
添加的代碼是第21行、29行、72行至74行。
這樣的話,連續點擊Unbind Service按鈕,就不會使程序出現異常。
3、started服務與bind服務的區別:
區別一:生命週期
- 通過started方式的服務會一直運行在後臺,需要由組件本身或外部組件來停止服務纔會以結束運行
- bind方式的服務,生命週期就要依賴綁定的組件
區別二:參數傳遞
- started服務可以給啓動的服務對象傳遞參數,但無法獲取服務中方法的返回值
- bind服務可以給啓動的服務對象傳遞參數,也可以通過綁定的業務對象獲取返回結果
實際開發中的技巧;
- 第一次先使用started方式來啓動一個服務
- 之後可以使用bind的方式綁定服務,從而可以直接調用業務方法獲取返回值
4、Service的生命週期:
一旦在項目的任何位置調用了Context的startService()方法,相應的服務就會啓動起來,並回調onstartCommand()方法。如果這個服務之前還沒有創建過,onCreate()方法會先於onstartCommand()方法執行。服務啓動過後,會一直保持運行狀態,直到stopService()或stopself()方法被調用。注意雖然每次調用一次startService()方法,onstartCommand()方法就會以執行一次,但實際上每個服務都只會存在一個實例。所以不管你調用了多少次startService()方法,只需調用一次stopService()或stopself()方法,服務就會停止。
另外,還可以調用Context的bindService()來獲取一個服務的持久連接,這時就會回調服務中的onBind()方法。類似地,如果這個服務之前還沒有創建過,onCreate()方法會先於onBind()方法執行。之後調用方可以獲取到onBind()方法裏返回的IBinder對象的實例,這樣,就能自由地和服務進行通信了。只要調用方和服務之間的連接沒有斷開,服務就會一直保持運行狀態。
五、使用Bind Service完成IPC進程間通信:(在同一個APP內模擬)
既然是在在同一個APP內模擬進程間通信,其實就是完成進程內通信,但是原理都是一樣的嘛。
也就是說,要實現:讓Activity與一個遠程Service建立關聯,這就要使用AIDL來進行跨進程通信了(IPC)。這裏把Bind Service及其他的概念再重複一下:
1、Bind Service的介紹:
應用程序組件(客戶端)通過調用bindService()方法能夠綁定服務,然後Android系統會調用服務的onBind()回調方法,則個方法會返回一個跟服務器端交互的Binder對象。
這個綁定是異步的,bindService()方法立即返回,並且不給客戶端返回IBinder對象。要接收IBinder對象,客戶端必須創建一個ServiceConnection類的實例,並且把這個實例傳遞給bindService()方法。ServiceConnection對象包含了一個系統調用的傳遞IBinder對象的回調方法。
注意:只有Activity、Service、Content Provider能夠綁定服務;BroadcastReceiver廣播接收器不能綁定服務。
2、在客戶端綁定一個服務的步驟:
(1)實現ServiceConnection抽象類。實現過程中,必須重寫一下兩個回調方法:
- onServiceConnected() 和服務綁定成功後,系統會調用這個方法來發送由服務的onBind()方法返回的IBinder對象
- onServiceDisconnected() 當服務異常終止時會調用(如服務崩潰或被殺死時)。注意,在客戶端解除綁定時不會調用該方法。
(2)調用bindService()方法來傳遞ServiceConnection類的實現;
(3)當系統調用你的onServiceConnected()回調方法時,你就可以開始使用接口中定義的方法來調用服務了
(4)調用unbindService()方法斷開與服務的鏈接。
注:bindService()和unbindService()方法都是Context類中的方法。
3、IPC(Inter-Process Communication)進程間通信機制:
在同一進程中,各個組件進行通信是十分方便的,普通的函數調用就可以解決;但是對於不同的進程中的組件來說,要進行通信,就需要用到Android的IPC機制了。
對應用開發者來說,Android的IBinder/Binder框架實現了Android的IPC通信。當然,IBinder/Binder框架也可以用來實現進程內通信(本地通信),也可以實現進程間通信(遠程通信)
從Android SDK中對IBinder/Binder的解釋可知,IBinder/Binder是Android遠程對象的基本接口,它是Android用於提供高性能IPC通信而設計的一套輕量級遠程調用機制的核心部分。該接口描述了與一個遠程對象進行通信的抽象協議。
4、AIDL(Android Interface Definition Language)Android接口定義語言:
AIDL它可以用於讓某個Service與多個應用程序組件之間進行跨進程通信,從而可以實現多個應用程序共享同一個Service的功能。
AIDL支持的類型:八大基本數據類型、String類型、CharSequence、List、Map、自定義。
來看下面的這張原理圖:
上圖中,如果A應用程序想訪問B應用程序中的業務對象,可以先讓A綁定B應用中的Service,然後通過Service去訪問B中的業務對象。我們可以用AIDL來描述需要被別人調用的接口(即B中的業務對象)。
5、IPC(進程間通訊)具體的步驟如下:
- 使用AIDL定義業務接口,通過ADT工具來生成一個java類,此類實現了進程間遠程通訊的代理
- 編寫自己的業務類(繼承生成的類中的Stub)來實現業務接口功能
- 再通過綁定Service的方式來暴露此業務對象,給其它組件提供功能
- 調用者組件通過bindService方法綁定服務,從而獲取綁定成功後的遠程業務對象或本地業務對象,然後就可以調用相關功能。
- 注意:一般在使用完綁定服務後,需要解除綁定。
下面就通過代碼來實現。
6、讓Activity與一個遠程Service建立關聯的步驟:(在同一個APP內模擬)
新建一個全新的Android工程ServiceTest02。
(1)新建IPerson.aidl文件,代碼如下所示:
1 package com.example.servicetest02; 2 interface IPerson{ 3 void setName(String name); 4 void setSex(String sex); 5 void setAge(int age); 6 String getPerson(); 7 }
這個文件裏,添加我們需要的業務方法。第01行是包名。注意不要寫public等修飾符。(如果這個文件寫錯了,程序會報錯,後面的Java文件也不會自從生成)
文件保存之後,ADT會在gen目錄下自動生成一個對應的Java文件,如下圖所示:
之後,程序運行的時候使用的是這個Java文件,與aidl文件就沒有關係了。
我們來大致分析一下這個自動生成的Java文件。完整版代碼如下:
1 /* 2 * This file is auto-generated. DO NOT MODIFY. 3 * Original file: E:\\workspace\\xiongdilian\\ServiceTest02\\src\\com\\example\\servicetest02\\IPerson.aidl 4 */ 5 package com.example.servicetest02; 6 7 public interface IPerson extends android.os.IInterface { 8 /** Local-side IPC implementation stub class. */ 9 public static abstract class Stub extends android.os.Binder implements 10 com.example.servicetest02.IPerson { 11 private static final java.lang.String DESCRIPTOR = "com.example.servicetest02.IPerson"; 12 13 /** Construct the stub at attach it to the interface. */ 14 public Stub() { 15 this.attachInterface(this, DESCRIPTOR); 16 } 17 18 /** 19 * Cast an IBinder object into an com.example.servicetest02.IPerson 20 * interface, generating a proxy if needed. 21 */ 22 public static com.example.servicetest02.IPerson asInterface( 23 android.os.IBinder obj) { 24 if ((obj == null)) { 25 return null; 26 } 27 android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); 28 if (((iin != null) && (iin instanceof com.example.servicetest02.IPerson))) { 29 return ((com.example.servicetest02.IPerson) iin); 30 } 31 return new com.example.servicetest02.IPerson.Stub.Proxy(obj); 32 } 33 34 @Override 35 public android.os.IBinder asBinder() { 36 return this; 37 } 38 39 @Override 40 public boolean onTransact(int code, android.os.Parcel data, 41 android.os.Parcel reply, int flags) 42 throws android.os.RemoteException { 43 switch (code) { 44 case INTERFACE_TRANSACTION: { 45 reply.writeString(DESCRIPTOR); 46 return true; 47 } 48 case TRANSACTION_setName: { 49 data.enforceInterface(DESCRIPTOR); 50 java.lang.String _arg0; 51 _arg0 = data.readString(); 52 this.setName(_arg0); 53 reply.writeNoException(); 54 return true; 55 } 56 case TRANSACTION_setSex: { 57 data.enforceInterface(DESCRIPTOR); 58 java.lang.String _arg0; 59 _arg0 = data.readString(); 60 this.setSex(_arg0); 61 reply.writeNoException(); 62 return true; 63 } 64 case TRANSACTION_setAge: { 65 data.enforceInterface(DESCRIPTOR); 66 int _arg0; 67 _arg0 = data.readInt(); 68 this.setAge(_arg0); 69 reply.writeNoException(); 70 return true; 71 } 72 case TRANSACTION_getPerson: { 73 data.enforceInterface(DESCRIPTOR); 74 java.lang.String _result = this.getPerson(); 75 reply.writeNoException(); 76 reply.writeString(_result); 77 return true; 78 } 79 } 80 return super.onTransact(code, data, reply, flags); 81 } 82 83 private static class Proxy implements com.example.servicetest02.IPerson { 84 private android.os.IBinder mRemote; 85 86 Proxy(android.os.IBinder remote) { 87 mRemote = remote; 88 } 89 90 @Override 91 public android.os.IBinder asBinder() { 92 return mRemote; 93 } 94 95 public java.lang.String getInterfaceDescriptor() { 96 return DESCRIPTOR; 97 } 98 99 @Override 100 public void setName(java.lang.String name) 101 throws android.os.RemoteException { 102 android.os.Parcel _data = android.os.Parcel.obtain(); 103 android.os.Parcel _reply = android.os.Parcel.obtain(); 104 try { 105 _data.writeInterfaceToken(DESCRIPTOR); 106 _data.writeString(name); 107 mRemote.transact(Stub.TRANSACTION_setName, _data, _reply, 0); 108 _reply.readException(); 109 } finally { 110 _reply.recycle(); 111 _data.recycle(); 112 } 113 } 114 115 @Override 116 public void setSex(java.lang.String sex) 117 throws android.os.RemoteException { 118 android.os.Parcel _data = android.os.Parcel.obtain(); 119 android.os.Parcel _reply = android.os.Parcel.obtain(); 120 try { 121 _data.writeInterfaceToken(DESCRIPTOR); 122 _data.writeString(sex); 123 mRemote.transact(Stub.TRANSACTION_setSex, _data, _reply, 0); 124 _reply.readException(); 125 } finally { 126 _reply.recycle(); 127 _data.recycle(); 128 } 129 } 130 131 @Override 132 public void setAge(int age) throws android.os.RemoteException { 133 android.os.Parcel _data = android.os.Parcel.obtain(); 134 android.os.Parcel _reply = android.os.Parcel.obtain(); 135 try { 136 _data.writeInterfaceToken(DESCRIPTOR); 137 _data.writeInt(age); 138 mRemote.transact(Stub.TRANSACTION_setAge, _data, _reply, 0); 139 _reply.readException(); 140 } finally { 141 _reply.recycle(); 142 _data.recycle(); 143 } 144 } 145 146 @Override 147 public java.lang.String getPerson() 148 throws android.os.RemoteException { 149 android.os.Parcel _data = android.os.Parcel.obtain(); 150 android.os.Parcel _reply = android.os.Parcel.obtain(); 151 java.lang.String _result; 152 try { 153 _data.writeInterfaceToken(DESCRIPTOR); 154 mRemote.transact(Stub.TRANSACTION_getPerson, _data, _reply, 155 0); 156 _reply.readException(); 157 _result = _reply.readString(); 158 } finally { 159 _reply.recycle(); 160 _data.recycle(); 161 } 162 return _result; 163 } 164 } 165 166 static final int TRANSACTION_setName = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); 167 static final int TRANSACTION_setSex = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1); 168 static final int TRANSACTION_setAge = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2); 169 static final int TRANSACTION_getPerson = (android.os.IBinder.FIRST_CALL_TRANSACTION + 3); 170 } 171 172 public void setName(java.lang.String name) 173 throws android.os.RemoteException; 174 175 public void setSex(java.lang.String sex) throws android.os.RemoteException; 176 177 public void setAge(int age) throws android.os.RemoteException; 178 179 public java.lang.String getPerson() throws android.os.RemoteException; 180 }
分析:
這個Java文件實際上是一個接口,同時生成了在aidl文件中定義的四個方法,並拋出了遠程調用的異常。我們按住Ctrl鍵,點開上圖中藍框部分的IInterface,查看一下源代碼:
可以看到,在IInterface接口裏,定義了一個接口IBinder,這是IPC機制的核心接口。
再回來看IPerson.java文件的第9行定義了這樣一個抽象類:
上圖中的Stub類可以比作存根。Stub類繼承了Binder類,同時實現了IPerson接口(沒有實現IPerson裏的方法)。所以進一步理解爲:Stub既是IPerson裏的內部類,也是一個IPerson。
(2)新建PersonImpl類,繼承IPerson.Stub類,重寫父類裏的方法。代碼如下:(也就是說,根據上面的java類,生成業務對象,即原理圖中B應用的業務對象)
1 package com.example.servicetest02; 2 3 import android.os.RemoteException; 4 5 public class PersonImpl extends IPerson.Stub{ 6 7 private String name; 8 private String sex; 9 private int age; 10 11 @Override 12 public void setName(String name) throws RemoteException { 13 // TODO Auto-generated method stub 14 this.name = name; 15 16 } 17 18 @Override 19 public void setSex(String sex) throws RemoteException { 20 // TODO Auto-generated method stub 21 this.sex = sex; 22 23 } 24 @Override 25 public void setAge(int age) throws RemoteException { 26 // TODO Auto-generated method stub 27 this.age = age; 28 29 } 30 31 @Override 32 public String getPerson() throws RemoteException { 33 // TODO Auto-generated method stub 34 return "name="+name+",sex="+sex+",age="+age; 35 } 36 }
(3)新建類MyService,代碼如下:
1 package com.example.servicetest02; 2 3 import android.app.Service; 4 import android.content.Intent; 5 import android.os.IBinder; 6 import android.util.Log; 7 8 public class MyService extends Service { 9 10 public static final String TAG = "MyService"; 11 12 PersonImpl mBinder = new PersonImpl(); 13 14 @Override 15 public void onCreate() { 16 super.onCreate(); 17 Log.d(TAG, "onCreate"); 18 } 19 20 @Override 21 public int onStartCommand(Intent intent, int flags, int startId) { 22 Log.d(TAG, "onStartCommand"); 23 return super.onStartCommand(intent, flags, startId); 24 } 25 26 @Override 27 public void onDestroy() { 28 super.onDestroy(); 29 Log.d(TAG, "onDestroy"); 30 } 31 32 @Override 33 public IBinder onBind(Intent intent) { 34 Log.d("MyService", "onBind"); 35 return mBinder; //在這裏返回新建的MyBinder類 36 } 37 38 }
核心代碼:12行和35行。
因爲PersonImpl類繼承了IPerson.Stub,而Stub繼承了Binder,Binder又實現了IBinder。所以,PersonImpl可以理解爲一個IBinder。於是可以在第35行返回PersonImpl的實例。
(4)在清單文件中添加權限:
<service android:name=".MyService"> </service>
現在,B應用的業務對象和服務建立好了。B應用的業務對象通過與Service綁定,讓Service把業務對象暴露給了A應用或者其他的應用。也就是說,Service最終並沒有實現業務功能。
如果要讓A應用來訪問,該怎麼做呢?
(5)在activity_main.xml中添加兩個按鈕button_bind_service和button_unbind_service,用於綁定遠程服務和取消綁定。activity_main.xml的代碼如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" ><span style="color: #0000ff;"><</span><span style="color: #800000;">Button </span><span style="color: #ff0000;">android:id</span><span style="color: #0000ff;">="@+id/button_bind_service"</span><span style="color: #ff0000;"> android:layout_width</span><span style="color: #0000ff;">="match_parent"</span><span style="color: #ff0000;"> android:layout_height</span><span style="color: #0000ff;">="wrap_content"</span><span style="color: #ff0000;"> android:text</span><span style="color: #0000ff;">="bind Service"</span> <span style="color: #0000ff;">/></span> <span style="color: #0000ff;"><</span><span style="color: #800000;">Button </span><span style="color: #ff0000;">android:id</span><span style="color: #0000ff;">="@+id/button_unbind_service"</span><span style="color: #ff0000;"> android:layout_width</span><span style="color: #0000ff;">="match_parent"</span><span style="color: #ff0000;"> android:layout_height</span><span style="color: #0000ff;">="wrap_content"</span><span style="color: #ff0000;"> android:text</span><span style="color: #0000ff;">="unbind Service"</span> <span style="color: #0000ff;">/></span>
</LinearLayout>
(6)MainActivity中的代碼,如下所示:
1 package com.example.servicetest02; 2 3 import android.app.Activity; 4 import android.content.ComponentName; 5 import android.content.Intent; 6 import android.content.ServiceConnection; 7 import android.os.Bundle; 8 import android.os.IBinder; 9 import android.os.RemoteException; 10 import android.util.Log; 11 import android.view.View; 12 import android.view.View.OnClickListener; 13 import android.widget.Button; 14 15 public class MainActivity extends Activity implements OnClickListener { 16 17 public static final String TAG = "MainActivity"; 18 private Button button_bind_service; 19 private Button button_unbind_service; 20 21 private IPerson person; 22 23 boolean mBound = false; // 一開始,並沒有和Service綁定.這個參數是用來判斷綁定狀態 24 25 // 匿名內部類:服務連接對象 26 private ServiceConnection connection = new ServiceConnection() { 27 28 // 當服務異常終止時會調用。注意,解除綁定服務時不會調用 29 @Override 30 public void onServiceDisconnected(ComponentName name) { 31 mBound = false; // 服務異常終止時,狀態爲未綁定 32 } 33 34 // 和服務綁定成功後,服務會回調該方法。在這個方法裏調用的業務對象中的內容 35 @Override 36 public void onServiceConnected(ComponentName name, IBinder service) { 37 Log.d(TAG, "onServiceConnected"); 38 person = IPerson.Stub.asInterface(service); // 得到person對象 39 Log.d("person", "person對象的內存地址是" + person); // 打印出person對象的內存地址 40 try { 41 person.setName("生命壹號"); 42 person.setAge(22); 43 person.setSex("男"); 44 String p = person.getPerson(); 45 Log.d("person", "person的信息是" + p); 46 } catch (RemoteException e) { 47 // TODO Auto-generated catch block 48 e.printStackTrace(); 49 } 50 mBound = true; //true說明是綁定狀態 51 52 } 53 }; 54 55 @Override 56 protected void onCreate(Bundle savedInstanceState) { 57 super.onCreate(savedInstanceState); 58 setContentView(R.layout.activity_main); 59 button_bind_service = (Button) findViewById(R.id.button_bind_service); 60 button_unbind_service = (Button) findViewById(R.id.button_unbind_service); 61 button_bind_service.setOnClickListener(this); 62 button_unbind_service.setOnClickListener(this); 63 64 } 65 66 @Override 67 public void onClick(View v) { 68 switch (v.getId()) { 69 case R.id.button_bind_service: 70 Intent bindIntent = new Intent(this, MyService.class); 71 bindService(bindIntent, connection, BIND_AUTO_CREATE); 72 break; 73 case R.id.button_unbind_service: 74 // 如果和Service是綁定的狀態,就解除綁定。 75 if (mBound) { 76 unbindService(connection); 77 mBound = false; 78 } 79 break; 80 81 default: 82 break; 83 } 84 } 85 86 }
核心代碼是第38行:可以看到,這裏首先使用了MyAIDLService.Stub.asInterface()方法將傳入的IBinder對象傳換成了IPerson對象,接下來就可以調用在IPerson.aidl文件中定義的所有接口了(41至44行)。調用之後,我們在後臺打印輸出。
運行程序,點擊按鈕,效果如下:
由此可見,我們確實已經成功實現跨進程通信了,在一個進程中訪問到了另外一個進程中的方法。 注意,這個Service是運行在主線程當中的,畢竟我們是在本地模擬的嘛。
另外注意藍色箭頭處,可以看出,這個person其實就是personImpl,因爲是在本地調用。所以說,目前的跨進程通信其實並沒有什麼實質上的作用,因爲這只是在一個Activity裏調用了同一個應用程序的Service裏的方法。而跨進程通信的真正意義是爲了讓一個應用程序去訪問另一個應用程序中的Service,以實現共享Service的功能。那麼下面我們自然要學習一下,如何才能在其它的應用程序中調用到MyService裏的方法。
繼續回顧第(1)步中自動生成的IPerson.java文件,截取第22至32行,摘抄如下:
1 public static com.example.servicetest02.IPerson asInterface( 2 android.os.IBinder obj) { 3 if ((obj == null)) { 4 return null; 5 } 6 android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); 7 if (((iin != null) && (iin instanceof com.example.servicetest.IPerson))) { 8 return ((com.example.servicetest.IPerson) iin); 9 } 10 return new com.example.servicetest.IPerson.Stub.Proxy(obj); 11 }
代碼解釋:
上方第06行的iin代表的是,查詢本地對象返回的結果。
07行:如果iin不爲空,並且iin爲IPerson,那就將iin強制轉換爲IPerson(08行)。很顯然,這裏是進程內通信(本地通信)要用到的。也就是本段中的例子。
10行:代表的是進程間通信(遠程通信),此時第07行的if語句不成立,於是返回第10行的代理對象Proxy(obj)。
實現進程間通信的兩個辦法:
如果要實現進程間通信,就必須讓MainActivity和Service相互獨立。有兩個辦法:
(1)辦法一:將本地的Service設置爲遠程。只需要在清單文件中註冊Service的時候將它的android:process屬性指定成:remote就可以了,代碼如下所示:
<service android:name=".MyService" android:process=":remote"> </service>
後臺打印日誌如下:
上圖的紅框部分顯示,Service和Activity並非在同一個線程內,連包名都不一樣。而IPeron也並非是本地的IPeron。
如果將這種方法應用到上面的第四段中,情形是這樣的:
點擊button1_start_service,服務啓動。既然已經將Service的android:process屬性指定成:remote,此時Service和Activity不在同一個線程內,那麼即使在Service的onStartCommand()方法中執行耗時操作而不重新開啓子線程,程序也不會阻塞。
但是,如果點擊button3_bind_service按鈕綁定服務,程序會崩潰的。這是因爲,目前MyService已經是一個遠程Service了,Activity和Service運行在兩個不同的進程當中,這時就不能再使用傳統的建立關聯的方式,程序也就崩潰了。
現在我們總結一下:
第四段中使用的是傳統的方式和Service建立關聯,默認MainActivity和MyService在同一個線程內,如果將Service的android:process屬性指定成:remote,此時MainActivity和MyService將在不同的線程內,但是無法綁定服務。
本段中(第五段)使用的是IPC跨進程通信,MainActivity和MyService在不同的進程中,可以綁定遠程服務。
(2)辦法二:新建另外一個工程,真正實現遠程通信。這就是我們下一段(第六段)要講的內容。
我們還是先回過頭來再鞏固一下本段中AIDL的知識吧。
7、AIDL支持的自定義數據類型:
我們在本段中的第4小結講到,AIDL支持的類型:八大基本數據類型、String類型、CharSequence、List、Map、自定義,那我們就來詳細說下這個自定義數據類型。
由於這是在不同的進程之間傳遞數據,Android對這類數據的格式支持是非常有限的,基本上只能傳遞Java的基本數據類型、字符串、List或Map等。那麼如果我想傳遞一個自定義的類該怎麼辦呢?這就必須要讓這個類去實現Parcelable接口,並且要給這個類也定義一個同名的AIDL文件進行聲明。這部分內容並不複雜,而且和Service關係不大。具體操作如下:
重新建一個工程ServiceTest03。步驟如下:
(1)新建一個Student類去實現Parcelable接口。Student類是作爲傳遞的自定義類:
1 package com.example.servicetest; 2 3 import android.os.Parcel; 4 import android.os.Parcelable; 5 6 public class Student implements Parcelable { 7 private String name; 8 private String sex; 9 10 public Student() { 11 super(); 12 } 13 14 public String getName() { 15 return name; 16 } 17 18 public void setName(String name) { 19 this.name = name; 20 } 21 22 public String getSex() { 23 return sex; 24 } 25 26 public void setSex(String sex) { 27 this.sex = sex; 28 } 29 30 @Override 31 public int describeContents() { 32 // TODO Auto-generated method stub 33 return 0; 34 } 35 36 // 重寫父類的方法:將需要傳送的數據放進來 37 @Override 38 public void writeToParcel(Parcel dest, int flags) { 39 // TODO Auto-generated method stub 40 dest.writeString(name); 41 dest.writeString(name); 42 } 43 44 public static final Parcelable.Creator<Student> CREATOR = new Parcelable.Creator<Student>() { 45 public Student createFromParcel(Parcel in) { 46 Student s = new Student(); 47 s.setName(in.readString()); 48 s.setSex(in.readString()); 49 return s; 50 } 51 52 public Student[] newArray(int size) { 53 return new Student[size]; 54 } 55 }; 56 57 }
我們在這個類中放入了name和age這兩個參數,並實現了Parcelable接口。注意第44行至55行代碼的修改。
接着,新建一個和類同名的aidl文件,即新建Student.aidl,代碼如下:
parcelable Student;
注意這個parcelable的第一個字母是小寫。
繼續,新建IStudent.aidl,作爲需要遠程傳遞的業務方法。代碼如下:
1 package com.example.servicetest03; 2 3 import com.example.servicetest03.Student; 4 interface IStudent{ 5 void setStudent(String name,String sex); 6 Student getStudent(); 7 }
核心代碼是第03行,雖然Student類文件和本文件是在同一個包下,但是依然要導包,否則將無法識別Student類。然後在第06行代碼中,就可以把Student這個類傳遞出去了。注意了,第06行返回的是Student類型,這不就是ADIL所支持的自定義類型嘛。
文件結構如下:
綜上所述,傳遞自定義類,有三個步驟:
- 自定義類實現Parcelable接口
- 新建同名的aidl文件,聲明這個Parcelable類型的自定義類
- 在需要遠程傳遞的aidl文件中導包,引用進來
那麼接下來的步驟就和本段中的第6小節一樣了,就不再多解釋了,這裏只貼代碼:
(2)新建StudentImpl類,繼承IStudent.Stub類。代碼如下:(也就是說,根據步驟(1)中的java類,生成業務對象,即原理圖中B應用的業務對象)
1 package com.example.servicetest03; 2 3 import android.os.RemoteException; 4 5 //業務對象的實現 6 public class StudentImpl extends IStudent.Stub{ 7 8 private Student student; 9 10 public StudentImpl(){ 11 student = new Student(); 12 } 13 @Override 14 public void setStudent(String name, String sex) throws RemoteException { 15 // TODO Auto-generated method stub 16 student.setName(name); 17 student.setSex(sex); 18 19 } 20 21 @Override 22 public Student getStudent() throws RemoteException { 23 // TODO Auto-generated method stub 24 return student; 25 } 26 27 }
(3)新建Service類,代碼如下:
1 package com.example.servicetest03; 2 3 import android.app.Service; 4 import android.content.Intent; 5 import android.os.IBinder; 6 import android.util.Log; 7 8 public class MyService extends Service { 9 10 public static final String TAG = "MyService"; 11 12 private StudentImpl studentImpl ; 13 14 @Override 15 public void onCreate() { 16 super.onCreate(); 17 Log.d(TAG, "onCreate"); 18 } 19 20 @Override 21 public int onStartCommand(Intent intent, int flags, int startId) { 22 Log.d(TAG, "onStartCommand"); 23 return super.onStartCommand(intent, flags, startId); 24 } 25 26 @Override 27 public void onDestroy() { 28 super.onDestroy(); 29 Log.d(TAG, "onDestroy"); 30 } 31 32 @Override 33 public IBinder onBind(Intent intent) { 34 Log.d("MyService", "onBind"); 35 studentImpl = new StudentImpl(); 36 return studentImpl; //在這裏返回新建的MyBinder類 37 } 38 39 }
核心代碼:12行、35行、36行。
(4)在清單文件中添加權限:
<service android:name=".MyService"> </service>
現在,B應用的業務對象和服務建立好了。B應用的業務對象通過與Service綁定,讓Service把業務對象暴露給了A應用或者其他的應用。也就是說,Service最終並沒有實現業務功能。
如果要讓A應用來訪問,該怎麼做呢?
(5)在activity_main.xml中添加兩個按鈕button1_setStudent和button2_getStudent。activity_main.xml的代碼如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <Button android:id="@+id/button1_setStudent" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="調用setStudent方法" /><span style="color: #0000ff;"><</span><span style="color: #800000;">Button </span><span style="color: #ff0000;">android:id</span><span style="color: #0000ff;">="@+id/button2_getStudent"</span><span style="color: #ff0000;"> android:layout_width</span><span style="color: #0000ff;">="match_parent"</span><span style="color: #ff0000;"> android:layout_height</span><span style="color: #0000ff;">="wrap_content"</span><span style="color: #ff0000;"> android:text</span><span style="color: #0000ff;">="調用getStudent方法"</span> <span style="color: #0000ff;">/></span>
</LinearLayout>
注:佈局文件裏不再添加綁定服務和取消綁定的按鈕,我們稍後在Activity的生命週期裏完成這件事。
(6)MainActivity中的代碼,如下所示:
1 package com.example.servicetest03; 2 3 import android.app.Activity; 4 import android.content.ComponentName; 5 import android.content.Intent; 6 import android.content.ServiceConnection; 7 import android.os.Bundle; 8 import android.os.IBinder; 9 import android.os.RemoteException; 10 import android.view.View; 11 import android.view.View.OnClickListener; 12 import android.widget.Button; 13 import android.widget.Toast; 14 15 16 public class MainActivity extends Activity implements OnClickListener { 17 public static final String TAG = "MainActivity"; 18 private Button button1_setStudent; 19 private Button button2_getStudent; 20 private IStudent studentImpl; 21 boolean mBound = false; // 一開始,並沒有和Service綁定.這個參數是用來判斷綁定狀態 22 23 @Override 24 protected void onCreate(Bundle savedInstanceState) { 25 super.onCreate(savedInstanceState); 26 setContentView(R.layout.activity_main); 27 button1_setStudent = (Button) findViewById(R.id.button1_setStudent); 28 button2_getStudent = (Button) findViewById(R.id.button2_getStudent); 29 button1_setStudent.setOnClickListener(this); 30 button2_getStudent.setOnClickListener(this); 31 } 32 33 34 // 匿名內部類:服務連接對象 35 private ServiceConnection connection = new ServiceConnection() { 36 // 當服務異常終止時會調用。注意,解除綁定服務時不會調用 37 @Override 38 public void onServiceDisconnected(ComponentName name) { 39 mBound = false; // 服務異常終止時,狀態爲未綁定 40 } 41 // 和服務綁定成功後,服務會回調該方法。在這個方法裏調用的業務對象中的內容 42 @Override 43 public void onServiceConnected(ComponentName name, IBinder service) { 44 studentImpl = IStudent.Stub.asInterface(service); // 得到person對象 45 mBound = true; //true說明是綁定狀態 46 } 47 }; 48 49 //程序啓動時,開始綁定服務 50 @Override 51 protected void onStart() { 52 super.onStart(); 53 Intent bindIntent = new Intent(this, MyService.class); 54 bindService(bindIntent, connection, BIND_AUTO_CREATE); 55 } 56 57 //程序退出時,取消綁定服務 58 @Override 59 protected void onDestroy() { 60 super.onDestroy(); 61 // 如果和Service是綁定的狀態,就解除綁定。 62 if (mBound) { 63 unbindService(connection); 64 mBound = false; 65 } 66 } 67 68 69 @Override 70 public void onClick(View v) { 71 switch (v.getId()) { 72 //點擊button1_setStudent按鈕,設置Student的值 73 case R.id.button1_setStudent: 74 try { 75 studentImpl.setStudent("生命壹號", "男"); 76 Toast.makeText(this, "設置成功", Toast.LENGTH_SHORT).show(); 77 } catch (RemoteException e) { 78 // TODO Auto-generated catch block 79 e.printStackTrace(); 80 } 81 break; 82 //點擊button2_getStudent按鈕,獲取Student的值 83 case R.id.button2_getStudent: 84 Student s; 85 try { 86 s = studentImpl.getStudent(); 87 Toast.makeText(this, "name="+s.getName()+",sex="+s.getSex(), Toast.LENGTH_SHORT).show(); 88 } catch (RemoteException e) { 89 // TODO Auto-generated catch block 90 e.printStackTrace(); 91 } 92 break; 93 94 default: 95 break; 96 } 97 } 98 }
核心代碼是第44行。
我們在第75行、86至87行使用到了IStudent中的業務方法。
運行程序,點擊第一個按鈕,然後點擊第二個按鈕,效果如下:
這樣,Acitivity就成功調用了遠程Service的自定義類。
六、使用Bind Service完成IPC進程間通信:(兩個APP之間)
上一段中的跨進程通信其實並沒有什麼實質上的作用,因爲這只是在一個Activity裏調用了同一個應用程序的Service裏的方法。而跨進程通信的真正意義是爲了讓一個應用程序去訪問另一個應用程序中的Service,以實現共享Service的功能。那麼下面我們自然要學習一下,如何才能在其它的應用程序中調用到MyService裏的方法。
在第四段中我們已經知道,如果想要讓Activity與Service之間建立關聯,需要調用bindService()方法,並將Intent作爲參數傳遞進去,在Intent裏指定好要綁定的Service,核心代碼如下:
Intent bindIntent = new Intent(this, MyService.class); bindService(bindIntent, connection, BIND_AUTO_CREATE);
這裏在構建Intent的時候是使用MyService.class來指定要綁定哪一個Service的,但是在另一個應用程序中去綁定Service的時候並沒有MyService這個類,這時就必須使用到隱式Intent了。
具體步驟如下:
我們在第六段中的MyService02這個工程文件中進行修改。代碼實現如下:
(1)現在修改AndroidManifest.xml中的代碼,給MyService加上一個action,如下所示:
1 <service android:name=".MyService" > 2 <intent-filter> 3 <action android:name="com.example.servicetest02.MyService" /> 4 </intent-filter> 5 </service>
這就說明,MyService可以響應帶有com.example.servicetest02.MyService這個action的Intent。
現在重新運行一下MyService02這個程序,這樣就把遠程Service端的工作全部完成了。
然後新建一個新的工程,起名爲ClientTest,我們就嘗試在這個程序中遠程調用MyService中的方法。
ClientTest中的Activity如果想要和MyService建立關聯其實也不難,首先需要將IPerson.aidl文件從ServiceTest02項目中拷貝過來,注意要將原有的包路徑一起拷貝過來,完成後項目的結構如下圖所示:
(2)在activity_main.xml中添加兩個按鈕button_bind_service和button_unbind_service,用於綁定遠程服務和取消綁定。activity_main.xml的代碼如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <Button android:id="@+id/button_bind_service" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="bind Service" /><span style="color: #0000ff;"><</span><span style="color: #800000;">Button </span><span style="color: #ff0000;">android:id</span><span style="color: #0000ff;">="@+id/button_unbind_service"</span><span style="color: #ff0000;"> android:layout_width</span><span style="color: #0000ff;">="match_parent"</span><span style="color: #ff0000;"> android:layout_height</span><span style="color: #0000ff;">="wrap_content"</span><span style="color: #ff0000;"> android:text</span><span style="color: #0000ff;">="unbind Service"</span> <span style="color: #0000ff;">/></span>
</LinearLayout>
(3)在MainActivity中加入和遠程的MyService建立關聯的代碼,如下所示:
1 package com.example.clienttest; 2 3 import android.app.Activity; 4 import android.content.ComponentName; 5 import android.content.Intent; 6 import android.content.ServiceConnection; 7 import android.os.Bundle; 8 import android.os.IBinder; 9 import android.os.RemoteException; 10 import android.util.Log; 11 import android.view.View; 12 import android.view.View.OnClickListener; 13 import android.widget.Button; 14 15 import com.example.servicetest02.IPerson; 16 17 18 public class MainActivity extends Activity implements OnClickListener { 19 public static final String TAG = "MainActivity"; 20 private Button button_bind_service; 21 private Button button_unbind_service; 22 private IPerson person; 23 boolean mBound = false; // 一開始,並沒有和Service綁定.這個參數是用來判斷綁定狀態 24 // 匿名內部類:服務連接對象 25 private ServiceConnection connection = new ServiceConnection() { 26 // 當服務異常終止時會調用。注意,解除綁定服務時不會調用 27 @Override 28 public void onServiceDisconnected(ComponentName name) { 29 mBound = false; // 服務異常終止時,狀態爲未綁定 30 } 31 // 和服務綁定成功後,服務會回調該方法。在這個方法裏調用的業務對象中的內容 32 @Override 33 public void onServiceConnected(ComponentName name, IBinder service) { 34 Log.d(TAG, "onServiceConnected"); 35 person = IPerson.Stub.asInterface(service); // 得到person對象 36 Log.d("person", "person對象的內存地址是" + person); // 打印出person對象的內存地址 37 try { 38 person.setName("生命壹號"); 39 person.setAge(22); 40 person.setSex("男"); 41 String p = person.getPerson(); 42 Log.d("person", "person的信息是" + p); 43 } catch (RemoteException e) { 44 // TODO Auto-generated catch block 45 e.printStackTrace(); 46 } 47 mBound = true; //true說明是綁定狀態 48 } 49 }; 50 @Override 51 protected void onCreate(Bundle savedInstanceState) { 52 super.onCreate(savedInstanceState); 53 setContentView(R.layout.activity_main); 54 button_bind_service = (Button) findViewById(R.id.button_bind_service); 55 button_unbind_service = (Button) findViewById(R.id.button_unbind_service); 56 button_bind_service.setOnClickListener(this); 57 button_unbind_service.setOnClickListener(this); 58 } 59 @Override 60 public void onClick(View v) { 61 switch (v.getId()) { 62 case R.id.button_bind_service: 63 Intent bindIntent = new Intent("com.example.servicetest02.MyService"); 64 bindService(bindIntent, connection, BIND_AUTO_CREATE); 65 break; 66 case R.id.button_unbind_service: 67 // 如果和Service是綁定的狀態,就解除綁定。 68 if (mBound) { 69 unbindService(connection); 70 mBound = false; 71 } 72 break; 73 default: 74 break; 75 } 76 } 77 }
這部分代碼大家一定會非常眼熟吧?沒錯,這和在ServiceTest02的MainActivity中的代碼幾乎是完全相同的,只是在讓Activity和Service建立關聯的時候我們使用了隱式Intent,將Intent的action指定成了com.example.servicetest02.MyAIDLService(63行)。
在當前Activity和MyService建立關聯之後,我們仍然是調用了setName、setAge、setSex、getPerson()這幾個方法,遠程的MyService會對傳入的參數進行處理並返回結果,然後將結果打印出來。
這樣的話,ClientTest中的代碼也就全部完成了,現在運行一下這個項目,然後點擊Bind Service按鈕,此時就會去和遠程的MyService建立關聯,觀察LogCat中的打印信息如下所示:
注意紅框部分,包名是不一樣的哦。由此可見,我們確實已經成功實現跨進程通信了,在一個程序中訪問到了另外一個程序中的方法。
七、Messenger的使用:
public final class Messenger extends Object implements Parcelable
介紹:Messenger實現了IPC通信,底層也是使用了AIDL方式。和AIDL方式不同的是,Messenger方式是利用Handler形式處理,因此,它是線程安全的,這也表示它不支持併發處理;而AIDL方式是非線程安全的,支持併發處理,因此,我們使用AIDL方式時,需要保證代碼的線程安全。大部分情況下,應用中不需要併發處理,因此我們通常只需要使用Messenger方式。
過程:在進程A中創建一個Message,將這個Message對象通過Messenger.send(message)方法傳遞到進程B的消息隊列裏,然後交給Handler去處理。
當然,Message對象本身是無法被傳遞到進程B的,send(message)方法會使用一個Pacel對象對Message對象編集,再將Pacel對象傳遞到進程B中,然後解編集,得到一個和進程A中的Message對象內容一樣的對象。
關於多線程的Handler機制,如果不清楚的話,可以參考本人另外一篇博客:
使用Messenger來實現IPC的步驟:
- 在Service中創建一個Messenger對象並綁定一個Handler
- 在onBind方法中通過Messenger.getIbinder方法返回一個IBinder對象。
- 在調用的組件中的ServiceConnection的onServiceConnected事件方法中根據iBinder對象來創建一個Messenger對象。這樣,兩個Messenger就同時綁定到一個IBinder上,從而實現通信。
- 在調用的組件中使用Messenger的send方法來發送消息到Service的Messenger對象中。
那我們通過代碼來實現以下吧。新建一個全新的工程MessengerTest。步驟如下:
(1)新建一個MessengerService類,繼承Service類,代碼如下:
1 package com.example.messengertest; 2 3 import android.app.Service; 4 import android.content.Intent; 5 import android.os.Handler; 6 import android.os.IBinder; 7 import android.os.Message; 8 import android.os.Messenger; 9 import android.util.Log; 10 import android.widget.Toast; 11 12 public class MessengerService extends Service{ 13 14 public static final int MSG_SAY_HELLO = 1; 15 16 private Handler handler = new Handler() { 17 public void handleMessage(Message msg) { 18 switch (msg.what) { 19 case MSG_SAY_HELLO: 20 //在服務裏定義業務方法 21 Toast.makeText(MessengerService.this, "hello", Toast.LENGTH_SHORT).show(); 22 Log.d("MessengerService", "MessengerService thread id is " + Thread.currentThread().getId()); //打印MessengerService的線程id 23 break; 24 default: 25 break; 26 } 27 } 28 }; 29 30 31 private Messenger messenger = new Messenger(handler); 32 33 34 @Override 35 public IBinder onBind(Intent intent) { 36 // TODO Auto-generated method stub 37 return messenger.getBinder(); 38 } 39 }
核心代碼:16至28行、31行、37行。
37行中,將IBinder類型返回之後,就已經和Messenger進行綁定了。
(2)在清單文件中註冊服務:(和Activity標籤並列)
<service android:name=".MessengerService"> </service>
(3)修改activity_main.xml代碼,添加一個按鈕,用於發送Message,代碼如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" ><span style="color: #0000ff;"><</span><span style="color: #800000;">Button </span><span style="color: #ff0000;">android:id</span><span style="color: #0000ff;">="@+id/button_messenger"</span><span style="color: #ff0000;"> android:layout_width</span><span style="color: #0000ff;">="match_parent"</span><span style="color: #ff0000;"> android:layout_height</span><span style="color: #0000ff;">="wrap_content"</span><span style="color: #ff0000;"> android:text</span><span style="color: #0000ff;">="使用messenger"</span> <span style="color: #0000ff;">/></span>
</LinearLayout>
(4)在MainActivity作爲程序的主Activity,在裏面加入發送Message消息和建立Service連接的邏輯,代碼如下:
1 package com.example.messengertest; 2 3 import android.app.Activity; 4 import android.content.ComponentName; 5 import android.content.Intent; 6 import android.content.ServiceConnection; 7 import android.os.Bundle; 8 import android.os.IBinder; 9 import android.os.Message; 10 import android.os.Messenger; 11 import android.os.RemoteException; 12 import android.util.Log; 13 import android.view.View; 14 import android.view.View.OnClickListener; 15 import android.widget.Button; 16 17 public class MainActivity extends Activity implements OnClickListener { 18 19 private Button button_messenger; 20 21 private Messenger messenger; 22 boolean mBound = false; 23 24 @Override 25 protected void onCreate(Bundle savedInstanceState) { 26 super.onCreate(savedInstanceState); 27 setContentView(R.layout.activity_main); 28 button_messenger = (Button) findViewById(R.id.button_messenger); 29 button_messenger.setOnClickListener(this); 30 } 31 32 @Override 33 protected void onStart() { 34 // TODO Auto-generated method stub 35 super.onStart(); 36 Intent bindIntent = new Intent(this, MessengerService.class); 37 bindService(bindIntent, connection, BIND_AUTO_CREATE); 38 } 39 40 @Override 41 protected void onDestroy() { 42 // TODO Auto-generated method stub 43 super.onDestroy(); 44 if (mBound) { 45 unbindService(connection); 46 mBound = false; 47 } 48 } 49 50 private ServiceConnection connection = new ServiceConnection() { 51 @Override 52 public void onServiceConnected(ComponentName name, IBinder service) { 53 // TODO Auto-generated method stub 54 messenger = new Messenger(service); 55 56 mBound = true; 57 } 58 59 @Override 60 public void onServiceDisconnected(ComponentName name) { 61 // TODO Auto-generated method stub 62 mBound = false; 63 64 } 65 66 }; 67 68 //點擊按鈕,發送Message消息,在MessengerService裏接收,從而執行Service裏面的方法 69 @Override 70 public void onClick(View v) { 71 switch (v.getId()) { 72 case R.id.button_messenger: 73 Message msg = Message.obtain(); 74 msg.what = MessengerService.MSG_SAY_HELLO; 75 try { 76 messenger.send(msg); 77 Log.d("MainActivity", "MainActivity thread id is " + Thread.currentThread().getId()); //打印MainActivity的線程id 78 } catch (RemoteException e) { 79 // TODO Auto-generated catch block 80 e.printStackTrace(); 81 } 82 break; 83 84 default: 85 break; 86 } 87 } 88 89 }
我們在上一步的MessengerService類新建了一個Messenger,在這裏又新建另一個Messenger(54行)。兩個Messenger綁定了同一個服務,Activity就可以和Service實現通訊了。
點擊按鈕(72行),發送消息,讓MessengerService類裏的Messenger去接收,然後交給handler去處理,從而執行handleMessage()裏方法,也就是說,執行了Service裏面的方法。
運行程序,點擊按鈕,顯示效果如下:
後臺打印日誌如下:
說明這個MessengerService和普通Service一樣,也是運行在主線程當中的。
當然了,這裏的Messenger的實現比較簡單,如果以後需要實現複雜IPC訪問,還是需要自己去寫AIDL才更加直接有效,可控性強。