棒棒糖之——Android中全套異步處理的詳細講解


1、前言

在應用的開發中咱們正確處理好主線程和子線程之間的關係,耗時的操做都放到子線程中處理,避免阻塞主線程,致使ANR。異步處理技術是提升應用性能,解決主線程和子線程之間通訊問題的關鍵。linux

首先看一個異步技術鏈:android


2、Thread

Thread是Android中異步處理技術的基礎,建立線程有兩種方法。安全

  • 繼承Thread類並重寫run方法,以下:
public class MyThread extends Thread {

    @Override
    public void run() {
        super.run();
    }

    public void startThread() {
        MyThread myThread = new MyThread();
        myThread.start();
    }
}複製代碼
  • 實現Runnable接口並實現run方法,以下:
public class MyRunnable implements Runnable {

    @Override
    public void run() {

    }

    public void startThread() {
        MyRunnable myRunnable = new MyRunnable();
        Thread thread = new Thread(myRunnable);
        thread.start();
    }

}複製代碼

Android應用各類類型的線程本質上基於linux系統的pthreads,在應用層能夠分爲三種類型線程。bash

  • 主線程:主線程也稱爲UI線程,隨着應用啓動而啓動,主線程用來運行Android組件,同時刷新屏幕上的UI元素。Android系統若是檢測到非主線程更新UI組件,那麼就會拋出CalledFromWrongThreadException異常,只有主線程才能操做UI,是由於Android的UI工具包不是線程安全的。主線程中建立的Handler會順序執行接受到的消息,包括從其餘線程發送的消息。所以,若是消息隊列中前面的消息沒有很快執行完,那麼它可能會阻塞隊列中的其餘消息的及時處理。
  • Binder線程:Binder線程用於不經過進程之間線程的通訊,每一個進程都維護了一個線程池,用來處理其餘進程中線程發送的消息,這些進程包括系統服務、Intents、ContentProviders和Service等。在大部分狀況下,應用不須要關心Binder線程,由於系統會優先將請求轉換爲使用主線程。一個典型的須要使用Binder線程的場景是應用提供一個給其餘進程經過AIDL接口綁定的Service。
  • 後臺線程:在應用中顯式建立的線程都是後臺線程,也就是當剛建立出來時,這些線程的執行體是空的,須要手動添加任務。在Linux系統層面,主線程和後臺線程是同樣的。在Android框架中,經過WindowManager賦予了主線程只能處理UI更新以及後臺線程不能直接操做UI的限制。

3、HandlerThread

HandlerThread是一個集成了Looper和MessageQueue的線程,當啓動HandlerThread時,會同時生成Looper和MessageQueue,而後等待消息進行處理,它的run方法源碼:cookie

@Override
public void run() {
    mTid = Process.myTid();
    Looper.prepare();
    synchronized (this) {
        mLooper = Looper.myLooper();
        notifyAll();
    }
    Process.setThreadPriority(mPriority);
    onLooperPrepared();
    Looper.loop();
    mTid = -1;
}複製代碼

使用HandlerThread的好處是開發者不須要本身去建立和維護Looper,它的用法和普通線程同樣,以下:框架

HandlerThread handlerThread = new HandlerThread("HandlerThread");
handlerThread.start();

handler = new Handler(handlerThread.getLooper()){
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);

        //處理接受的消息
    }
};複製代碼

HandlerThread中只有一個消息隊列,隊列中的消息是順序執行的,所以是線程安全的,固然吞吐量天然受到必定影響,隊列中的任務可能會被前面沒有執行完的任務阻塞。HandlerThread的內部機制確保了在建立Looper和發送消息之間不存在競態條件(是指一個在設備或者系統試圖同時執行兩個操做的時候出現的不但願的情況,可是因爲設備和系統的天然特性,爲了正確地執行,操做必須按照合適順序進行),這個是經過將HandlerThread.getLooper()實現爲一個阻塞操做實現的,只有當HandlerThread準備好接受消息以後纔會返回,源碼以下:異步

public Looper getLooper() {
    if (!isAlive()) {
        return null;
    }
    
    // 若是線程已經啓動,那麼在Looper準備好以前應先等待
    synchronized (this) {
        while (isAlive() && mLooper == null) {
            try {
                wait();
            } catch (InterruptedException e) {
            }
        }
    }
    return mLooper;
}複製代碼

若是具體業務要求在HandlerThread開始接受消息以前要進行某些初始化操做的話,能夠重寫HandlerThread的onLooperPrepared函數,例如能夠在這個函數中建立於HandlerThread關聯的Handler實例,這同時也能夠對外隱藏咱們的Handler實例,代碼以下:async

public class MyHandlerThread extends HandlerThread {

    private Handler mHandler;

    public MyHandlerThread() {
        super("MyHandlerThread", Process.THREAD_PRIORITY_BACKGROUND);
    }


    @Override
    protected void onLooperPrepared() {
        super.onLooperPrepared();
        mHandler = new Handler(getLooper()) {
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case 1:

                        break;
                    case 2:

                        break;
                }
            }
        };
    }


    public void publishedMethod1() {
        mHandler.sendEmptyMessage(1);
    }
    

    public void publishedMethod2() {
        mHandler.sendEmptyMessage(2);
    }

}複製代碼

4、AsyncQueryHandler

AsyncQueryHandler是用於在ContentProvider上面執行異步的CRUD操做的工具類,CRUD操做會被放到一個單獨的子線程中執行,當操做結束獲取到結果後,將經過消息的方式傳遞給調用AsyncQueryHandler的線程,一般就是主線程。AsyncQueryHandler是一個抽象類,集成自Handler,經過封裝ContentResolver、HandlerThread、AsyncQueryHandler等實現對ContentProvider的異步操做。ide

AsyncQueryHandler封裝了四個方法操做ContentProvider,對應CRUD以下:函數

public final void startDelete(int token, Object cookie, Uri uri,String selection, String[] selectionArgs);複製代碼
public final void startInsert(int token, Object cookie, Uri uri,ContentValues initialValues);複製代碼
public void startQuery(int token, Object cookie, Uri uri,
        String[] projection, String selection, String[] selectionArgs,
        String orderBy);複製代碼
public final void startUpdate(int token, Object cookie, Uri uri,
        ContentValues values, String selection, String[] selectionArgs);複製代碼

AsyncQueryHandler的子類能夠根據實際需求實現下面的回調函數,對應上面操做的CRUD操做的返回結果。

/**
 * Called when an asynchronous query is completed.
 *
 * @param token the token to identify the query, passed in from
 *            {@link #startQuery}.
 * @param cookie the cookie object passed in from {@link #startQuery}.
 * @param cursor The cursor holding the results from the query.
 */
protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
    // Empty
}

/**
 * Called when an asynchronous insert is completed.
 *
 * @param token the token to identify the query, passed in from
 *        {@link #startInsert}.
 * @param cookie the cookie object that's passed in from * {@link #startInsert}. * @param uri the uri returned from the insert operation. */ protected void onInsertComplete(int token, Object cookie, Uri uri) { // Empty } /** * Called when an asynchronous update is completed. * * @param token the token to identify the query, passed in from * {@link #startUpdate}. * @param cookie the cookie object that's passed in from
 *        {@link #startUpdate}.
 * @param result the result returned from the update operation
 */
protected void onUpdateComplete(int token, Object cookie, int result) {
    // Empty
}

/**
 * Called when an asynchronous delete is completed.
 *
 * @param token the token to identify the query, passed in from
 *        {@link #startDelete}.
 * @param cookie the cookie object that's passed in from * {@link #startDelete}. * @param result the result returned from the delete operation */ protected void onDeleteComplete(int token, Object cookie, int result) { // Empty }複製代碼

5、IntentService

Service的各個生命週期函數是運行在主線程,所以它自己並非一個異步處理技術。爲了可以在Service中實如今子線程中處理耗時任務,Android引入了一個Service的子類:IntentService。IntentService具備Service同樣的生命週期,同時也提供了在後臺線程中處理異步任務的機制。與HandlerThread相似,IntentService也是在一個後臺線程中順序執行全部的任務,咱們經過給Context.startService傳遞一個Intent類型的參數能夠啓動IntentService的異步執行,若是此時IntentService正在運行中,那麼這個新的Intent將會進入隊列進行排隊,直到後臺線程處理完隊列前面的任務;若是此時IntentService沒有在運行,那麼將會啓動一個新的IntentService,當後臺線程隊列中全部任務處理完成以後,IntentService將會結束它的生命週期,所以IntentService不須要開發者手動結束。

IntentService自己是一個抽象類,所以,使用前須要繼承它並實現onHandlerIntent方法,在這個方法中實現具體的後臺處理業務邏輯,同時在子類的構造方法中須要調用super(String name)傳入子類的名字,以下:

public class SimpleIntentService extends IntentService {

     
    public SimpleIntentService() {
        super(SimpleIntentService.class.getName());
        setIntentRedelivery(true);

    }

    @Override
    protected void onHandleIntent(Intent intent) {
        //該方法在後臺調用
    }
}複製代碼

上面代碼中的setIntentRedelivery方法若是設置爲true,那麼IntentService的onStartCOmmand方法將會返回START_REDELIVER_INTENT。這時,若是onHandlerIntent方法返回以前進程死掉了,那麼進程將會從新啓動,intent將會從新投遞。

固然,相似Service,不要忘記在AndroidManifest.xml文件中註冊SimpleIntentService 。

<service android:name=".SimpleIntentService" />複製代碼

經過查看IntentService 的源碼,咱們能夠發現事實上IntentService 是經過HandlerThread來實現後臺任務的處理的,代碼邏輯很簡單:

public abstract class IntentService extends Service {
    private volatile Looper mServiceLooper;
    private volatile ServiceHandler mServiceHandler;
    private String mName;
    private boolean mRedelivery;

    private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            onHandleIntent((Intent)msg.obj);
            stopSelf(msg.arg1);
        }
    }

     
    public IntentService(String name) {
        super();
        mName = name;
    }

   
    public void setIntentRedelivery(boolean enabled) {
        mRedelivery = enabled;
    }

    @Override
    public void onCreate() {
         

        super.onCreate();
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();

        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }

    @Override
    public void onStart(Intent intent, int startId) {
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
    }

    
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        onStart(intent, startId);
        return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
    }

    @Override
    public void onDestroy() {
        mServiceLooper.quit();
    }

    
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

     
    @WorkerThread
    protected abstract void onHandleIntent(Intent intent);
}複製代碼

6、Executor Framework

建立和銷燬對象是存在開銷的,在應用中頻繁出現線程的建立和銷燬,那麼會影響到應用的性能,使用Executor框架能夠經過線程池機制解決這個問題,改善應用的體驗。Executor框架爲開發者提供了以下:

  • 建立工做線程池,同時經過隊列來控制可以在這些線程執行的任務的個數。
  • 檢測致使線程意外終止的錯誤。
  • 等待線程執行完成並獲取執行結果。
  • 批量執行線程,並經過固定的順序獲取執行結構。
  • 在合適的時機啓動後臺線程,從而保證線程執行結果能夠很快反饋給用戶。

Executor框架的基礎是一個名爲Executor的接口定義,Executor的主要目的是分離任務的建立和它的執行,最終實現上述功能點。

public interface Executor {

    /**
     * Executes the given command at some time in the future.  The command
     * may execute in a new thread, in a pooled thread, or in the calling
     * thread, at the discretion of the {@code Executor} implementation.
     *
     * @param command the runnable task
     * @throws RejectedExecutionException if this task cannot be
     * accepted for execution
     * @throws NullPointerException if command is null
     */
    void execute(Runnable command);
}複製代碼

開發者經過實現Executor接口並重寫execute方法從而實現本身的Executor類,最簡單的是直接在這個方法中建立一個線程來執行Runnable。

public class SimpleExecutor implements Executor {
    @Override
    public void execute(Runnable command) {
        new Thread(command).start();
    }
}複製代碼

線程池是任務隊列和工做線程的集合,這二者組合起來實現生產者消費者模式。Executor框架爲開發者提供了預約義的線程池實現。

  • 固定大小的線程池:
    Executors.newFixedThreadPool(3);複製代碼
  • 可變大小的線程池:
    Executors.newCachedThreadPool();複製代碼

當有新任務須要執行時,線程池會建立新的線程來處理它,空閒的線程池會等待60秒來執行新任務,當沒有任務可執行時就自動銷燬,所以可變大小線程池會根據任務隊列的大小而變化。

  • 單個線程的線程池:
    Executors.newSingleThreadExecutor();複製代碼

這個線程池中永遠只有一個線程來串行執行任務隊列中的任務。

預約義的線程池都是基於ThreadPoolExecutor類之上構建的,而經過ThreadPoolExecutor開發者能夠自定義線程池的一些行爲,咱們主要來看看這個類的構造函數:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), defaultHandler);
}複製代碼
  • corePoolSize:核心線程數,核心線程會一直在於線程池中,即便當前沒有任務須要處理;當線程數小於核心線程數時,即便當前有空閒的線程,線程池也會優先建立新的線程來處理任務。
  • maximumPoolSize:最大線程數,當線程數大於核心線程數,且任務隊列已經滿了,這時線程池就會建立新的線程,直到線程數量達到最大線程數爲止。
  • keepAliveTime:線程的空閒存活時間,當線程的空閒時間超過這個之時,線程會被撤毀,直到線程數等於核心線程數。
  • unit:keepAliveTime的單位,可選的有TimeUnit 類中的
    NANOSECONDS,//微秒
    MICROSECONDS,//毫秒
    MILLISECONDS,// 毫微秒
    SECONDS // 秒複製代碼
  • workQueue:線程池全部使用的任務緩衝隊列。

7、AsyncTask

AsyncTask是在Executor框架基礎上進行的封裝,它實現將耗時任務移動到工做線程中執行,同時提供方便的接口實現工做線程和主線程的通訊,使用AsyncTask通常會用到以下:

public class FullTask extends AsyncTask<String,String,String> {

