學習筆記|AS入門(十) 元件篇之Service

釐米姑娘發表於2017-12-21

Service(服務)是Android中實現程式後臺執行的解決方案,它非常適合用於去執行那些不需要和使用者互動而且還要求長期執行的任務。Service主要負責與UI無關的工作,比如耗時操作。本篇學習Service相關知識點有:

  • Service概要
    • 開啟子執行緒
    • 非同步訊息處理機制
  • Service生命週期
  • Service的基本用法
    • 普通Service
    • 前臺Service
    • 系統Service
      • 例子:後臺定時任務
    • IntentService
  • Service與Activity的通訊

1.Service概要

Service的執行不依賴於任何使用者介面,因此即便程式被切換到後臺或者使用者開啟了另一個應用程式,Service仍能夠保持正常執行。但當某個應用程式程式被殺掉時,所有依賴於該程式的服務也會停止執行。

實際上Service預設並不會執行在子執行緒中,也不執行在一個獨立的程式中,它同樣執行在主執行緒中(UI執行緒)。換句話說,不要在Service裡執行耗時操作,除非你手動開啟一個子執行緒,否則有可能出現主執行緒被阻塞(ANR)的情況。首先來學習如何開啟一個子執行緒。

(1)開啟子執行緒

常用方法是,用Thread類的匿名類的形式並且實現Runnable介面,再呼叫它的start() 方法,就使得被重寫的run() 方法中的耗時操作執行在子執行緒當中了。程式碼如下:

new Thread(new Runnable() {
          @Override
          public void run() {
          //耗時操作的邏輯
          }
}).start();
複製程式碼

(2)非同步訊息處理機制

還要注意一點:Android不允許在子執行緒中進行UI操作。但有時候,在子執行緒裡執行一些耗時任務之後需要根據任務的執行結果來更新相應的UI控制元件,在這裡Android提供了一套非同步訊息處理機制,它可以很好地解決在子執行緒中更新UI的問題。主要用到兩個類:Handler(處理者,主要用於傳送和處理訊息)和Message(資訊,可攜帶少量資訊用於在不同執行緒之間交換)。下圖展示瞭如何用它們實現從子執行緒到主執行緒的轉換:

學習筆記|AS入門(十) 元件篇之Service

可以看到,只要在需要轉換到主執行緒進行UI操作的子執行緒中例項化一個Message物件並攜帶相關資料,再由Handle的sendMessage() 將它傳送出去,之後這個資料就會被在主執行緒中例項化的Handle物件的重寫方法handleMessage() 收到並處理。現在在子執行緒中更新UI就很容易了。

現在來個具體的例子感受一下,新建佈局,這裡就放一個文字和按鈕:

學習筆記|AS入門(十) 元件篇之Service

在主活動中按鈕的點選事件裡開啟一個子執行緒,但又希望點選按鈕改變文字內容,此時就用非同步訊息處理機制,程式碼如下:

學習筆記|AS入門(十) 元件篇之Service

效果如圖:

學習筆記|AS入門(十) 元件篇之Service

2.Service生命週期

官方文件提供的Sevice生命週期圖如下:

學習筆記|AS入門(十) 元件篇之Service

先來看這幾種回撥方法含義:

onCreate():服務第一次被建立時呼叫

onStartComand():服務啟動時呼叫

onBind():服務被繫結時呼叫

onUnBind():服務被解綁時呼叫

onDestroy():服務停止時呼叫

從上圖可看到有兩種方法可以啟動Service,下面分別介紹: 第一種:其他元件呼叫Context的startService() 方法可以啟動一個Service,並回撥服務中的onStartCommand()。如果該服務之前還沒建立,那麼回撥的順序是onCreate()->onStartCommand()。服務啟動了之後會一直保持執行狀態,直到stopService()stopSelf() 方法被呼叫,服務停止並回撥onDestroy()。另外,無論呼叫多少次startService()方法,只需呼叫一次stopService()或stopSelf()方法,服務就會停止了。

第二種:其它元件呼叫Context的bindService() 可以繫結一個Service,並回撥服務中的onBind()方法。類似地,如果該服務之前還沒建立,那麼回撥的順序是onCreate()->onBind()。之後,呼叫方可以獲取到onBind()方法裡返回的IBinder物件的例項,從而實現和服務進行通訊。只要呼叫方和服務之間的連線沒有斷開,服務就會一直保持執行狀態,直到呼叫了unbindService() 方法服務會停止,回撥順序onUnBind()->onDestroy()。

