記:Handler中知識點(diǎn)梳理

知識點(diǎn)1.Handler中重要的幾個(gè)概念

主線程:應(yīng)用程序第一次啟動,會自動開啟的一條線程及刻。主要用來處理更新UI的操作岂丘。
子線程:程序手動開啟的一條線程。主要用來執(zhí)行一些耗時(shí)任務(wù)销部。
Message:線程間通信的數(shù)據(jù)單元摸航。主要用于存儲子線程要向主線程傳遞的數(shù)據(jù)內(nèi)容
MessageQueue:消息隊(duì)列,Handler存在的必要條件舅桩。主要用于存儲Messge對象酱虎,是一種單鏈表形式,采用先進(jìn)先出
Looper:循環(huán)器擂涛,MessageQueue與Handler的通信媒介,Handler存在的必要條件读串。主要用來循環(huán)從MessageQueue取出Message,并將Message分配給對應(yīng)的Handler

知識點(diǎn)2.Handler 的流程圖

Handler流程圖.jpg

流程分析:Handler有兩個(gè)作用,分別是發(fā)送消息和接收并處理消息恢暖。先從發(fā)送消息說·起排监,發(fā)送消息時(shí),會將Message發(fā)送到MessageQueue隊(duì)列中杰捂,Looper會不斷循環(huán)從MessageQueue取出Message發(fā)送給對應(yīng)的Handler舆床,Handler接收傳遞過來的消息,并進(jìn)項(xiàng)相應(yīng)的邏輯處理嫁佳。
由于Handler要發(fā)送Message挨队,而Message需要存儲到一個(gè)隊(duì)列中才能得以保存,證明必須需要MessageQueue,而MessageQueue并不是我們自己手動創(chuàng)建的脱拼,是Looper創(chuàng)建時(shí)候便會自動創(chuàng)建MessageQueue隊(duì)列瞒瘸,且Handler處理消息是由Looper進(jìn)行分發(fā),故我們知道了MessageQueue和Looper是Handler存在必要條件熄浓。

知識點(diǎn)3.創(chuàng)建Looper的兩種方式:

(1)Looper.prepareMainLooper():在主線程當(dāng)應(yīng)用啟動時(shí)情臭,在main()執(zhí)行。不需要我們手動調(diào)用赌蔑。這就是為什么Handler我們可以直接寫在主線程中俯在,并進(jìn)行處理UI相關(guān)邏輯。而在子線程我們就需要自己調(diào)用Looper
(1)Looper.prepare():在子線程當(dāng)需要啟動一個(gè)Handler娃惯,需在new Handler()之前先寫Looper.prepare()跷乐,否則會報(bào):

 if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
  }

Handler必須綁定線程才能使用,而它綁定了Looper便會綁定Looper所在的線程趾浅。故必須有Looper,且Looper不能為空愕提。

知識點(diǎn)4.MessageQueue和Looper之間的關(guān)系:

MessageQueue主要任務(wù)是用來存儲Message,采用單鏈表的形式,先進(jìn)先出的方式皿哨。
MessageQueue是什么時(shí)候創(chuàng)建的呢浅侨?
(1)進(jìn)入Looper.prepare()

    public static void prepare() {
        prepare(true);
    }

(2)進(jìn)入prepare()

    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

(3)進(jìn)入new Looper(),Looper的構(gòu)造方法

    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

至此會發(fā)現(xiàn)证膨,Looper初始化便會創(chuàng)建一個(gè)對應(yīng)的MessageQueue如输。
生成Looper和MessageQueue后,便會自動進(jìn)入消息循環(huán)(Looper.loop())央勒,當(dāng)有消息便會進(jìn)行操作不见,沒有便會掛起。

知識點(diǎn)5.消息循環(huán)(Looper.loop()):
    public static void loop() {
        final Looper me = myLooper();//分析點(diǎn)1
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;;//分析點(diǎn)2

        ...

        for (;;) {//分析點(diǎn)3
            Message msg = queue.next(); // might block      //分析點(diǎn)4
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

           ...

            try {
                msg.target.dispatchMessage(msg);//分析點(diǎn)5
                end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }

           ...

            msg.recycleUnchecked();
        }
    }