    @Override
    protected void onPreExecute() {
        super.onPreExecute();
        //主線程執行
    }

    @Override
    protected String doInBackground(String... params) {
        return null;
        //子線程執行
    }

    @Override
    protected void onProgressUpdate(String... values) {
        super.onProgressUpdate(values);
        //主線程執行
    }

    @Override
    protected void onPostExecute(String s) {
        super.onPostExecute(s);
        //主線程執行
    }

    @Override
    protected void onCancelled() {
        super.onCancelled();
        //主線程執行
    }

}複製代碼

一個應用中使用的全部AsyncTask實例會共享全局的屬性,也就是說若是AsnycTask中的任務是串行執行,那麼應用中全部的AsyncTask都會進行排隊,只有等前面的任務執行完成以後,纔會接着執行下一個AsnycTask中的任務,在executeOnExecutor(AsyncTask.SERIAL_EXECUTOR)或者API大於13的系統上面執行execute()方法,都會是這個效果;若是AsyncTask是異步執行,那麼在四核的CPU系統上,最多隻有五個任務能夠同時進行,其餘任務須要在隊列中排隊,等待空閒的線程。之因此會出現這種狀況是因爲AsyncTask中的ThreadPoolExecutor指定核心線程數是系統CPU核數+1,以下:

public abstract class AsyncTask<Params, Progress, Result> {
    private static final String LOG_TAG = "AsyncTask";

    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
    private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
    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());
        }
    };

    private static final BlockingQueue<Runnable> sPoolWorkQueue =
            new LinkedBlockingQueue<Runnable>(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);
}複製代碼

8、Loader

Loader是Android3.0開始引入的一個異步數據加載框架,它使得在Activity或者Fragment中異步加載數據變得簡單,同時它在數據源發生變化時,可以及時發出消息通知。Loader框架涉及的API以下:

  • Loader:加載器框架的基類,封裝了實現異步數據加載的接口,當一個加載器被激活後,它就會開始監聽數據源並在數據發生改變時發送新的結果。
  • AsyncTaskLoader:Loader的子類,它是基於AsyncTask實現的異步數據加載,它是一個抽象類,子類必須實現loadInBackground方法,在其中進行具體的數據加載操做。
  • CursorLoader:AsyncTaskLoader的子類,封裝了對ContentResolver的query操做,實現從ContentProvider中查詢數據的功能。
  • LoaderManager:抽象類,Activity和Fragment默認都會關聯一個LoaderManager的對象,開發者只須要經過getLoaderManager便可獲取。LoaderManager是用來管理一個或者多個加載器對象的。
  • LoaderManager.LaoderCallbacks:LoaderManager的回調接口,有如下三個方法
    • onCreateLoader():初始化並返回一個新的Loader實例。
    • onLoadFinished():當一個加載器完成加載過程以後會回調這個方法。
    • onLoaderReset():當一個加載器被重置而且數據無效時會回調這個方法。
public class ContactActivity extends ListActivity implements LoaderManager.LoaderCallbacks<Cursor> {

    private static final int CONTACT_NAME_LOADER_ID = 0;


    static final String[] CONTACTS_SUMMARY_PROJECTION = new String[]{ContactsContract.Contacts._ID, ContactsContract.Contacts.DISPLAY_NAME};

    SimpleCursorAdapter mAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        initAdapter();
        //經過LoaderManger初始化Loader,這會回調到onCreateLoader
        getLoaderManager().initLoader(CONTACT_NAME_LOADER_ID, null, this);

    }

    private void initAdapter() {
        mAdapter = new SimpleCursorAdapter(this, android.R.layout.simple_list_item_1, null, new String[]{ContactsContract.Contacts.DISPLAY_NAME}, new int[]{android.R.id.text1}, 0);
        setListAdapter(mAdapter);
    }

    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        //實際建立Loader的地方  此處使用CursorLoader
        return new CursorLoader(this, ContactsContract.Contacts.CONTENT_URI, CONTACTS_SUMMARY_PROJECTION, null, null, ContactsContract.Contacts.DISPLAY_NAME + " ASC ");
    }
    
    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
        //後臺線程中加載完數據後,回調這個方法將數據傳遞給主線程
        mAdapter.swapCursor(data);
    }

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
        //Loader 被重置後的回調,在這裏能夠從新刷新頁面數據
        mAdapter.swapCursor(null);
    }
}複製代碼

9、總結

根據以上列出的異步處理技術,使用的時候須要根據如下結果因素:

  • 儘可能使用更少的系統資源,例如cpu和內存等。
  • 爲應用提供更好的性能和響應度。
  • 實現和使用起來不復雜。
  • 寫出來的代碼是否符合好的設計,是否易於理解和維護。
相關文章
相關標籤/搜索