本篇的主要內容是Android的線程和線程池:
- 概述
- 線程形態
- AsyncTask
- HandlerThread
- IntentService
- 線程池
一.概述
1.含義:線程是CPU調度的最小單元。
2.特點:線程是一種受限的系統資源。即線程不可無限制的產生且線程的創建和銷毀都有一定的開銷。
Q:如何避免頻繁創建和銷毀線程所帶來的系統開銷?
A:采用線程池,池中會緩存一定數量的線程,進而達到效果。
3.分類:
- 按用途可分為兩類:
- 主線程:一般一個進程只有一個主線程,主要處理界面交互相關的邏輯。
- 子線程:除主線程之外都是子線程,主要用于執行耗時操作。
- 按形態可分為三類:
- AsyncTask:底層封裝了線程池和Handler,便于執行后臺任務以及在主線程中進行UI操作。
- HandlerThread:一種具有消息循環的線程,其內部可使用Handler。
- IntentService:是一種異步、會自動停止的服務,內部采用HandlerThread。
二.線程形態
對于主線程和子線程相信已經非常熟悉了,現在主要學習以下三種形態的線程:
1.AsyncTask
a.AsyncTask:一種輕量級的異步任務類。
在Android中實現異步任務機制有兩種方式:Handler和AsyncTask。
- Handler機制存在的問題:代碼相對臃腫;多任務同時執行時不易精確控制線程。
- 引入AsyncTask的好處:創建異步任務更簡單,直接繼承它可方便實現后臺異步任務的執行和進度的回調更新UI,而無需編寫任務線程和Handler實例就能完成相同的任務。
b.AsyncTask是抽象的泛型類,其組成成員有:
- 三個泛型參數:
-
Params
:表示執行AsyncTask需要傳入的參數,可用于在后臺任務中使用; -
Progress
:表示后臺任務執行的進度; -
Result
: 表示后臺任務的返回結果的類型; - 若沒有傳遞具體的參數,這三個泛型參數都可使用void。如:
-
//含義:在執行AsyncTask時不需要傳入參數給后臺任務、使用整型數據來作為進度顯示單位,最后使用布爾型數據來反饋執行結果
public abstract class AsyncTask<Void, Integer, Boolean>
- 五個核心方法:
-
onPreExecute()
:- 運行在:主線程
- 調用時刻:在異步任務執行之前被調用
- 作用:可用于進行一些界面上的初始化操作
-
doInBackground(Params…params)
:- 運行在:子線程
- 作用:可用于處理所有的耗時任務。若需要更新UI需調用
publishProgress(Progress...)
方法 - 注意:任務一旦完成就通過return語句將任務的執行結果返回,若Result被指定為void,就可不返回執行結果
-
onProgressUpdate(Progress…values)
:- 運行在:主線程
- 調用時刻:在后臺任務中調用
publishProgress(Progress...)
之后該方法會被調用 - 作用:可利用方法中攜帶的參數如Progress來對UI進行相應地更新
-
onPostExecute(Result result)
:- 運行在:主線程
- 調用時刻:在異步任務執行完畢并通過return語句返回時被調用
- 作用:可利用方法中返回的數據來進行一些UI操作
-
onCancelled()
:- 運行在:主線程
- 調用時刻:當異步任務被取消時被調用
- 作用:可用于做界面取消的更新
- 注意:
- 不要直接調用onPreExecute()、doInBackground()、onProgressUpdate()、onPostExecute)和onCancelled()方法
- AsyncTask對象必須在主線程創建
-
- 開始和結束異步任務的方法:
-
execute(Params...params)
- 必須在主線程中調用
- 作用:表示開始一個異步任務
- 注意:一個異步對象只能調用一次execute()方法
-
cancel(booleanmayInterruptIfRunning)
- 必須在主線程中調用
- 作用:表示停止一個異步任務
-
比如自定義一個AsyncTask,來模擬一個下載任務:
class DownloadTask extends AsyncTask<Void, Integer, Boolean> {
@Override//初始化一個ProgressDialog
protected void onPreExecute() {
progressDialog.show();
}
@Override//具體的下載邏輯
protected Boolean doInBackground(Void... params) {
try {
while (true) {
int downloadPercent = doDownload();
publishProgress(downloadPercent);
if (downloadPercent >= 100) {
break;
}
}
} catch (Exception e) {
return false;
}
return true;
}
@Override//顯示當前的下載進度
protected void onProgressUpdate(Integer... values) {
progressDialog.setMessage("當前下載進度:" + values[0] + "%");
}
@Override//提示任務的執行結果
protected void onPostExecute(Boolean result) {
progressDialog.dismiss();
if (result) {
Toast.makeText(context, "下載成功", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(context, "下載失敗", Toast.LENGTH_SHORT).show();
}
}
}
任務的啟動和停止只需要以下幾行代碼:
// 開始任務
DownloadTask mDownloadTask = new DownloadTask();
mDownloadTask .execute();
// 停止任務
mDownloadTask .cancel(true);
c.工作原理
- 內部有一個靜態的Handler對象即InternalHandler:
- 作用:將執行環境從線程池切換到主線程;通過它來發送任務執行的進度以及執行結束等消息。
- 注意:必須在主線程中創建
- 內部有兩個線程池:
- SerialExecutor:用于任務的排隊,默認是串行的線程池
- THREAD_POOL_EXECUTOR:用于真正執行任務。
- 排隊執行過程:
- 把參數Params封裝為FutureTask對象,相當于Runnable;
- 調用
SerialExecutor.execute()
將FutureTask插入到任務隊列tasks; - 若沒有正在活動的AsyncTask任務,則就會執行下一個AsyncTask任務。執行完畢后會繼續執行其他任務直到所有任務都完成。即默認使用串行方式執行任務。
注意:AsyncTask不適用于進行特別耗時的后臺任務,而是建議用線程池。
推薦閱讀:Android AsyncTask完全解析,帶你從源碼的角度徹底理解、AsyncTask原理及不足
2.HandlerThread
a.HandlerThread是一個線程類,它繼承自Thread
與普通Thread的區別:具有消息循環的效果。原理:
- 內部
HandlerThread.run()
方法中有Looper,通過Looper.prepare()
來創建消息隊列,并通過Looper.loop()
來開啟消息循環。
b實現方法
- 實例化一個HandlerThread對象,參數是該線程的名稱;
- 通過
HandlerThread.start()
開啟線程; - 實例化一個Handler并傳入HandlerThread中的looper對象,使得與HandlerThread綁定;
- 利用Handler即可執行異步任務;
- 當不需要HandlerThread時,通過
HandlerThread.quit()
/quitSafely()
方法來終止線程的執行。
private HandlerThread myHandlerThread ;
private Handler handler ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//實例化HandlerThread
myHandlerThread = new HandlerThread("myHandler") ;
//開啟HandlerThread
myHandlerThread.start();
//將Handler對象與HandlerThread線程綁定
handler =new Handler(myHandlerThread.getLooper()){
@Override
publicvoid handleMessage(Message msg) {
super.handleMessage(msg);
// 這里接收Handler發來的消息,運行在handler_thread線程中
//TODO...
}
};
//在主線程給Handler發送消息
handler.sendEmptyMessage(1) ;
new Thread(new Runnable() {
@Override
publicvoid run() {
//在子線程給Handler發送數據
handler.sendEmptyMessage(2) ;
}
}).start();
}
@Override
protected void onDestroy() {
super.onDestroy();
//終止HandlerThread運行
myHandlerThread.quit() ;
}
補充實例:Android 多線程之HandlerThread 完全詳解
c.用途:
- 進行串行異步通信
- 構造IntentService
在之前學習Handler機制時知道在子線程使用Handler的方法,其實HandlerThread的出現使得這一過程變得更加簡便,更多解析見淺析HandlerThread
3.IntentService
a.IntentService是一個繼承自Service的抽象類
b.優點:
- 相比于線程:由于是服務,優先級比線程高,更不容易被系統殺死。因此較適合執行一些高優先級的后臺任務。
- 相比于普通Service:可自動創建子線程來執行任務,且任務執行完畢后自動退出。
c.IntentService內部封裝了HandlerThread和Handler,工作原理:
- 在
IntentService.onCreate()
里創建一個Handle對象即HandlerThread,利用其內部的Looper會實例化一個ServiceHandler對象; - 任務請求的Intent會被封裝到Message并通過ServiceHandler發送給Looper的MessageQueue,最終在HandlerThread中執行;
- 在
ServiceHandler.handleMessage()
中會調用IntentService.onHandleIntent()
,可在該方法中處理后臺任務的邏輯。
圖片來源:Android IntentService的使用和源碼分析
d.使用方法:
- 新建類并繼承IntentService,重寫
onHandleIntent()
方法,該方法:- 運行在:子線程,因此可以去處理一些耗時操作。
- 作用:從Intent參數中區分具體的任務并執行這些任務
- 在配置文件中進行注冊。
- 在活動中利用Intent實現IntentService的啟動:
Intent intent = new Intent(this, MyService.class);
intent.putExtra("xxx",xxx);
startService(intent);//啟動服務
注意:無需手動停止服務,
onHandleIntent()
執行結束之后,IntentService會自動停止。
推薦閱讀:Android多線程:IntentService用法&源碼分析
三.線程池
1.優點
- 重用線程池中的線程,避免線程的創建和銷毀帶來的性能消耗;
- 有效控制線程池的最大并發數,避免大量的線程之間因互相搶占系統資源而導致阻塞現象;
- 進行線程管理,提供定時/循環間隔執行等功能。
- 線程池的概念來源:Java中的Executor,它是一個接口。
- 線程池的真正實現:ThreadPoolExecutor,提供一系列參數來配置線程池。
//構造參數
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
-
corePoolSize:核心線程數
- 默認情況下,核心線程會在線程中一直存活。
- 當設置ThreadPoolExecutor的allowCoreThreadTimeOut屬性為
- true:表示核心線程閑置超過超時時長,會被回收;
- false:表示核心線程不會被回收,會在線程池中一直存活。
-
maximumPoolSize:最大線程數
- 當活動線程數達到這個數值后,后續的任務將會被阻塞。
-
keepAliveTime:非核心線程超時時間
- 超過這個時長,閑置的非核心線程就會被回收。
- 當設置ThreadPoolExecutor的allowCoreThreadTimeTout屬性為true時,keepAliveTime對核心線程同樣有效。
-
unit:用于指定keepAliveTime參數的時間單位
- 單位有:TimeUnit.MILLISECONDS、TimeUnit.SECONDS、TimeUnit.MINUTES等;
-
workQueue:任務隊列
- 通過線程池的
execute()
方法提交的Runnable對象會存儲在這個參數中。
- 通過線程池的
-
threadFactory:線程工廠,可創建新線程
- 是個接口,只有一個方法
Thread newThread(Runnable r)
。
- 是個接口,只有一個方法
- handler:在線程池無法執行新任務時進行調度。
實例:線程池的原理及實現
3.ThreadPoolExecutor的默認工作策略:
- 若程池中的線程數量未達到核心線程數,則會直接啟動一個核心線程執行任務。
- 若線程池中的線程數量已達到或者超過核心線程數量,則任務會被插入到任務列表等待執行。
- 若任務無法插入到任務列表中,往往由于任務列表已滿,此時如果
- 線程數量未達到線程池最大線程數,則會啟動一個非核心線程執行任務;
- 線程數量已達到線程池規定的最大值,則拒絕執行此任務,ThreadPoolExecutor會調用RejectedExecutionHandler的rejectedExecution方法來通知調用者。
- 若任務無法插入到任務列表中,往往由于任務列表已滿,此時如果
4.ThreadPoolExecutor線程池的分類:
-
FixThreadPool:
- 含義:線程數量固定的線程池,所有線程都是核心線程,當線程空閑時不會被回收。
- 特點:能快速響應外界請求。
-
CachedThreadPool:
- 含義:線程數量不定的線程池(最大線程數為Integer.MAX_VALUE),只有非核心線程,空閑線程有超時機制,超時回收。
- 特點:適合于執行大量的耗時較少的任務
-
ScheduledThreadPool:
- 含義:核心線程數量固定,非核心線程數量不定。
- 特點:定時任務和固定周期的任務。
-
SingleThreadExecutor:
- 含義:只有一個核心線程,可確保所有的任務都在同一個線程中按順序執行。
- 特點:無需處理線程同步問題。
推薦閱讀:Java四種線程池的使用
希望這篇文章對你有幫助~