Handler原理及基本概念
- Message 意為消息,發送到Handler進行處理的對象,攜帶描述信息和任意數據。
- MessageQueue 意為消息隊列,Message的集合。
- Looper 有着一個很難聽的中文名字,消息泵,用來從MessageQueue中抽取Message,發送給Handler進行處理。
- Handler 處理Looper抽取出來的Message。
在如下操作中都是基於UI主線程,在異步任務中使用Handler機制更新UI必須用new Handler();
來初始化。
// 默認使用UI主線程的Looper
Handler mHandler = new Handler();
mHandler.post(new Runnable(){});
Thread創建與銷毀
在Android開發中經常會使用到線程,一想到線程,很多同學就立即使用
new Thread(){...}.start();
這樣的方式。這樣如果在一個Activity中多次調用上面的代碼,那么將創建多個匿名線程,程序運行的越久可能會越來越慢。因此,需要一個Handler來啟動一個線程,以及刪除一個線程。
保證線程不會重復的創建。
使用HandlerThread和Handler配合實現異步后台任務
特點:
- 由2個Handler和1個HandlerThread來實現
- 后台線程串行執行
代碼示例:
// UI線程的Handler
Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
// 處理UI更新
};
HandlerThread mBackThread = new HandlerThread("mybackthread");
mBackThread.start();
// 后台線程的Handler
Handler mBackHandler = new Handler(mBackThread.getLooper());
mBackHandler.post(new Runnable() {
@Override
public void run() {
// 后台線程執行耗時操作,異步
...
// mHandler發消息,回到主線程更新UI
mHandler.sendMessage(msg);
}
});
注意:mBackHandler
的初始化必須在mBackThread.start();
之后,否則拿不到這個線程的looper。
這種模式通過mBackHandler.post(new Runnable() {})
來實現后台異步任務執行,所有后台任務都是通過HandlerThread
這個線程執行的,但是HandlerThread
是串行執行任務的,也就是每次post
后進入隊列排隊執行。
HandlerThread
的退出:
@Override
protected void onDestroy() {
super.onDestroy();
if(mBackThread != null){
mBackThread.quitSafely();
try {
mBackThread.join();
mBackThread = null;
mBackHandler = null;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
AsyncTask實現異步任務執行
查看源碼AsyncTask只是對Thread和Handler的一個封裝。
基本概念
- 3個泛型參數
AsyncTask <Params, Progress, Result>
Params
: 指定的是我們傳遞給異步任務執行時的參數的類型
Progress
: 指定的是我們的異步任務在執行的時候將執行的進度返回給UI線程的參數的類型
Result
: 指定的是異步任務執行完后返回給UI線程的結果的類型
我們在定義一個類繼承AsyncTask類的時候,必須指定好這三個泛型的類型,如果都不指定的話,則都將其寫成Void - 4個執行步驟
onPreExecute()
:UI Thread當中執行,這個方法是在執行異步任務之前的時候執行,我們可以在異步任務執行前做UI提示
doInBackground(Params... params)
:這個方法就是來處理異步任務的方法,執行耗時操作。這個方法也是必須要實現的抽象方法。
onProgressUpdate(Progess... values)
:UI Thread當中執行,用來更新進度條等
onPostExecute(Result... result)
:UI Thread當中執行,當異步任務執行完之后,將doInBackground
結果返回給這個方法來更新UI - 2種執行方式
后台線程可以設置為串行或者並行執行
串行:execute(Params... params)
並行:executeOnExecutor(Executor exec, Params... params)
注意:各SDK版本execute
默認執行方式不統一,1.5中順序執行,1.6到 2.3中並行執行,3.0以后又改回串行執行,並添加並行執行接口executeOnExecutor
注意事項
- 必須在UI線程中加載和創建,以及調用
execute
- 不能做特別耗時的操作,建議只幾秒內的異步任務
- 一個
AsyncTask
對象只能被執行一次,即只能調用一次execute
,否則會拋出異常報錯
Caused by: java.lang.IllegalStateException: Cannot execute task: the task has already been executed (a task can be executed only once)
- 不能在程序中主動調用4個步驟
代碼示例
// 初始化AsyncTask及執行
protected void function() {
...
// 串行執行,識別一張bitmap,每次執行前都需要重新new一個對象
mClassifierAsyncTask = new ClassifierAsyncTask();
mClassifierAsyncTask.execute(bitmap);
}
// 自定義AsyncTask任務類,實現doInBackground
private ClassifierAsyncTask mClassifierAsyncTask;
private class ClassifierAsyncTask extends AsyncTask<Bitmap , Void, String >{
@Override
protected void onPreExecute() {
super.onPreExecute();
mTvResult.setText(getString(R.string.classifying));
}
@Override
protected String doInBackground(Bitmap... bitmaps) {
if(mMyTfClassifier == null) {
mMyTfClassifier = new MyTfClassifier(MainActivity.this);
}
String result = mMyTfClassifier.recognizeImage(bitmaps[0]);
return result;
}
@Override
protected void onPostExecute(String result) {
super.onPostExecute(result);
mTvResult.setText(result);
}
}
存在的問題
Activity
屏幕旋轉或銷毀時,如果AsyncTask
沒有執行完畢就會存在內存泄露。特別是屏幕旋轉時AsyncTask
沒有執行完畢,會導致屏幕異常。