AsyncTask的理解與應用

諸葛佩奇發表於2018-07-10

執行緒

在Android當中,通常將執行緒分為兩種,一種叫做Main Thread,除了Main Thread之外的執行緒都可稱為Worker Thread。

當一個應用程式執行的時候,Android作業系統就會給該應用程式啟動一個執行緒,這個執行緒就是我們的Main Thread,這個執行緒非常的重要,它主要用來載入我們的UI介面,完成系統和我們使用者之間的互動,並將互動後的結果又展示給我們使用者,所以Main Thread又被稱為UI Thread。

Android系統預設不會給我們的應用程式元件建立一個額外的執行緒,所有的這些元件預設都是在同一個執行緒中執行。然而,某些時候當我們的應用程式需要完成一個耗時的操作的時候,例如訪問網路或者是對資料庫進行查詢時,此時我們的UI Thread就會被阻塞。例如,當我們點選一個Button,然後希望其從網路中獲取一些資料,如果此操作在UI Thread當中完成的話,當我們點選Button的時候,UI執行緒就會處於阻塞的狀態,此時,我們的系統不會排程任何其它的事件,更糟糕的是,當我們的整個現場如果阻塞時間超過5秒鐘(官方是這樣說的),這個時候就會出現 ANR (Application Not Responding)的現象,此時,應用程式會彈出一個框,讓使用者選擇是否退出該程式。對於Android開發來說,出現ANR的現象是絕對不能被允許的。

另外,由於我們的Android UI控制元件是執行緒不安全的,所以我們不能在UI Thread之外的執行緒當中對我們的UI控制元件進行操作。因此在Android的多執行緒程式設計當中,我們有兩條非常重要的原則必須要遵守:

  1. 絕對不能在UI Thread當中進行耗時的操作,不能阻塞我們的UI Thread
  2. 不能在UI Thread之外的執行緒當中操縱我們的UI元素

執行緒間通訊

既然在Android當中有兩條重要的原則要遵守,那麼我們可能就有疑問了?我們既不能在主執行緒當中處理耗時的操作,又不能在工作執行緒中來訪問我們的UI控制元件,那麼我們比如從網路中要下載一張圖片,又怎麼能將其更新到UI控制元件上呢?這就關係到了我們的主執行緒和工作執行緒之間的通訊問題了。在Android當中,提供了兩種方式來解決執行緒直接的通訊問題,一種是通過Handler的機制( 可以閱讀-關於Handler的理解),還有一種就是今天要詳細講解的 AsyncTask 機制。

AsyncTask

關於AsyncTask的解釋,Google上面是這樣說的:

AsyncTask enables proper and easy use of the UI thread. This class allows you to perform background operations and publish results on the UI thread without having to manipulate threads and/or handlers.

An asynchronous task is defined by a computation that runs on a background thread and whose result is published on the UI thread. An asynchronous task is defined by 3 generic types, called Params, Progress and Result, and 4 steps, called onPreExecute, doInBackground, onProgressUpdate and onPostExecute.

大概意思就是說“它使建立非同步任務變得更加簡單,不再需要編寫任務執行緒和Handler例項即可完成相同的任務。一個非同步任務通常是在後臺執行的計算等然後將結果傳送到UI主執行緒中去。通常情況下,非同步任務被定義為3中通用型別,分別為:引數、過程以及結果和4個步驟,分別為“onPreExecute、doInBackground、onProgressUpdate、onPostExecute””這就是關於非同步任務的大概說明。

怎麼來理解AsyncTask呢?通俗一點來說,AsyncTask就相當於Android給我們提供了一個多執行緒程式設計的一個框架,其介於Thread和Handler之間,我們如果要定義一個AsyncTask,就需要定義一個類來繼承AsyncTask這個抽象類,並實現其唯一的一個 doInBackgroud 抽象方法。要掌握AsyncTask,我們就必須要一個概念,總結起來就是: 3個泛型,4個步驟。

3個泛型

3個泛型指的是什麼呢?我們來看看AsyncTask這個抽象類的定義,當我們定義一個類來繼承AsyncTask這個類的時候,我們需要為其指定3個泛型引數:

public abstract class AsyncTask<Params, Progress, Result> 
複製程式碼

Params: 這個泛型指定的是我們傳遞給非同步任務執行時的引數的型別 Progress: 這個泛型指定的是我們的非同步任務在執行的時候將執行的進度返回給UI執行緒的引數的型別 Result: 這個泛型指定的非同步任務執行完後返回給UI執行緒的結果的型別

4個步驟

4個步驟:當我們執行一個非同步任務的時候,其需要按照下面的4個步驟分別執行:

1、onPreExecute(): 這個方法是在執行非同步任務之前的時候執行,並且是在UI Thread當中執行的,通常我們在這個方法裡做一些UI控制元件的初始化的操作,例如彈出要給ProgressDialog。

2、doInBackground(Params... params): 在onPreExecute()方法執行完之後,會馬上執行這個方法,這個方法就是來處理非同步任務的方法,Android作業系統會在後臺的執行緒池當中開啟一個worker thread來執行我們的這個方法,所以這個方法是在worker thread當中執行的,這個方法執行完之後就可以將我們的執行結果傳送給我們的最後一個 onPostExecute 方法,在這個方法裡,我們可以從網路當中獲取資料等一些耗時的操作。

3、onProgressUpdate(Progess... values): 這個方法也是在UI Thread當中執行的,我們在非同步任務執行的時候,有時候需要將執行的進度返回給我們的UI介面,例如下載一張網路圖片,我們需要時刻顯示其下載的進度,就可以使用這個方法來更新我們的進度。這個方法在呼叫之前,我們需要在 doInBackground 方法中呼叫一個 publishProgress(Progress) 的方法來將我們的進度時時刻刻傳遞給 onProgressUpdate 方法來更新。

4、onPostExecute(Result... result): 當我們的非同步任務執行完之後,就會將結果返回給這個方法,這個方法也是在UI Thread當中呼叫的,我們可以將返回的結果顯示在UI控制元件上。

為什麼我們的AsyncTask抽象類只有一個 doInBackground 的抽象方法呢??原因是,我們如果要做一個非同步任務,我們必須要為其開闢一個新的Thread,讓其完成一些操作,而在完成這個非同步任務時,我可能並不需要彈出要給ProgressDialog,我並不需要隨時更新我的ProgressDialog的進度條,我也並不需要將結果更新給我們的UI介面,所以除了 doInBackground 方法之外的三個方法,都不是必須有的,因此我們必須要實現的方法是 doInBackground 方法。

例項演示

我們來了解一些相關程式碼。其實下載的程式碼原理很簡單,就是通過流的方式轉為位元組陣列,然後再轉化為Bitmap而已。


//進度框顯示

        progressDialog = new ProgressDialog(MainActivity.this);
        progressDialog.setTitle("提示資訊");
        progressDialog.setMessage("正在下載中,請稍後......");
        //    設定setCancelable(false); 表示我們不能取消這個彈出框,等下載完成之後再讓彈出框消失
        progressDialog.setCancelable(false);
        //    設定ProgressDialog樣式為水平的樣式
        progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);



//下載類
public class MyAsyncTask extends AsyncTask<String, Integer, Bitmap> {
        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            //    在onPreExecute()中我們讓ProgressDialog顯示出來
            progressDialog.show();
        }

        @Override
        protected Bitmap doInBackground(String... params) {
            Bitmap bitmap = null;

            try {
                URL url = new URL(params[0]);
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                conn.setConnectTimeout(5 * 1000);
                conn.setRequestMethod("GET");
                InputStream inputStream = conn.getInputStream();
                if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
                    int fileLength = conn.getContentLength();
                    ByteArrayOutputStream outStread = new ByteArrayOutputStream();
                    byte[] buffer = new byte[1024];
                    int length = 0;
                    long total = 0;
                    while ((length = inputStream.read(buffer)) != -1) {
                        outStread.write(buffer, 0, length);
                        total += length;
                        if (fileLength > 0) {
                            publishProgress((int) (total * 100 / fileLength));
                        }
                    }

                    outStread.close();
                    inputStream.close();
                    byte[] data = outStread.toByteArray();
                    if (data != null) {
                        bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
                    } else {
                        Toast.makeText(MainActivity.this, "Image error!", Toast.LENGTH_LONG).show();
                    }
                    return bitmap;

                }
            } catch (Exception e) {
                e.printStackTrace();
            }

            return null;

        }

        @Override
        protected void onProgressUpdate(Integer... values) {
            super.onProgressUpdate(values);
            //    更新ProgressDialog的進度條
            progressDialog.setProgress(values[0]);
        }

        @Override
        protected void onPostExecute(Bitmap bitmap) {
            super.onPostExecute(bitmap);
            imageView.setImageBitmap(bitmap);
            try {
                saveFile(bitmap, "netpic.jpg");
            } catch (IOException e) {
                e.printStackTrace();
            }
            progressDialog.dismiss();
        }
    }


//在UI主執行緒中執行下載程式
String picUrl = "http://img3.imgtn.bdimg.com/it/u=2437337628,1430863508&fm=214&gp=0.jpg";

new MyAsyncTask().execute(picUrl);
複製程式碼

詳細程式碼請檢視github-easy-net封裝學習基本的網路請求庫


到這裡基本上就結束了。這就是簡單的運用AsyncTask進行UI執行緒和Work執行緒進行通訊的基本方式。接下來我們就原始碼進行深入的研究關於AsyncTask的相關內容。

原始碼解讀(基於API25)

首先我們從非同步任務的起點execute開始分析:


//<p>This method must be invoked on the UI thread.
//必須在UI主執行緒中呼叫該方法。
@MainThread
    public final AsyncTask<Params, Progress, Result> execute(Params... params) {
        return executeOnExecutor(sDefaultExecutor, params);
    }
    
//跳轉到executeOnExecutor方法
@MainThread
    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)");
            }
        }

       //設定當前AsyncTask的狀態為RUNNING
        mStatus = Status.RUNNING;

        //還是在UI主執行緒,這個時候可以進行一些初始化操作
        onPreExecute();

        mWorker.mParams = params;
        exec.execute(mFuture);

        return this;
    }
複製程式碼

程式碼比較簡單,其中出現了mWork和mFuture變數,接下來我們跟蹤這兩個變數進行研究。

1、對於mWork變數

private final WorkerRunnable<Params, Result> mWorker;
 
private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
        Params[] mParams;
    }
//可以看到是Callable的子類,且包含一個mParams用於儲存我們傳入的引數,接下來看看 mWork的初始化操作
//這是在AsyncTask的建構函式中進行初始化的
mWorker = new WorkerRunnable<Params, Result>() {
            public Result call() throws Exception {
				//設定為true,下面要用到
                mTaskInvoked.set(true);
                Result result = null;
                try {
                    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                    //noinspection unchecked 
                    //這就是我們使用到的4個方法中的一個,獲取處理結果
                    result = doInBackground(mParams);
                    Binder.flushPendingCommands();
                } catch (Throwable tr) {
                    mCancelled.set(true);
                    throw tr;
                } finally {

					//傳送執行結果
                    postResult(result);
                }
                return result;
            }
        };
複製程式碼

從上面原始碼我們可以分析出mWork在AsyncTask的建構函式中進行初始化,然後實現CallBack的call方法,進行一些設定,然後呼叫doInBackground方法,最後執行postResult(result)進行結果處理,接下來我們繼續分析postResult(result)方法。

private Result postResult(Result result) {
        @SuppressWarnings("unchecked")
        Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
                new AsyncTaskResult<Result>(this, result));
        message.sendToTarget();
        return result;
    }
複製程式碼

我們看到了熟悉的非同步訊息處理,Handler和Message,傳送一個訊息,

msg.what=MESSAGE_POST_RESULT;
msg.obj=new AsyncTaskResult<Result>(this, result);
複製程式碼

從上面的程式碼我們可以知道,既然handler已經傳送出了訊息的話,,那麼肯定會存在一個Handler,並在某處進行訊息的處理,我們來繼續尋找一下這些內容:

//找到相關的Handler
private static Handler getHandler() {
        synchronized (AsyncTask.class) {
            if (sHandler == null) {
                sHandler = new InternalHandler();
            }
            return sHandler;
        }
    }


//訊息處理
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;
            }
        }
    }

//訊息處理完之後,設定狀態為finished
  private void finish(Result result) {
        if (isCancelled()) {
            onCancelled(result);
        } else {

			//執行4個方法中的最後一步,處理結果
            onPostExecute(result);
        }

		//設定最後的狀態為結束finished
        mStatus = Status.FINISHED;
    }
複製程式碼

2、對於mFuture變數

//申明變數
private final FutureTask<Result> mFuture;

//在AsyncTask的建構函式中進行變數的初始化
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 occurred while executing doInBackground()",
                            e.getCause());
                } catch (CancellationException e) {
                    postResultIfNotInvoked(null);
                }
            }
        };


//檢視postResultIfNotInvoked方法,引數是get(),get()表示獲取mWorker的call的返回值,即Result。

    private void postResultIfNotInvoked(Result result) {
        final boolean wasTaskInvoked = mTaskInvoked.get();
        if (!wasTaskInvoked) {
            postResult(result);
        }
    }

//注意上面的mWork初始化時設定的變數值mTaskInvoked.set(true),所以判斷中一般都是wasTaskInvoked=true,所以基本不會執行
複製程式碼

