安卓开发之Handler、HandlerThread学习篇
生活随笔
收集整理的這篇文章主要介紹了
安卓开发之Handler、HandlerThread学习篇
小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
安卓開發(fā)之Handler、HandlerThread學(xué)習(xí)心得篇 ? ????????開篇說明:本文采用的都是最基礎(chǔ)最簡單的例子,目的只有一個:希望大家將學(xué)習(xí)的焦點放在Handler的理解和使用上,我不想給出復(fù)雜的案例來分析,切以為容易讓人“誤入歧途”,忽略文章的主題。所以力求用最簡單的例子說明使用原理的機(jī)制。理解和掌握了基本東西,復(fù)雜的東西只需在此基礎(chǔ)上去擴(kuò)展罷了。 其次我是初學(xué)者,所以難免理解的比較膚淺,寫出來跟大家分享分享,如有不正確的地方,望大家多多指正。 ? 基本概念知悉: 1、消息(Message):消息其實就是一個載體,它存儲著一些信息,比如消息的ID號、數(shù)據(jù)等,作為函數(shù)的參數(shù)在各個函數(shù)中進(jìn)行傳遞,以便做出相應(yīng)的處理。 2、消息隊列(Message Queue):消息隊列其實就是一個鏈表,它按照FIFO(先進(jìn)先出的順序)存放著消息。每個線程都對應(yīng)著一個消息隊列。 ????????廢話不多說,直接上例子。個人認(rèn)為掌握基本概念之后,從例子中學(xué)習(xí)是加深理解最為有效的方式,也是最愉悅的方式。 ? 例一、 package com.example.handlerbrightfirst; ? import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.widget.Button; ? public class HandlerBrightFirst extends Activity { Button mybutton = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_handler_bright_first); mybutton = (Button)findViewById(R.id.mybutton); ????????????? ? // 新建一個Handler對象 Handler mHandler = new Handler(); runTest test = new runTest(); ? ? mHandler.post(test); } ? ????? ? // 新建一個類,實現(xiàn)于Runnable接口 class runTest implements Runnable{ ? @Override public void run() { // TODO Auto-generated method stub mybutton.setText(R.string.mybutton); } ? } } ? 案例說明要點: ????????1. 切記誤解此處run方法是在一個新線程中執(zhí)行的。Runnable本身只不過是一個接口,post一個runnable對象也只不過是將runnable對象放到handler所綁定的線程(這里指的即是UI主線程)的消息隊列中而已。 ????? ? 2. 如果想使runnable對象的run方法是在新開啟的線程中執(zhí)行的話,需要用到Thread類的start方法,案例二會介紹。 ????? ? 3. 假如此例中run方法中處理操作的時間過長,比如大于5秒的話,UI界面就會提示強(qiáng)迫關(guān)閉。因為都是在同一個線程中,所以run方法處理時間不能過長。 ? ? 例二、 package com.example.handlerbrightsecond; ? import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.view.Menu; ? public class HandlerBrightSecond extends Activity { ? @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_handler_bright_second); final Handler myHandler = new Handler(); // 運(yùn)行于主線程的runnable對象 class UpdateUI implements Runnable{ ? @Override public void run() { // TODO Auto-generated method stub System.out.println("222222222222222222"); } ? } ? final UpdateUI updateUI = new UpdateUI(); ? // 子線程啟動運(yùn)行的runnabel對象 class runner implements Runnable{ ? @Override public void run() { // TODO Auto-generated method stub System.out.println("0000000000000000000000"); myHandler.post(updateUI); System.out.println("111111111111111111111"); } } ? runner mywork = new runner(); Thread myThread = new Thread(mywork, "mywork"); // 啟動一個子線程,運(yùn)行mywork對象的run方法 myThread.start(); } } ? ????????上例中輸出結(jié)果為:0000000000000000000000000 ?????????????????????????????????????1111111111111111111111111
????????????????????????? ? ?????????2222222222222222222222222 ????????此例主要用來跟案例一進(jìn)行比較分析。不同點在于案例二中采用的是利用產(chǎn)生的新線程,執(zhí)行新線程的run方法,然后再調(diào)用handler.post將一個runnable對象加入到UI主線程中的消息隊列中,這樣就可以從子線程中切換到主線程去執(zhí)行了。 ? 案例說明要點: ????????1.post方法的主要作用是將一個runnable對象發(fā)送到調(diào)用它的Handler對象所在線程的消息隊列中去,而不是創(chuàng)建一個新的線程。 ????? ? 2.可以利用post方法實現(xiàn)線程的通訊,因此可以得知Handler的主要用處之一:便是將子線程中對UI線程的操作更新到UI主線程中,因為Android系統(tǒng)是不允許在子線程中更新UI的。 ? ????? ? 當(dāng)然,Handler的用法有很多,出了post之外,利用handler發(fā)送消息也是可以讓子線程與UI主線程進(jìn)行通信的。那下面就我們一起看看Handler是如何發(fā)送和接收消息,并處理消息的。 ????? ? 例二中我們post的是一個runnable對象,然后在handler所在線程中執(zhí)行runnable的run方法。其實handler也可以發(fā)送一個message,然后利用重寫handler的message方法在handler所在線程中處理消息。舉例如下: ? 例三、 ? package com.example.handlerbrightthird; ? import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.widget.TextView; ? public class HandlerBrightThird extends Activity { private TextView myText = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_handler_bright_third); myText = (TextView)findViewById(R.id.mytext); ????????????? ? // 新建一個handler對象,重寫其中的handlerMessage方法,用來對消息進(jìn)行處理。 final Handler myhandler = new Handler(){ ? @Override public void handleMessage(Message msg) { // TODO Auto-generated method stub super.handleMessage(msg); if(1 == msg.what){ myText.setText(R.string.hello_world); } } ? }; // 實現(xiàn)Runnable接口,運(yùn)行于新創(chuàng)建的線程。在子線程中發(fā)送一個消息。 class MyRun implements Runnable{ @Override public void run() { // TODO Auto-generated method stub Message msg = new Message(); msg.what = 1; myhandler.sendMessage(msg); } ? } ? MyRun myRun = new MyRun(); Thread myThread = new Thread(myRun); myThread.start(); } ? } ? 案例說明要點: ????? ??1.本例在新啟動的線程中發(fā)送消息,然后在主線程中接收和處理消息。問題是:新啟動的線程何時發(fā)送消息?主線程何時去獲取消息并處理消息呢?為了讓主線程能夠“適時”地處理新啟動線程發(fā)送的消息,顯然只能通過回調(diào)的方式來實現(xiàn)------我們只需重寫Handler類中處理消息的方法,當(dāng)新啟動線程發(fā)送消息時,Handler類中處理消息的方法就會被自動回調(diào)。 ????? ? 2.分析例二和例三,會發(fā)現(xiàn)這兩個案例非常的相似。確實如此,一個是post runnable對象到消息隊列中,然后在主線程中執(zhí)行run方法;一個是sendMessage 發(fā)送一個消息msg對象,然后在主線程中執(zhí)行消息處理方法,比如handlemessage。所以說本質(zhì)其實是:handler對象通過它的方法將另外一個對象加入到handler對應(yīng)線程的消息隊列中去,然后與該handler和消息隊列有關(guān)的一個Looper負(fù)責(zé)將消息從隊列中取出并執(zhí)行。post方法、sendmessage等方法都能夠達(dá)到這個目的:即將一個對象加入到handler所在線程的消息隊列中。 ? ????? ? 接著又有一個問題:上面三個示例都有個共同點,便是新建的handler對象都在UI主線程中創(chuàng)建的。思考一下:假如我們希望在子線程創(chuàng)建一個handler對象,然后同樣希望在子線程中也能接收其他線程發(fā)送過來的信息,仍然采用上述的方法可以嗎?答案是不可以。原因是:Handler能夠發(fā)送和接收并處理消息的原理跟三個元素有關(guān),一個是消息Message對象,一個是Looper,一個是handler所在線程的消息隊列。但是在new Thread()方法創(chuàng)建的子線程中創(chuàng)建的handler是沒有對應(yīng)的Looper的,所以該子線程中創(chuàng)建handler是不會做任何的消息處理的。那么又該如何處理呢?如果需要在子線程中使用Handler類,首先需要創(chuàng)建Looper類實例,這時可以通過Looper.prepare()和Looper.loop()函數(shù)來實現(xiàn)的。Android為我們提供了一個HandlerThread類,該類繼承Thread類,并使用上面兩個函數(shù)創(chuàng)建Looper對象,而且使用wait/notifyAll解決了多線程中子線程1獲取子線程2的Looper對象為空的問題。 ????? ?這樣我們就可以直接使用HandlerThread類來創(chuàng)建和啟動一個新的線程,而不使用Thread即可。這樣創(chuàng)建的子線程中的Handler是由Looper對象實例的,所以該Handler也是可以處理消息的。 ???????? ????? ??注意:Android應(yīng)用中的消息循環(huán)由Looper和Handler配合完成,Looper類用于封裝消息循環(huán),類中有個MessageQueue消息隊列;Handler類封裝了消息投遞和消息處理等功能。 ????????系統(tǒng)默認(rèn)情況下只有主線程(即UI線程)綁定Looper對象,因此在主線程中可以直接創(chuàng)建Handler的實例,但是在子線程中就不能直接new出Handler的實例了,因為子線程默認(rèn)并沒有Looper對象,此時會拋出RuntimeException異常: ? ????? ? 下面看看如何通過HandlerThread啟動一個新線程,并在該子線程中創(chuàng)建handler對象和使用handler對象來處理消息。
? HandlerThread類官方說明: ????????Handy class for starting a new thread that has a looper. The looper can then be used to create handler classes. Note that start() must still be called. ????? ? HandlerThread類是用來啟動一個新的線程,并且它所啟動的新線程包含有對應(yīng)的Looper實例。Looper對象能用來創(chuàng)建handler對象。注意:HandlerThread的start方法必須要調(diào)用,否則線程無法開啟運(yùn)行。
????????下面這個例子給出的是一個比較綜合的例子,基本上包括了前面三個例子的所有知識。 ? 例四、? ? package com.example.handlerthreadtest; ? import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import android.widget.TextView; ? public class HandlerThreadTest extends Activity { private TextView myText = null; String textDisplay; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_handler_thread_test); // 根據(jù)ID獲取控件 myText = (TextView)findViewById(R.id.mytext); // 實現(xiàn)一個Runnable接口,實現(xiàn)其中的run方法,以便在子線程3中執(zhí)行。 class Runner implements Runnable{ ? @Override public void run() { // TODO Auto-generated method stub // 子線程3中執(zhí)行 隨便產(chǎn)生一個隨機(jī)數(shù)吧 double randomData = Math.random(); System.out.println("The random number is = " + randomData); System.out.println("線程3的ID號是 = " + Thread.currentThread().getId()); } ? } // 創(chuàng)建一個Runna類的runnable對象,供handle03發(fā)送。 Runner run03 = new Runner(); ? // 實現(xiàn)一個Runnable接口,實現(xiàn)其中的run方法,以便完成子線程2要更新UI的操作。 class UpdateUI implements Runnable{ ? @Override public void run() { // TODO Auto-generated method stub // 更新顯示文本 myText.setText(textDisplay); System.out.println("UI線程的ID號是 = " + Thread.currentThread().getId()); } ? } // 為了完成線程2的功能,此處需要創(chuàng)建UI線程的Handler。因為系統(tǒng)默認(rèn)創(chuàng)建的Handler便是UI的handler,所以無需根據(jù)UI線程的Looper獲取。 final Handler handlerUI = new Handler(); ? // 創(chuàng)建一個Runna類的runnable對象,供handle03發(fā)送。 final UpdateUI updateUI = new UpdateUI(); ? // 重寫Handler的handlemsg方法,以便在不同的線程當(dāng)中處理各個消息。 class MyHandler extends Handler{ ? public MyHandler(Looper looper) { // TODO Auto-generated constructor stub // 使用父類的構(gòu)造函數(shù),此項必須要有 super(looper); } // 別看僅僅只有一個消息處理函數(shù),其實系統(tǒng)會fork幾個線程各自執(zhí)行自己的handlerMessage方法。并行處理的 @Override public void handleMessage(Message msg) { // TODO Auto-generated method stub super.handleMessage(msg); // 所有消息的處理都在此處進(jìn)行,此處對消息進(jìn)行分類處理 Bundle data = msg.getData(); // 如果此消息是由handler01發(fā)送的,將會在子線程1中執(zhí)行下面if的代碼。此線程中沒有關(guān)于UI的操作 if(data.getInt("threadNum") == 1){ String str = data.getString("name"); System.out.println(str); System.out.println("線程1的ID號是 = " + Thread.currentThread().getId()); } // 如果此消息是由handler02發(fā)送的,將會在子線程2中執(zhí)行下面else if的代碼。為了區(qū)分子線程1,線程2中加入了UI的操作。 // Android系統(tǒng)中是禁止子線程執(zhí)行更新UI的操作的。所以如果想要將子線程的執(zhí)行數(shù)據(jù)結(jié)果用來更新UI,那么可以借助UI線程 // 的handlerUI,這樣就可以調(diào)去UI線程執(zhí)行更新。只需用UI線程的handler post或send一個消息過去即可。 else if(data.getInt("threadNum") == 2){ textDisplay = data.getString("display") + data.getString("text"); //倘若直接執(zhí)行myText.setText(textDisplay);便會發(fā)生異常 handlerUI.post(updateUI); System.out.println("線程2的ID號是 = " + Thread.currentThread().getId()); } } ? } ? // 以下每個線程都是有HandlerThread創(chuàng)建,所以3個子線程都有其Looper實例。如果采用Thread類創(chuàng)建新線程則沒有,這是兩個線程類最根本的區(qū)別。 HandlerThread subThread01 = new HandlerThread("子線程1"); HandlerThread subThread02 = new HandlerThread("子線程2"); HandlerThread subThread03 = new HandlerThread("子線程3"); ? // 在使用getLooper方法之前必須要先執(zhí)行start方法,這是官方文檔的重要說明。 subThread01.start(); subThread02.start(); subThread03.start(); ? ? ? // 要想將消息對象或runnable對象分發(fā)到各個子線程,并讓子線程執(zhí)行。必須要獲得發(fā)送消息的工具:Handler對象。 // 注意:每個特定的handler對象只負(fù)責(zé)向一個特定的線程發(fā)送消息,讓那個特定的線程來執(zhí)行和處理消息。比如:UI線程的handler,它的post runnable // 或者 sendmessage方法都只會將消息發(fā)送到主線程,讓主線程來從其消息隊列中取出并執(zhí)行。 // 因此,下面對各個特定線程分別創(chuàng)建它們的handler對象 MyHandler handler01 = new MyHandler(subThread01.getLooper()); MyHandler handler02 = new MyHandler(subThread02.getLooper()); MyHandler handler03 = new MyHandler(subThread03.getLooper()); ? // 接下來我們便可以利用各個線程對應(yīng)的handler對象實例來向各個線程分發(fā)消息或runnable對象了。分發(fā)完成以后,各個線程將在CPU的調(diào)度下,并行運(yùn)行, //各自在自己的線程中去處理自己的消息。 // 因為handler既能發(fā)送消息,也能發(fā)送runnable對象,所以下面將給出不同。 ? // handler01發(fā)送一個消息到子線程1中去處理消息 Message msg01 = new Message(); // 創(chuàng)建bundle對象,封裝消息的數(shù)據(jù)信息 Bundle data01 = new Bundle(); data01.putString("name", "您好!我是李茂!"); data01.putInt("threadNum", 1); msg01.setData(data01); // 異步機(jī)制:此處發(fā)送完消息以后,sendMessage函數(shù)會立馬返回。消息被放入到子線程1的Looper中的消息隊列中了,等待Looper取出msg01執(zhí)行。 handler01.sendMessage(msg01); ? // handler02發(fā)送一個消息到子線程2中去處理消息 Message msg02 = new Message(); Bundle data02 = new Bundle(); data02.putString("display", "這是Handler02"); data02.putString("text", "發(fā)送的更新UI的消息"); data02.putInt("threadNum", 2); msg02.setData(data02); handler02.sendMessage(msg02); ? // handler03發(fā)送一個runnable對象,然后在子線程三種執(zhí)行該runnable對象中的run方法。 handler03.post(run03); } } ? 輸出結(jié)果如下: ????????代碼中注釋已經(jīng)寫的非常清楚了,從輸出結(jié)果中也能看出消息處理和線程之間的關(guān)系,以及如何使用。 ? ? ? ? ? 最后總結(jié)要點: ? ??1. 一個線程對應(yīng)一個Looper,一個Looper封裝著消息循環(huán)和消息隊列,一個線程可以對應(yīng)多個handler。
? ? 2. 系統(tǒng)默認(rèn)情況下UI主線程有Looper,但是其他由Thread類創(chuàng)建的且沒有實現(xiàn)looper實例的子線程均沒有Looper;但是
????? ? 由Thread類的子類HandlerThread直接創(chuàng)建的線程都有其對應(yīng)的Looper實例。
? ? 3. 如果想把不同的消息分發(fā)到不同的線程中去處理,可以創(chuàng)建幾個含有Looper的線程(一般用HandlerThread創(chuàng)建即可),????
????? ? 然后根據(jù)線程的Looper對象來獲取對應(yīng)的handler對象。最后由各個線程的handler發(fā)送消息到各自的線程中去處理消????????????????????????????? ? ??息。
? ? ? 簡單畫了一個圖,幫助理解。希望這篇文章有所幫助。
? ??
? ? ? ? ? ? ? ? ? ?
????????????????????????? ? ?????????2222222222222222222222222 ????????此例主要用來跟案例一進(jìn)行比較分析。不同點在于案例二中采用的是利用產(chǎn)生的新線程,執(zhí)行新線程的run方法,然后再調(diào)用handler.post將一個runnable對象加入到UI主線程中的消息隊列中,這樣就可以從子線程中切換到主線程去執(zhí)行了。 ? 案例說明要點: ????????1.post方法的主要作用是將一個runnable對象發(fā)送到調(diào)用它的Handler對象所在線程的消息隊列中去,而不是創(chuàng)建一個新的線程。 ????? ? 2.可以利用post方法實現(xiàn)線程的通訊,因此可以得知Handler的主要用處之一:便是將子線程中對UI線程的操作更新到UI主線程中,因為Android系統(tǒng)是不允許在子線程中更新UI的。 ? ????? ? 當(dāng)然,Handler的用法有很多,出了post之外,利用handler發(fā)送消息也是可以讓子線程與UI主線程進(jìn)行通信的。那下面就我們一起看看Handler是如何發(fā)送和接收消息,并處理消息的。 ????? ? 例二中我們post的是一個runnable對象,然后在handler所在線程中執(zhí)行runnable的run方法。其實handler也可以發(fā)送一個message,然后利用重寫handler的message方法在handler所在線程中處理消息。舉例如下: ? 例三、 ? package com.example.handlerbrightthird; ? import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.widget.TextView; ? public class HandlerBrightThird extends Activity { private TextView myText = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_handler_bright_third); myText = (TextView)findViewById(R.id.mytext); ????????????? ? // 新建一個handler對象,重寫其中的handlerMessage方法,用來對消息進(jìn)行處理。 final Handler myhandler = new Handler(){ ? @Override public void handleMessage(Message msg) { // TODO Auto-generated method stub super.handleMessage(msg); if(1 == msg.what){ myText.setText(R.string.hello_world); } } ? }; // 實現(xiàn)Runnable接口,運(yùn)行于新創(chuàng)建的線程。在子線程中發(fā)送一個消息。 class MyRun implements Runnable{ @Override public void run() { // TODO Auto-generated method stub Message msg = new Message(); msg.what = 1; myhandler.sendMessage(msg); } ? } ? MyRun myRun = new MyRun(); Thread myThread = new Thread(myRun); myThread.start(); } ? } ? 案例說明要點: ????? ??1.本例在新啟動的線程中發(fā)送消息,然后在主線程中接收和處理消息。問題是:新啟動的線程何時發(fā)送消息?主線程何時去獲取消息并處理消息呢?為了讓主線程能夠“適時”地處理新啟動線程發(fā)送的消息,顯然只能通過回調(diào)的方式來實現(xiàn)------我們只需重寫Handler類中處理消息的方法,當(dāng)新啟動線程發(fā)送消息時,Handler類中處理消息的方法就會被自動回調(diào)。 ????? ? 2.分析例二和例三,會發(fā)現(xiàn)這兩個案例非常的相似。確實如此,一個是post runnable對象到消息隊列中,然后在主線程中執(zhí)行run方法;一個是sendMessage 發(fā)送一個消息msg對象,然后在主線程中執(zhí)行消息處理方法,比如handlemessage。所以說本質(zhì)其實是:handler對象通過它的方法將另外一個對象加入到handler對應(yīng)線程的消息隊列中去,然后與該handler和消息隊列有關(guān)的一個Looper負(fù)責(zé)將消息從隊列中取出并執(zhí)行。post方法、sendmessage等方法都能夠達(dá)到這個目的:即將一個對象加入到handler所在線程的消息隊列中。 ? ????? ? 接著又有一個問題:上面三個示例都有個共同點,便是新建的handler對象都在UI主線程中創(chuàng)建的。思考一下:假如我們希望在子線程創(chuàng)建一個handler對象,然后同樣希望在子線程中也能接收其他線程發(fā)送過來的信息,仍然采用上述的方法可以嗎?答案是不可以。原因是:Handler能夠發(fā)送和接收并處理消息的原理跟三個元素有關(guān),一個是消息Message對象,一個是Looper,一個是handler所在線程的消息隊列。但是在new Thread()方法創(chuàng)建的子線程中創(chuàng)建的handler是沒有對應(yīng)的Looper的,所以該子線程中創(chuàng)建handler是不會做任何的消息處理的。那么又該如何處理呢?如果需要在子線程中使用Handler類,首先需要創(chuàng)建Looper類實例,這時可以通過Looper.prepare()和Looper.loop()函數(shù)來實現(xiàn)的。Android為我們提供了一個HandlerThread類,該類繼承Thread類,并使用上面兩個函數(shù)創(chuàng)建Looper對象,而且使用wait/notifyAll解決了多線程中子線程1獲取子線程2的Looper對象為空的問題。 ????? ?這樣我們就可以直接使用HandlerThread類來創(chuàng)建和啟動一個新的線程,而不使用Thread即可。這樣創(chuàng)建的子線程中的Handler是由Looper對象實例的,所以該Handler也是可以處理消息的。 ???????? ????? ??注意:Android應(yīng)用中的消息循環(huán)由Looper和Handler配合完成,Looper類用于封裝消息循環(huán),類中有個MessageQueue消息隊列;Handler類封裝了消息投遞和消息處理等功能。 ????????系統(tǒng)默認(rèn)情況下只有主線程(即UI線程)綁定Looper對象,因此在主線程中可以直接創(chuàng)建Handler的實例,但是在子線程中就不能直接new出Handler的實例了,因為子線程默認(rèn)并沒有Looper對象,此時會拋出RuntimeException異常: ? ????? ? 下面看看如何通過HandlerThread啟動一個新線程,并在該子線程中創(chuàng)建handler對象和使用handler對象來處理消息。
? HandlerThread類官方說明: ????????Handy class for starting a new thread that has a looper. The looper can then be used to create handler classes. Note that start() must still be called. ????? ? HandlerThread類是用來啟動一個新的線程,并且它所啟動的新線程包含有對應(yīng)的Looper實例。Looper對象能用來創(chuàng)建handler對象。注意:HandlerThread的start方法必須要調(diào)用,否則線程無法開啟運(yùn)行。
????????? ? ?HandlerThread繼承于Thread,所以它本質(zhì)就是個Thread。與普通Thread的差別就在于,它有個Looper成員變量。這個Looper其實就是對消息隊列以及隊列處理邏輯的封裝,簡單說就是 消息隊列+消息循環(huán)。
? ????????????????下面這個例子給出的是一個比較綜合的例子,基本上包括了前面三個例子的所有知識。 ? 例四、? ? package com.example.handlerthreadtest; ? import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import android.widget.TextView; ? public class HandlerThreadTest extends Activity { private TextView myText = null; String textDisplay; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_handler_thread_test); // 根據(jù)ID獲取控件 myText = (TextView)findViewById(R.id.mytext); // 實現(xiàn)一個Runnable接口,實現(xiàn)其中的run方法,以便在子線程3中執(zhí)行。 class Runner implements Runnable{ ? @Override public void run() { // TODO Auto-generated method stub // 子線程3中執(zhí)行 隨便產(chǎn)生一個隨機(jī)數(shù)吧 double randomData = Math.random(); System.out.println("The random number is = " + randomData); System.out.println("線程3的ID號是 = " + Thread.currentThread().getId()); } ? } // 創(chuàng)建一個Runna類的runnable對象,供handle03發(fā)送。 Runner run03 = new Runner(); ? // 實現(xiàn)一個Runnable接口,實現(xiàn)其中的run方法,以便完成子線程2要更新UI的操作。 class UpdateUI implements Runnable{ ? @Override public void run() { // TODO Auto-generated method stub // 更新顯示文本 myText.setText(textDisplay); System.out.println("UI線程的ID號是 = " + Thread.currentThread().getId()); } ? } // 為了完成線程2的功能,此處需要創(chuàng)建UI線程的Handler。因為系統(tǒng)默認(rèn)創(chuàng)建的Handler便是UI的handler,所以無需根據(jù)UI線程的Looper獲取。 final Handler handlerUI = new Handler(); ? // 創(chuàng)建一個Runna類的runnable對象,供handle03發(fā)送。 final UpdateUI updateUI = new UpdateUI(); ? // 重寫Handler的handlemsg方法,以便在不同的線程當(dāng)中處理各個消息。 class MyHandler extends Handler{ ? public MyHandler(Looper looper) { // TODO Auto-generated constructor stub // 使用父類的構(gòu)造函數(shù),此項必須要有 super(looper); } // 別看僅僅只有一個消息處理函數(shù),其實系統(tǒng)會fork幾個線程各自執(zhí)行自己的handlerMessage方法。并行處理的 @Override public void handleMessage(Message msg) { // TODO Auto-generated method stub super.handleMessage(msg); // 所有消息的處理都在此處進(jìn)行,此處對消息進(jìn)行分類處理 Bundle data = msg.getData(); // 如果此消息是由handler01發(fā)送的,將會在子線程1中執(zhí)行下面if的代碼。此線程中沒有關(guān)于UI的操作 if(data.getInt("threadNum") == 1){ String str = data.getString("name"); System.out.println(str); System.out.println("線程1的ID號是 = " + Thread.currentThread().getId()); } // 如果此消息是由handler02發(fā)送的,將會在子線程2中執(zhí)行下面else if的代碼。為了區(qū)分子線程1,線程2中加入了UI的操作。 // Android系統(tǒng)中是禁止子線程執(zhí)行更新UI的操作的。所以如果想要將子線程的執(zhí)行數(shù)據(jù)結(jié)果用來更新UI,那么可以借助UI線程 // 的handlerUI,這樣就可以調(diào)去UI線程執(zhí)行更新。只需用UI線程的handler post或send一個消息過去即可。 else if(data.getInt("threadNum") == 2){ textDisplay = data.getString("display") + data.getString("text"); //倘若直接執(zhí)行myText.setText(textDisplay);便會發(fā)生異常 handlerUI.post(updateUI); System.out.println("線程2的ID號是 = " + Thread.currentThread().getId()); } } ? } ? // 以下每個線程都是有HandlerThread創(chuàng)建,所以3個子線程都有其Looper實例。如果采用Thread類創(chuàng)建新線程則沒有,這是兩個線程類最根本的區(qū)別。 HandlerThread subThread01 = new HandlerThread("子線程1"); HandlerThread subThread02 = new HandlerThread("子線程2"); HandlerThread subThread03 = new HandlerThread("子線程3"); ? // 在使用getLooper方法之前必須要先執(zhí)行start方法,這是官方文檔的重要說明。 subThread01.start(); subThread02.start(); subThread03.start(); ? ? ? // 要想將消息對象或runnable對象分發(fā)到各個子線程,并讓子線程執(zhí)行。必須要獲得發(fā)送消息的工具:Handler對象。 // 注意:每個特定的handler對象只負(fù)責(zé)向一個特定的線程發(fā)送消息,讓那個特定的線程來執(zhí)行和處理消息。比如:UI線程的handler,它的post runnable // 或者 sendmessage方法都只會將消息發(fā)送到主線程,讓主線程來從其消息隊列中取出并執(zhí)行。 // 因此,下面對各個特定線程分別創(chuàng)建它們的handler對象 MyHandler handler01 = new MyHandler(subThread01.getLooper()); MyHandler handler02 = new MyHandler(subThread02.getLooper()); MyHandler handler03 = new MyHandler(subThread03.getLooper()); ? // 接下來我們便可以利用各個線程對應(yīng)的handler對象實例來向各個線程分發(fā)消息或runnable對象了。分發(fā)完成以后,各個線程將在CPU的調(diào)度下,并行運(yùn)行, //各自在自己的線程中去處理自己的消息。 // 因為handler既能發(fā)送消息,也能發(fā)送runnable對象,所以下面將給出不同。 ? // handler01發(fā)送一個消息到子線程1中去處理消息 Message msg01 = new Message(); // 創(chuàng)建bundle對象,封裝消息的數(shù)據(jù)信息 Bundle data01 = new Bundle(); data01.putString("name", "您好!我是李茂!"); data01.putInt("threadNum", 1); msg01.setData(data01); // 異步機(jī)制:此處發(fā)送完消息以后,sendMessage函數(shù)會立馬返回。消息被放入到子線程1的Looper中的消息隊列中了,等待Looper取出msg01執(zhí)行。 handler01.sendMessage(msg01); ? // handler02發(fā)送一個消息到子線程2中去處理消息 Message msg02 = new Message(); Bundle data02 = new Bundle(); data02.putString("display", "這是Handler02"); data02.putString("text", "發(fā)送的更新UI的消息"); data02.putInt("threadNum", 2); msg02.setData(data02); handler02.sendMessage(msg02); ? // handler03發(fā)送一個runnable對象,然后在子線程三種執(zhí)行該runnable對象中的run方法。 handler03.post(run03); } } ? 輸出結(jié)果如下: ????????代碼中注釋已經(jīng)寫的非常清楚了,從輸出結(jié)果中也能看出消息處理和線程之間的關(guān)系,以及如何使用。 ? ? ? ? ? 最后總結(jié)要點: ? ??1. 一個線程對應(yīng)一個Looper,一個Looper封裝著消息循環(huán)和消息隊列,一個線程可以對應(yīng)多個handler。
? ? 2. 系統(tǒng)默認(rèn)情況下UI主線程有Looper,但是其他由Thread類創(chuàng)建的且沒有實現(xiàn)looper實例的子線程均沒有Looper;但是
????? ? 由Thread類的子類HandlerThread直接創(chuàng)建的線程都有其對應(yīng)的Looper實例。
? ? 3. 如果想把不同的消息分發(fā)到不同的線程中去處理,可以創(chuàng)建幾個含有Looper的線程(一般用HandlerThread創(chuàng)建即可),????
????? ? 然后根據(jù)線程的Looper對象來獲取對應(yīng)的handler對象。最后由各個線程的handler發(fā)送消息到各自的線程中去處理消????????????????????????????? ? ??息。
? ? ? 簡單畫了一個圖,幫助理解。希望這篇文章有所幫助。
? ??
? ? ? ? ? ? ? ? ? ?
轉(zhuǎn)載于:https://www.cnblogs.com/SoftArcLee/archive/2013/03/19/2969905.html
總結(jié)
以上是生活随笔為你收集整理的安卓开发之Handler、HandlerThread学习篇的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: php关闭warning
- 下一篇: (转)使用异步 I/O 大大提高应用程序