當前位置:
首頁 > 最新 > Android開發學習-Day17-19 多線程&Service

Android開發學習-Day17-19 多線程&Service

Service(服務)是Android 中實現程序後台運行的解決方案,它非常適合去執行那些不需要和用戶交互而且還要求長期運行的任務。服務的運行不依賴於任何用戶界面,即使程序被切換到後台或者用戶打開了另外一個應用程序,服務仍然能夠保持正常運行。

不過需要注意的是,服務並不是運行在一個獨立的進程當中的,而是依賴於創建服務時所在的應用程序進程。當某個應用程序進程被殺掉時, 所有依賴於該進程的服務也會停止運行。

另外,也不耍被服務的後台概念所迷惑,實際上服務並不會自動開啟線程,所有的代碼都是默認運行在主線程當中的。也就是說,我們需要在服務的內部手動創建子線程,並在這裡執行具體的任務,否則就有可能出現主線程被阻塞住的情況。所以我們先來學習一下多線程:


當我們需要執行一些耗時的操作時,比如發送網路請求等,如果不將這類任務放到子線程中去時,很容易就會導致主線程被阻塞,從而影響用戶對軟體的正常使用。

1、線程的基本用法

Android多線程編程跟Java多線程編程類似,都使用相同的語法。例如,定義一個線程只需要新建一個類並繼承自Thread,然後重寫父類的run()方法,在裡面添加耗時操作就可以了。或者使用內部類的寫法:

2、在子線程中更新UI

Android的UI線程是不安全的,如果想要更新程序里的UI,就必須在主線程中進行。不信的話可以試試如果在子線程更新UI會發生什麼:

我們在crash按鈕裡面開啟了一個子線程,然後在子線程中調用了TextView的setText()方法,將TextView默認顯示的文案變成我們重新定義的文案,這裡引用了string中的一個字元串。重新運行程序,點擊按鈕,一會發現程序立馬崩潰了,通過logcat日誌觀察,可以知道crash來自於在子線程中更新UI。

但是有時候,我們需要在子線程做一些耗時操作,然後根據執行結果來更新UI控制項,對於這種情況,Android提供了一套非同步消息處理機制,解決了在子線程中進行UI操作的問題。修改代碼:

首先我們定義了一個整型的常量UPDATETEXT用來表示更新TextView的動作。然後新增了一個Handler對象,並重寫了父類的handleMessage()方法,在這裡對具體的Message進行處理。如果發現Message的what欄位為UPDATETEXT,那麼就調用TextView的setText()方法來更新顯示內容。

再來看點擊事件內部邏輯,首先開啟一個子線程,然後新建一個Message對象(android.os.Message),並將它的what欄位值指定為UPDATETEXT,然後調用Handler的sendMessage()方法將這條Message發送出去。很快Handler就會收到這條Message,並在handlerMessage()方法中處理消息。此時,handlerMessage()方法中的代碼是運行在主線程上,所以可以進行UI操作,當what欄位值為UPDATETEXT,我們就開始更新UI。

這樣就已經初步掌握了Android的非同步消息處理的基本用法。下面在分析一下非同步消息處理到底是如何工作的。

3、解析非同步消息處理機制

Android中非同步消息處理主要由4個部分組成:Message、Handler、MessageQueue、Looper。其中Message、Handler我們已經應用過了,MessageQueue、Looper則隱藏在它們之下。

□ Message:在線程間傳遞的消息,可以在內部攜帶少量的信息,用於在不同線程之間交換數據。我們之前使用到了Message的what欄位,除此之外,還能使用arg1和arg2欄位攜帶一些整型數據,使用obj欄位攜帶一個Object對象。

□ Handler:它主要用於發送和處理消息。發送的消息一般是使用Handler的sendMessage()方法,發出去的消息經過一系列的輾轉之後,最終會傳遞到Handler的handlerMessage()方法中。

□ MessageQueue:是消息隊列,主要用於存放所有通過Handler發送的消息。這部分消息一直存在消息隊列匯總,等待被處理。每個線程只會有一個MessageQueue對象。

□ Looper:是每個線程中的MessageQueue的管家,調用Looper的loop()方法後,就會進入到一個無限循環之中,每當發現MessageQueue中存在一條消息,就會將它取出,並傳遞到Handler的handlerMessage()方法中。每個線程中也只會有一個Looper對象。

