Handler詳解

本文包含與Handler有關(guān)的以下問題

1.Handler的作用
2.為什么Android中要設(shè)計為只能在UI線程中去更新UI呢?
3.Handler的兩個異常
4.Handler口锭、Looper MessageQueue之間的關(guān)系(源碼角度)
5.跟線程相關(guān)的Handler拜效,即HandlerThread(源碼角度分析)
6.主線程往子線程發(fā)消息
7.子線程往子線程發(fā)消息
8.在同一線程中android.Handler和android.MessaegQueue的數(shù)量對應(yīng)關(guān)系是怎樣的壤靶?

一比然、Handler的作用

1.在非UI線程中完成耗時操作坷备,在UI線程中去更新UI。
2.可以在主線程中發(fā)送延時消息。

二谐岁、為什么Android中要設(shè)計為只能在UI線程中去更新UI

1.解決多線程并發(fā)問題(根本原因)
2.提高界面更新的性能問題
3.架構(gòu)設(shè)計的簡單
你可能會說墨礁,既然是擔(dān)心多線程并發(fā)問題幢竹,那我在子線程中加鎖進行更新UI行不行呢?你這樣想的話恩静,會容易造成UI卡頓的焕毫,而且性能也不好。
注1:大部分面試者很難去說出一個令面試官滿意的答案驶乾。
注2:關(guān)于多線程咬荷,這里舉一個例子,比如說銀行取款的問題轻掩。正常情況下幸乒,銀行卡余額不能少于取款金額,如果多線程進行取款的話唇牧,就會造成線程不安全罕扎。
注3:Android中之所以說架構(gòu)簡單,是因為幫我們封裝了很多更新UI的操作丐重。

三腔召、Handler的兩個異常

在使用Handler時,經(jīng)常會出現(xiàn)以下兩個異常:
1.CalledFromWrongThreadException:這種異常是因為嘗試在子線程中去更新UI扮惦,進而產(chǎn)生異常臀蛛。
2.Can't create handle inside thread that ha not called Looper.prepared:是因為我們在子線程中去創(chuàng)建Handler,沒有調(diào)用Looper.prepared而產(chǎn)生的異常崖蜜。

四浊仆、Handler、Looper MessageQueue之間的關(guān)系(源碼角度)

原理分析:

Handler是Android類庫提供的用于發(fā)送豫领、處理消息或Runnable對象的處理類抡柿,它結(jié)合Message、MessageQueue和Looper類以及當(dāng)前線程實現(xiàn)了一個消息循環(huán)機制等恐,用于實現(xiàn)任務(wù)的異步加載和處理洲劣。整個異步消息處理流程的示意圖如下圖所示:


image.png

根據(jù)上面的圖片,我們現(xiàn)在來解析一下異步消息處理機制:

Message:消息體课蔬,用于裝載需要發(fā)送的對象囱稽。
Handler:它直接繼承自O(shè)bject。作用是:在子線程中發(fā)送Message或者Runnable對象到MessageQueue中二跋;在UI線程中接收战惊、處理從MessageQueue分發(fā)出來的Message或者Runnable對象。發(fā)送消息一般使用Handler的sendMessage()方法同欠,而發(fā)出去的消息經(jīng)過處理后最終會傳遞到Handler的handlerMessage()方法中样傍。
MessageQueue:用于存放Message或Runnable對象的消息隊列横缔。它由對應(yīng)的Looper對象創(chuàng)建,并由Looper對象管理衫哥。每個線程中都只會有一個MessageQueue對象茎刚。
Looper:是每個線程中的MessageQueue的管家,負(fù)責(zé)接收和分發(fā)Message或Runnable的工作撤逢。調(diào)用Looper.loop()方法膛锭,就是一個死循環(huán),不斷地從MessageQueue中取消息:如果有消息蚊荣,就取出初狰,并調(diào)用Handler的handlerMessage()方法;如果沒有消息阻塞互例。
現(xiàn)在可以做出如下總結(jié):
(1)Handler負(fù)責(zé)發(fā)送消息奢入,Looper負(fù)責(zé)接收Handler發(fā)送的消息放到MessageQueue,Looper又將消息回傳給Handler自己媳叨。
(2)一個Handler對應(yīng)一個Looper對象腥光,一個Looper對應(yīng)一個MessageQueue對象(Looper內(nèi)部包含一個MessageQueue),一個Handler可以生成多個Message糊秆。
(3)Handler就是公開給外部線程的接口武福,用于線程間的通信。Looper是由系統(tǒng)支持的用于創(chuàng)建和管理MessageQueue的依附于一個線程的循環(huán)處理對象痘番,而Handler是用于操作線程內(nèi)部的消息隊列的捉片,所以 Handler也必須依附一個線程,而且只能是一個線程汞舱。
(4)由于Handler是在主線程中創(chuàng)建的伍纫,所以此時handleMessage()方法中的代碼也會在主線程中運行,于是我們在這里就可以安心地進行UI操作了

