Android非同步機制的幾種實現方式剖析
今天來談一談android中非同步處理機制,眾所周知在android中由於UI主執行緒是不安全的,因此不能直接在子執行緒中操作UI,一般我們會用到非同步機制來解決這種問題,下面會介紹兩種常用的非同步機制Thread+Handler與Async Task機制;
一、Thread+Handler
提起Thread'很多人都不會陌生,做過Android手機開發的人都知道,手機UI是一個單獨的執行緒在執行,並且該執行緒最好不會因為使用者的操作而阻塞。換句話說,如果使用者進行的操作需要耗時幾十秒甚至幾十分鐘,那麼在這段時間內佔用UI執行緒是一個非常不明智的做法。它會阻塞掉UI執行緒,導致手機不再顯示或者接受使用者新的操作,給使用者一種宕機的感覺,因此我們開闢出子執行緒用來執行耗時操作。我們可以通過Thread+Handler來實現具體UI與子執行緒的協同;
下面是常用的handler:
我們通過thread中的傳送訊息操作將訊息傳遞給handlerprivate Handler mHandler = new Handler() { public void handleMessage (Message msg) {//此方法在ui執行緒執行 switch(msg.what) { case MSG_SUCCESS: break; case MSG_FAILURE: break; } } };
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都不會影響你的主執行緒的生命週期。
精益求精方可融會貫通!