分析完了mWork和mFuture這兩個變數,我們接著分析下面的程式碼:

exec.execute(mFuture);

這個exec其實就是sDefaultExecutor,那麼這個sDefaultExecutor是什麼東西呢?


//sDefaultExecutor的定義
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

//繼續跟蹤SERIAL_EXECUTOR
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();

//SerialExecutor的定義
private static class SerialExecutor implements Executor {

		//維護一個陣列佇列
        final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
        Runnable mActive;

		//執行內容
        public synchronized void execute(final Runnable r) {
            //在佇列的尾部插入一個任務task
            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);
            }
        }
    }
複製程式碼

那麼這個THREAD_POOL_EXECUTOR又是什麼東西呢?接著分析這個變數:

/**
     * An {@link Executor} that can be used to execute tasks in parallel.
     */
    public static final Executor THREAD_POOL_EXECUTOR;

    //執行緒池配置
    static {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
                sPoolWorkQueue, sThreadFactory);
        threadPoolExecutor.allowCoreThreadTimeOut(true);
        THREAD_POOL_EXECUTOR = threadPoolExecutor;
    }


    //變數設定
    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
    // We want at least 2 threads and at most 4 threads in the core pool,
    // preferring to have 1 less than the CPU count to avoid saturating
    // the CPU with background work
    private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
    private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
    private static final int KEEP_ALIVE_SECONDS = 30;
複製程式碼

以上就是過程分析,接下來我們來進一步總結說明具體的流程。

首先設定當前AsyncTask的狀態為RUNNING,然後執行了onPreExecute(),當前依然在UI執行緒,所以我們可以在其中做一些準備工作。其次將我們傳入的引數賦值給了mWorker.mParams ,mWorker為一個Callable的子類,且在內部的call()方法中,呼叫了doInBackground(mParams),然後得到的返回值作為postResult的引數進行執行;postResult中通過sHandler傳送訊息,最終sHandler的handleMessage中完成onPostExecute的呼叫。最後執行exec.execute(mFuture),mFuture為真正的執行任務的單元,將mWorker進行封裝,然後由sDefaultExecutor交給執行緒池進行執行。

這裡面我們涉及到了4個方法中的三個,那麼還有一個方法:

//更新進度
@Override
        protected void onProgressUpdate(Integer... values) {
            super.onProgressUpdate(values);
            progressDialog.setProgress(values[0]);
        }
複製程式碼

那麼這個方法是什麼時候呼叫的的呢?我們在使用AsyncTask中的第三個方法doInBackground時在裡面我們呼叫了一個傳遞進度的方法 publishProgress(int progress),我們進入到該方法中檢視一下:

//工作執行緒中執行該方法
@WorkerThread
    protected final void publishProgress(Progress... values) {
        if (!isCancelled()) {
         //通過Handler和Message非同步訊息機制進行UI執行緒和Work執行緒通訊
            getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
                    new AsyncTaskResult<Progress>(this, values)).sendToTarget();
        }
    }
複製程式碼

publishProgress方法其實就是傳送一個訊息,

msg.what=MESSAGE_POST_PROGRESS//訊息型別
msg.obj=new AsyncTaskResult<Progress>(this, values)//進度


//處理訊息
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://處理進度訊息
                
                    //呼叫onProgressUpdate方法顯示進度
                    result.mTask.onProgressUpdate(result.mData);
                    break;
            }
        }
    }
複製程式碼

這就很明朗了,四個方法都呼叫到了。以上便是AsyncTask所有的執行流程,通過原始碼分析可得AsyncTask的內部也是使用Handler+Message的方式進行訊息傳遞和處理的。

關於AsyncTask的內幕

1、深入理解AsyncTask的內幕,執行緒池引發的重大問題

注意

Android6.0 谷歌把HttpClient相關的類移除了,所以如果繼續使用的話,需要新增相關的jar包。

1、對於AndroidStudio的新增方法是:

在相應的module下的build.gradle中加入:
android {
    useLibrary 'org.apache.http.legacy'
}
複製程式碼

2、對於Eclipse的新增方法是:

libs中加入
org.apache.http.legacy.jar
上面的jar包在:**\android-sdk-windows\platforms\android-23\optional下(需要下載android 6.0的SDK)
複製程式碼

參考連結

1、http://www.cnblogs.com/xiaoluo501395377/p/3430542.html

2、http://blog.csdn.net/liuhe688/article/details/6532519

3、http://blog.csdn.net/lmj623565791/article/details/38614699

相關文章