無痛執行緒

無痛執行緒 原文連結
無痛執行緒
每當你一開始啟動android應用程式,
會有一個叫做main的執行緒自動地被產生。
這個主執行緒(也叫做UI執行緒)非常重要,
因為它用來調度事件給適合的元件也包含繪製使用者介面的事件。
它也是那個你與Android互動的widget所在的執行緒。
舉個例子,假設你在畫面上碰觸一個按鈕,
UI執行緒會將這個碰觸事件分派給那個依序設定為「已按下」狀態及
提出一個「失效」的請求(request)到事件佇列裡的widget。
這個UI執行緒會從佇列取得該請求並且通知元件去重畫自己。
這個單一的執行緒模型在沒考慮到其影響的Android應用程式中
可能造成較差的效能。
如果在一個單一執行緒發生的每件事都做了很久的運算,
像是網路存取或者跟資料庫要資料,這樣的執行緒將會把使用者的畫面鎖住。
當這些長時間的運算正在進行中時,將會沒有任何事件可以被觸發,
包含重畫畫面的事件。
當使用者看到畫面停住不動,甚至更壞的情況,
像UI執行緒佔住畫面約超過5秒的時間,
畫面就會跳出萬惡的ANR警告視窗。
假設你想看見這樣情況有多糟,寫一個簡單的應用程式,
上面有一個button並且按下去的事件會執行Thread.sleep(2000),
這個button將會出現被按下去的畫面約2秒才會回到正常彈起來的畫面,
當這樣的情況發生的時候,這個程式很容易讓使用者感覺『吼~有夠慢』。
現在你知道你必需避免在UI執行緒上有長的運算。
你也許將使用額外的執行緒(背景處理的執行緒)來執行這些運算,
這的確是必須做的。
讓我們來看看一個例子,當執行click事件的時候,
它會從網路下載圖片並且載入ImageView裡面。
public void onClick(View v) {
    new Thread(new Runnable() {
        public void run() {
            Bitmap b = loadImageFromNetwork();
            mImageView.setImageBitmap(b);
        }
    }).start();
}
首先,這段程式碼似乎可以簡單的解決你的問題,
它並不會佔住UI執行緒,但是很不幸的
,它違反單一執行緒的模型:Android UI 工具不是thread-safe
(很多執行緒執行互相不影響),而且必須被操作在UI 執行緒上面。
在上面的程式碼內,ImageView是執行在其他的執行緒,
它將會造成很詭異的問題,要找出這樣的錯誤是非常困難而且耗時的。
Android提供數個方法從其他的執行緒來存取UI執行緒。
以下是這些方法的完整清單,你可能已經熟悉其中的一些了。
‧ Activity.runOnUiThread(Runnable)
‧ View.post(Runnable)
‧ View.postDelayed(Runnable, long)
‧ Handler
在這些類別和方法的其中之一都能用來解決我們先前範例的問題。
public void onClick(View v) {
    new Thread(new Runnable() {
        public void run() {
            final Bitmap b = loadImageFromNetwork();
            mImageView.post(new Runnable() {
                public void run() {
                    mImageView.setImageBitmap(b);
                }
            });
        }
    }).start();
}
不幸地,這些類別跟方法也趨使你的程式碼變的更複雜難以閱讀。
它更糟糕的是當你執行複雜的運算時,會頻繁要求UI更新。
為了解決這個問題,Android1.5提供一個新的類別叫做AsyncTask,
它簡化了需要與使用者介面溝通的長時間執行任務的創建程序。
AsyncTask在Android1.0跟1.1也以UserTask這個名字存在著。
它提供額外相同的API而且你只需要複製原始碼在你的程式裡面。
AsyncTask的目的是幫你處理執行緒的管理,
我們先前的例子可以很容易地用AsyncTask重寫如下:
public void onClick(View v) {
    new DownloadImageTask().execute("http://example.com/image.png");
}

private class DownloadImageTask extends AsyncTask {
    protected Bitmap doInBackground(String... urls) {
        return loadImageFromNetwork(urls[0]);
    }
    protected void onPostExecute(Bitmap result) {
        mImageView.setImageBitmap(result);
    }
}
如你所見,AsyncTask必須被繼承,
AsyncTask實體必須被建立在UI執行緒而且只能執行一次,
這個非常的重要。
你可以從AsyncTask的文件得到如何使用這個class的完整了解,
而這是對於它如何運作的一個快速概述。