Android應用程序的四大組件分別是Activity、Service、BroadcastReceiver和ContentProvider。其中,有關Activity的介紹可以參閱博文《Android學習筆記38:Android四大組件之Activity》。有關ContentProvider的使用方法可以參閱博文《Android學習筆記37:使用Content Providers方式共享數據》
本文將主要對Service進行介紹。
1.Service簡介
與Activity不同,Service沒有提供與用戶進行交互的用戶界面。Service是運行在后台的一種Android組件,當應用程序需要進行某種不需要前台顯示的計算或數據處理時,就可以啟動一個Service來實現。
使用Service的目的通常有兩個:后台運行和跨進程訪問。通過啟動一個Service,可以在不顯示界面的前提下在后台運行指定的任務,這樣可以不影響用戶進行界面操作。通過AIDL(Android Interface Definition Language)服務實現不同進程之間的通信。
1.1Service的生命周期
Service一般由Activity或其他的Context對象來啟動,啟動方式有兩種,對應的生命周期也不相同,具體如圖1所示。
圖1 Service生命周期示意圖
由圖1可以看出,通過startService()方法和通過bindService()方法啟動Service是不一樣的,下面就具體說說這兩種方式的區別。
1.2通過startService()方法啟動Service
一個Service從啟動到銷毀會經歷3個階段,分別是啟動服務、服務執行和銷毀服務。當Service經歷上述3個階段時,會分別調用Service類中的3個相應的事件方法:
(1)public void onCreate();
(2)public int onStartCommand(Intent intent, int flags, int startId);
(3)public void onDestroy();
其中,onCreate()方法用於創建Service;onStartCommand()方法用於開始Service;onDestroy()方法用於銷毀服務。
需要注意的一點是,一個Service只會被創建一次,銷毀一次,但可以開始多次,也就是說,onCreate()方法和onDestroy()只會被調用一次,而onStartCommand()方法卻可以被多次調用。
下面就來簡單的驗證一下。
首先,我們需要自定義一個Service,讓其繼承android.app.Service類,並實現其中的onCreate()、onStartCommand()和onDestroy()方法。這里,我創建了一個名為“MyService”的Service類,具體代碼如下:
1 /* 2 * 自定義的Service類 3 * 博客園-依舊淡然 4 */ 5 public class MyService extends Service { 6 7 private static final String TAG = "MyService"; 8 9 @Override 10 public void onCreate() { 11 super.onCreate(); 12 Log.i(TAG, "MyService-->onCreate()"); 13 } 14 15 @Override 16 public int onStartCommand(Intent intent, int flags, int startId) { 17 Log.i(TAG, "MyService-->onStartCommand()"); 18 return super.onStartCommand(intent, flags, startId); 19 } 20 21 @Override 22 public IBinder onBind(Intent intent) { 23 return null; 24 } 25 26 @Override 27 public void onDestroy() { 28 super.onDestroy(); 29 Log.i(TAG, "MyService-->onDestroy()"); 30 } 31 }
其中,onBind()方法是android.app.Service類的抽象方法,所以必須在子類MyService中實現。使用bindService()方法啟動Service時,需要用到該方法,這里暫時未用到,所以直接返回null即可。
實現了自定義的MyService之后,我們便可以在Context對象(比如Activity)中通過調用startService(Intent intent)方法來啟動Service,或是通過調用stopService(Intent intent)方法來終止Service。其中,startService()方法和stopService()方法中的參數intent可以用來指定想要啟動和終止Service是哪一個。如下的代碼,實現了通過Button按鈕button_startService啟動MyService,通過Button按鈕button_stopService停止Service。
1 /* 2 * 實現事件監聽器接口 3 * 博客園-依舊淡然 4 */ 5 private OnClickListener clickListenter = new OnClickListener() { 6 public void onClick(View view) { 7 switch(view.getId()) { 8 case R.id.button_startService: //啟動Service 9 Intent intent_startService = new Intent(MainActivity.this, MyService.class); 10 startService(intent_startService); 11 break; 12 case R.id.button_stopService: //停止Service 13 Intent intent_stopService = new Intent(MainActivity.this, MyService.class); 14 stopService(intent_stopService); 15 break; 16 } 17 } 18 };
最后,我們還需要在AndroidManifest.xml文件中注冊我們自定義的MyService組件,即在<application></application>標簽中添加如下代碼即可:
1 <!-- 注冊MyService組件 --> 2 <service 3 android:name=".MyService" > 4 </service>
至此,我們便完成了MyService的創建。
運行程序,點擊Button按鈕button_startService啟動MyService,可以看到“MyService-->onCreate()”以及“MyService-->onStartCommand()”的Log輸出信息,表明MyService已經啟動。同時,我們也可以點擊Menu,退出MainActivity,並依次選擇Settings/Applications/Running Services,可以看到如圖2所示的界面。
圖2 MyService運行在后台
由圖2可以看出,即使退出了MainActivity(MyService的啟動者),MyService也不會停止,它仍然會在后台運行。
當我們再次運行程序,並再次點擊Button按鈕button_startService啟動MyService時,可以看到“MyService-->onStartCommand()”的Log輸出信息,表明當Service已經啟動時,如果再次調用startService()方法,將只會調用Service類的onStartCommand()方法。
最后,當我們Button按鈕button_stopService時,可以看到“MyService-->onDestroy()”的Log輸出信息,表明MyService調用了onDestroy()方法停止了Service。
1.3通過bindService()方法啟動Service
可以看到,通過startService()方式啟動Service時,即使啟動該Service的Context對象(比如Activity)關閉了,Service仍然會一直運行在后台(如果沒有調用stopService()方法的話)。有時我們希望當啟動Service的Context對象關閉時,Service也隨之關閉,這時我們就可以通過使用bindService()方法來啟動Service。通過bindService()方法啟動Service可以將Activity和Service綁定。
下面同樣以一個簡單的實例來說明如何創建一個Service,並通過bindService()方法來啟動該Service。
首先,我們需要自定義一個Service,讓其繼承android.app.Service類,並實現其中的onBind()、onRebind()和onUnbind()方法。這里,我創建了一個名為“BinderService”的Service類,具體代碼如下:
1 /* 2 * 自定義的Service類 3 * 博客園-依舊淡然 4 */ 5 public class BinderService extends Service { 6 7 private MyBinder myBinder = new MyBinder(); //MyBinder對象,用於獲得BinderService對象 8 private static final String TAG = "BinderService"; 9 10 //成功綁定時調用該方法 11 public IBinder onBind(Intent intent) { 12 Log.i(TAG, "-->onBind()"); 13 return myBinder; 14 } 15 16 //重新綁定時調用該方法 17 public void onRebind(Intent intent) { 18 super.onRebind(intent); 19 Log.i(TAG, "-->onRebind()"); 20 } 21 22 //解除綁定時調用該方法 23 public boolean onUnbind(Intent intent) { 24 Log.i(TAG, "-->onUnbind()"); 25 return super.onUnbind(intent); 26 } 27 28 /* 29 * MyBinder內部類,擴展自Binder類 30 */ 31 public class MyBinder extends Binder { 32 33 //獲取BinderService對象 34 public BinderService getBinderService() { 35 return BinderService.this; 36 } 37 38 } 39 40 }
其中,當BinderService成功綁定時會調用onBind()方法;當BinderService重新綁定時會調用onRebind()方法;當BinderService解除綁定時會調用onUnbind()方法。MyBinder內部類繼承自Binder類,並實現了getBinderService()方法,用於獲取當前BinderService對象。
實現了自定義的BinderService之后,我們便可以在Context對象(比如Activity)中通過調用bindService(Intent service, ServiceConnection conn, int flags)方法啟動Service,或是通過調用unbindService(ServiceConnection conn)方法來終止Service。
在bindService()方法中,第一個參數service表示與Service類相關聯的Intent對象;第二個參數conn是一個ServiceConnection接口對象,負責連接Intent對象指定的Service,通過ServiceConnection對象可以獲得連接Service成功或者失敗的狀態;第三個參數flags,一般設置為Context.BIND_AUTO_CREATE。
創建ServiceConnection接口對象時,需要實現ServiceConnection接口中的抽象方法onServiceConnected()和onServiceDisconnected()。其中,onServiceConnected()方法在連接Service成功時會被調用,onServiceDisconnected()方法在連接Service失敗時會被調用。如下的代碼實現了這兩個方法。
1 /* 2 * 實現ServiceConnection接口 3 * 博客園-依舊淡然 4 */ 5 private ServiceConnection serviceConnection = new ServiceConnection() { 6 7 //連接Service 8 public void onServiceConnected(ComponentName name, IBinder iBinder) { 9 isBinderServiceConnected = true; 10 MyBinder myBinder = (MyBinder)iBinder; 11 BinderService binderService = myBinder.getBinderService();//獲得BinderService對象 12 //binderService.xxx(); //調用BinderService中的自定義方法,進行Service相關操作 13 } 14 15 //斷開Service 16 public void onServiceDisconnected(ComponentName name) { 17 isBinderServiceConnected = false; 18 } 19 20 };
通過以上的代碼可以看到,我們之前在BinderService類中定義的內部類MyBinder的作用就是獲取BinderService對象的實例。獲得了BinderService對象之后,便可以在Context對象中調用BinderService中的自定義方法,進行Service相關操作了。
需要注意的一點是,在Context對象中通過調用bindService()方法啟動Service時,onCreate()方法和onRebind()方法會被調用,在Context對象中通過調用unbindService()方法來終止Service時,onUnbind()方法和onDestroy()方法會被調用。
2.IntentService的使用
上面分別介紹了如何通過startService()方式和bindService()方法來啟動一個自定義的Service,在使用這兩個方法時需要注意以下兩個問題。
2.1 UI界面卡死
因為Service和啟動它的Context對象(比如Activity)是在同一個線程里面,所以當我們直接在onStartCommand()方法中進行Service操作時,將會導致UI界面卡死。
我們可以通過Thread.currentThread().getId()方法來獲得主線程以及Service線程的線程Id號,輸出Log信息如圖3所示。
圖3 Log輸出信息(1)
由圖3可以看出,Service和啟動它的Activity確實是在同一個線程里。這樣所帶來的直接后果就是,當Service進行比較耗時的操作時UI界面卡死。比如,點擊Button啟動一個Service進行文件下載,那么在文件下載期間,UI界面將無法和用戶進行交互。
2.2多進程調用
為了解決上面出現的問題,一種簡單的解決方法就是在Service的onStartCommand()方法中重新啟動一個線程,然后在新啟動的線程中進行文件下載等耗時的操作。如下面的代碼所示:
1 @Override 2 public int onStartCommand(Intent intent, int flags, int startId) { 3 new MyThread().start(); //啟動一個新的線程 4 return super.onStartCommand(intent, flags, startId); 5 } 6 7 /* 8 * 內部類,用於啟動一個新的線程 9 * 博客園-依舊淡然 10 */ 11 private class MyThread extends Thread { 12 13 @Override 14 public void run() { 15 try { 16 Thread.sleep(2000); //模擬文件下載等耗時的操作 17 Log.i(TAG, "MyService線程ID-->" + Thread.currentThread().getId()); 18 } catch (InterruptedException e) { 19 e.printStackTrace(); 20 } 21 } 22 23 }
很顯然,使用如上的方式,會比直接將文件下載等耗時的操作放在onStartCommand()方法中進行處理要好的多。
但是,當我們多次啟動Service時,onStartCommand()方法將多次被調用,從而導致啟動多個線程,如圖4所示。
圖4 Log輸出信息(2)
由圖4可以看出,使用改進后的方法,UI界面能夠實時響應用戶請求了,但是與此同時也引入了多線程調用的問題。
2.3 IntentService的使用
為了避免多線程調用的問題,Android提供了IntentService。
IntentService是Service的一個子類,主要用於處理異步請求。客戶端通過startService(Intent intent)方法傳遞Intent請求給IntentService。在IntentService中,創建了一個與應用程序主線程分開的worker thread,用來處理所有傳過來的Intent請求,當處理完所有的intent后停止Service。
實現IntentService的方法也很簡單,覆寫IntentService類的構造方法以及onHandleIntent()方法即可,在onHandleIntent()方法中可以完成比較耗時的操作。
如下的代碼自定義了一個名為“MyIntentService”的類,該類繼承自IntentService,並覆寫IntentService類的構造方法以及onHandleIntent()方法。
1 /* 2 * MyIntentService類,繼承自IntentService 3 * 博客園-依舊淡然 4 */ 5 public class MyIntentService extends IntentService { 6 7 private static final String TAG = "MyIntentService"; 8 9 //構造方法 10 public MyIntentService(String name) { 11 super("MyIntentService"); 12 } 13 14 @Override 15 protected void onHandleIntent(Intent intent) { 16 try { 17 Thread.sleep(2000); //模擬文件下載等耗時的操作 18 Log.i(TAG, "MyIntentService線程ID-->" + Thread.currentThread().getId()); 19 } catch (InterruptedException e) { 20 e.printStackTrace(); 21 } 22 } 23 24 }
當我們多次啟動MyIntentService時,可以看到如圖5所示的Log輸出信息。
圖5 Log輸出信息(3)
由圖5可以看出,使用IntentService確實可以避免多線程調用。這是因為,在IntentService中默認創建了一個work queue,當IntentService收到多個Intent請求時,IntentService會將這些Intent請求依次放入work queue中,然后一次只傳遞一個Intent請求到onHandleIntent()方法中進行處理,從而避免了多線程調用。
需要注意的一點是,在IntentService中默認實現了onBind()方法,其返回值為null。同樣,在IntentService中也默認實現了onStartCommand()方法,在這個方法中實現了將Intent請求放到work queue中。