注意,這兩種啟動方法並不衝突,當使用startService()啟動Service之後,還可再使用bindService()繫結,只不過需要同時呼叫 stopService()和 unbindService()方法才能讓服務銷燬掉。

3.Service的基本用法

介紹完Service生命週期和啟動方法之後,下面來具體學習一下如何在Activity中啟動一個Service。

(1)普通Service

第一步:新建類並繼承Service且必須重寫onBind()方法,有選擇的重寫onCreate()、onStartCommand()及onDestroy()方法。 第二步:在配置檔案中進行註冊。學到現在會發現,四大元件除了廣播接收器可用動態註冊,定義好元件之後都要完成在配置檔案註冊的這一步。 第三步:在活動中利用Intent可實現Service的啟動,程式碼如下:

 Intent intent = new Intent(this, MyService.class);// MyService是剛剛定義好的Service
 startService(intent);
複製程式碼

對應的,停止Service方法:

Intent intent = new Intent(this, MyService.class);
stopService(intent);
複製程式碼

來實戰一下!定義一個MyService,重寫以下四種方法並都列印一行日誌:

學習筆記|AS入門(十) 元件篇之Service

在配置檔案對MyService進行註冊:

學習筆記|AS入門(十) 元件篇之Service

準備主活動佈局,就放兩個按鈕用來開啟和停止Service,然後設定相應的點選事件:

學習筆記|AS入門(十) 元件篇之Service

現在執行程式,順便檢驗一下之前學過的Service在啟動和停止時呼叫的方法是不是對的,結果如圖:

學習筆記|AS入門(十) 元件篇之Service

(2)前臺Service

前臺服務和普通服務最大的區別是,前者會一直有一個正在執行的圖示在系統的狀態列顯示,下拉狀態列後可以看到更加詳細的資訊,非常類似於通知的效果。使用前臺服務或者為了防止服務被回收掉,比如聽歌,或者由於特殊的需求,比如實時天氣狀況。

想要實現一個前臺服務非常簡單,它和之前學過的傳送一個通知非常類似,只不過在構建好一個Notification之後,不需要NotificationManager將通知顯示出來,而是呼叫了startForeground() 方法。

修改MyService的onCreate()方法:

學習筆記|AS入門(十) 元件篇之Service

現在重新執行程式,然後點選START SERVICE的按鈕,一個前臺服務就出現了:

學習筆記|AS入門(十) 元件篇之Service

(3)系統Service

除了自定義一個Service,當然還有現有的系統服務,比如之前接觸過的NotificationManage。通過getSyetemService() 方法並傳入一個Name就可以得到相應的服務物件了,常用的系統服務如下表:

學習筆記|AS入門(十) 元件篇之Service

現在再學習一個系統服務AlarmManager,來實現一個後臺定時任務。非常簡單,呼叫AlarmManager的set() 方法就可以設定一個定時任務,並提供三個引數(工作型別,定時任務觸發的時間,PendingIntent物件)。下面一一解釋以上三個引數:

1)工作型別:有四個值可選,見下圖:

學習筆記|AS入門(十) 元件篇之Service

2)定時任務觸發的時間:以毫秒為單位,傳入值和第一個引數對應關係是:

學習筆記|AS入門(十) 元件篇之Service

3)PendingIntent物件:一般會呼叫它的getBroadcast() 方法來獲取一個能夠執行廣播的PendingIntent。這樣當定時任務被觸發的時候,廣播接收器的onReceive()方法就可以得到執行。

接著實戰,修改MyService,將前臺服務程式碼都刪掉,重寫onStartCommand()方法,這裡先是獲取到了AlarmManager的例項,然後定義任務的觸發時間為10秒後,再使用PendingIntent指定處理定時任務的廣播接收器為MyReceiver,最後呼叫 set()方法完成設定,程式碼如圖:

學習筆記|AS入門(十) 元件篇之Service

然後定義一個廣播接收器為MyReceiver,這裡利用Intent物件去啟動MyService這個服務。這樣做的目的是,一旦啟動MyService,就會在onStartCommand()方法裡設定一個定時任務,10秒後MyReceiver的onReceive()方法將得到執行,緊接著又啟動MyService,反覆迴圈。從而一個能長期在後臺進行定時任務的服務就完成了。

學習筆記|AS入門(十) 元件篇之Service

MyReceiver也在配置檔案中註冊好之後,重新執行,點選START SERVICE的按鈕,觀察日誌的情況:

學習筆記|AS入門(十) 元件篇之Service

另外,從Android 4.4版本開始,由於系統在耗電性方面進行了優化使得Alarm任務的觸發時間會變得不準確。如果一定要求Alarm任務的執行時間精確,把AlarmManager的setExact() 方法替代 set()方法就可以了。

