Android學習筆記39:Android四大組件之Service


  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中。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM