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

1008711發表於2017-05-02


一、前言

在應用的開發中我們正確處理好主執行緒和子執行緒之間的關係,耗時的操作都放到子執行緒中處理,避免阻塞主執行緒,導致ANR。非同步處理技術是提高應用效能,解決主執行緒和子執行緒之間通訊問題的關鍵。

首先看一個非同步技術鏈:

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


二、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,在應用層可以分為三種型別執行緒。

  • 主執行緒:主執行緒也稱為UI執行緒,隨著應用啟動而啟動,主執行緒用來執行Android元件,同時重新整理螢幕上的UI元素。Android系統如果檢測到非主執行緒更新UI元件,那麼就會丟擲CalledFromWrongThreadException異常,只有主執行緒才能操作UI,是因為Android的UI工具包不是執行緒安全的。主執行緒中建立的Handler會順序執行接受到的訊息,包括從其他執行緒傳送的訊息。因此,如果訊息佇列中前面的訊息沒有很快執行完,那麼它可能會阻塞佇列中的其他訊息的及時處理。
  • Binder執行緒:Binder執行緒用於不通過程式之間執行緒的通訊,每個程式都維護了一個執行緒池,用來處理其他程式中執行緒傳送的訊息,這些程式包括系統服務、Intents、ContentProviders和Service等。在大部分情況下,應用不需要關心Binder執行緒,因為系統會優先將請求轉換為使用主執行緒。一個典型的需要使用Binder執行緒的場景是應用提供一個給其他程式通過AIDL介面繫結的Service。
  • 後臺執行緒:在應用中顯式建立的執行緒都是後臺執行緒,也就是當剛建立出來時,這些執行緒的執行體是空的,需要手動新增任務。在Linux系統層面,主執行緒和後臺執行緒是一樣的。在Android框架中,通過WindowManager賦予了主執行緒只能處理UI更新以及後臺執行緒不能直接操作UI的限制。

三、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();

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例項,程式碼如下:

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

}複製程式碼

四、AsyncQueryHandler

AsyncQueryHandler是用於在ContentProvider上面執行非同步的CRUD操作的工具類,CRUD操作會被放到一個單獨的子執行緒中執行,當操作結束獲取到結果後,將通過訊息的方式傳遞給呼叫AsyncQueryHandler的執行緒,通常就是主執行緒。AsyncQueryHandler是一個抽象類,整合自Handler,通過封裝ContentResolver、HandlerThread、AsyncQueryHandler等實現對ContentProvider的非同步操作。

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
}複製程式碼

五、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);
}複製程式碼

六、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:執行緒池所有使用的任務緩衝佇列。

七、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);
}複製程式碼

八、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);
    }
}複製程式碼

九、總結

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

  • 儘量使用更少的系統資源,例如cpu和記憶體等。
  • 為應用提供更好的效能和響應度。
  • 實現和使用起來不復雜。
  • 寫出來的程式碼是否符合好的設計,是否易於理解和維護。


相關文章