2010年9月14日 星期二

小心,AsyncTask 不是萬能的

Don't only use AsyncTask for your download task

要進入 Android 應用開發之門,真的一點都不難,不就是 Java 嘛。這是我最常聽到,剛跨過入門檻開發者告訴我的一句話。

這句話其實是沒有錯的,Android 應用開發的入門檻,比起其他平台,已經低了許多。要跨過這門檻,的確是輕鬆容易許多。不過,如果要從入門到進階,邁向下一個關卡,你第一個要了解的是 Activity 的生命週期Process 的生命週期,而且是要『透 . 徹 . 了 . 解』。這一步很重要,卻被許多開發者輕忽了。

尤其這 Activity 的生命週期,實際上的行為會比你從文件上看到的說明還來的複雜。也因此,我發現有不少,即使已有多個 Android 應用開發經驗的開發者,在開發 Android 應用時,還是栽了不少跟斗。這第一個問題,就出在這些開發者,還是沒達到我說的『透徹了解』境界。因此,今年的進階應用開發課程,特地將這一部分加入進來,希望對想踏入進階之門的開發者,有所助益。

好了,如果你已經有一段 Android 應用的開發經驗,那應該知道在 Main-thread(UI-thread) 中,你不能執行一件需時 5 秒以上的工作,例如網路或資料庫的存取、音樂的播放等等。要不然你的應用就會產生 ANR 錯誤。要解決這個 ANR 錯誤,唯一的方法就是自行建立一個新的 Thread 物件,並將該費時的工作放在 Thread.run() 中執行。關於如何解決 ANR 的細節,我建議你先讀 Painless threading,這是一篇值得一讀的好文章。

在這篇文章中,介紹了從 Android 1.5 才加入的 AsyncTask。AsyncTask 很好用,同時我也建議你研究他的 原始碼。AsyncTask 就是太好用了,有些開發者就認為,單用 AsyncTask 就能解決他的問題。其實,我早在請將要執行很久的程式碼,放在 Service 中執行這篇中,就已提過。單將費時的程式碼放在 Thread.run() 中執行,還是不夠的,你只解決一半的問題。不過,多數開發者不是便宜行事,就是不相信我說的。這些便宜行事的開發者,就是在賭這系統強制殺掉你應用的機率有多少;而那些不相信我說的,就是因為他沒有透徹了解 Activity/Process 的生命週期。

完整解決費時工作的方法,不僅要將費時的工作放在 Thread.run() 中執行,還要將這個 Thread 放在 Service 中執行。

你要知道 Android 的四大元件,Activity, BroadcastReceiver, Servcice and ContentProvider,除了 ContentProvide 外,全都是在 main-thread 中執行。而這些元件中,就只有 Service 的生命週期是最持續(長久)的。Activity 只要執行到 onPause(),BroadcastReceiver 只要離開 onReceiver(),系統隨時會殺掉這些元件,而且機率還很高。Service 當然也是會被系統砍掉,只不過它的優先順序,排在較低等級。自然被系統砍掉的機率就低很多。你還可以更進一步利用 Serivce.startForeground() 降低你被系統殺掉的優先順序。關於這部分,你要熟讀 Process 的生命週期What is a Service?

其實為了減輕開發者的負擔, Android 1.5 已經加了 IntentService 這個新類別。如果你要寫個用到網路的應用,用這個 IntentService 才是你的完美解決方案。

要使用這個 IntentService 其實很簡單,你只要繼承這個 IntentService 並將該項費時的工作,移到 onHandleIntent() 中即可。onHandleIntent() 是被 non-UI thread 所喚起的。因此在這裏面你可以放心地去執行你的下載工作。

下次,我們來研究一下這個 IntentService 的原始碼,看他是如何做的。

參考資料:

12 則留言:

Indiana 提到...

謝謝老師的分享!最近剛好需要用到網路應用,能看到這篇文章真是太棒了!IntentService 真的很方便~ 謝謝!

匿名 提到...

謝謝老師的文章,想請教 若想將 IntentService 內的產生的多個 List 傳回 UI Thread 內不知道有甚麼方式可達到?

samlu 提到...

要從 service 傳資料回 UI thread 有很多方式,BroadcastReceiver, IBinder 都可以。

匿名 提到...

老師您好:

想請問一下如果一個project內有數個activity
例如由A->B->C
怎麼樣才能直接從activity C中把整個project全部結束掉呢?
finish()、system.exit(0)都無效
這兩個方法結束的究竟是什麼呢?
謝謝!

samlu 提到...

用底下這行可殺掉你自己的程序。

android.os.Process.killProcess(android.os.Process.myPid());

不過,我不建議用,你的 user flow 應該有問題。

匿名 提到...

老師您好:

很感謝您的建議
想請問所謂"user flow應該有問題"是什麼意思呢?
由A activity藉由 startActivity(intent)到另一activity但想在B activity關閉整個project這樣子是很奇怪的嗎?
Android寫到後來不會只有單一activity
當我有多個頁面在切換,而又不知道end user會跑到哪裡去,總不能請end user不斷按下back鍵直到第一個進入的activity然後真的退出整個專案。此時應如何才能真正「退出」整個project才是正常的程序?
我嘗試設置一個button讓end user可以從任何頁面回到main menu頁面,再由main menu finish();但此法沒有辦法退出,只會回到上一個activity,所以很困惑
不知道這樣描述老師是否能明白?
簡言之,是一個project由多個activity組成,end user會到處閒逛,而最後總是要能讓end user真正退出整個程式
謝謝老師!

samlu 提到...

建議你多玩玩別人的程式體驗一下。
根據我自己的經驗,很少有人會像你這麼做。

那他們是怎麼解決你說的問題呢?這可要你自己去發掘了。

匿名 提到...

謝謝老師的指導
前兩天我回頭觀察憤怒鳥的流程
自己的user flow應該出了不小的問題
發生了一點小慘劇
即便使用android.os.Process.killProcess(android.os.Process.myPid());
它會自動跳至前一個轉過來的activity
我進入跳不出程式的無窮activity switch
自己也不知道怎麼會把自己搞到這種神奇的情況
覺得自己很好笑
和老師分享自己的糗狀

匿名 提到...

謝謝老師的分享,想請教一個存在好一陣子的疑惑,
為什麼finish()掉的程式還能在DDMS中看見該process呢?
需要處理這一塊嗎?

samlu 提到...

Activity 結束不代表 process 也結束。你一定沒上過我的課。
請自行研讀 http://developer.android.com/guide/topics/fundamentals/processes-and-threads.html#Lifecycle

愉★悅 提到...

不好意思, 請教一下, 那如果使用 java.util.Timer, 這算是用 thread 嗎?

samlu 提到...

gg, 請自行看文件說明 http://developer.android.com/reference/java/util/Timer.html

張貼留言