(4)IntentService

為了可以簡單地建立一個非同步的、會自動停止的服務,Android 專門提供了一個IntentService類。它的使用和普通Service非常像,下面來學習一下:

第一步:新建類並繼承IntentService,在這裡需要提供一個無參的建構函式且必須在其內部呼叫父類的有參建構函式,然後具體實現 onHandleIntent() 方法,在裡可以去處理一些耗時操作而不用擔心 ANR的問題,因為這個方法已經是在子執行緒中執行的了。 第二步:在配置檔案中進行註冊。 第三步:在活動中利用Intent實現IntentService的啟動,和Service用的方法是完全一樣的。

再來實戰,定義一個MyIntentService,準備好無參建構函式,並重寫onHandleIntent()方法,這裡列印了一行日誌,為了證實這個方法確實已經在子執行緒,又列印了當前執行緒的id。另外,根據IntentService的特性,這個服務在執行結束後應該是會自動停止的,所以重寫onDestroy()方法,在這裡也列印了一行日誌,以證實服務是不是停止掉了。

學習筆記|AS入門(十) 元件篇之Service

在配置檔案對MyIntentService進行註冊:

學習筆記|AS入門(十) 元件篇之Service

現在在主活動佈局再準備一個按鈕用來開啟這個IntentService,其點選事件程式碼如圖,在這裡列印了一下主執行緒的 id,稍後用於和IntentService進行比對:

學習筆記|AS入門(十) 元件篇之Service

執行程式,列印的日誌結果,證實了IntentService非同步和自動停止:

學習筆記|AS入門(十) 元件篇之Service

4.Service與Activity的通訊

最後來學習如何讓Service與Activity進行通訊。這就需要藉助服務的onBind() 方法了。比如希望在MyService裡提供一個下載功能,然後在活動中可以決定何時開始下載,以及隨時檢視下載進度。一起學習一下:

第一步:在MyService裡自定義一個類MyBinder並繼承Binder,在它的內部提供了開始下載以及檢視下載進度的方法,為了模擬一下,這裡就分別列印了一行日誌。

學習筆記|AS入門(十) 元件篇之Service

第二步:在MyService的onBind() 方法裡返回剛剛定義好的MyBinder類。

學習筆記|AS入門(十) 元件篇之Service

第三步:在活動中例項化一個ServiceConnection類,並重寫它的onServiceConnection()onServiceDisconnection() 方法,這兩個方法分別會在活動與服務成功繫結以及解除繫結的時候呼叫。在 onServiceConnected()方法中,又通過向下轉型得到了MyBinder 的例項,有了它活動和服務之間的關係就變得非常緊密了。現在可以在活動中根據具體的場景來呼叫MyBinder中的任何非private方法了。這裡簡單呼叫MyBinder中的兩個模擬方法。

學習筆記|AS入門(十) 元件篇之Service

第四步:在活動佈局裡再準備兩個按鈕用於繫結和解綁服務,在它們的點選事件裡利用Intent物件實現活動和服務的繫結和解綁。方法是:bindService() 實現繫結,它接收三個引數(Intent物件,ServiceConnection物件,標誌位),這裡傳入BIND_AUTO_CREATE 表示在活動和服務進行繫結後自動建立服務。unbindService() 實現解綁,傳入ServiceConnection物件就可以了。

學習筆記|AS入門(十) 元件篇之Service

執行程式,點選一下Bind Service 按鈕:

學習筆記|AS入門(十) 元件篇之Service

可以看到MyService的兩個模擬方法都得到了執行,說明確實已經在活動裡成功呼叫了服務裡提供的方法了。

現在學完四大元件現在可以總結一下了,Activity提供UI介面管理,Service提供與UI無關的服務,ContentProvider用於儲存和資料共享,Broadcast解決元件和應用的通訊問題,而Intent是將四大元件聯結在一起的粘結劑,但彼此之間幾乎沒有耦合。這種元件化的設計思想使得Android變得非常靈活。

到這裡安卓開發入門整篇已經接近尾聲了,小菜鳥終於開啟安卓世界的大門,但安卓的精彩遠不止此,深入探索之旅也才剛剛開始。縱觀全篇,整個入門過程中學習到的基礎知識還只停留在會用的階段,很多要點只是淺談沒有深究,對於基本原理幾乎隻字未提,所以在入門之後,小菜鳥還會繼續深入學習,不定時分享學習筆記,最後感謝和我一起進步的你們!

相關文章