分析Looper.loop()中的源碼崔步,是如何開啟的消息循環(huán)并把消息發(fā)給Handler?
分析點(diǎn)1:Looper me = myLooper(),進(jìn)入 myLooper()->sThreadLocal.get(),此處Looper就是從ThreadLocal中獲取(ThreadLocal:線程中的數(shù)據(jù)存儲類稳吮,這里用于存儲每個(gè)線程對應(yīng)的Looper)
分析點(diǎn)2:MessageQueue是從Looper中獲取的,每個(gè)Looper有一個(gè)對應(yīng)MessageQueue
分析點(diǎn)3:這里是個(gè)死循環(huán)井濒,不斷從MessageQueue中獲取待處理Message
分析點(diǎn)4:這里是個(gè)消息出隊(duì)的操作灶似,若MessageQueue不為空慎陵,就將此消息進(jìn)行出隊(duì)。若為空就會掛起Looper
分析點(diǎn)5:msg.target.dispatchMessage(msg);這里msg.target通過其它源碼可知喻奥,指代的是Handler,將Message和Handler連在了一起,將消息分發(fā)給對應(yīng)的handler,完成了消息的發(fā)送,dispatchMessage下一個(gè)知識點(diǎn)繼續(xù)分析捏悬。

消息循環(huán)的操作 = 消息出隊(duì) + 分發(fā)給對應(yīng)的Handler實(shí)例

知識點(diǎn)6.handler.dispatchMessage():
public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }

            handleMessage(msg);

        }
}

第一步:判斷callback 是否為空撞蚕,不為空則是handler,post()方式(下面會說handler的兩種方式),回調(diào)Runnable種重寫的 run()
第二步:為空則是handler.sendMessage(message)方式过牙,回調(diào)handleMessage()

知識點(diǎn)7.handler的兩種使用方法:

方法1
在子線程中調(diào)用甥厦,主線程中處理

 new Thread(new Runnable() {
     @Override
        public void run() {
            Message message=Message.obtain();
            message.what=1;
            message.obj="hello";
            handler1.sendMessage(message);
      }
});
    Handler handler1 = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            switch (msg.what) {
                case 1:
                    //更新UI
                    break;
            }
            return false;
        }
    });

這里涉及到一個(gè)知識點(diǎn),Message的創(chuàng)建,有兩種方式
(1) Message message=new Message();
(2) Message message=Message.obtain();
推薦第二種方式通過源碼查看,Message.obtain()方法中有一個(gè)線程池寇钉,如果線程池不為空刀疙,先是取出線程池中的線程,對其所有值賦值為空扫倡。屬于線程的復(fù)用谦秧。避免了每次都new重新分配內(nèi)存空間。

方法2
在主線程中創(chuàng)建Handler,然后直接調(diào)用即可

  Handler handler2 = new Handler();
  handler2.post(new Runnable() {
       @Override
       public void run() {
          //更新UI
       }
  });

發(fā)現(xiàn)handler.post()方式并沒有創(chuàng)建Message撵溃,它也并不是內(nèi)部啟動了一個(gè)線程疚鲤。而是將Runnable封裝成一個(gè)Message,也是傳遞到了MessageQueue缘挑,其余操作和流程便是一致了集歇。

總結(jié)
(1)handler.post()不用創(chuàng)建外部Message;handler.sendMessage()需要傳入Message
(2)兩種方法的消息處理方式不同

兩種handler方式.jpg

知識點(diǎn)8.一道面試題:主線程中Looper的輪詢死循環(huán)為何沒有阻塞主線程语淘?

參考答案:Android是依靠事件驅(qū)動的诲宇,通過Loop.loop()不斷進(jìn)行消息循環(huán),可以說Activity的生命周期都是運(yùn)行在 Looper.loop()的控制之下惶翻,一旦退出消息循環(huán)姑蓝,應(yīng)用也就退出了。而所謂的導(dǎo)致ANR多是因?yàn)槟硞€(gè)事件在主線程中處理時(shí)間太耗時(shí)维贺,因此只能說是對某個(gè)消息的處理阻塞了Looper.loop()它掂,反之則不然。


知識點(diǎn)9.新增:HandlerThread

HandlerThread:繼承Thread,封裝一個(gè)Handler
按順序依次執(zhí)行發(fā)送的Message

實(shí)現(xiàn)步驟:

1.聲明初始化HandlerThread
2.線程開始 start()
3.初始化工作線程溯泣,new Handler(這里傳參為HandlerThread中初始化的Looper)虐秋,所以此工作線程是子線程,不是主線程垃沦,不可以用來進(jìn)行更新UI客给。重寫handleMessage()處理傳遞進(jìn)來的消息
4.發(fā)送消息
5.線程關(guān)閉handlerThread.quit()

完整代碼:
public class MainActivity extends AppCompatActivity {
    HandlerThread handlerThread;
    Handler mainHandler;
    Handler workHandler;


    private Button btFirst, btSecond,btQuit;
    private TextView tvMsg;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btFirst = (Button) findViewById(R.id.btFirst);
        btSecond = (Button) findViewById(R.id.btSecond);
        btQuit= (Button) findViewById(R.id.btQuit);
        tvMsg = (TextView) findViewById(R.id.tvMsg);

        mainHandler = new Handler();