所以梳理下來,首先需要在主線程創建一個Handler對象,並重寫handlerMessage()方法。然後當子線程需要更新UI時,就創建一個Message對象,並通過Handler將這條消息發送出去。之後這條消息會進入到MessageQueue中等待被處理,而Looper會一直嘗試從MessageQueue取出待處理的消息,最後分發回Handler的handlerMessage()方法中。由於Handler是在主線程中創建的,所以handlerMessage()方法中的代碼也是在主線程中運行,接下來就可以進行相應的UI操作了。

我們在之前web的練習中使用過的runOnUIThread()方法,其實也是對非同步消息處理機制的一個介面封裝,它們背後的原理都是一樣的:

4、使用AsyncTask

為了更方便的在子線程對UI進行操作,Android還提供了另外一些好用的工具,比如AsyncTask。藉助AsyncTask,即使對非同步消息處理機制完全不了解,也可以十分簡單的從子線程切換到主線程。

AsyncTask是一個抽象類,要想使用它就得創建一個子類繼承它。在繼承時,我們可以為AsyncTask指定3個泛型參數

□ Params:在執行AsyncTask時需要傳入的參數,可用於後台任務中使用。

□ Progress:在後台任務執行時,如果需要在UI上顯示進度,則使用這裡指定的泛型作為進度單位。

□ Result:當任務執行完畢後,如果需要對結果進行返回,則使用這個指定的泛型作為返回值類型。

例如如下代碼,這裡我們把第一個泛型參數指定為String,代表執行AsyncTask時,傳入String參數給後台任務。第二泛型參數指定為整型,表示用整型數據作為進度顯示單位。第三個泛型參數指定為整型,表示當任務結束後,使用整型數據反饋執行結果。

使用AsyncTask時,我們通常需要重寫它幾個的方法才能完成對任務的定製。□ onPreExecute():@MainThread這個方法在執行後台任務前調用,用於進行一些界面上的準備初始化操作,比如顯示一個進度條對話框等

□ doInBackground(Params...):@WorkerThread這個方法的所有代碼都會在子線程中運行,我們在這裡去執行耗時的任務。任務完成後,就會通過return語句將任務的執行結果返回,如果AsyncTask的第三個泛型參數指定為Void,則可以不用返回執行結果。由於這個這裡是在子線程中運行,所以不能進行UI操作。如果需要更新UI元素,可以調用publishProgress(Progress...)方法來完成。

□ onProgressUpdate(Progress...):@MainThread當在後台任務中調用了publishProgress(Progress...)方法後,onProgressUpdate(Progress...)會很快被調用,該方法中攜帶的參數就是後台任務中傳遞過來的。因為這個方法在主線程中運行,所以可以根據參數中的值來進行UI操作。

□ onPostExecute(Result...):@MainThread當後台任務執行完畢並通過return語句返回時,這個方法就會很快被調用。返回的數據會作為參數傳遞到這個方法中,利用這些參數就可以進行相應的操作。因為也是在主線程中運行,可以操作UI控制項,例如關閉進度條或者提醒任務結果等。

通過介紹我們能看出來,使用AsyncTask的訣竅就是,在doInBackground()方法執行具體的耗時操作,在onProgressUpdate()方法進行UI操做,在onPostExecute()進行任務的收尾工作。開啟這個AsyncTask也很簡單,調用execute()方法,或者executeOnExecutor()。

後面我們將在實踐中貼出代碼。


Service之前,我們已經掌握了Android四大組件中的3個:Activity、BroadcastReceiver和ContentProvider。跟以前的思路一樣,從最基本的用法開始學習。

1、定義一個Service

我們可以通過Android Studio的快捷方式創建,New -> Service -> Service

Exported表示是否允許除了當前應用之外的其他應用程序訪問這個服務,Enabled表示是否啟動這個服務。都勾選後點擊finish完成自動創建。

可以看到MyService繼承自Service,說明這是一個服務。另外還有一個onBind()方法,這是Service中唯一的一個抽象方法,所以必須在子類中實現。記得我們之前說過,Android的四大組件都需要在Manifest中註冊後才能使用,不過這一步Android Studio已經幫我們做好了。

創建了服務,我們還需要它去做一些事情,這時我們就可以重寫Service中另一些方法了,繼續修改代碼:

這裡我們重寫了onCreate()、onStartCommand()、onDestroy()方法。其中onCreate()方法會在服務創建的時候調用,onStartCommand()會在每次啟動服務的時候調用,onDestroy()方法在服務銷毀的時候調用。

2、啟動和停止服務

定義好服務後,接下來就是如何啟動和停止服務了。方法還是我們熟悉的Intent,代碼:

