Android異步處理技術

《Android 高級進階》讀書筆記
Android中,異步處理技術有很多種,常見的有Thread、AsyncTask、Handler&Looper、Executors等,在實際項目中,我們需要根據具體業務需求進行選擇、一個完整的異步處理技術繼承樹如下:

這裏寫圖片描述

1. Thread

線程是Java語言的一個概念,它是實際執行任務的基本單元,創建線程有兩種方法。

  • 繼承Thread類並重寫run方法,語句如下:
public class MyThread extends Thread {
    @Override
    public void run() {
        // 具體實現邏輯
    }
}

// 需要的地方調用
MyThread myThread = new MyThread();
myThread.start(); // 使用start啓動線程
  • 實現Runnable接口並實現run方法,如下:
public class MyRunnable implements Runnable {
    @Override
    public void run() {
        // 實現具體邏輯
    }
}

// 需要調用的地方
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();

Android應用中各種類型的線程本質上都是基於Linux系統的pthreads,在應用層可以分爲三種類型的線程。

  • 主線程:主線程也稱爲UI線程,隨着應用啓動而啓動,主線程用來運行Android組件,同時刷新屏幕上的UI元素。主線程中創建的Handler會順序執行接收到的消息,包括從其他線程中發送的消息。如果消息隊列中前面的消息沒有很快執行完,那麼它很可能會阻塞隊列中的其他消息的及時處理。
  • Binder線程:Binder線程用於不同進程之間線程的通信,每個進程都維護了一個線程池,用來處理其他進程中線程發送的消息,這些進程包括系統服務,Intents、ContentProviders和Service等。一個典型的需要使用Binder線程的場景是應用提供一個給其他進程通過AIDL接口綁定的Service。
  • 後臺線程:在應用中顯式創建的線程都是後臺線程,也就是當剛創建出來時,這些線程的執行體是空的,需要手動添加任務。在Linux系統層面,主線程和後臺線程是一樣的。在Android框架中,通過WindowManager賦予了主線程只能處理UI更新以及後臺線程不能直接操作UI的限制。

2. HandlerThread

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

    @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();

        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;
        }

        // If the thread has been started, wait until the looper has been created.
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }

如果具體業務要求在HandleThread開始接收消息之前要進行某些初始化操作的話,可以重寫HnadlerThread的onLooperPrepared函數,例如可以在這個函數中創建與HandlerThread關聯的Handler實例,這同時也可以對外隱藏我們的Handler實例

public class MyHandlerThread extends HandlerThread {
    private Handler mHandler;

    public MyHandlerThread(String name) {
        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:
                        // Handler message
                        break;
                    case 2:
                        // Handler message
                        break;
                }
            }
        };
    }

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

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

3. AsyncQueryHandler

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

// 查詢
 public void startQuery(int token, Object cookie, Uri uri,
            String[] projection, String selection, String[] selectionArgs,
            String orderBy) {
    ...
    }   
// 插入
public final void startInsert(int token, Object cookie, Uri uri,
            ContentValues initialValues) {
    ...
    }
// 更新
public final void startUpdate(int token, Object cookie, Uri uri,
            ContentValues values, String selection, String[] selectionArgs) {
    ...
    }
// 刪除
public final void startDelete(int token, Object cookie, Uri uri,
            String selection, String[] selectionArgs) {
    ...
    }

AsyncQueryHandler的子類實現可以根據實際需求實現回調函數

public class MyAsyncQueryHandler extends AsyncQueryHandler {
    public MyAsyncQueryHandler(ContentResolver cr) {
        super(cr);
    }

    @Override
    protected void onDeleteComplete(int token, Object cookie, int result) {
        super.onDeleteComplete(token, cookie, result);
    }

    @Override
    protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
        super.onQueryComplete(token, cookie, cursor);
    }

    @Override
    protected void onInsertComplete(int token, Object cookie, Uri uri) {
        super.onInsertComplete(token, cookie, uri);
    }

    @Override
    protected void onUpdateComplete(int token, Object cookie, int result) {
        super.onUpdateComplete(token, cookie, result);
    }
}

