2015年6月1日 星期一

[Android] AsyncTask - 非同步任務

AsyncTask非同步任務,或稱異步任務,是一個相當常用的類別,是專門用來處理背景任務與UI的類別。

Android 4.0 之後,有明文規定所有的網路行為都不能在主執行緒(Main Thread)執行,

主執行緒又稱UI執行緒(UI Thread),任何有關UI的東西都在主執行緒中執行,若是你的程式佔據主執行緒很久,使用者體驗會非常的差。

想像一下,按了一個按鈕後,整個App停住五秒會是怎樣的感覺,因此許多耗時的程式建議寫在背景執行,而其中最常見的就是網路的功能。

在此先介紹一下有關ANR(Application Not Responding)的問題,也就是應用程式沒有回應。

你可以試著加入一個Button,在onClick事件裡面做一件很花時間的事情。

跑了一個很大的迴圈,過一下子你的手機會跑出這個警告。
















這就是典型的ANR,因為onClick事件是在主執行緒,你佔據主執行緒太久的時間,因此跳出了這個警告,若是按下確定就會關閉你的程式。

要怎麼知道我是不是在主執行緒呢,你可以用以下的程式碼來判斷。

Thread.currentThread().getId()

像是你在onClick裡面加入這個Log,

Log.d("onClick = " , String.valueOf(Thread.currentThread().getId()));

然後試著印出來,你會發現他會寫1,1就是主執行緒的ID,也是UI Thread。

這樣你大概了解其中一種會產生ANR的問題。

接下來來談談AsyncTask的用法吧,這次舉的例子是從網路下載圖片。

你可能會想,只是下載一張小圖片應該不會花費太久時間,那我寫在主執行緒就好了。

然後就在onClick裡面寫了以下的程式碼。


透過BitmapFactory來下載圖片,在使用Try Catch來捕捉一些可能的例外,看起來滿正確的,但是實際執行會發現有一個例外。

android.os.NetworkOnMainThreadException

意思是,你不能在主執行緒做網路的事情,還是乖乖用AsyncTask吧XD


AsyncTask<Params, Progress, Result>,這是基本的架構,使用泛型來定義參數,

泛型意思是,你可以定義任意的資料型態給他。

Params : 參數,你要餵什麼樣的參數給它。

Progress : 進度條,進度條的資料型態要用哪種

Result : 結果,你希望這個背景任務最後會有什麼樣的結果回傳給你。

此外,AsyncTask會有四個步驟。

onPreExecute : 執行前,一些基本設定可以在這邊做。

doInBackground : 執行中,在背景做任務。

onProgressUpdate : 執行中,當你呼叫publishProgress的時候會到這邊,可以告知使用者進度。

onPostExecute : 執行後,最後的結果會在這邊。

拿下載圖片的例子來寫,繼承AsyncTask,並實作四個步驟,

參數說明 : 丟入網址(String),進度條用整數(Integer),拿到圖片(Bitmap)



這邊你可能不明白String...是什麼意思,這東西的意思是你可以傳單一一個String,
或者是一個String陣列都可以。

假如你丟一個String進去,你只要取得第一個元素即可。

String urlStr = params[0];

接著在把剛才下載圖片的程式改寫到doInBackground之中。


如此一來,你就會在背景下載圖片,當沒有例外的時候就會回傳。

此時,你可能會想,那我不要等回傳,我直接在doInBackground去改我的圖片就好了。

這時候會跳出一個例外:

Only the original thread that created a view hierarchy can touch its views.

意思是,你只能在UI Thread去修改UI,因為你現在是在背景,因此你必須回到UI Thread才能對UI做事情,很勤勞的跑去Google找解,最後你的程式碼可能變成這樣。

事實上,這樣是可以運作沒錯,但是有點太多此一舉。

主執行緒沒辦法用網路 -> 用背景執行 -> 背景執行沒辦法改UI -> 在回去主執行緒。

我們有提到,AsyncTask有四個步驟,我們試著將這四個步驟的執行緒ID都印出來。













除了背景任務以外都回到主執行緒了,因此你可以在結果的部分在對UI做修改,不用特定在背景那邊在呼叫回主執行緒,這樣太多此一舉了。

如此一來基本的認識應該有了,這裏提供兩個範例。

1. 傳入一個網址,下載網路圖片後顯示在ImageView之中

執行背景程式的方法

2. 實作進度條的功能。

模擬下載三張圖,可是下載完後沒有回傳圖片回去,這個例子是說明怎麼使用進度條。
當你呼叫publishProgress時,丟入一個值,會到onProgressUpdate之中,在這邊更新進度條的進度。



























當完成之時,在呼叫dismiss將進度條除去。

看完這篇文章你應該對AsyncTask有一些基本的認識了。