Android入門(十七)Android多線程

來源:互聯網
上載者:User

標籤:

原文連結:http://www.orlion.ga/670/

一、在子線程中更新UI    

    Android中不允許在子線程中更新UI,只能在主線程中更新,但是我們有時候必須在子線程中執行一些耗時的任務,然後根據運行結果來更新UI,對於這種情況Android提供了一套非同步訊息處理機制。

    建立項目AndroidThreadDemo,修改activity_main.xml:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent">        <Button         android:id="@+id/change_text"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:text="Change Text" />    <TextView        android:id="@+id/text"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_centerInParent="true"        android:text="@string/hello_world"         android:textSize="20sp"/></RelativeLayout>

    MainActivity.java:

package ga.orlion.androidthreaddemo;import android.app.Activity;import android.os.Bundle;import android.os.Handler;import android.os.Message;import android.view.View;import android.view.View.OnClickListener;import android.widget.Button;import android.widget.TextView;public class MainActivity extends Activity implements OnClickListener{public static final int UPDATE_TEXT = 1;private TextView text;private Button changeText;private Handler handler = new Handler() {public void handleMessage(Message msg) {switch (msg.what) {case UPDATE_TEXT:// 在這裡可以進行UI操作text.setText("Nice to meet you");break;default:break;}}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);text = (TextView) findViewById(R.id.text);changeText = (Button) findViewById(R.id.change_text);changeText.setOnClickListener(this);}@Overridepublic void onClick(View v) {switch (v.getId()) {case R.id.change_text:new Thread(new Runnable() {@Overridepublic void run() {Message message = new Message();message.what = UPDATE_TEXT;handler.sendMessage(message); // 將message對象發送過去}}).start();;break;default:break;}}}

    

    這裡我們先是定義了一個整型常量 UPDATE_TEXT, 用於表示更新 TextView這個動作。然後新增一個 Handler對象,並重寫父類的 handleMessage方法,在這裡對具體的 Message進行處理。如果發現 Message的 what欄位的值等於 UPDATE_TEXT,就將 TextView顯示的內容改成 Nice to meet you。

    下面再來看一下 Change Text按鈕的點擊事件中的代碼。可以看到,這次我們並沒有在子線程裡直接進行 UI操作,而是建立了一個 Message(android.os.Message)對象,並將它的 what 欄位的值指定為 UPDATE_TEXT,然後調用 Handler 的 sendMessage()方法將這條Message發送出去。很快,Handler就會收到這條 Message,並在 handleMessage()方法中對它進行處理。注意此時 handleMessage()方法中的代碼就是在主線程當中啟動並執行了,所以我們可以放心地在這裡進行 UI操作。接下來對 Message攜帶的 what欄位的值進行判斷,如果等於UPDATE_TEXT,就將 TextView顯示的內容改成 Nice to meet you。

 

二、解析非同步處理機制

    

    Android中的非同步處理主要由四個部分組成,Message、Handler、MessageQueue和Looper

    1.Message

    Message是線上程間傳遞的訊息,它可以在內部攜帶少量的資訊,用於在不同線程之間交換資料,上面的代碼中我們用了Message的what欄位,除此之外還可以使用arg1和arg2欄位來攜帶整型資料,使用obj欄位攜帶一個Object對象

    2.Handler

    Handler顧名思義也就是處理者的意思,它主要是用於發送和處理訊息的。發送訊息一般是使用Handler的sendMessage()方法,而發出的訊息經過一系列地輾轉處理後,最終會傳遞到Handler的handleMessage()方法中。

    3.MessageQueue

    MessageQueue是訊息佇列的意思,它主要用於存放所有通過Handler發送的訊息。這部分訊息會一直存在於訊息佇列中,等待被處理。每個線程中只會有一個MessageQueue對象

    4.Looper

    Looper是每個線程中的MessageQueue的管家,調用Looper的loop()方法後,就會進入到一個無限迴圈中,然後每當發現MessageQueue中存在一條訊息,就會將它取出並傳遞到Handler的handleMessage()方法中。每個線程中也會只有一個Looper對象。

    非同步訊息處理的整個流程:首先需要在主線程中建立一個Handler對象,並重寫handleMessage()方法,然後當子線程中需要進行UI操作時,就建立一個Message對象,並通過Handler將這條訊息發送出去。之後這條訊息會被添加到MessageQueue的隊列中等待被處理,而Looper則會一直嘗試從MessageQueue中取出待處理訊息,最後分發會Handler的handleMessage()方法中。由於Handler是在主線程中被建立的,所以此時handleMessage()方法中的代碼也會在主線程中執行。

        

一條Message經過這樣一個流程的輾轉調用後,也就從子線程進入到了主線程,從不能

更新 UI變成了可以更新 UI,整個非同步訊息處理的核心思想也就是如此。

 

三、使用AsyncTask

    

    AsyncTask是一個抽象類別,在繼承時我們可以為AsyncTask類指定三個泛型參數,這三個參數的用途如下:

    1.Params

    在執行AsyncTask時需要傳入的參數,可用於在背景工作中使用

    2.Progress

    背景工作執行時,如果需要在介面上顯示當前的進度,則使用這裡指定的泛型作為進度單位

    3.Result

    當任務執行完畢後,如果需要對結果進行返回,則使用這裡指定的泛型作為傳回值類型

    

    因此,一個最簡單的自訂AsyncTask可以寫成如下:

class DownloadTask extends AsyncTask<Void , Integer , Boolean>{}

    這裡我們把AsyncTask的第一個泛型參數指定為Void,表示在執行AsyncTask的時候不需要傳入參數給背景工作。第二個允許存取參數指定為Integer,表示使用整型資料來作為進度顯示單位。第三個泛型參數指定為Boolean,則表示使用布爾型資料來反饋執行結果。

    目前我們自訂DownloadTask還是一個空任務,並不能進行任何實際的操作,我們還需要取重寫AsyncTask中的幾個方法才能完成對任務的定製。經常需要去重寫的方法有一下四個:

    1.onPreExecute()

    這個方法會在背景工作執行之前調用,用於進行一些介面上的初始化操作,比如顯示一個進度條對話方塊

    2.doInBackground(Params…)

    這個方法中的所有代碼都會在子線程中運行,我們應該在這裡去處理所有的耗時任務。任務一旦完成就可以通過return語句來將任務的執行結果返回,如果AsyncTask的第三個泛型參數指定的是Void,就可以不返回任務執行結果。注意,在這個方法中是不可以進行UI操作的,如果需要更新UI元素,比如說反饋當前任務的執行進度,可以調用publishProgress(Progress…)方法來完成

    3.onProgressUpdate(Progress…)

    當在背景工作中調用了publishProgress(Progress…)方法後,這個方法就會很快被調用,方法中攜帶的參數就是在背景工作中傳遞過來的。在這個方法中可以對UI進行操作,利用參數中的數值就可以對介面元素進行相應的更新。

    4.onPostExecute(Result)

    當背景工作執行完畢並通過return語句進行返回時,這個方法就會很快被調用。返回的資料會作為參數傳遞到此方法中,可以利用返回的資料來進行一些UI操作,比如說提醒任務執行的結果,以及關閉掉進度條對話方塊等。

    因此,一個完整的自訂AsyncTask就可以寫成如下方式:

class DownloadTask extends AsyncTask<Void, Integer, Boolean> {    @Override    protected void onPreExecute() {        progressDialog.show(); // 顯示進度對話方塊    }    @Override    protected Boolean doInBackground(Void... params) {        try {            while (true) {                int downloadPercent = doDownload(); // 這是一個虛構的方法                publishProgress(downloadPercent);                if (downloadPercent >= 100) {                    break;                }            }        } catch (Exception e) {            return false;        }        return true;    }    @Override    protected void onProgressUpdate(Integer... values) {        // 在這裡更新下載進度        progressDialog.setMessage("Downloaded " + values[0] + "%");    }    @Override    protected void onPostExecute(Boolean result) {        progressDialog.dismiss(); // 關閉進度對話方塊        // 在這裡提示下載結果        if (result) {            Toast.makeText(context, "Download succeeded",            Toast.LENGTH_SHORT).show();        } else {            Toast.makeText(context, " Download failed",            Toast.LENGTH_SHORT).show();        }    }}

    在這個 DownloadTask中,我們在 doInBackground()方法裡去執行具體的下載任務。這個方法裡的代碼都是在子線程中啟動並執行,因而不會影響到主線程的運行。注意這裡虛構了一個doDownload()方法,這個方法用於計算當前的下載進度並返回,我們假設這個方法已經存在了。在得到了當前的下載進度後,下面就該考慮如何把它顯示到介面上了,由於doInBackground()方法是在子線程中啟動並執行,在這裡肯定不能進行 UI操作,所以我們可以調用 publishProgress()方法並將當前的下載進度傳進來,這樣 onProgressUpdate()方法就會很快被調用,在這裡就可以進行 UI操作了。

    當下載完成後,doInBackground()方法會返回一個布爾型變數,這樣 onPostExecute()方法就會很快被調用,這個方法也是在主線程中啟動並執行。然後在這裡我們會根據下載的結果來彈出相應的 Toast提示,從而完成整個 DownloadTask任務。

    簡單來說,使用 AsyncTask的訣竅就是,在 doInBackground()方法中去執行具體的耗時任務,在 onProgressUpdate()方法中進行 UI操作,在 onPostExecute()方法中執行一些任務的收尾工作。

    如果想要啟動這個任務,只需編寫以下代碼即可:

new DownloadTask().execute();

Android入門(十七)Android多線程

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.