我們直接點擊事件,開始服務我們先是構建一個Intent對象,然後調用startService()方法來啟動MyService這個服務。停止服務也一樣,先構建Intent對象,然後調用stopService()方法結束服務。我們之前已經在MyService中打下了日誌,重新運行應用程序:

同時,當開啟服務後,我們從系統的運行狀態中也可以看到詳細的信息:

3、Activity和Service進行通信

前面我們通過Activity啟動Service後,二者就幾乎沒什麼關係了,Activity也不知道Service都去幹了些什麼。那麼如何讓它們的關係更緊密一些呢,記得我們在創建Service的時候,有一個待實現的onBind()方法,現在我們就要藉助它的力量了。

例如我們需要在MyService中提供一個下載功能,然後在Activity中可以決定什麼時候開始下載,以及下載的進度如何,實現的思路就是創建一個專門的Binder對象來對下載功能進行管理,修改代碼:

可以看到我們構建了一個DownloadBinder類並讓它繼承自Binder,然後內部提供了開始下載和下載進度的方法(這裡只是模擬功能)。接著在MyService中創建DownloadBinder的實例,並在onBind()方法中返回該實例。這樣MyService中的代碼就完成了。

接下來修改Activity中的代碼,在布局文件添加綁定服務按鈕和解綁按鈕:

我們先創建了一個ServiceConnection匿名類,裡面重寫了onServiceConnected()和onServiceDisconnected()方法,這兩個方法會分別在Activity與Service綁定和解綁的時候調用。在onServiceConnected()中,我們通過向下轉型得到了DownloadBinder實例,有了這個實例,Activity與Service的聯繫就緊密的多了。現在我們可以根據場景來使用DownloadBinder中任何public的方法了。

然後我們在點擊事件中,仍然是先構建了Intent對象,然後調用bindService()方法。該方法接收3個參數,第一個參數是intent,傳我們剛剛創建的Intent對象;第二個參數為ServiceConnection的實例;第三個參數是一個flag標誌位,我們傳入BINDAUTOCREATE表示Activity和Service綁定後自動創建Service。這樣會使MyService中的onCreate()得到執行,而onStartCommand()不會執行。同樣的,解綁需要調用unbindService,同樣是傳入ServiceConnection的實例。重新運行應用程序:

點擊綁定服務後,就會發現MyService的onCreate()方法得到了執行,然後startDownload()和getProgress()也得到了執行。點擊解綁按鈕後,onDestroy()方法得到了執行。

4、Service的生命周期

之前我們學習過了活動以及碎片的生命周期。類似地,服務也有自己的生命周期,前面我們使用到的 onCreate( ) 、onStartCommand( ) 、onBind()和onDestroy()等方法都是在服務的生命周期內可能回調的方法。

一旦在項目的任何位置調用了Context的 startService()方法,相應的服務就會啟動起來並回調 onstartCommand()方法。 如果這個服務之前還沒有創建過 onCreate()方法會先於onstartCommand()方法執行。服務啟動了之後會一直保持運行狀態直到stopService()或stopSelf()方法被調用。 注意,雖然每調用一次startService()方法,onstartCommand()就會執行一次,但實際上每個服務都只會存在一個實例。所以不管你調用了多少次 startService()方法,只需調用一次 StopService()或者stopSelf()方法, 服務就會停止下來了。

另外還可以調用 Context的bindService()來獲取一個服務的持久連接,這時就會回調服務中的onBind()方法。類似地,如果這個服務之前還沒有創建過,onCreate()方法會先於onBind()方法執行。之後,調用方可以獲取到onBind()方法里返回的 IBinder對象的實例。這樣就能自由地和服務進行通信了,只要調用方和服務之間的連接沒有斷開, 服務就會一直保持運行狀態。

當調用了startService()方法後,又去調用stopService()方法, 這時服務中的onDeStroy()方法就會執行,表示服務已經銷毀了。類似地當調用了bindService()方法後,又去調用 unbindService()方法,onDestroy()方法也會執行,這兩種情況都很好理解。但需要注意,我們是完全有可能對一個服務既調用了startService()方法,又調用了bindService()方法的,這種情況下該如何才能讓服務銷毀掉呢? 根據Android系統的機制,一個服務只要被啟動或者被綁定了之後,就會一直處於運行狀態,必須要讓以上兩種條件同時不滿足,服務才能被銷毀。 所以,這種情況下耍同時調用 stopService( )和 unbindService()方法,onDestroy()方法才會執行。

這樣,Service的生命周期我們就了解了。

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 TesterJohn 的精彩文章:

Android開發學習-Day16 內容提供器

TAG:TesterJohn |