Android線程和Handler基礎入門
現在大多數的移動設備已經變得越來越快,但是它們其實也不算是非常快。如果你想讓你的APP既可以承受一些繁雜的工作而又不影響用戶體驗的話,那么必須把任務并行執行。在Android上,我們使用線程。
端一杯咖啡,然后仔細閱讀這篇文章。我會給大家介紹一下線程的概念,還有在Java中怎么使用線程,在線程中怎么使用Handler等。
如果需要使用異步處理或者并行任務的話,那么你一定會用到線程。
什么是線程?
線程或者線程執行本質上就是一串命令(也是程序代碼),然后我們把它發送給操作系統執行。
一般來說,我們的CPU在任何時候一個核只能處理一個線程。多核處理器(目前大多數Android設備已經都是多核)顧名思義,就是可以同時處理多線程(通俗地講就是可以同時處理多件事)。
多核處理與單核多任務處理的實質
上面我說的是一般情況,并不是所有的描述都是一定正確的。因為單核也可以用多任務模擬出多線程。
每個運行在線程中的任務都可以分解成多條指令,而且這些指令不用同時執行。所以,單核設備可以首先切換到線程1去執行指令1A,然后切換到線程2去執行指令2A,接著返回到線程1再去執行1B、1C、1D,然后繼續切換到線程2,執行2B、2C等等,以此類推。
這個線程之間的切換十分迅速,以至于在單核的設備中也會發生。幾乎所有的線程都在相同的時間內進行任務處理。其實,這都是因為速度太快造成的假象,就像電影《黑客帝國》里的特工Brown一樣,可以變幻出很多的頭和手。
接下來我們來看一些代碼。
Java核心里的線程
在Java中,如果要想做平行任務處理的話,會在Runnable里面執行你的代碼。可以繼承Thread類,或者實現Runnable接口:
// Version 1 public class IAmAThread extends Thread { public IAmAThread() { super("IAmAThread"); }@Override public void run() { // your code (sequence of instructions) } } // to execute this sequence of instructions in a separate thread. new IAmAThread().start(); // Version 2 public class IAmARunnable implements Runnable { @Override public void run() { // your code (sequence of instructions) } } // to execute this sequence of instructions in a separate thread. IAmARunnable myRunnable = new IAmARunnable(); new Thread(myRunnable).start();</pre>
這兩個方法基本上是一樣的。第一個版本是創建一個Thread類,第二個版本是需要創建一個Runnable對象,然后也需要一個Thread類來調用它。
第二個版是通常建議使用的方法。這也是一個很大的主題了,超過了本文的范圍,以后會再做討論。
Android上的線程
無論何時啟動APP,所有的組件都會運行在一個單獨的線程中(默認的)——叫做主線程。這個線程主要用于處理UI的操作并為視圖組件和小部件分發事件等,因此主線程也被稱作UI線程。
如果你在UI線程中運行一個耗時操作,那么UI就會被鎖住,直到這個耗時操作結束。對于用戶體驗來說,這是非常糟糕的!這也就是為什么我們要理解Android上的線程機制了。理解這些機制就可以把一些復雜的工作移動到其它的線程中去執行。如果你在UI線程中運行一個耗時的任務,那么很有可能會發生ANR(應用無響應),這樣用戶就會很快地結束掉你的APP。
Android和Java一樣,它支持使用Java里面的Thread類來進行一步任務處理。所以可以輕松地像上面Java的例子一樣來使用Android上的線程,不過那好像還是有點困難。
為什么在Android上使用標準Java的線程會困難呢?
其實平行任務處理沒有想象中的那么簡單,你必須在多線程中保證并發,就像偉大的Tim Bray說的那樣:ordinary humans can’t do concurrency at scale (or really at all) …
特別對于Android來說,以下這些功能就略顯臃腫:
- 異步對于UI線程來說是一個主要的PITA(如果你需要在后臺線程中向主線程更新界面,那么你就會用到)。
- 如果屏幕方向或者屏幕配置改變的話,就會出現一些更加奇怪的現象。因為改變屏幕方向,會引起Activity重建(所以后臺線程就需要去改變被銷毀的Activity的狀態了,而如果后臺線程不是在UI線程之上的話,那情況會更加復雜,原因如條件1)。
- 對于線程池來說,沒有默認的處理方式。
- 取消線程操作需要自定義代碼實現。
那么在Android上怎么進行任務并發處理呢?
你可能聽過一些Android上一些常見的名詞:
1、Handler
這就是我們今天要討論的詳細主題。2、AsyncTask
使用AsyncTask是在Android上操作線程最簡單的方式,也是最容易出錯的方式。3、IntentService
這種方式需要寫更多的代碼,但是這是把耗時任務移動到后臺的很好的方式,也是我最喜歡的方式。配上使用一個EventBus機制的框架如Otto,這樣的話實現IntentService就非常簡單了。4、Loader
關于處理異步任務,還有很多事情需要做,比如從數據庫或者內容提供者那里處理一些數據。5、Service
如果你曾經使用過Service的話,你應該知道這里會有一點誤區,其中一個常見的誤解就是服務是運行在后臺線程的。其實不是!看似運行在后臺是因為它們不與UI組件關聯,但是它們(默認)是運行在UI線程上的……所以默認運行在UI線程上,甚至在上面沒有UI部件。如果想要把服務運行在后臺線程中,那么必須自定義一個線程,然后把操作代碼都運行在那個線程中(與上面提到的方法很類似)。事實上你應該使用IntentService實現,但是這不是本文討論的主題。
Android上的Handler
以下是從 Android developer documentation for Handlers:中摘選的一段話:
> A Handler allows you to send and process Message and Runnable objects associated with a thread’s MessageQueue. Each Handler instance is associated with a single thread and that thread’s message queue. When you create a new Handler, it is bound to the thread/message queue of the thread that is creating it — from that point on, it will deliver messages and runnables to that message queue and execute them as they come out of the message queue.
為了更好地了解這個概念,也許你需要去看看什么是Message Queues。
消息隊列
在線程里基本都有一個叫做“消息隊列”的東西,它負責線程間通信。這是一種設計模式,所有控制指令或者內容在線程間傳遞。
消息隊列如同它的名字那樣,對于線程來說,它就是一個指令隊列。這里我們還可以做一些更酷的事:
- 定時消息和線程在某個時間點才執行。
- 需要在另一個線程中去添加入隊動作,而不是在本線程中。
注意:這里說的“消息”和Runnable對象、指令隊列的概念是一樣的。
回到Android上的Handler……如果你仔細閱讀的話,可以看到文檔是這樣說的:
> A Handler allows you to send and process Message and Runnable objects associated with a thread’s MessageQueue.
所以Handler可以讓你給線程隊列發消息:
> Each Handler instance is associated with a single thread and that thread’s message queue.
一個Handler對象只能和一個線程關聯:
> When you create a new Handler, it is bound to the thread / message queue of the thread that is creating it
所以一個Handler到底和哪個線程關聯呢?就是創造它的線程。
> — from that point on, it will deliver messages and runnables to that message queue and execute them as they come out of the message queue.、
在我們了解這些知識后,請繼續看……
小貼士: 這里有幾點可能你還不知道。每個線程都和一個Handler類實例綁定,而且可以和別的線程一起運行,相互通信。
還有一個小建議(如果用過AsyncTask的話),AsyncTask內部也是使用Handler進行處理的,只是不是運行在UI線程而已,它會提供一個channel來和UI線程通信,使用postExecute方法即可實現。
這還挺酷的,那怎么創建Handler呢?
有兩種方式:
- 使用默認的構造方法:new Handler()。
- 使用帶參的構造方法,參數是一個Runnable對象或者回調對象。
Handler里面有什么實用的API嗎?
請記住:
- Handler只是簡單往消息隊列中發送消息而已(或者使用post方式)
- 它們有更方便的方法可以幫助與UI線程通信。
如果你現在看看Handler的API,可以清楚看到這幾個方法:
- post
- postDelayed
- postAtTime
代碼示例
這里的代碼都是很基礎的,不過你可以好好看看注釋。
示例1:使用Handler的“post”方法
public class TestActivity extends Activity {// ... // all standard stuff @Override public void onCreate(Bundle savedInstanceState) { // ... // all standard stuff // we're creating a new handler here // and we're in the UI Thread (default) // so this Handler is associated with the UI thread Handler mHandler = new Handler(); // I want to start doing something really long // which means I should run the fella in another thread. // I do that by sending a message - in the form of another runnable object // But first, I'm going to create a Runnable object or a message for this Runnable mRunnableOnSeparateThread = new Runnable() { @Override public void run () { // do some long operation longOperation(); // After mRunnableOnSeparateThread is done with it's job, // I need to tell the user that i'm done // which means I need to send a message back to the UI thread // who do we know that's associated with the UI thread? mHandler.post(new Runnable(){ @Override public void run(){ // do some UI related thing // like update a progress bar or TextView // .... } }); } }; // Cool but I've not executed the mRunnableOnSeparateThread yet // I've only defined the message to be sent // When I execute it though, I want it to be in a different thread // that was the whole point. new Thread(mRunnableOnSeparateThread).start(); } }</pre>
如果根本就沒有Handler對象,回調post方法會比較難辦。
示例2:使用postDelayed方法
近期本站新介紹的特性中,我每次都要模擬EditText的自動完成功能,每次文字改變后都會觸發一個API的調用,從服務器中檢索數據。
我想減少APP調用API的次數,所以決定使用Handler的postDelayed方法來實現這個功能。
本例不針對平行處理,只是關于Handler給消息隊列發送消息還有安排消息在未來的某一點執行等。
// the below code is inside a TextWatcher // which implements the onTextChanged method // I've simplified it to only highlight the parts we're // interested inprivate long lastChange = 0; @Override public void onTextChanged(final CharSequence chars, int start, int before, int count) { // The handler is spawned from the UI thread new Handler().postDelayed( // argument 1 for postDelated = message to be sent new Runnable() { @Override public void run() { if (noChangeInText_InTheLastFewSeconds()) { searchAndPopulateListView(chars.toString()); // logic } } }, // argument 2 for postDelated = delay before execution 300); lastChange = System.currentTimeMillis(); } private boolean noChangeInText_InTheLastFewSeconds() { return System.currentTimeMillis() - lastChange >= 300 }</pre>
最后我就把“postAtTime”這個方法作為聯系留給讀者們了,掌握Handler了嗎?如果是的話,那么可以盡情使用線程了。
原文鏈接: weddingpartyapp 翻譯: 伯樂在線 - chris
譯文鏈接: http://blog.jobbole.com/73267/