4. IntentService

IntentService和Service一樣的生命週期,同時也提供了在後臺線程中處理異步任務的機制。與HandlerThread類似,IntentService也是在一個後臺線程中順序執行所有的任務。通過Context.startService傳遞一個Intent類型的參數可以啓動IntentService的異步執行。當後臺線程隊列中所有任務處理完成之後,IntentService將會結束它的生命週期,因此IntentService不需要開發者手動結束。
IntentService本身是一個抽象類,因此,使用前需要集成它並實現onHandleIntent方法,在這個方法中實現具體的後臺處理業務邏輯,同時在子類的構造方法中需要調用super(String name)傳入子類的名稱,語句如下:

public class SimpleIntentService extends IntentService {
    /**
     * Creates an IntentService.  Invoked by your subclass's constructor.
     *
     * @param name Used to name the worker thread, important only for debugging.
     */
    public SimpleIntentService(String name) {
        super(SimpleIntentService.class.getName());
        setIntentRedelivery(true);
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        // 這個方法是在後臺線程中調用的
    }
}

setIntentRedelivery()方法設置爲true,那麼IntentService的onStartCommand方法將會返回START_REDELIVER_INTENT。這時,如果onHandleIntent方法返回之前進程死掉了,那麼進程將會重新啓動,intent將會重新投遞。
不要忘記在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);
        }
    }

    /**
     * Creates an IntentService.  Invoked by your subclass's constructor.
     *
     * @param name Used to name the worker thread, important only for debugging.
     */
    public IntentService(String name) {
        super();
        mName = name;
    }

    /**
     * Sets intent redelivery preferences.  Usually called from the constructor
     * with your preferred semantics.
     *
     * <p>If enabled is true,
     * {@link #onStartCommand(Intent, int, int)} will return
     * {@link Service#START_REDELIVER_INTENT}, so if this process dies before
     * {@link #onHandleIntent(Intent)} returns, the process will be restarted
     * and the intent redelivered.  If multiple Intents have been sent, only
     * the most recent one is guaranteed to be redelivered.
     *
     * <p>If enabled is false (the default),
     * {@link #onStartCommand(Intent, int, int)} will return
     * {@link Service#START_NOT_STICKY}, and if the process dies, the Intent
     * dies along with it.
     */
    public void setIntentRedelivery(boolean enabled) {
        mRedelivery = enabled;
    }

    @Override
    public void onCreate() {
        // TODO: It would be nice to have an option to hold a partial wakelock
        // during processing, and to have a static startService(Context, Intent)
        // method that would launch the service & hand off a wakelock.

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

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

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

    /**
     * You should not override this method for your IntentService. Instead,
     * override {@link #onHandleIntent}, which the system calls when the IntentService
     * receives a start request.
     * @see android.app.Service#onStartCommand
     */
    @Override
    public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
        onStart(intent, startId);
        return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
    }
    ...
}

5. Executor Framework

爲了解決頻繁出現線程的創建和銷燬影響應用的性能,使用Java Executor框架可以通過線程池等機制解決這個問題,改善應用體驗。Executor框架爲開發者提供如下功能:

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

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

public interface Executor{
    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(n)創建,其中n表示線程池中線程的個數。
  • 可變大小的線程池:通過Executors.newCachedThreadPool()創建,當有新的任務需要執行時,線程池會增加新的線程來處理它,空閒的線程會等待60秒來執行新任務,當沒有任務時自動銷燬,因此可變大小線程池也會根據任務隊列的大小而變化。
  • 單個線程的線程池:通過Executors.newSingleThreadExecutor()創建,這個線程池中永遠只有一個線程來執行串行執行任務列表中的任務。

預定義的線程池都是基於ThreadPoolExecutor類之上構建的,而通過ThreadPoolExecutor開發者可以自定義線程池的一些行爲,我們主要來看看這個類的構造函數定義。

