Android線程和Handler基礎入門

jopen 10年前發布 | 20K 次閱讀 Android Android開發 移動開發

現在大多數的移動設備已經變得越來越快,但是它們其實也不算是非常快。如果你想讓你的APP既可以承受一些繁雜的工作而又不影響用戶體驗的話,那么必須把任務并行執行。在Android上,我們使用線程。

端一杯咖啡,然后仔細閱讀這篇文章。我會給大家介紹一下線程的概念,還有在Java中怎么使用線程,在線程中怎么使用Handler等。

如果需要使用異步處理或者并行任務的話,那么你一定會用到線程。

什么是線程?

線程或者線程執行本質上就是一串命令(也是程序代碼),然后我們把它發送給操作系統執行。

Android線程和Handler基礎入門

一般來說,我們的CPU在任何時候一個核只能處理一個線程。多核處理器(目前大多數Android設備已經都是多核)顧名思義,就是可以同時處理多線程(通俗地講就是可以同時處理多件事)。

多核處理與單核多任務處理的實質

上面我說的是一般情況,并不是所有的描述都是一定正確的。因為單核也可以用多任務模擬出多線程。

每個運行在線程中的任務都可以分解成多條指令,而且這些指令不用同時執行。所以,單核設備可以首先切換到線程1去執行指令1A,然后切換到線程2去執行指令2A,接著返回到線程1再去執行1B、1C、1D,然后繼續切換到線程2,執行2B、2C等等,以此類推。

這個線程之間的切換十分迅速,以至于在單核的設備中也會發生。幾乎所有的線程都在相同的時間內進行任務處理。其實,這都是因為速度太快造成的假象,就像電影《黑客帝國》里的特工Brown一樣,可以變幻出很多的頭和手。

agent_brown_dodging_bullets

接下來我們來看一些代碼。

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來說,以下這些功能就略顯臃腫:

  1. 異步對于UI線程來說是一個主要的PITA(如果你需要在后臺線程中向主線程更新界面,那么你就會用到)。
  2. 如果屏幕方向或者屏幕配置改變的話,就會出現一些更加奇怪的現象。因為改變屏幕方向,會引起Activity重建(所以后臺線程就需要去改變被銷毀的Activity的狀態了,而如果后臺線程不是在UI線程之上的話,那情況會更加復雜,原因如條件1)。
  3. 對于線程池來說,沒有默認的處理方式。
  4. 取消線程操作需要自定義代碼實現。

那么在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呢?

有兩種方式:

  1. 使用默認的構造方法:new Handler()。
  2. 使用帶參的構造方法,參數是一個Runnable對象或者回調對象。

Handler里面有什么實用的API嗎?

請記住:

  • Handler只是簡單往消息隊列中發送消息而已(或者使用post方式)
  • 它們有更方便的方法可以幫助與UI線程通信。

如果你現在看看Handler的API,可以清楚看到這幾個方法:

  1. post
  2. postDelayed
  3. 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 in

private 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/


 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!