原文地址: https://blog.nikitaog.me/2014/10/11/android-looper-handler-handlerthread-i/
當有人問你在Android中thread是怎樣使用的?你可能會說“我用過AsyncTask在后臺處理異步任務(wù)”柄粹。很好弦讽,還有其他的嗎涧偷?啊,我還聽說過Handler柏靶,因為我使用Handler來展示toast诈火,或者執(zhí)行延遲任務(wù)魔种。很不錯,但是我在下文將介紹一個新的框架封寞。
從我們熟悉的AsyncTask類開始然评,我相信每個Android開發(fā)者都遇到過它。首先狈究,我們可以從官方文檔得到AsyncTask的一個很好的介紹碗淌。如果你不想浪費精力去學(xué)習如何管理Android threads,那么用AsyncTask來執(zhí)行后臺任務(wù)將是一個不錯的選擇抖锥。比較重要的一點你應(yīng)該了解亿眠,那就是AsyncTask中只有doInBackground()方法運行在非UI線程,其他方法都運行在UI線程磅废。這兒有一個AsyncTask的使用例子:
//MyActivity.java
public class MyActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
MyAsyncTask myTask = new MyAsyncTask(this);
myTask.execute("http://developer.android.com");
}
}
//MyAsyncTask.java
public class MyAsyncTask extends AsyncTask<String, Void, Integer> {
private Context mContext;
public MyAsyncTask(Context context) {
mContext = context.getApplicationContext();
}
@Override
protected void onPreExecute() {
Toast.makeText(mContext, "Let's start!", Toast.LENGTH_LONG).show();
}
@Override
protected Integer doInBackground(String... params) {
HttpURLConnection connection;
try {
connection = (HttpURLConnection) new URL(params[0])
.openConnection();
return connection.getResponseCode();
} catch (IOException e) {
e.printStackTrace();
}
return -1;
}
@Override
protected void onPostExecute(Integer integer) {
if (integer != -1) {
Toast.makeText(mContext, "Got the following code: " + integer,
Toast.LENGTH_LONG).show();
}
}
}
接下來我們在main布局文件中放一個ProgressBar用來運行我們的demo:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center">
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/progressBar"/>
</LinearLayout>
從服務(wù)端得到狀態(tài)響應(yīng)碼是一個耗時操作纳像,在這期間我們不想阻塞UI線程,所以我們在這里使用AsyncTask拯勉。當然使用AsyncTask有很多缺點(如果AsyncTask是一個Activity/Fragment的內(nèi)部類竟趾,那么它持有外部類的一個隱式對象憔购,這種是不好的。為什么呢潭兽?因為Activity/Fragment在configuration change的時候會被銷毀倦始,但是AsyncTask存活的時候,他們?nèi)匀粫槐A粼趦?nèi)存中山卦。如果AsyncTask被聲明為獨立的或者靜態(tài)內(nèi)部類鞋邑,然后使用Context的引用去更新UI,更新UI前我們應(yīng)該檢查Context是否為空)账蓉。AsyncTask是一個一次性任務(wù)枚碗,同一個AsyncTask對象,不能通過重新調(diào)用execute來讓任務(wù)重新運行铸本,應(yīng)該重新創(chuàng)建一個AsyncTask對象肮雨。
有意思的是如果你試圖在doInBackground()方法中顯示一個toast,程序會報下面的錯:
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
at android.os.Handler.<init>(Handler.java:121)
at android.widget.Toast$TN.<init>(Toast.java:322)
at android.widget.Toast.<init>(Toast.java:91)
at android.widget.Toast.makeText(Toast.java:238)
at com.example.testapp.MyActivity$MyAsyncTask.doInBackground(MyActivity.java:25)
at com.example.testapp.MyActivity$MyAsyncTask.doInBackground(MyActivity.java:21)
為什么會有這個錯誤呢箱玷?說的簡單點怨规,因為Toast僅僅能再UI線程中展示。說的準確點锡足,因為Toast能在擁有Looper的線程中展示波丰。你可能會問了“什么是Looper啊舶得?”好了掰烟,是時候刨根問底了。AsyncTask是一個不錯的類沐批,但是如果它不能夠滿足你的需求呢纫骑?如果我們深入研究AsyncTask的實現(xiàn),就會發(fā)現(xiàn)九孩,AsyncTask包含了Handler先馆,Runnable,Thread捻撑。我們都清楚Java中的thread磨隘,但是在Android中,你會發(fā)現(xiàn)繼承Thread的另一個類HandlerThread顾患。HandlerThread和Thread唯一比較大的區(qū)別是番捂,HandlerThread包含Looper,Thread以及MessageQueue江解。Looper對當前線程提供了一個MessageQueue设预。那么什么是MessageQueue呢?MessageQueue是一個包含將要執(zhí)行任務(wù)的隊列犁河。Looper遍歷這個隊列鳖枕,然后發(fā)送Message到對應(yīng)的handler來處理魄梯。任何thread有一個獨一無二的Looper,ThreadLocal幫我們做到了這一點宾符。
**你可能會問了酿秸,如果task都被Handler處理,為什么要設(shè)計這么復(fù)雜魏烫?
至少有兩點優(yōu)勢:
- 正如之前提到的辣苏,當同時好幾個線程工作時,如果我們想要順序依次執(zhí)行哄褒,HandlerThread幫助我們避免了資源競爭稀蟋。
- 當Thread中任務(wù)結(jié)束后,Thread是不能被重用的呐赡,但是因為Looper的存在退客,包含Looper的線程被保存了活動狀態(tài),直到你調(diào)用了quit方法链嘀。所以每次你想運行一個后臺任務(wù)的時候萌狂,你不需要創(chuàng)建一個新的實例對象。**
你也可以自己創(chuàng)建一個帶Looper的Thread怀泊,但是我推薦你使用HandlerThread粥脚。HandlerThread自帶Looper,并且一些初始化的工作都為你做好了包个。那么什么是Handler呢?Handler包含兩個基本功能:post任務(wù)到MessageQueue并且執(zhí)行它們冤留。Handler通過Looper默認與thread關(guān)聯(lián)碧囊,但是你也可以在構(gòu)造函數(shù)中提供Looper,用來關(guān)聯(lián)Handler到其他Thread纤怒。下面來看一個實際的例子糯而,設(shè)想一下我們有一個Activity,我們想post任務(wù)到MessageQueue泊窘,這些任務(wù)被延遲執(zhí)行熄驼。
public class MyActivity extends Activity {
private Handler mUiHandler = new Handler();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Thread myThread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 4; i++) {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (i == 2) {
mUiHandler.post(new Runnable() {
@Override
public void run() {
Toast.makeText(MyActivity.this,
"I am at the middle of background task",
Toast.LENGTH_LONG)
.show();
}
});
}
}
mUiHandler.post(new Runnable() {
@Override
public void run() {
Toast.makeText(MyActivity.this,
"Background task is completed",
Toast.LENGTH_LONG)
.show();
}
});
}
});
myThread.start();
}
}
由于mUiHandler與UI線程相關(guān)聯(lián)(Handler在默認構(gòu)造函數(shù)中,得到UI線程的Looper)烘豹,mUiHandler是一個類成員瓜贾,我們通過匿名內(nèi)部類來使用它,所以可以post任務(wù)到UI線程携悯。在上面的例子中祭芦,我們使用Thread,如果我們要post一個新的任務(wù)憔鬼,我們不能重新使用這個Thread實例龟劲,我們必須創(chuàng)建一個新的Thread對象胃夏。那么有其他辦法嗎?當然昌跌。我們可以使用帶Looper的Thread仰禀。下面有一個簡單的例子,不同的是我們使用了HandlerThread而不是Thread蚕愤,優(yōu)點就是Thread可以被重復(fù)執(zhí)行:
//MyActivity.java
public class MyActivity extends Activity {
private Handler mUiHandler = new Handler();
private MyWorkerThread mWorkerThread;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mWorkerThread = new MyWorkerThread("myWorkerThread");
Runnable task = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 4; i++) {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (i == 2) {
mUiHandler.post(new Runnable() {
@Override
public void run() {
Toast.makeText(MyActivity.this,
"I am at the middle of background task",
Toast.LENGTH_LONG)
.show();
}
});
}
}
mUiHandler.post(new Runnable() {
@Override
public void run() {
Toast.makeText(MyActivity.this,
"Background task is completed",
Toast.LENGTH_LONG)
.show();
}
});
}
};
mWorkerThread.start();
mWorkerThread.prepareHandler();
mWorkerThread.postTask(task);
mWorkerThread.postTask(task);
}
@Override
protected void onDestroy() {
mWorkerThread.quit();
super.onDestroy();
}
}
//MyWorkerThread.java
public class MyWorkerThread extends HandlerThread {
private Handler mWorkerHandler;
public MyWorkerThread(String name) {
super(name);
}
public void postTask(Runnable task){
mWorkerHandler.post(task);
}
public void prepareHandler(){
mWorkerHandler = new Handler(getLooper());
}
}
在這個例子中答恶,我使用了HandlerThread,因為我不想自己去管理Looper审胸,HandlerThread已經(jīng)幫我們實現(xiàn)了它亥宿。一旦我們開始了HandlerThread,在任何時間我們可以post任務(wù)砂沛,當你想停止HandlerThread的時候別忘了調(diào)用quit方法烫扼。mWorkerHandler通過myWorkerThread的Looper與myWorkerThread相綁定。你不能在HandlerThread構(gòu)造函數(shù)中初始化mWorkerHandler碍庵,因為在thread不是活動狀態(tài)的時候getLooper會返回null映企。你或許會發(fā)現(xiàn)下面這種handler的初始化方式:
private class MyWorkerThread extends HandlerThread {
private Handler mWorkerHandler;
public MyWorkerThread(String name) {
super(name);
}
@Override
protected void onLooperPrepared() {
mWorkerHandler = new Handler(getLooper());
}
public void postTask(Runnable task){
mWorkerHandler.post(task);
}
}
有時它會起作用,有時在調(diào)用postTask的時候静浴,會出現(xiàn)NullPointerException堰氓,mWorkerHandler為null。臥槽苹享?
[站外圖片上傳中...(image-19d4aa-1531388491240)]
為什么呢双絮?那是因為在新線程創(chuàng)建的時候需要native調(diào)用。如果我們看onLooperPrepared被調(diào)用的代碼得问,就會在HandlerThread中發(fā)現(xiàn)如下的代碼片段:
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
僅僅在新線程被創(chuàng)建并且啟動的情況下才會run方法才會被調(diào)用囤攀。出現(xiàn)NullPointerException異常的原因在于,run()方法在postTask()之后調(diào)用宫纬。所以就是被兩個線程(主線程和后臺線程)之間的競爭條件害慘了焚挠。