1. 程式人生 > >Android耗時任務處理方案--AsyncTask

Android耗時任務處理方案--AsyncTask

在android應用中,每一個應用都對應一個程序,而應用的程序預設情況下只會開啟一個執行緒即主執行緒。所有的操作都發生在主執行緒(或者稱為UI執行緒)。
主執行緒的生死存亡是和程序一致的。
執行在UI執行緒中的操作主要有System Event, Input Event, Application, Service, Alarm, UI Drawing.這些類的程式碼都會執行在UI執行緒中。而且在這類操作中寫的所有程式碼,包括自己寫的程式碼也都會在UI執行緒執行。所以,可以看出UI執行緒真的比較繁忙。
假如你給一個Button 上一個監聽器,他的功能是執行一個耗時的操作(比如下載),而你寫的所有關於下載的程式碼都放在onClick方法中。這就會出現對UI執行緒的阻塞,而且這個阻塞會讓UI執行緒中其他所有的工作都等待他執行完再進行!簡單一點說就是:執行在UI執行緒中的操作System Event, Input Event, Application, Service, Alarm, UI Drawing都要會等待著!想一想,下載過程中如果使用者戳你的螢幕,點一個輸入框都不會有任何反應,如果有一個轉菊花的動畫,那朵菊花也會卡住,就是因為你的程式碼太耗時了。嚴重的情況下應用就會直接崩潰掉。這對於使用者來說是一件非常災難性的事情。
非常重要的一點就是UI執行緒還負責UI 渲染繪製。就比如說轉菊花這個動畫,菊花之所以能轉,就是因為主執行緒每16ms就繪製一次螢幕。為了保證UI介面的流暢,就必須把任何UI執行緒的程式碼執行操作耗時限制在16ms以內。否則菊花就會卡住(遺漏了很多幀畫面的繪製)。
如此嚴苛的程式碼執行16ms耗時限制肯定不能完成多數的耗時操作,哪怕是解析Json的工作也是耗時大概百ms的。更別提下載這樣相當耗時的程式碼操作了。為了解決這個問題,所以多執行緒誕生了。
耗時的任務我們開一個子執行緒,子執行緒去執行耗時的程式碼操作,做完主執行緒交給的任務,結束自己這個執行緒。這樣主執行緒就可以繼續16ms就繪製一次UI,因此菊花就會正常轉動了。
那麼在android中如何開執行緒做耗時任務呢?Android框架給我們提供了這樣4個方案:
1.AsyncTask
AysncTask是一個最常用的耗時任務非同步處理方案。他的優勢在於可以輕鬆完成耗時任務和UI執行緒之間的切換。
2.HandlerThread
有些任務並不是一定與UI發生關係,當需要一個專門用作API回撥的工作執行緒時,HT很適合
3.ThreadPool
ThreadPool適合做一些頻繁細小的耗時任務,執行緒池執行著許多並行的執行緒。所以對於頻繁的小的工作執行緒池執行的更快。
4.IntentService
IntentService適合做後臺任務,尤其是UI執行緒的有些Intent並不適合交給主執行緒中的任何元件處理。交給這個天然擁有HandlerThread的IntentService去後臺執行這個Intent的任務反而更合適。

AsyncTask處理耗時任務

AsyncTask處理耗時任務也是基於子執行緒來處理的。
普通的子執行緒就做三件事:1.開始執行緒 2.執行執行緒中的任務程式碼 3.把自己這個執行緒結束掉。這樣的執行緒功能是很有限的。如果你想讓一個執行緒做完第一個任務以後先別結束,等著連續不斷的接新任務程式碼接著執行怎麼辦?反正普通的執行緒就是簡單的三件事。為了能夠不斷的接任務程式碼就需要對普通的執行緒加一點東西。那就是不斷地給這個執行緒喂任務。這就是Looper以及Handler要做的事情。這樣這個執行緒才能保持繼續工作。但是不斷喂得這些任務需要排隊執行,那麼就需要一個能夠管理要執行的任務的東西。這就是MessageQueue要做的事情。MQ保證任務有序的交給執行緒去執行。同時任務從哪裡來呢,還需要有一個執行緒去產生任務然後放進MQ裡面。

AysncTask是利用Handler來實現耗時任務子執行緒與UI執行緒的資料傳遞的。既然本質上是一樣的,那就先了解一下Handler是怎麼運作的。

這裡寫圖片描述

想想一下這個場景:你有一個耗時的任務,然後要改變UI執行緒(UI執行緒本身就是一個HandlerThread)的某個UI引數。這時候就要開一個執行緒(OtherThread)去做這件耗時的事情,但是完成之後又不能在該執行緒去直接改變UI。那就要把執行完成後的返回值做成一個Message交給Handler(這個Handler是屬於HandlerThread的)發到MQ(這個MQ也是屬於HandlerThread的)中。(當然也不一定是非要是一個Message,做成一個Intent,Runnable物件交給Handler發到MQ中也是沒問題的,看具體自己的目的)。之後MQ會把發過來的Message交給HandlerThread處理。
當然Handler MQ Looper機制之所以存在就是因為很多時候,我們程式設計師有太多的耗時任務要做,然後改變UI執行緒的變數。如果有數十個執行緒想要改變同一個UI變數怎麼辦(注意UI執行緒是執行緒不安全的,也就是說UI執行緒的UI控制元件是不上鎖的)?所以就要有MQ來保證這件事有序的進行下去。
AysncTask也是同樣的內部機制。

AysncTask導致的記憶體洩漏問題

AsyncTask在作為Activity的內部類使用的時候很容易產生記憶體洩漏的問題。在使用過程中可能有這種場景:旋轉螢幕Activity被destroy掉。但是內部的AsyncTask的任務還沒有執行完。而內部類天然持有外部包裹類的引用。所以Activity物件佔用的記憶體空間就不會被GC回收,直到AT自己的任務完成了銷燬的自己,那麼他持有的Activity的引用才會為null.洩漏一個Activity的引用。記憶體可能對使用者影響不大。但是當連續不斷的翻轉手機,就會有大量的記憶體被不再需要的Activity物件所佔據,直到記憶體被消耗光應用崩潰。或者AT完成了任務,想要去更新自己外部包裹類Activity的UI時,發現他已經destory掉了,導致應用崩潰。所以這是一個很嚴重的問題。
比較常用的辦法就是把AT做成static的