編輯:關於Android編程
AsyncTask是一種輕量級的異步任務類,它內部封裝了Handler和Thread,能將後台線程執行的進度和最終的結果分發到UI線程中進行處理,通過AsyncTask可以更加方便地執行後台任務。
AsyncTask並不構成一個通用的線程處理框架,理想情況下它只應該用於短時間的操作(最多幾秒鐘),若是需要保持線程運行較長一段時間的話,Google推薦我們使用java.util.concurrent包中的API,比如:Executor, ThreadPoolExecutor和FutureTask.
一個異步任務定義了三個泛型參數,分別是:Params, Progress和Result,還定義了4個方法執行的步驟,分別是:onPreExecute, doInBackground, onProgressUpdate和onPostExecute.
AsyncTask是抽象類,必須實現它的抽象方法doInBackground(…),大多數情況下我們可能還需要實現onPostExecute(…)方法去獲取異步任務處理的結果。在使用AsyncTask類時,我們需要指定3個泛型參數,分別是:
Params:表示後台任務執行時傳入的參數的類型 Progress:表示後台任務的執行進度的類型 Result:表示後台任務的返回結果的類型這三種參數類型並非一定要使用,若不想用它,傳一個Void就行。
private class MyTask extends AsyncTask{ ... }
AsyncTask使用中經常需要重寫的四個方法分別是:
onPreExecute():執行在UI線程中,在異步任務開始執行之前調用,用於進行一些界面上的初始化操作,比如顯示一個進度條對話框等。
doInBackground(Params…):執行在後台線程中,在onPreExecute()方法執行之後調用,參數Params表示異步任務的輸入參數,該方法計算的結果Result參數會返回給onPostExecute()方法,在此方法中可以通過調用publishProgress()方法來更新任務的進度,然後傳遞到onProgressUpdate()進行顯示。
onProgressUpdate(Progress…):執行在UI線程中,publishProgress(Progress…)方法執行後調用,用於更新當前異步任務執行的進度,方法中攜帶的參數就是在doInBackground()中傳遞過來的。
onPostExecute(Result):執行在UI線程中,在異步任務執行完後調用,其中result參數是doInBackground()的返回值,可以利用該參數來進行一些UI操作,比如說提醒任務執行的結果,以及關閉掉進度條對話框等。
AsyncTask的線程規則:
AsyncTask的類必須在主線程中被加載,在4.1及以上的版本已經被系統自動完成了; AsyncTask的實例對象必須在UI中創建; AsyncTask的execute()方法必須在UI線程調用; 不要在程序中直接調用onPreExecute(), onPostExecute(), doInBackground()和onProgressUpdate()方法; 一個AsyncTask對象只能被執行一次, 即只能調用一次execute()方法, 否則會報運行時異常;如何取消一個任務:
通過調用cancel()方法可以在任意時刻取消一個正在進行的異步任務; 調用cancel()方法後,後續調用isCancelled()方法會返回true; 調用cancel()方法後,一旦doInBackground()方法執行完,onPostExecute()方法不會被調用,而onCancelled()方法會被調用; 調用cancel()方法後,為確保任務能盡快的取消,我們應該在doInBackground()方法中對isCancelled()方法的返回值進行檢查。AsyncTask的執行順序:
在DONUT(即Android 1.6)之前,AsyncTask是串行執行任務的; 在Android 1.6的時候AsyncTask開始采用在線程池裡處理並行任務; 後來,從HONEYCOMB(即Android 3.0)開始,為了避免AsyncTask帶來的並發錯誤,AsyncTask又采用了在一個線程中串行的執行任務。不過,在3.0以後,我們仍然可以通過AsyncTask#executeOnExecute()方法來並行的執行任務。
異步文件下載AsyncTask類:
import android.app.ProgressDialog; import android.content.Context; import android.os.AsyncTask; import android.widget.Toast; public class DownloadFilesTask extends AsyncTask{ private ProgressDialog mProgressDialog; private Context mContext; public DownloadFilesTask(Context context) { this.mContext = context; } @Override protected void onPreExecute() { mProgressDialog = new ProgressDialog(mContext); mProgressDialog.show(); } @Override protected Long doInBackground(String... urls) { int count = urls.length; long totalSize = 0; for (int i = 0; i < count; i++) { totalSize += Downloader.downloadFile(urls[i]); // pseudo-code publishProgress((int) ((i / (float) (count - 1)) * 100)); // Escape early if cancel() is called if (isCancelled()) { break; } } return totalSize; } @Override protected void onProgressUpdate(Integer... values) { mProgressDialog.setMessage("Current download progress:" + values[0] + "%"); } @Override protected void onPostExecute(Long result) { mProgressDialog.dismiss(); Toast.makeText(mContext, "Download " + result + " bytes.", Toast.LENGTH_SHORT).show(); } @Override protected void onCancelled(Long result) { mProgressDialog.dismiss(); Toast.makeText(mContext, "Download cancelled!", Toast.LENGTH_SHORT).show(); } }
啟動AsyncTask:
new DownloadFilesTask(this).execute(url1, url2, url3);
由於各個版本的AsyncTask實現大同小異,我這裡僅以Android 5.1的源碼來進行分析。
初看AsyncTask的源碼,會發現裡面有一大堆的用static標識的成員變量、類及代碼塊,對此我們需要了解一下類的初始化過程,不太了解的請移步博客:Java中的類加載順序
接下類,我們看下AsyncTask類被加載時執行的代碼片段:
public abstract class AsyncTask{ private static final String LOG_TAG = "AsyncTask"; //獲得當前運行狀態的cup數量 private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors(); //根據當前機器CPU的個數設置線程池中的核心線程數 private static final int CORE_POOL_SIZE = CPU_COUNT + 1; //根據當前機器CPU的個數設置線程池中的最大線程數 private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1; //線程的存活時間 private static final int KEEP_ALIVE = 1; //初始化線程工廠類,為線程池創建所需的線程 private static final ThreadFactory sThreadFactory = new ThreadFactory() { private final AtomicInteger mCount = new AtomicInteger(1); public Thread newThread(Runnable r) { return new Thread(r, "AsyncTask #" + mCount.getAndIncrement()); } }; //初始化線程池中的緩存隊列,設置為128個 private static final BlockingQueue sPoolWorkQueue = new LinkedBlockingQueue (128); /** * An {@link Executor} that can be used to execute tasks in parallel. */ //初始化線程池執行器 public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory); /** * An {@link Executor} that executes tasks one at a time in serial * order. This serialization is global to a particular process. */ //初始化順序執行的線程池執行器 public static final Executor SERIAL_EXECUTOR = new SerialExecutor(); //初始化異步任務處理結果碼及進度更新碼 private static final int MESSAGE_POST_RESULT = 0x1; private static final int MESSAGE_POST_PROGRESS = 0x2; //初始化線程池中默認的執行器 private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR; //初始化消息的執行者Handler對象,在getHandler()中進行構造 private static InternalHandler sHandler; //異步任務回調接口 private final WorkerRunnable mWorker; private final FutureTask mFuture; //當前異步任務的狀態,初始狀態為“未執行”狀態 private volatile Status mStatus = Status.PENDING; //初始化boolean型原子類 private final AtomicBoolean mCancelled = new AtomicBoolean(); private final AtomicBoolean mTaskInvoked = new AtomicBoolean(); ...................... private static class SerialExecutor implements Executor { final ArrayDeque mTasks = new ArrayDeque (); 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); } } } /** * Creates a new asynchronous task. This constructor must be invoked on the UI thread. */ //創建一個異步任務實例,該構造方法必須在UI線程中調用 public AsyncTask() { mWorker = new WorkerRunnable () { public Result call() throws Exception { mTaskInvoked.set(true); Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); //noinspection unchecked return postResult(doInBackground(mParams)); } }; mFuture = new FutureTask (mWorker) { @Override protected void done() { try { postResultIfNotInvoked(get()); } catch (InterruptedException e) { android.util.Log.w(LOG_TAG, e); } catch (ExecutionException e) { throw new RuntimeException("An error occured while executing doInBackground()", e.getCause()); } catch (CancellationException e) { postResultIfNotInvoked(null); } } }; } .................. }
從這裡可以知道,AsyncTask的成員變量大部分都是static的,也就是說一個應用中的內存中只保存有一份這些成員變量的值。 然後構造函數中只初始化了兩個變量,分別是mWorker和mFuture,並在初始化mFuture的時候將mWorker作為參數傳入,而mWorker是一個Callable對象,mFuture是一個FutureTask對象,FutureTask繼承自Runnable,也即可以理解成:mFuture對象封裝了一個後台的異步耗時任務,並等待線程池執行器去處理該耗時任務,而且mFuture對象會作為一個線程接口提供給調用者。這裡面,實際的後台工作是由mWorker的call() 方法調用的,當然,call() 方法可能會失敗,所以在mFuture的done() 方法裡做了進一步的判斷。
其中mWorker的實現如下:
private static abstract class WorkerRunnableimplements Callable { Params[] mParams; }
接著我們看下它執行的入口方法execute()。
public static void execute(Runnable runnable) { sDefaultExecutor.execute(runnable); }
該方法僅僅是執行一個Runnable對象,實際用的並不多,我們看下另外一個方法:
public final AsyncTaskexecute(Params... params) { return executeOnExecutor(sDefaultExecutor, params); }
通過傳遞指定的參數執行異步任務,實際執行的是executeOnExecutor() 方法,我們看下executeOnExecutor() 的實現:
public final AsyncTaskexecuteOnExecutor(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; }
該方法帶有兩個參數,分別是Executor和Params,第一個參數在類加載的時候就已經初始化了,這裡傳遞進來的是sDefaultExecutor,實際開啟異步任務的線程池也是它,那麼它到底是串行執行還是並行執行呢?其實在前面我們已經總結了,它是串行執行還是並行執行跟平台版本有關。在1.6前通過單個後台線程去處理隊列中的任務,1.6後改為固定線程數的線程池去處理隊列中的任務,後來到了3.0開始,又改回到單個後台線程去處理隊列中的任務,這是為了解決Android1.6以後如果異步任務超過138個時AsyncTask會拋出異常。
當然如果我們想在3.0後並行執行異步任務也是可以的,直接用executeOnExecutor()方法,並往裡面傳入THREAD_POOL_EXECUTOR變量來啟動異步任務。
這裡需要知道,AsyncTask裡面有兩個線程池,分別是SerialExecutor和THREAD_POOL_EXECUTOR,其中SerialExecutor用於任務的排列,而THREAD_POOL_EXECUTOR用於實際的任務執行。
execute() 執行啟動後,最終在mFuture所構造的後台線程裡面運行異步耗時任務,耗時任務的執行實際上會通過抽象函數doInBackground() 回調給調用者進行處理,這也是我們必須要實現的方法。執行完後,通過postResult() 進行線程間的切換,通過Handler機制將後台線程切回到UI線程。
private Result postResult(Result result) { @SuppressWarnings("unchecked") Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT, new AsyncTaskResult(this, result)); message.sendToTarget(); return result; }
這裡將後台線程執行的結果Result通過AsyncTaskResult的包裝傳遞到UI線程,AsyncTaskResult是一個私有靜態內部類,只是封裝了AsyncTask的實例和泛型對象Data數組。
@SuppressWarnings({"RawUseOfParameterizedType"}) private static class AsyncTaskResult { final AsyncTask mTask; final Data[] mData; AsyncTaskResult(AsyncTask task, Data... data) { mTask = task; mData = data; } }
從前面的的類加載中我們知道,static標識的對象和代碼塊會優先進行加載,不過sHandler一開始初始化為null,通過調用靜態方法getHandler() 後才進行實例化,而該方法內部通過同步加鎖機制防止了多線程同時持有InternalHandler對象而導致的多線程安全問題。
private static Handler getHandler() { synchronized (AsyncTask.class) { if (sHandler == null) { sHandler = new InternalHandler(); } return sHandler; } }
在前面,我們說過:AsyncTask的類必須在主線程中被加載,為什麼呢?因為sHandler的緣故,它是靜態的,在類加載的時候進行初始化,而且我們還要用它來進行線程的切換呢,這就必須要求AsyncTask必須在UI線程中加載,否則AsyncTask工作就不正常了。
接下來我們看下InternalHandler對消息處理的實現:
private static class InternalHandler extends Handler { public InternalHandler() { super(Looper.getMainLooper()); } @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; } } }
InternalHandler 也是私有內部類,外部不能訪問,我們看到它的構造函數中傳入了Looper.getMainLooper(),說明它是通過UI線程的Looper作為參數構造的,因此InternalHandler是在UI線程中處理消息的。
它裡面有兩種消息類型的處理,分別是MESSAGE_POST_RESULT和MESSAGE_POST_PROGRESS。
1、MESSAGE_POST_RESULT:通過調用postResult() 方法,將結果處理分發到UI線程的finish() 方法中:
private void finish(Result result) { if (isCancelled()) { onCancelled(result); } else { onPostExecute(result); } mStatus = Status.FINISHED; }
該方法首先判斷當前異步任務是否已經被取消,若取消回調onCancelled() 方法,否則回調onPostExecute() 方法,最後將mStatus標記為FINISHED。其中onPostExecute() 和onCancelled() 就是留給子類去實現的。
2、MESSAGE_POST_PROGRESS:表示這是一個進度更新消息,需要通過子類在doInBackground() 方法內部調用publishProgress() 方法來觸發,然後在onProgressUpdate() 方法中回調給子類去實現。
protected final void publishProgress(Progress... values) { if (!isCancelled()) { getHandler().obtainMessage(MESSAGE_POST_PROGRESS, new AsyncTaskResult
接下來我們看下怎麼取消異步任務:
/** *
Attempts to cancel execution of this task. This attempt will * fail if the task has already completed, already been cancelled, * or could not be cancelled for some other reason. If successful, * and this task has not started when cancel is called, * this task should never run. If the task has already started, * then the mayInterruptIfRunning parameter determines * whether the thread executing this task should be interrupted in * an attempt to stop the task.
* *Calling this method will result in {@link #onCancelled(Object)} being * invoked on the UI thread after {@link #doInBackground(Object[])} * returns. Calling this method guarantees that {@link #onPostExecute(Object)} * is never invoked. After invoking this method, you should check te * value returned by {@link #isCancelled()} periodically from * {@link #doInBackground(Object[])} to finish the task as early as * possible.
* * @param mayInterruptIfRunning true if the thread executing this * task should be interrupted; otherwise, in-progress tasks are allowed * to complete. * * @return false if the task could not be cancelled, * typically because it has already completed normally; * true otherwise * * @see #isCancelled() * @see #onCancelled(Object) */ public final boolean cancel(boolean mayInterruptIfRunning) { mCancelled.set(true); return mFuture.cancel(mayInterruptIfRunning); }方法注釋挺多的,總結起來就是:
若異步任務已經結束,或已經取消過了,或由於某些原因不能被取消—>返回false; 若異步任務還沒啟動,但調用了cancel()方法,那麼任務永遠也不會運行; 若異步任務已經啟動了但未結束,則由mayInterruptIfRunning決定。此時參數若為true,那麼當前正在執行異步任務的線程會立即中斷;若為false,則等當前正在執行的異步任務完成之後再取消後面其他的異步任務。正如Google官方文檔所說,AsyncTask只適合處理短時間的耗時操作,再根據我們前面的分析,總結如下:
- AsyncTask用於不需要進行大數據下載的簡單耗時任務;
- 對磁盤讀寫只需要花費幾毫秒的短時間操作任務。
其它情況還是用Java並發包裡面的API靠譜些。
思路分析:1、自定義View實現字母導航欄2、ListView實現聯系人列表3、字母導航欄滑動事件處理4、字母導航欄與中間字母的聯動5、字母導航欄與ListView的聯動
小豬的Android入門之路 Day 7 part 4 Android的數據存儲與訪問之——ContentProvider(內容提供者)
Android四大基本組件分別是Activity,Service服務,Content Provider內容提供者,BroadcastReceiver廣播接收器。一、Act
引言Google I/O 2015 推出的 Android Design Support Library令人非常激動。Material Design的推出確實振奮了不少