    // 需要如下幾個參數
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
    }
  • corePoolSize:核心線程數,核心線程會一直存在於線程池中,即使當前沒有任務需要處理;當線程數小於核心線程數時,即使當前有空閒的線程,線程池也會優先創建新的線程來處理任務。
  • maximumPoolSize:最大線程數,當線程數大於核心線程數,且任務隊列已經滿了,這時線程池就會創建新的線程,直到線程數量達到最大線程數爲止。
  • keepAliveTime:線程的空閒存活時間,當線程的空閒時間超過這個時,線程會被銷燬,直到線程數等於核心線程數。
  • unit:keepAliveTime的單位,可選的有TimeUnit類中的NANOSECONDS、MICROSECONDS、MILLISECONDS和SECONDS。
  • workQueue:線程池所使用的任務緩存隊列。

6. AsyncTask

AsyncTask是在Executor框架基礎上進行的封裝,它實現將耗時任務移動到工作線程中執行,同時提供方便的接口實現工作線程和主線程的通信,使用AsyncTask一般會用到如下方法。

public class FullTask extends AsyncTask{

    @Override
    protected void onPreExecute() {
        super.onPreExecute();
    }

    @Override
    protected Object doInBackground(Object[] params) {
        return null;
    }

    @Override
    protected void onProgressUpdate(Object[] values) {
        super.onProgressUpdate(values);
    }

    @Override
    protected void onPostExecute(Object o) {
        super.onPostExecute(o);
    }

    @Override
    protected void onCancelled(Object o) {
        super.onCancelled(o);
    }
}

除了doInBackground方法是在工作線程中執行,其他的都是在主線程中執行。如果使用AsyncTask執行的任務需要並行執行的話,那麼在API Level大於13的版本上建議使用executeOnExecutor代替execute。

7. 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.LoaderCallbacks:LoaderManager的回調接口,主要有如下三個方法
    • onCreateLoader():初始化並返回一個新的Loader實例
    • onLoadFinished():當一個加載器完成加載過程之後會回調這個方法
    • onLoaderReset():當一個加載器被重置並且數據無效時會回調這個方法

一個例子看懂所有:

public class ContactActivity extends ListActivity implements LoaderManager.LoaderCallbacks {
    private static final int CONTACT_NAME_LOADER_ID = 0;

    // 這裏的PROJECTION 只獲取ID和用戶名兩個字段
    static final String[] CONTACTS_SUMMARY_PROJECTION = new String[]{
            ContactsContract.Contacts._ID,
            ContactsContract.Contacts.DISPLAY_NAME
    };

    @Override
    public void onCreate(Bundle savedInstanceState, PersistableBundle persistentState) {
        super.onCreate(savedInstanceState, persistentState);
        initAdapter();

        // 通過LoaderManager 初始化Loader,這會回調到onCreateLoader
        getLoaderManager().initLoader(CONTACT_NAME_LOADER_ID, null, this);
    }

    SimpleCursorAdapter mAdapter;

    private void initAdapter() {
        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 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 loader, Object data) {
        // 後臺線程中加載完數據後,回調這個方法將數據傳遞給主線程
        mAdapter.swapCursor((Cursor) data);
    }

    @Override
    public void onLoaderReset(Loader loader) {
        // Loader 被重置後的回調,在這裏可以重新刷新頁面數據
        mAdapter.swapCursor(null);
    }

}

8. 總結

Android 平臺提供瞭如此多的異步處理技術,我們在進行選擇的時候需要根據具體的業務需求而定,綜合考慮一下幾個因素:

  • 儘量使用更少的系統資源,例如CPU和內存等
  • 爲應用提供更好的性能和響應度
  • 實現和使用起來不復雜
  • 寫出來的代碼是否符合好的設計,是否易於理解和維護
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章