五兵拢、跟線程相關(guān)的Handler翻斟,即HandlerThread(源碼角度分析)

HandlerThread本質(zhì)上就是一個普通Thread,只不過內(nèi)部建立了Looper

HandlerThread的特點

1.HandlerThread將loop轉(zhuǎn)到子線程中處理,說白了就是將分擔(dān)MainLooper的工作量说铃,降低了主線程的壓力,使主界面更流暢嘹履。
2.開啟一個線程起到多個線程的作用腻扇。處理任務(wù)是串行執(zhí)行,按消息發(fā)送順序進行處理砾嫉。
3.相比多次使用new Thread(){…}.start()這樣的方式節(jié)省系統(tǒng)資源幼苛。
4.但是由于每一個任務(wù)都將以隊列的方式逐個被執(zhí)行到,一旦隊列中有某個任務(wù)執(zhí)行時間過長焕刮,那么就會導(dǎo)致后續(xù)的任務(wù)都會被延遲處理舶沿。
5.HandlerThread擁有自己的消息隊列墙杯,它不會干擾或阻塞UI線程。
6.通過設(shè)置優(yōu)先級就可以同步工作順序的執(zhí)行括荡,而又不影響UI的初始化高镐;

六、主線程往子線程發(fā)消息

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends Activity implements OnClickListener {
    public static final int UPDATE_TEXT = 1;
    private TextView tv;
    private Button btn;
    private Handler handler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tv = (TextView) findViewById(R.id.tv);
        btn = (Button) findViewById(R.id.btn);
        btn.setOnClickListener(this);
        //疑問:為什么這段代碼如果寫在onClick方法里面會報空指針畸冲?
        new Thread(new Runnable() {
            @Override
            public void run() {
                //1嫉髓、準(zhǔn)備Looper對象
                Looper.prepare();
                //2、在子線程中創(chuàng)建Handler
                handler = new Handler() {
                    @Override
                    public void handleMessage(Message msg) {
                        super.handleMessage(msg);
                        Log.i("handleMessage:", Thread.currentThread().getName());
                        Log.i("后臺輸出", "收到了消息對象");
                    }
                };
                //3邑闲、調(diào)用Looper的loop()方法算行,取出消息對象
                Looper.loop();
            }
        }).start();

    }
    @Override
    public void onClick(View v) {
        Log.i("onClick:", Thread.currentThread().getName());
        switch (v.getId()) {
            case R.id.btn:
                Message msg = handler.obtainMessage();
                handler.sendMessage(msg);
                break;

            default:
                break;
        }
    }
}

總結(jié):

首先執(zhí)行Looper的prepare()方法,這個方法有兩個作用:一是生成Looper對象苫耸,而是把Looper對象和當(dāng)前線程對象形成鍵值對(線程為鍵)州邢,存放在ThreadLocal當(dāng)中,然后生成handler對象褪子,調(diào)用Looper的myLooper()方法偷霉,得到與Handler所對應(yīng)的Looper對象,這樣的話褐筛,handler类少、looper 、消息隊列就形成了一一對應(yīng)的關(guān)系渔扎,然后執(zhí)行上面的第三個步驟硫狞,即Looper在消息隊列當(dāng)中循環(huán)的取數(shù)據(jù)。
另外晃痴,在本文最開頭的第一段中残吩,我們在主線程中創(chuàng)建Handler也沒有調(diào)用Looper.prepare()方法,為什么就沒有崩潰呢倘核?泣侮,這是由于在程序啟動的時候,系統(tǒng)已經(jīng)幫我們自動調(diào)用了Looper.prepare()方法紧唱。

七活尊、子線程往子線程發(fā)送消息

