1. 程式人生 > >Android非同步機制的幾種實現方式剖析

Android非同步機制的幾種實現方式剖析

今天來談一談android中非同步處理機制,眾所周知在android中由於UI主執行緒是不安全的,因此不能直接在子執行緒中操作UI,一般我們會用到非同步機制來解決這種問題,下面會介紹兩種常用的非同步機制Thread+Handler與Async Task機制;

一、Thread+Handler

提起Thread'很多人都不會陌生,做過Android手機開發的人都知道,手機UI是一個單獨的執行緒在執行,並且該執行緒最好不會因為使用者的操作而阻塞。換句話說,如果使用者進行的操作需要耗時幾十秒甚至幾十分鐘,那麼在這段時間內佔用UI執行緒是一個非常不明智的做法。它會阻塞掉UI執行緒,導致手機不再顯示或者接受使用者新的操作,給使用者一種宕機的感覺,因此我們開闢出子執行緒用來執行耗時操作。我們可以通過Thread+Handler來實現具體UI與子執行緒的協同;

下面是常用的handler:

private Handler mHandler = new Handler() {  
        public void handleMessage (Message msg) {//此方法在ui執行緒執行  
            switch(msg.what) {  
            case MSG_SUCCESS:  
                
                break;  
  
            case MSG_FAILURE:  
                break; 
            }  
        }  
    };  
我們通過thread中的傳送訊息操作將訊息傳遞給handler
Runnable runnable = new Runnable() {  
          
        @Override  
        public void run() {//run()在新的執行緒中執行 
<span style="white-space:pre">		</span>//執行耗時操作 
           
            mHandler.obtainMessage(MSG_SUCCESS,bm).sendToTarget();//獲取圖片成功,向ui執行緒傳送MSG_SUCCESS標識和bitmap物件  
  

            
        }  
    };  
      
}  
由以上步驟我們便完成了一個最基本的非同步機制,但是這種方式內部到底是如何傳遞訊息的呢?就讓我們在原始碼中找到它們的身影。

在上述程式碼中我們發現了最終是把資訊通過sendToTarget()方法傳送了,我們來看他的原始碼:

/** 
   * Sends this Message to the Handler specified by {@link #getTarget}. 
   * Throws a null pointer exception if this field has not been set. 
   */  
  public void sendToTarget() {  
     <span style="background-color:#ff0000"> target.sendMessage(this);</span>  
  }  
我們發現最終還是呼叫了sendMessage方法,繼續看它的原始碼:
/** 
     * Pushes a message onto the end of the message queue after all pending messages 
     * before the current time. It will be received in {@link #handleMessage}, 
     * in the thread attached to this handler. 
     *   
     * @return Returns true if the message was successfully placed in to the  
     *         message queue.  Returns false on failure, usually because the 
     *         looper processing the message queue is exiting. 
     */  
    public final boolean sendMessage(Message msg)  
    {  
        return sendMessageDelayed(msg, 0);  
    } 
它是呼叫了sendMessageDelayed方法,下面我摘出了幾個片段我們分析一下:
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
呼叫sendMessageAtTime();
public boolean sendMessageAtTime(Message msg, long uptimeMillis)  
{  
    boolean sent = false;  
    MessageQueue queue = mQueue;  
    if (queue != null) {  
        msg.target = this;  
        sent = queue.enqueueMessage(msg, uptimeMillis);  
    }  
    else {  
        RuntimeException e = new RuntimeException(  
            this + " sendMessageAtTime() called with no mQueue");  
        Log.w("Looper", e.getMessage(), e);  
    }  
    return sent;  
}  

sendMessageAtTime()方法接收兩個引數,其中msg引數就是我們傳送的Message物件,而uptimeMillis引數則表示傳送訊息的時間,它的值等於自系統開機到當前時間的毫秒數再加上延遲時間,如果你呼叫的不是sendMessageDelayed()方法,延遲時間就為0,然後將這兩個引數都傳遞到MessageQueue的enqueueMessage()方法中。這個MessageQueue又是什麼東西呢?其實從名字上就可以看出了,它是一個訊息佇列,用於將所有收到的訊息以佇列的形式進行排列,並提供入隊和出隊的方法。這個類是在Looper的建構函式中建立的,因此一個Looper也就對應了一個MessageQueue。那麼enqueueMessage()就是所謂的入隊操作,入隊已經完成接下來就需要進行出隊獲取訊息。先看如下程式碼段:
public static final void loop() {  
    Looper me = myLooper();  
    MessageQueue queue = me.mQueue;  
    while (true) {  
        Message msg = queue.next(); // might block  
        if (msg != null) {  
            if (msg.target == null) {  
                return;  
            }  
            if (me.mLogging!= null) me.mLogging.println(  
                    ">>>>> Dispatching to " + msg.target + " "  
                    + msg.callback + ": " + msg.what  
                    );  
            msg.target.dispatchMessage(msg);  
            if (me.mLogging!= null) me.mLogging.println(  
                    "<<<<< Finished to    " + msg.target + " "  
                    + msg.callback);  
            msg.recycle();  
        }  
    }  
}  
在while的死迴圈裡不斷地去進行出隊操作,出隊的方法就是next();出隊後訊息去了哪裡呢?我們發現他呼叫了msg.target的dispatchMessage(),這裡msg.targe就是handler,也就是說它呼叫的handler的dispatchMessage()
public void dispatchMessage(Message msg) {  
    if (msg.callback != null) {  
        handleCallback(msg);  
    } else {  
        if (mCallback != null) {  
            if (mCallback.handleMessage(msg)) {  
                return;  
            }  
        }  
        handleMessage(msg);  
    }  
} 
我們發現了一個熟悉的方法handleMessage(),這不正是我們寫在handler中的方法嗎?並且這裡還傳遞了message過去,到這裡一切豁然開朗,經過一圈,訊息最終由子執行緒傳遞到了handler的handleMessage()中進行UI處理。