        //1.初始化 名字自定義,用來標(biāo)識線程
        handlerThread = new HandlerThread("myThread");
        //2.線程開始
        handlerThread.start();
        //3.綁定HandlerThread的線程,是thread子線程肢簿,所以不能進(jìn)行更新ui操作
        workHandler = new Handler(handlerThread.getLooper()) {
            @Override
            public void handleMessage(final Message msg) {
                final String message = msg.obj.toString();
                switch (msg.what) {
                    case 1:
                        mainHandler.post(new Runnable() {
                            @Override
                            public void run() {
                                tvMsg.setText(message);
                            }
                        });
                        break;
                    case 2:
                        mainHandler.post(new Runnable() {
                            @Override
                            public void run() {
                                tvMsg.setText(message);
                            }
                        });
                        break;
                }
            }
        };
        //4.發(fā)message
        btFirst.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Message message = Message.obtain();
                message.what = 1;
                message.obj = "我是第一個(gè)按鈕";
                workHandler.sendMessage(message);
            }
        });
        btSecond.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Message message = Message.obtain();
                message.what = 2;
                message.obj = "我是第二個(gè)按鈕";
                workHandler.sendMessage(message);
            }
        });
        //5.停止
        btQuit.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                handlerThread.quit();
                //當(dāng)handler被停止后靶剑,再調(diào)用sendMessage()會報(bào):正在操作一個(gè)銷毀了的handler蜻拨,故之后不應(yīng)在操作或操作之前加判斷
            }
        });
    }
}

分享一篇Handler好文章:http://www.reibang.com/p/b4d745c7ff7a

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市桩引,隨后出現(xiàn)的幾起案子缎讼,更是在濱河造成了極大的恐慌,老刑警劉巖坑匠,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件血崭,死亡現(xiàn)場離奇詭異,居然都是意外死亡厘灼,警方通過查閱死者的電腦和手機(jī)夹纫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來设凹,“玉大人舰讹,你說我怎么就攤上這事∩林欤” “怎么了月匣?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長监透。 經(jīng)常有香客問我桶错,道長,這世上最難降的妖魔是什么胀蛮? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任院刁,我火速辦了婚禮,結(jié)果婚禮上粪狼,老公的妹妹穿的比我還像新娘退腥。我一直安慰自己,他們只是感情好再榄,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布狡刘。 她就那樣靜靜地躺著,像睡著了一般困鸥。 火紅的嫁衣襯著肌膚如雪嗅蔬。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天疾就,我揣著相機(jī)與錄音澜术,去河邊找鬼。 笑死猬腰,一個(gè)胖子當(dāng)著我的面吹牛鸟废,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播姑荷,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼盒延,長吁一口氣:“原來是場噩夢啊……” “哼缩擂!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起添寺,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤胯盯,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后计露,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體陨闹,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年薄坏,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片寨闹。...
    茶點(diǎn)故事閱讀 39,711評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡胶坠,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出繁堡,到底是詐尸還是另有隱情沈善,我是刑警寧澤,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布椭蹄,位于F島的核電站闻牡,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏绳矩。R本人自食惡果不足惜罩润,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望翼馆。 院中可真熱鬧割以,春花似錦、人聲如沸应媚。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽中姜。三九已至消玄,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間丢胚,已是汗流浹背翩瓜。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留嗜桌,地道東北人奥溺。 一個(gè)月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像骨宠,于是被迫代替她去往敵國和親浮定。 傳聞我的和親對象是個(gè)殘疾皇子相满,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評論 2 353

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

  • 【Android Handler 消息機(jī)制】 前言 在Android開發(fā)中,我們都知道不能在主線程中執(zhí)行耗時(shí)的任務(wù)...
    Rtia閱讀 4,835評論 1 28
  • Handler是Android消息通訊當(dāng)中最常用的方式之一桦卒。本篇小文將會從Handler的源碼角度去淺析Handl...
    黑色小老虎丶閱讀 1,389評論 0 10
  • 異步消息處理線程啟動后會進(jìn)入一個(gè)無限的循環(huán)體之中立美,每循環(huán)一次,從其內(nèi)部的消息隊(duì)列中取出一個(gè)消息方灾,然后回調(diào)相應(yīng)的消息...
    cxm11閱讀 6,424評論 2 39
  • 前言 在Android開發(fā)的多線程應(yīng)用場景中建蹄,Handler機(jī)制十分常用 今天,我將手把手帶你深入分析Handle...
    BrotherChen閱讀 472評論 0 0
  • 為了更好的理解 Looper 的工作原理裕偿,我們需要對 ThreadLocal 進(jìn)行了解洞慎,如果對 ThreadLoc...
    墨染書閱讀 1,475評論 0 3