1. 線程的定義
線程(thread)是操作系統能夠進行運算調度的最小單位。它被包含在進程之中,是進程中的實際運作單位。
一個進程中可以并發多個線程,每條線程并行執行不同的任務。每個線程都會分配一個私有內存區域,該區域主要用于在執行過程中存儲方法、局部變量和參數。一旦線程終止私有內存區將會被銷毀。更多關于進程與線程的區別和聯系請參照上一篇文章關于Android進程的那些事兒,在此不再重復。
2. Android的單線程模式
在Android的系統啟動流程和應用啟動流程這篇文章中有提到,Android應用啟動時,zygote進程fork自身,開啟一個Linux進程和一個主線程(Main Thread),主線程負責UI顯示、更新、控件交互,因此主線程又叫做UI線程。
Android中的單線程模式必須遵循兩條規則:
不要阻塞 UI 線程
原因:
由于UI繪制和事件分發采用單線程模式,當UI線程中執行耗時操作(網絡訪問或數據庫查詢)時,UI線程會被阻塞,從而出現ANR(application not responding)或者類似NetworkOnMainThreadException之類的錯誤。不要在 UI 線程之外訪問 Android UI 工具包
原因:
UI的顯示、更新和控件交互主要通過Android UI 工具包(Android UI toolkit,包括android.widget和android.view)來實現的,而Android UI toolkit并非線程安全的工具包,如果我們在工作線程中刷新UI的時候,主線程也恰好在刷新UI,就會出現沖突。因此Android不允許在UI線程之外進行UI的相關操作,否則會出現CalledFromWrongThreadException的錯誤。
3. 工作線程和UI線程(主線程)
為了遵循上述單線程模式的兩條規則,我們必須將耗時操作放在工作線程中,將UI操作放在UI線程中,同時有需要兩個線程之間的切換。
3.1 主線程中開啟工作線程
Java中提供了兩種方法:Thread和Runnable
Thread
繼承Thread類覆寫run()然后thread.start()Runnable
實現Runnable接口復寫run()然后New Thread(Runnable).start()。
但是,在Android中這兩種方法是不值得推薦的。最好使用Android自帶的Handler,AsyncTask,IntentService, Loader,Service等方式來實現。下面會詳細說明。
3.2 工作線程中訪問UI線程
下面三個方法可以訪問UI線程:
- Activity.runOnUIThread(Runable)
- View.post(Runable)
- View.postDelayed(Runable,long)
但是如果主進程和工作線程之間需要更復雜的交互和操作,上面的方法就無能為力了,現在輪到Handler, AsyncTask, HandlerThread上場了,下面會展開詳述。
4. 線程間通信
4.1 Handler
由于Android單線程模式下的兩條原則的存在,線程間的通信顯得尤為重要,Handler正是用來解決這個問題的。消息(Message)的傳遞需要Handler,MessageQueue, Looper共同來完成。其中Looper在Handler和MessageQueue之間起到橋梁和紐帶的作用。在此之前,我們有必要了解一下相關概念
4.1.1 相關概念
Message
Message類實現了Parcelable接口,因此它可以包含任意的數據對象,并把它傳遞給Handler。盡管Message的構造方法是public,但是我們還是推薦使用Message.obtain()或者Message.obtainMessage()來得到一個Message實例。-
MessageQueue
MessageQueue(消息隊列)顧名思義,Message組成的一個List,但是Message并不是直接加入到MessageQueue的,而是通過與Looper綁定的Handler來實現的。獲取當前線程的MessageQueue的方法是Looper.MyQueue()。
Looper
Looper類用來操作一個線程的消息循環。用法如下:
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();//創建looper
mHandler = new Handler() {
public void handleMessage(Message msg) {
// process incoming messages here
}
};
Looper.loop();//
}
}
-
Handler
handler跟一個線程和該線程的messagequeue綁定的,當我們新建一個Handler的時候,它就會與當前線程,和線程的消息隊列綁定在一起。Handler負責傳遞Message或者Runnable對象給MessageQueue,并當他們從消息隊列出來的時候執行相關操作。
Handler有兩個用途:
(1)安排message和runnable在將來的某個時刻得到執行
常用實現方法有
post(Runnable)
[postAtTime(Runnable, long)](https://developer.android.com/reference/android/os/Handler.html#postAtTime(java.lang.Runnable, long))
[postDelayed(Runnable, long)](https://developer.android.com/reference/android/os/Handler.html#postDelayed(java.lang.Runnable, long))
sendEmptyMessage(int)
sendMessage(Message)
[sendMessageAtTime(Message, long)](https://developer.android.com/reference/android/os/Handler.html#sendMessageAtTime(android.os.Message, long))
[sendMessageDelayed(Message, long)](https://developer.android.com/reference/android/os/Handler.html#sendMessageDelayed(android.os.Message, long))
其中,Message的處理是通過Handler的 handleMessage(Message)來實現的(需要繼承Handler的基類)。
(2)在其他線程中執行某些操作。
Handler可以在線程間傳遞消息,工作原理如下
4.1.2 工作機制
Handler用來發送和處理Message或者Runnable對象,每個Handler實例都會綁定到一個線程和這個線程的MessageQueue。當創建Handler的時候,他會默認綁定到創建它的線程上,他會給MessageQueue發送message和runnable,在Looper輪訓到該條消息的時候,回調創建該消息的Handler的handlerMessage方法,處理從message queue出來的message和runnable。
具體流程如下圖:
當應用啟動時,新的進程被創建的時候,它的主線程會運行一個message queue來管理上層應用對象(Activity,Broadcast Receiver)和創建的窗口。也可以創建工作線程,然后創建Handler,在工作線程中調用postRunnable或者sendMessage類方法來傳遞消息給主線程。這時message或者Runnable對象將會在handler的message queue中排隊并在它出站時得到執行。
4.2 AsyncTask
4.2.1 使用方法
首先,繼承AsyncTask類,至少重寫doInBackground這個方法。比如
private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
@Override
protected Long doInBackground(URL... urls) {//這個方法必須被重寫
int count = urls.length;
long totalSize = 0;
for (int i = 0; i < count; i++) {
totalSize += Downloader.downloadFile(urls[i]);
publishProgress((int) ((i / (float) count) * 100));
// Escape early if cancel() is called
if (isCancelled()) break;
}
return totalSize;
}
@Override
protected void onProgressUpdate(Integer... progress) {
setProgressPercent(progress[0]);
}
@Override
protected void onPreExecute() {
}
@Override
protected void onPostExecute(Long result) {//doInBackground執行完成之后調用UI線程
showDialog("Downloaded " + result + " bytes");
}
}
然后在主線程中執行:
new DownloadFilesTask().execute(url1, url2, url3);
4.2.2 執行過程
一個異步任務的4步走:
onPreExecute
doInBackground
onProgressUpdate
onPostExecute
從方法名可以理解這四個方法的功能和執行順序,不再贅述。
4.2.3 注意事項
- 上述四個方法不可以手動調用
- 每個task的加載,創建,執行都必須在UI線程中執行。
- 該任務只能執行一次,第二次執行會拋異常
- AsyncTask開啟線程的方法asyncTask.execute()默認是也是開啟一個線程和一個隊列的,不過也可以通過asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, 0)開啟一個含有5個新線程的線程池,也就是說有個5個隊列了,假如說你執行第6個耗時任務時,除非前面5個都還沒執行完,否則任務是不會阻塞的,這樣就可以大大減少耗時任務延遲的可能性。
除了常用的Handler和AsyncTask之外,IntentService和HandlerThread也為線程間的通信提供了極大地便利
4.3 IntentService
有了intentService就可以不用自己啟動和結束線程了,IntentService內部會自動執行線程的開啟和銷毀。
4.3.1 使用步驟
(1)繼承IntentService類,并重寫onHandleIntent()方法,這個方法中,執行耗時操作
public class myIntentService extends IntentService {
public myIntentService() {
super("myIntentService");
// 注意構造函數參數為空,這個字符串就是worker thread的名字
}
@Override
protected void onHandleIntent(Intent intent) {
//根據Intent的不同進行不同的事務處理
String taskName = intent.getExtras().getString("taskName");
switch (taskName) {
case "task1":
Log.i("myIntentService", "do task1");
break;
case "task2":
Log.i("myIntentService", "do task2");
break;
default:
break;
}
}
//--------------------用于打印生命周期--------------------
@Override
public void onCreate() {
Log.i("myIntentService", "onCreate");
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i("myIntentService", "onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
Log.i("myIntentService", "onDestroy");
super.onDestroy();
}
}
然后通過startService(Intent i)啟動服務,Intent 會攜帶相關數據,onHandleIntent會對這些數據進行識別,。
//同一服務只會開啟一個worker thread,在onHandleIntent函數里依次處理intent請求。
Intent i = new Intent("cn.scu.finch");
Bundle bundle = new Bundle();
bundle.putString("taskName", "task1");
i.putExtras(bundle);
startService(i);
4.3.2 執行過程
onCreate--->onStartCommand-->onHandleIntent-->onDestroy
intentservice的oncreate方法中會通過HandlerThread 開啟新線程,創建對應的Looper,和MessageQueue。
通過onStartCommand()傳遞給服務intent被依次插入到工作隊列中。工作隊列又把intent逐個發送給onHandleIntent()。
通過在onHandleIntent((Intent)msg.obj)中調用你的處理程序.
處理完后會自動停止自己的服務Ondestroy
4.3.3 注意事項
- IntentService 會創建一個線程,來處理所有傳給onStartCommand()的Intent請求。
- 對于startService()請求執行onHandleIntent()中的耗時任務,會生成一個消息隊列,每次只有一個Intent傳入onHandleIntent()方法并執行。也就是同一時間只會有一個耗時任務被執行,其他的請求還要在后面排隊, onHandleIntent()方法不會多線程并發執行。
- 當所有startService()請求被執行完成后,IntentService 會自動銷毀,所以不需要自己寫stopSelf()或stopService()來銷毀服務。
- 提供默認的onBind()實現 ,即返回null,不適合綁定的 Service。采用StartService來啟動,即使啟動它的Activity被銷毀也不會影響service的生命周期
- 提供默認的 onStartCommand() 實現,將intent傳入等待隊列中,然后到onHandleIntent()的實現。
- Service等四大組件默認是運行在主線程上的!沒有界面不代表不再UI線程。
4.4. HandlerThread
HandlerThread是Thread的子類,可以創建一個帶有looper的線程,并創建與這個looper綁定的Handler,代碼如下
HandlerThread mThread = new HandlerThread("message");
mThread.start();
Handler mHandler = new Handler(mThread.getLooper())
{
@Override
public void handleMessage(Message msg)
{
// to do sth
}
};
可以看出,跟Handler非常類似,只是新建HandlerThread時,與該線程綁定的looper也被創建了,所以不再需要我們自己去調用Looper.prepare(),Loop.loop()。直接通過thread.getLooper()就可以獲取到當前線程的looper,類似于根據looper獲取MesssageQueue的方法:Looper.MyQueue()。
5. 總結
由于Android系統的單線程模式,線程之間的通信必不可少,必須掌握Handler,AsyncTask, IntentService, HandlerThread的工作原理并加以熟練使用。其中Handler跟HandlerThread工作原理是一樣的,很多情況下使用HandlerThread可以簡化很多工作。