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都不會影響你的主線程的生命周期。

精益求精方可融會貫通!













 







注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com