AsyncTask學習,asynctask
記錄下AsyncTask的學習心得:
AsyncTask類是用來處理非同步請求的,當我們在UI線程(主線程)中執行耗時操作時(從網路擷取資料,從資料庫中擷取資料,或者讀取檔案)會阻塞主線程的運行,導致介面不流暢,卡。這種情況下,我們需要將這些耗時的操作另起一個線程來執行,盡量在主線程中執行UI操作。
一般做法,我們用Thread+handler去處理耗時操作,和處理善後工作。AsyncTask對Thread和Handler進行了一定的封裝,簡化代碼的編寫,方便我們使用。
AsyncTask的架構是建立一個Executor來執行ArrayDeque裡的Runnable對象,這個Executor可以是自訂的,也可以是AsyncTask預設實現的SerialExecutor
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); } } }
上面代碼 SerialExecutor實現了Executor中的execute方法,execute中將一個Runnable對象(在本類中是一個FutureTask)排入佇列中,然後判斷當前是否有活動的Runnable,如果沒有的話,就從Queue中take一個,然後將它設為當前活動Runnable, 並且執行這個Runnable。
現在來看一下則個Runnable做了什麼,首先他執行參數Runnable的run方法,finally去執行sheduleNext()函數,進入這個函數,他拉去隊列的首元素出來當成當前active Runnable,如果不為空白,就執行他,這樣Executor就順序執行了Queue裡的Runnable對象。當然還可以自訂自己的Executor,比如給隊列賦予權重,權重大的runnable先得到執行
PriorityBlockingQueue<PriorityRunnable> mTasks=new PriorityBlockingQueue<PriorityRunnable>(); private static class PriorityRunnable implements Comparable<PriorityRunnable>{ private Runnable mRunnable; private Priority mPriority; private Integer mSequence; private AtomicInteger mSequenceGenerator = new AtomicInteger();public PriorityRunnable(final Runnable r){ mRunnable=new Runnable() { public void run() { try { r.run(); } finally { scheduleNext(); } } }; this.setmSequence(getSequenceNumber()); // this.setmPriority(); } public int getSequenceNumber() { return mSequenceGenerator.incrementAndGet(); }public Runnable getRunnable(){return mRunnable;}public void setmRunnable(Runnable mRunnable) {this.mRunnable = mRunnable;}public Priority getmPriority() {return mPriority;}public void setmPriority(Priority mPriority) {this.mPriority = mPriority;}public Integer getmSequence() {return mSequence;}public void setmSequence(Integer mSequence) {this.mSequence = mSequence;}@Overridepublic int compareTo(PriorityRunnable another) {// TODO Auto-generated method stub Priority left = this.getmPriority(); Priority right = another.getmPriority(); return left == right ? this.mSequence - another.mSequence : right.ordinal() - left.ordinal();} }
上面的Runnable是自訂的,實現了compareable介面,因為我們用PriorityBlockingQueue來容納Runnable。
上面說到execute參數裡的Runnable是個FutureTask,FutrueTask是用來執行非同步計算的,它可以封裝一個Callable或者Runnable,並且獲得Callable的計算結果和運行狀態。在AsyncTask中,每一次耗時請求都new一個全新的FutureTask。FutureTask的初始化工作在 new AsyncTask()中。
public AsyncTask() { mWorker = new WorkerRunnable<Params, Result>() { public Result call() throws Exception { mTaskInvoked.set(true); Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); //noinspection unchecked return postResult(doInBackground(mParams)); } }; mFuture = new FutureTask<Result>(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初始化中,首先先new 一個WorkRunnable,WorkRunnable實現了Callable<Result>的抽象類別,在call方法體內,首先mTaskInvoked.set(true),這個mTaskInvoked是個AutomicBoolean類型的變數,用來追蹤Task的狀態,噹噹前Task被執行是,將狀態置為true,用來判斷Task是否被取消執行。設定完mTaskInvoked後,又為該Task設定了線程優先順序為Process.THREAD_PRIORITY_BACKGROUND層級,最後返回postResult函數值,進入postResult():
private Result postResult(Result result) { @SuppressWarnings("unchecked") Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT, new AsyncTaskResult<Result>(this, result)); message.sendToTarget(); return result; }
postResult函數參數為doInBackground()函數的執行結果,這裡的doInBackground()函數為AsyncTask的抽象方法,我們需要在自訂的AsyncTask類中去實現這個函數,我們的非同步請求的主要邏輯就在這裡執行。doInBackground()函數的傳回值類型是Result,Result是泛型型別,postResult()函數用來建立一個Message,然後將Result封裝為AsyncTaskResult類型,然後由Message攜帶傳給相應線程的Handler去處理。看一下AsyncTaskResult的資料結構:
private static class AsyncTaskResult<Data> { final AsyncTask mTask; final Data[] mData; AsyncTaskResult(AsyncTask task, Data... data) { mTask = task; mData = data; } }
它有兩個欄位,mTask和mDate, mTask用來儲存自己的引用,mData用來儲存mFuture.get()的值,這個類的用法下面會進一步分析。
new WorkRunnable後接著new FutureTask,並且mWorker作為參數傳入FutureTask作為其擷取資料的來源。FutureTask可以控制線程的執行和得到線程返回的資料,當FutureTask中的Callable執行完後,FutureTask的done函數得以觸發。如果線程在執行前就被Cancle掉,那麼直接執行FutureTask的done函數。FutureTask的狀態是isDone,postResultIfNotInvoked()函數主要是用來處理task被cancel的情況,當Task被cancel時,WorkRunnable的call函數不會執行,此時mTaskInvoked的值為false,然後執行postResult函數,這樣保證了Task不管是被執行還是被cancel都能得到處理。
private void postResultIfNotInvoked(Result result) { final boolean wasTaskInvoked = mTaskInvoked.get(); if (!wasTaskInvoked) { postResult(result); } }
現在來看InternalHandler,這是個Handler的子類,他接受Message傳來的資料,根據msg.what進行判斷以進行不同的邏輯處理。在這裡msg.what有兩種類型,一種是MESSAGE_POST_RESULT,表示處理mWorkRunnable執行完畢發送的message,執行AsyncTask的finish函數。另一種是MESSAGE_POST_PROGRESS,它表示處理mWorkRunnable在更新時發送的message,執行AsyncTask的onProgressUpdate函數.
finish函數:
private void finish(Result result) { if (isCancelled()) { onCancelled(result); } else { onPostExecute(result); } mStatus = Status.FINISHED; }
函數體先判斷當前task是否被cancelled,如果isCancelled,執行onCacelled(result)函數。
onCancelled函數:
protected void onCancelled(Result result) { onCancelled(); } protected void onCancelled() { }
我們可以override這個函數,執行自己的操作。
如果當前task沒有被cancelled掉,執行onPostExecute(result)函數,同樣我們可以在AsyncTask的子類中override這個函數,這個函數是在UI線程中執行的,可以對UI元素進行操作。
在這裡出現了一個變數mStatus,看它的定義:
public enum Status { /** * Indicates that the task has not been executed yet. */ PENDING, /** * Indicates that the task is running. */ RUNNING, /** * Indicates that {@link AsyncTask#onPostExecute} has finished. */ FINISHED, }
它有三種狀態,一種是Pendding,注釋明明白白的說道,這是為了標識還未執行的tasks的狀態,Running用來標識正在啟動並執行task的狀態,Finished用來標識已經執行完任務的task的狀態。
現在再看看到底AsyncTask是怎麼執行的,我們平常用的時候是這樣子的 new MyAsyncTask().executef();
看看execute函數的廬山真面目吧:
public final AsyncTask<Params, Progress, Result> execute(Params... params) { return executeOnExecutor(sDefaultExecutor, params); }
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; }
首先,函數體先判斷當前task的狀態,如果狀態不是未運行,則拋出異常,如果執行execute()時,當前task處於Running狀態,則拋出“cannot execute task:the task is already running"異常,
至於為什麼要這麼設計,我的猜想是:
Future 僅在計算完成時才能檢索結果;如果計算尚未完成,則阻塞 get 方法。一旦計算完成,就不能再重新開始或取消計算。
只要是重新new AsyncTask(),就會獲得一個全新的task。
回到代碼,如果當前task的狀態是pending,那麼更新它的狀態為Running。然後執行onPreExecute(),函數,它和onPostExecute還有onBackground()函數一樣,都可以在用戶端override,定義自己的商務邏輯。執行完畢後,將從用戶端傳來的可變參數params賦值給mWorkRunnable的mParams成員變數,用以儲存。最後開始調用線程池的execute()函數,之前說過execute()函數將當前task隊列加入queue隊列中,如果沒有正在執行的task,則執行剛建立的task,如果有正在執行任務的task,則先緩衝在隊列中,等待他執行的時機。
最後看一下線程池:
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()); } }; private static final BlockingQueue<Runnable> sPoolWorkQueue = new LinkedBlockingQueue<Runnable>(10); public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
ThreadFactory類是用來建立線程的,簡單的重寫newThread方法即可定義自己的ThreadFactory。
ThreadPoolExecutor類有幾個重要的參數,core_pool_size:池中所儲存的線程數,包括空閑線程。,maximum_pool_size:池中允許的最大線程數,keep_alive:當線程數大於核心時,此為終止前多餘的空閑線程等待新任務的最長時間。unit - keepAliveTime 參數的時間單位。workQueue - 執行前用於保持任務的隊列。此隊列僅保持由 execute 方法提交的 Runnable 任務。threadFactory - 執行程式建立新線程時使用的工廠。
對於AsycnTask的大體分析就到此為止了。談一下自己對AsycnTask的幾點感想:
1.AsycnTask在SerialExecutor設計時用到了設計模式中的責任鏈模式。
2.頻繁的擷取網路資料或者進行耗時操作時,new AsycnTask()可能造成大量的臨時對象,影響效能,導致介面”打嗝“性卡頓。
3.AsycnTask不適合上傳大檔案,一般上傳大檔案用socket上傳,或者斷點上傳,利用RandomAceccsFile來實現。
4AsycnTask利用線程池來管理Thread,在效能上比每次單獨建立一個Thread更省記憶體。
以上有不對的地方,敬請堪正。
關於ThreadPoolExecutor,點這http://blog.sina.com.cn/s/blog_70bcd7c10101a1w7.html