二、Async Task

Android從很早就引入了一個AsyncTask類,我們利用它可以非常靈活方便地從子執行緒切換到UI執行緒,由於Async Task是一個抽象類,因此我們必須去用子類去繼承他才能去使用,在繼承時我們可以為AsyncTask類指定三個泛型引數,這三個引數的用途如下:

1. Params

在執行AsyncTask時需要傳入的引數,可用於在後臺任務中使用。

2. Progress

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

3. Result

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

在子類中我們需要重寫以下幾個常用的方法來執行相應操作:

1. onPreExecute():

在後臺任務開始執行之前呼叫,我們可以用來進行控制元件的初始化操作。

2. doInBackground(Params...)

這個方法就是我們執行耗時操作的地方,完畢後返回子類中的第三個引數型別,若為void則不進行返回。並且在此方法中是不可以進行UI操作的,如果需要更新UI元素,比如說反饋當前任務的執行進度,可以呼叫publishProgress(Progress...)方法來完成。

3. onProgressUpdate(Progress...)

當在doInBackground(Params...)中呼叫了publishProgress(Progress...)方法後,這個方法就很快會被呼叫,方法中攜帶的引數就是在後臺任務中傳遞過來的。在這個方法中可以對UI進行操作,利用引數中的數值就可以對介面元素進行相應的更新。

4. onPostExecute(Result)

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

package com.example.asynctask;  
import android.os.AsyncTask;  
import android.widget.ProgressBar;  
import android.widget.TextView;  
  
/**  
 * 生成該類的物件,並呼叫execute方法之後  
 * 首先執行的是onProExecute方法  
 * 其次執行doInBackgroup方法  
 *  
 */  
public class ProgressBarAsyncTask extends AsyncTask<Integer, Integer, String> {  
  
    private TextView textView;  
    private ProgressBar progressBar;  
      
      
    public ProgressBarAsyncTask(TextView textView, ProgressBar progressBar) {  
        super();  
        this.textView = textView;  
        this.progressBar = progressBar;  
    }  
  
  
    /**  
     * 這裡的Integer引數對應AsyncTask中的第一個引數   
     * 這裡的String返回值對應AsyncTask的第三個引數  
     * 該方法並不執行在UI執行緒當中,主要用於非同步操作,所有在該方法中不能對UI當中的空間進行設定和修改  
     * 但是可以呼叫publishProgress方法觸發onProgressUpdate對UI進行操作  
     */  
    @Override  
    protected String doInBackground(Integer... params) {  
        NetOperator netOperator = new NetOperator();  
        int i = 0;  
        for (i = 10; i <= 100; i+=10) {  
            netOperator.operator();  
            publishProgress(i);  
        }  
        return i + params[0].intValue() + "";  
    }  
  
  
    /**  
     * 這裡的String引數對應AsyncTask中的第三個引數(也就是接收doInBackground的返回值)  
     * 在doInBackground方法執行結束之後在執行,並且執行在UI執行緒當中 可以對UI空間進行設定  
     */  
    @Override  
    protected void onPostExecute(String result) {  
        textView.setText("非同步操作執行結束" + result);  
    }  
  
  
    //該方法執行在UI執行緒當中,並且執行在UI執行緒當中 可以對UI空間進行設定  
    @Override  
    protected void onPreExecute() {  
        textView.setText("開始執行非同步執行緒");  
    }  
  
  
    /**  
     * 這裡的Intege引數對應AsyncTask中的第二個引數  
     * 在doInBackground方法當中,,每次呼叫publishProgress方法都會觸發onProgressUpdate執行  
     * onProgressUpdate是在UI執行緒中執行,所有可以對UI空間進行操作  
     */  
    @Override  
    protected void onProgressUpdate(Integer... values) {  
        int vlaue = values[0];  
        progressBar.setProgress(vlaue);  
    }  
  
      
      
      
  
}  
在呼叫子類時我們只需這樣:
new ProgressBarAsyncTask().execute();
到這裡AsyncTask的使用就完成了,下面我們對AsyncTask進行分析;
public final AsyncTask<Params, Progress, Result> execute(Params... params) {  
    return executeOnExecutor(sDefaultExecutor, params);  
} 

