Android 開發筆記 “線程互動(Handler+Thread 和 AsyncTask)”

來源:互聯網
上載者:User

標籤:

為什麼需要線程

  假設需要開發一個連網應用程式,需要從一個網址抓取網頁內容,這裡讀取的網頁地址是筆者在本地機器上自己建立的伺服器位址。當然在讀取網頁內容的時候,可以使用HttpClient提供的API,但是這並不是本文的介紹重點。缺乏連網程式開發經驗的程式員可能寫出下面的代碼。

package com.ophone.network;//這裡為了節省篇幅,忽略了import項public class NetworkActivity extends Activity {    // 顯示任務的執行狀態和返回結果    private TextView message;    private Button open;    private EditText url;    @Override    public void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.main);        message = (TextView) findViewById(R.id.message);        url = (EditText) findViewById(R.id.url);        open = (Button) findViewById(R.id.open);        open.setOnClickListener(new View.OnClickListener() {            public void onClick(View arg0) {                connect();            }        });    }    private String connect() {        try {            HttpClient client = new DefaultHttpClient();            // params[0]代表串連的url            HttpGet get = new HttpGet(url.getText().toString());            HttpResponse response = client.execute(get);            HttpEntity entity = response.getEntity();            long length = entity.getContentLength();            InputStream is = entity.getContent();            String s = null;            if (is != null) {                ByteArrayOutputStream baos = new ByteArrayOutputStream();                byte[] buf = new byte[128];                int ch = -1;                int count = 0;                while ((ch = is.read(buf)) != -1) {                    baos.write(buf, 0, ch);                    count += ch;                    // 為了在模擬器中清楚地看到進度,讓線程休眠1000ms                    Thread.sleep(50000);                }                s = new String(baos.toByteArray());            }            // 返回結果            return s;        } catch (Exception e) {            e.printStackTrace();        }        return null;    }}

網路連接通常是比較耗時的,尤其是在當前的GPRS這種低速率的網路情況下,這樣connect()方法可能需要3-5秒,

甚至更長的時間才能返回頁面的內容。如果此串連動作直接在主線程,也就是UI線程中處理,會發生什麼情況呢?

為了在模擬器中更好的類比網路讀取速度慢的情況,

筆者在讀取過程中讓線程休眠了50秒,

運行NetworkActivity,點擊“串連”按鈕。意外發生了,

按鈕長時間沒有反應,整個介面似乎是“死”掉了。系統隨後顯示出了 ANR(應用程式無響應)

 

線上程中連網

為什麼出現ANR?答案是連網動作阻塞在了主線程,長時間沒有返回,這樣OPhone彈出ANR錯誤。這個錯誤提示我們,

如果否個任務可能需要長時間的運行才能返回,則必須把這個任務放置到單獨線程中運行,

避免阻塞UI線程。Java語言內建了對線程的支援,可以使用Thread類建立一個新線程,然後在run()方法中讀取網頁的內容,

獲得頁面內容後調用TextView.setText()更新介面。修改後的connect()

方法如下所示:

private void connect() {        new Thread() {            public void run() {                try {                    HttpClient client = new DefaultHttpClient();                    // params[0]代表串連的url                    HttpGet get = new HttpGet(url.getText().toString());                    HttpResponse response = client.execute(get);                    HttpEntity entity = response.getEntity();                    long length = entity.getContentLength();                    InputStream is = entity.getContent();                    String s = null;                    if (is != null) {                        ByteArrayOutputStream baos = new ByteArrayOutputStream();                        byte[] buf = new byte[128];                        int ch = -1;                        int count = 0;                        while ((ch = is.read(buf)) != -1) {                            baos.write(buf, 0, ch);                            count += ch;                        }                        s = new String(baos.toByteArray());                        message.setText(s);                    }                } catch (Exception e) {                    e.printStackTrace();                }            }        }.start();    }

  重新運行NetworkActivity,點擊“串連”按鈕。程式並沒有像預期的那種獲得網頁的內容,並顯示到TextView上。查看log可以看到在connect的執行過程中拋出了異常。接下來分析問題的所在。

 

 

使用Handler更新介面

  其實,connect()方法中拋出的異常是由於介面更新引起的。Connect()方法直接在新啟動的線程中調用message.setText()方法是不正確的。OPhone平台只允許在主線程中調用相關View的方法來更新介面。如果返回結果在新線程中獲得,那麼必須藉助Handler來更新介面。為此,在NetworkActivity中建立一個Handler對象,並在handleMessage()中更新UI。

//Task在另外的線程執行,不能直接在Task中更新UI,因此建立了Handler    private Handler handler = new Handler() {        @Override        public void handleMessage(Message msg) {            String m = (String) msg.obj;            message.setText(m);        }    };

 

當從connect()方法中獲得網頁內容後,使用如下方法更新介面。

    s = new String(baos.toByteArray());    Message mg = Message.obtain();    mg.obj = s;    handler.sendMessage(mg);

 

重新運行NetworkActivity,點擊“串連”按鈕,正確讀取了網頁的內容。

 

AsyncTask

  看上去修改後的connect()方法已經可用了,但是這種匿名程的方式是存在缺陷的:第一,線程的開銷較大,如果每個任務都要建立一個線程,那麼應用程式的效率要低很多;第二,線程無法管理,匿名線程建立並啟動後就不受程式的控制了,如果有很多個請求發送,那麼就會啟動非常多的線程,系統將不堪重負。另外,前面已經看到,在新線程中更新UI還必須要引入handler,這讓代碼看上去非常臃腫。

  為瞭解決這一問題,OPhone在1.5版本引入了AsyncTask。AsyncTask的特點是任務在主線程之外運行,而回調方法是在主線程中執行,這就有效地避免了使用Handler帶來的麻煩。閱讀AsyncTask的源碼可知,AsyncTask是使用java.util.concurrent 架構來管理線程以及任務的執行的,concurrent架構是一個非常成熟,高效的架構,經過了嚴格的測試。這說明AsyncTask的設計很好的解決了匿名線程存在的問題。

  AsyncTask是抽象類別,子類必須實現抽象方法doInBackground(Params... p) ,在此方法中實現任務的執行工作,比如串連網路擷取資料等。通常還應該實現onPostExecute(Result r)方法,因為應用程式關心的結果在此方法中返回。需要注意的是AsyncTask一定要在主線程中建立執行個體。AsyncTask定義了三種泛型型別 Params,Progress和Result。

  • Params 啟動任務執行的輸入參數,比如HTTP請求的URL。
  • Progress 背景工作執行的百分比。
  • Result 後台執行任務最終返回的結果,比如String。
  • AsyncTask的執行分為四個步驟,與前面定義的TaskListener類似。每一步都對應一個回調方法,需要注意的是這些方法不應該由應用程式調用,開發人員需要做的就是實現這些方法。在任務的執行過程中,這些方法被自動調用。
  • onPreExecute() 當任務執行之前開始調用此方法,可以在這裡顯示進度對話方塊。
  • doInBackground(Params...) 此方法在後台線程執行,完成任務的主要工作,通常需要較長的時間。在執行過程中可以調用publicProgress(Progress...)來更新任務的進度。
  • onProgressUpdate(Progress...) 此方法在主線程執行,用於顯示任務執行的進度。
  • onPostExecute(Result) 此方法在主線程執行,任務執行的結果作為此方法的參數返回。

  PageTask擴充了AsyncTask,在doInBackground()方法中讀取網頁內容。PageTask的原始碼如下所示:

// 設定三種型別參數分別為String,Integer,String    class PageTask extends AsyncTask<String, Integer, String> {        // 可變長的輸入參數,與AsyncTask.exucute()對應        @Override        protected String doInBackground(String... params) {            try {                HttpClient client = new DefaultHttpClient();                // params[0]代表串連的url                HttpGet get = new HttpGet(params[0]);                HttpResponse response = client.execute(get);                HttpEntity entity = response.getEntity();                long length = entity.getContentLength();                InputStream is = entity.getContent();                String s = null;                if (is != null) {                    ByteArrayOutputStream baos = new ByteArrayOutputStream();                    byte[] buf = new byte[128];                    int ch = -1;                    int count = 0;                    while ((ch = is.read(buf)) != -1) {                        baos.write(buf, 0, ch);                        count += ch;                        if (length > 0) {                            // 如果知道響應的長度,調用publishProgress()更新進度                            publishProgress((int) ((count / (float) length) * 100));                        }                        // 為了在模擬器中清楚地看到進度,讓線程休眠100ms                        Thread.sleep(100);                    }                    s = new String(baos.toByteArray());                }                // 返回結果                return s;            } catch (Exception e) {                e.printStackTrace();            }            return null;        }        @Override        protected void onCancelled() {            super.onCancelled();        }        @Override        protected void onPostExecute(String result) {            // 返回HTML頁面的內容            message.setText(result);        }        @Override        protected void onPreExecute() {            // 任務啟動,可以在這裡顯示一個對話方塊,這裡簡單處理            message.setText(R.string.task_started);        }        @Override        protected void onProgressUpdate(Integer... values) {            // 更新進度            message.setText(values[0]);        }    }

 

執行PageTask非常簡單,只需要調用如下代碼。重新運行NetworkActivity,不但可以抓取網頁的內容,還可以即時更新讀取的進度。讀者嘗試讀取一個較大的網頁,看看百分比的更新情況。

 

PageTask task = new PageTask();task.execute(url.getText().toString());

 

總結

  本文介紹了OPhone連網應用開發中應該注意的兩個問題:線程管理和介面更新。不但分析了問題的所在,也給出了多種解決方案。這裡筆者推薦使用AsyncTask處理連網,播放大尺寸媒體檔案等較為耗時的工作,不但執行效率高,也可以節省代碼。

 

Android 開發筆記 “線程互動(Handler+Thread 和 AsyncTask)”

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.