new Thread(new Runnable() {

                      @Override
                      public void run() {
                          String msg;
                          Looper.prepare();

                          childHandler = new Handler() {
                              @Override
                    public void handleMessage(Message msg) {
                                 super.handleMessage(msg);

                                 System.out.println("這個消息是從-->>" + msg.obj+ "過來的,在" + "btn的子線程當(dāng)中" + "中執(zhí)行的");

                             }

                         };  
                      Looper.loop();//開始輪循

                     }
                 }).start();

創(chuàng)建第二個線程

new Thread(new Runnable() {

                      @Override
                      public void run() {
                         Looper loop = Looper.myLooper();
               Message msg = childHandler.obtainMessage();
                         msg.obj = "btn2當(dāng)中子線程";
                         childHandler.sendMessage(msg);
                     }
                 }).start();

其實子線程向子線程之間通信漏益,其實就是在一個子線程中創(chuàng)建一個Handler蛹锰,它的回調(diào)自然就在此子線程中,然后在另一個子線程中調(diào)用此handler來發(fā)送消息就可以了绰疤,不過記得寫上Looper哦铜犬。

八、在同一線程中android.Handler和android.MessaegQueue的數(shù)量對應(yīng)關(guān)系是怎樣的?

N(Handler):1(MessageQueue)
在同一線程中肯定會調(diào)用一個 Loop.prepare() 癣猾,其中就生成一個 MessageQueue .
而代碼中可以 new 出多個 Handler 發(fā)送各自的 Message 到這個 MessageQueue 中敛劝,最后調(diào)用 msg.target.dispatch 中這個>target來捕獲自己發(fā)送的massge,所以明顯是 N 個 Handler 對應(yīng)一個 MessageQueue.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末纷宇,一起剝皮案震驚了整個濱河市夸盟,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌呐粘,老刑警劉巖满俗,帶你破解...
    沈念sama閱讀 218,607評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異作岖,居然都是意外死亡唆垃,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評論 3 395
  • 文/潘曉璐 我一進店門痘儡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來辕万,“玉大人,你說我怎么就攤上這事沉删〗ツ颍” “怎么了?”我有些...
    開封第一講書人閱讀 164,960評論 0 355
  • 文/不壞的土叔 我叫張陵矾瑰,是天一觀的道長砖茸。 經(jīng)常有香客問我,道長殴穴,這世上最難降的妖魔是什么凉夯? 我笑而不...
    開封第一講書人閱讀 58,750評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮采幌,結(jié)果婚禮上劲够,老公的妹妹穿的比我還像新娘。我一直安慰自己休傍,他們只是感情好征绎,可當(dāng)我...
    茶點故事閱讀 67,764評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著磨取,像睡著了一般人柿。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上寝衫,一...
    開封第一講書人閱讀 51,604評論 1 305
  • 那天顷扩,我揣著相機與錄音,去河邊找鬼慰毅。 笑死,一個胖子當(dāng)著我的面吹牛扎阶,可吹牛的內(nèi)容都是我干的汹胃。 我是一名探鬼主播婶芭,決...
    沈念sama閱讀 40,347評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼着饥!你這毒婦竟也來了犀农?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,253評論 0 276
  • 序言:老撾萬榮一對情侶失蹤宰掉,失蹤者是張志新(化名)和其女友劉穎呵哨,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體轨奄,經(jīng)...
    沈念sama閱讀 45,702評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡孟害,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,893評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了挪拟。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片挨务。...
    茶點故事閱讀 40,015評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖玉组,靈堂內(nèi)的尸體忽然破棺而出谎柄,到底是詐尸還是另有隱情,我是刑警寧澤惯雳,帶...
    沈念sama閱讀 35,734評論 5 346
  • 正文 年R本政府宣布朝巫,位于F島的核電站,受9級特大地震影響石景,放射性物質(zhì)發(fā)生泄漏劈猿。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,352評論 3 330
  • 文/蒙蒙 一鸵钝、第九天 我趴在偏房一處隱蔽的房頂上張望糙臼。 院中可真熱鬧,春花似錦恩商、人聲如沸变逃。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,934評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽揽乱。三九已至,卻和暖如春粟矿,著一層夾襖步出監(jiān)牢的瞬間凰棉,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,052評論 1 270
  • 我被黑心中介騙來泰國打工陌粹, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留撒犀,地道東北人。 一個月前我還...
    沈念sama閱讀 48,216評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像或舞,于是被迫代替她去往敵國和親荆姆。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,969評論 2 355

推薦閱讀更多精彩內(nèi)容