棒棒糖之——Android中全套異步處理的詳細講解
一、前言
在應用的開發中我們正確處理好主線程和子線程之間的關系,耗時的操作都放到子線程中處理,避免阻塞主線程,導致ANR。異步處理技術是提高應用性能,解決主線程和子線程之間通信問題的關鍵。
首先看一個異步技術鏈:
二、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();
}
}</code></pre>
- 實現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();
}
}</code></pre>
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);
//處理接受的消息
}
};</code></pre>
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;
}</code></pre>
如果具體業務要求在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);
}
}</code></pre>
四、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
}</code></pre>
五、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) {
//該方法在后臺調用
}
}</code></pre>
上面代碼中的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);
}</code></pre>
六、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);
}</code></pre>
開發者通過實現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();
//主線程執行
}
}</code></pre>
一個應用中使用的所有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);
}</code></pre>
八、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():當一個加載器被重置并且數據無效時會回調這個方法。
</li>
</ul>
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);
}
}</code></pre>
九、總結
根據以上列出的異步處理技術,使用的時候需要根據以下結果因素:
- 盡量使用更少的系統資源,例如cpu和內存等。
- 為應用提供更好的性能和響應度。
- 實現和使用起來不復雜。
- 寫出來的代碼是否符合好的設計,是否易于理解和維護。
來自:https://juejin.im/post/5908c7b2ac502e006300dc3b