由上面可以知道execute呼叫了executeOnExecutor()方法,我們來看executeOnExecutor()方法
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,  
        Params... params) {  
    if (mStatus != Status.PENDING) {  
        switch (mStatus) {  
            case RUNNING:  
                throw new IllegalStateException("Cannot execute task:"  
                        + " the task is already running.");  
            case FINISHED:  
                throw new IllegalStateException("Cannot execute task:"  
                        + " the task has already been executed "  
                        + "(a task can be executed only once)");  
        }  
    }  
    mStatus = Status.RUNNING;  
    onPreExecute();  
    mWorker.mParams = params;  
    exec.execute(mFuture);  
    return this;  
}
發現了什麼,我們找到了子類中的onPreExecute這個方法,這也就是說此方法第一個執行;

在看如下程式碼:

public static final Executor SERIAL_EXECUTOR = new SerialExecutor();

因此上面二者是等同的,在exec.execute(mFuture); 中也就相當於SerialExecutor類中的execute(),我們看它的原始碼:
private static class SerialExecutor implements Executor {  
    final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();  
    Runnable mActive;  
  
    public synchronized void execute(final Runnable r) {  
        mTasks.offer(new Runnable() {  
            public void run() {  
                try {  
                    r.run();  
                } finally {  
                    scheduleNext();  
                }  
            }  
        });  
        if (mActive == null) {  
            scheduleNext();  
        }  
    }  
  
    protected synchronized void scheduleNext() {  
        if ((mActive = mTasks.poll()) != null) {  
            THREAD_POOL_EXECUTOR.execute(mActive);  
        }  
    }  
}  

我們發現了其中的run方法是執行的部分,在看run方法:
void innerRun() {  
    if (!compareAndSetState(READY, RUNNING))  
        return;  
    runner = Thread.currentThread();  
    if (getState() == RUNNING) { // recheck after setting thread  
        V result;  
        try {  
            result = callable.call();  
        } catch (Throwable ex) {  
            setException(ex);  
            return;  
        }  
        set(result);  
    } else {  
        releaseShared(0); // cancel  
    }  
}  

再找主要的執行函式call方法:
public Result call() throws Exception {  
    mTaskInvoked.set(true);  
    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);  
    return postResult(doInBackground(mParams));  
} 

仔細看我們發現了熟悉的方法doInBackground()方法,而此時我們還是處於子執行緒當中,這也是doInBackground()方法能執行耗時操作的原因,我們發現最終的結果傳給了postResult方法,我們來看它:
private Result postResult(Result result) {  
    Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT,  
            new AsyncTaskResult<Result>(this, result));  
    message.sendToTarget();  
    return result;  
}  
到這裡你發現了什麼,又回到了我們熟悉的handler訊息處理機制,那我們看看他的handler原始碼:
private static class InternalHandler extends Handler {  
    @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})  
    @Override  
    public void handleMessage(Message msg) {  
        AsyncTaskResult result = (AsyncTaskResult) msg.obj;  
        switch (msg.what) {  
            case MESSAGE_POST_RESULT:  
                // There is only one result  
                result.mTask.finish(result.mData[0]);  
                break;  
            case MESSAGE_POST_PROGRESS:  
                result.mTask.onProgressUpdate(result.mData);  
                break;  
        }  
    }  
}

private void finish(Result result) {  
    if (isCancelled()) {  
        onCancelled(result);  
    } else {  
        onPostExecute(result);  
    }  
    mStatus = Status.FINISHED;  
} 

發現了什麼,這裡進行呼叫了onPregressUpdate方法和onPostExecute方法進行UI的操作,到這裡我們理解了AsyncTask的內部機制。

三、二者的差異在什麼地方

有些人會問這兩種實現方式怎麼靈活運用,什麼地方改用哪一種呢,其實二者還是有且別的:

AsyncTask 提供了像onPreExecute, onProgressUpdate這類的快速呼叫方法,可以被UI執行緒方便的呼叫,Thread沒有。

AsyncTask 不能重複執行, 一旦執行過了,你需要下次需要時重新建立呼叫。 thread 可以建立成在佇列中獲取workitem持續呼叫的模式,不停地執行。

AsyncTasks的執行優先順序是3.0, 預設的執行模式是一次一個任務;thread的執行則與其它執行緒無關。

AsyncTask 寫起來較快, Thread則需要多做一些工作。

AsyncTask和Thread都不會影響你的主執行緒的生命週期。

精益求精方可融會貫通!