首先我們思考幾個問題,在Android應用中為什么要用多線程?為了解決哪些問題?或者為了實現哪些功能?有哪些好處?請先思考一分鐘,再繼續(xù)往下看。
學習而不思考就像吃東西而不嚼,要么無法下咽,要么嘗不出味道,同時都會影響消化吸收??刂埔幌履隳敲擁\野馬一樣的好奇心吧,先思考再往下看。
1. 為什么要用多線程
這里列出幾個原因:
a) 提高用戶體驗或者避免ANR
在事件處理代碼中需要使用多線程,否則會出現ANR(Application is not responding),或者因為響應較慢導致用戶體驗很差。
圖1 ANR對話框
b) 異步
應用中有些情況下并不一定需要同步阻塞去等待返回結果,可以通過多線程來實現異步,例如:上一點中提到的,你的應用中的某個Activity需要從云端獲取一些圖片,加載圖片比較耗時,這時需要使用異步加載,加載完成一個圖片刷新一個,見下面圖2、圖3 。
c) 多任務
例如多線程下載。
后兩點與Java中的多線程應用沒有太大區(qū)別,不細說。
下面重點說明第一點,即如何減少事件響應的時間從而提高用戶體驗,以及如何避免ANR。
2. 為什么通過多線程可以提高用戶體驗、避免ANR
大家還記得我在群里說過的移動開發(fā)的“三不要”原則嗎?即:不要讓我想、不要讓我等、不要讓我煩。響應慢了用戶需要等,等的次數多了就會煩,你的應用離被卸載不遠了。
首先我們來了解一下Android應用程序的main線程,它負責處理UI的繪制,Android系統(tǒng)為了防止應用程序反應較慢導致系統(tǒng)無法正常運行做了一個處理,一種情況是當用戶輸入事件在5秒內無法得到響應,那么系統(tǒng)會彈出ANR對話框,由用戶決定繼續(xù)等待還是強制結束應用程序(另一種情況是BroadcastReciever 超過10秒沒執(zhí)行完也會彈出ANR對話框)。
即使你的程序中某個事件響應不超過5秒鐘,人眼可以分辨的時間是0.1秒,小于0.1秒基本感覺不出來,超過0.2秒用戶就能感覺到有點兒卡了,俗稱打嗝現象,2秒以上就很慢了,用戶體驗會很差。有同學說我可以用進度條啊,但你的程序中不能到處都是進度條,否則那個圈圈會把用戶轉暈的,好像在對用戶說,畫個圈圈煩死你……
比如某些應用,它要顯示很多圖片,還好它是異步的,不過在圖片加載完成前每個圖片的位置上都有一個圈圈,讓人看了很煩。你可以變通一下,圖片加載成功之前顯示一個默認的圖片,加載成功后再刷新一下即可,何必弄那么多進度條呢?
圖2 加載圖片完成前顯示默認圖片,加載完成后再刷新
圖3 加載圖片完成前顯示默認圖片,加載完成后再刷新
圖4 轉暈你,煩死你
事件處理的原則:所有可能耗時的操作都放到其他線程去處理。
Android中的Main線程的事件處理不能太耗時,否則后續(xù)的事件無法在5秒內得到響應,就會彈出ANR對話框。那么哪些方法會在 Main線程執(zhí)行呢?
1) Activity的生命周期方法,例如:onCreate()、onStart()、onResume()等
2) 事件處理方法,例如onClick()、onItemClick()等
通常Android基類中以on開頭的方法是在Main線程被回調的。
提高應用的響應性,可以從這兩方面入手。
一般來說,Activity的onCreate()、onStart()、onResume()方法的執(zhí)行時間決定了你的應用首頁打開的時間,這里要盡量把不必要的操作放到其他線程去處理,如果仍然很耗時,可以使用SplashScreen。使用SplashScreen最好用動態(tài)的,這樣用戶知道你的應用沒有死掉。
圖5 動態(tài)SplashScreen
當用戶與你的應用交互時,事件處理方法的執(zhí)行快慢決定了應用的響應性是否良好,這里分兩種情況:
1) 同步,需要等待返回結果。例如用戶點擊了注冊按鈕,需要等待服務端返回結果,那么需要有一個進度條來提示用戶你的程序正在運行沒有死掉。一般與服務端交互的都要有進度條,例如系統(tǒng)自帶的瀏覽器,URL跳轉時會有進度條。
2) 異步,不需要等待返回結果。例如微博中的收藏功能,點擊完收藏按鈕后是否成功執(zhí)行完成后告訴我就行了,我不想等它,這里最好實現為異步的。
無論同步異步,事件處理都可能比較耗時,那么需要放到其他線程中處理,等處理完成后,再通知界面刷新。
這里有一點要注意,不是所有的界面刷新行為都需要放到Main線程處理,例如TextView的setText()方法需要在Main線程中,否則會拋出CalledFromWrongThreadException,而ProgressBar的setProgress()方法則不需要在Main線程中處理。
當然你也可以把所有UI組件相關行為都放到Main線程中處理,沒有問題??梢詼p輕你的思考負擔,但你最好了解他們之間的差別,掌握事物之間細微差別的是專家。把事件處理代碼放到其他線程中處理,如果處理的結果需要刷新界面,那么需要線程間通訊的方法來實現在其他線程中發(fā)消息給Main線程處理。
3. 如何實現線程間通訊
在Android中有多種方法可以實現其他線程與Main線程通訊,我們這里介紹常見的兩種。
1) 使用AsyncTask
AsyncTask是Android框架提供的異步處理的輔助類,它可以實現耗時操作在其他線程執(zhí)行,而處理結果在Main線程執(zhí)行,對于開發(fā)者而言,它屏蔽掉了多線程和后面要講的Handler的概念。你不了解怎么處理線程間通訊也沒有關系,AsyncTask體貼的幫你做好了。不過封裝越好越高級的API,對初級程序員反而越不利,就是你不了解它的原理。當你需要面對更加復雜的情況,而高級API無法完成得很好時,你就杯具了。所以,我們也要掌握功能更強大,更自由的與Main線程通訊的方法:Handler的使用。
AsyncTask的使用方法見示例工程《EX10_02AsyncTask》
2) 使用Handler
這里需要了解Android SDK提供的幾個線程間通訊的類。
2.1 Handler
Handler在android里負責發(fā)送和處理消息,通過它可以實現其他線程與Main線程之間的消息通訊。
2.2 Looper
Looper負責管理線程的消息隊列和消息循環(huán)
2.3 Message
Message是線程間通訊的消息載體。兩個碼頭之間運輸貨物,Message充當集裝箱的功能,里面可以存放任何你想要傳遞的消息。
2.4 MessageQueue
MessageQueue是消息隊列,先進先出,它的作用是保存有待線程處理的消息。
它們四者之間的關系是,在其他線程中調用Handler.sendMsg()方法(參數是Message對象),將需要Main線程處理的事件添加到Main線程的MessageQueue中,Main線程通過MainLooper從消息隊列中取出Handler發(fā)過來的這個消息時,會回調Handler的handlerMessage()方法。
除了以上兩種常用方法之外,還有幾種比較簡單的方法
3) Activity.runOnUiThread(Runnable)
4) View.post(Runnable)
View.postDelayed(Runnable, long)
5) Handler.post
Handler.postDelayed(Runnable, long)
4. 利用線程池提高性能
這里我們建議使用線程池來管理臨時的Thread對象,從而達到提高應用程序性能的目的。
線程池是資源池在線程應用中的一個實例。了解線程池之前我們首先要了解一下資源池的概念。在JAVA中,創(chuàng)建和銷毀對象是比較消耗資源的。我們如果在應用中需要頻繁創(chuàng)建銷毀某個類型的對象實例,這樣會產生很多臨時對象,當失去引用的臨時對象較多時,虛擬機會進行垃圾回收(GC),CPU在進行GC時會導致應用程序的運行得不到相應,從而導致應用的響應性降低。
資源池就是用來解決這個問題,當你需要使用對象時,從資源池來獲取,資源池負責維護對象的生命周期。
了解了資源池,就很好理解線程池了,線程池就是存放對象類型都是線程的資源池。
我增加了如何在其他線程中創(chuàng)建Handler的例子作為選學,前面都掌握好了的同學可以看一下,如果你需要實現一個跟Main線程類似的消息處理機制,需要其他線程可以跟你的線程通訊,可以通過這種方法實現。
(審核編輯: 智匯小新)
分享