Android個(gè)人對(duì)Handler機(jī)制的理解

一.系統(tǒng)為什么不允許子線程訪問(wèn)UI線程:

這是因?yàn)閁I線程里面的控件都是非線程安全的孵户,如果在多線程并發(fā)訪問(wèn)可能會(huì)導(dǎo)致UI控件處于不可預(yù)期的狀態(tài)熟嫩。那么為什么不給控件訪問(wèn)加上鎖呢?首先,加鎖之后會(huì)導(dǎo)致訪問(wèn)邏輯變得復(fù)雜式矫,其次鎖機(jī)制會(huì)降低UI訪問(wèn)的效率乡摹,因?yàn)殒i機(jī)制會(huì)阻塞某些線程的執(zhí)行役耕。

二.消息機(jī)制的幾個(gè)重要對(duì)象:

Handler 發(fā)送和接收消息
Looper 用于輪詢消息隊(duì)列,一個(gè)線程只能有一個(gè)Looper
Message 消息實(shí)體
MessageQueue 消息隊(duì)列聪廉,存放消息實(shí)體的載體

三.創(chuàng)建Looper

在ActivityThread中的main方法中為我們prepare了

public static void main(String[] args) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
        //其他代碼省略...
        Looper.prepareMainLooper(); //初始化Looper以及MessageQueue

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }
        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }
        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop(); //開(kāi)始輪循操作

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

Looper.prepareMainLooper();

 public static void prepareMainLooper() {
        prepare(false);//消息隊(duì)列不可以quit
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

prepare有兩個(gè)重載的方法瞬痘,主要看 prepare(boolean quitAllowed) quitAllowed的作用是在創(chuàng)建MessageQueue時(shí)標(biāo)識(shí)消息隊(duì)列是否可以銷(xiāo)毀, 主線程不可被銷(xiāo)毀 下面有介紹

  public static void prepare() {
        prepare(true);//消息隊(duì)列可以quit
    }
    //quitAllowed 主要
    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {//不為空表示當(dāng)前線程已經(jīng)創(chuàng)建了Looper
            throw new RuntimeException("Only one Looper may be created per thread");
            //每個(gè)線程只能創(chuàng)建一個(gè)Looper
        }
        sThreadLocal.set(new Looper(quitAllowed));//創(chuàng)建Looper并設(shè)置給sThreadLocal板熊,這樣get的時(shí)候就不會(huì)為null了
    }

創(chuàng)建MessageQueue以及Looper與當(dāng)前線程的綁定

 private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);//創(chuàng)建了MessageQueue
        mThread = Thread.currentThread(); //當(dāng)前線程的綁定
   }

MessageQueue的構(gòu)造方法

MessageQueue(boolean quitAllowed) {
 //mQuitAllowed決定隊(duì)列是否可以銷(xiāo)毀 主線程的隊(duì)列不可以被銷(xiāo)毀需要傳入false, 在MessageQueue的quit()方法就不貼源碼了
        mQuitAllowed = quitAllowed;
        mPtr = nativeInit();
    }

Looper.loop()
同時(shí)是在main方法中 Looper.prepareMainLooper() 后Looper.loop(); 開(kāi)始輪詢

public static void loop() {
        final Looper me = myLooper();//里面調(diào)用了sThreadLocal.get()獲得剛才創(chuàng)建的Looper對(duì)象
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }//如果Looper為空則會(huì)拋出異常
        final MessageQueue queue = me.mQueue;

        // Make sure the identity of this thread is that of the local process,
        // and keep track of what that identity token actually is.
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

        for (;;) {
            //這是一個(gè)死循環(huán)框全,從消息隊(duì)列不斷的取消息
            Message msg = queue.next(); // might block
            if (msg == null) {
                //由于剛創(chuàng)建MessageQueue就開(kāi)始輪詢,隊(duì)列里是沒(méi)有消息的,等到Handler sendMessage enqueueMessage后
                //隊(duì)列里才有消息
                // No message indicates that the message queue is quitting.
                return;
            }

            // This must be in a local variable, in case a UI event sets the logger
            Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            msg.target.dispatchMessage(msg);//msg.target就是綁定的Handler干签,詳見(jiàn)后面Message的部分津辩,Handler開(kāi)始
            //后面代碼省略.....

            msg.recycleUnchecked();
        }
    }

四.創(chuàng)建Handler

最常見(jiàn)的創(chuàng)建handler

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

在內(nèi)部調(diào)用 this(null, false);

public Handler(Callback callback, boolean async) {
        //前面省略
        mLooper = Looper.myLooper();//獲取Looper,**注意不是創(chuàng)建Looper**容劳!
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;//獲得消息隊(duì)列MessageQueue
        mCallback = callback; //初始化了回調(diào)接口
        mAsynchronous = async;
    }

Looper.myLooper()喘沿;

 //這是Handler中定義的ThreadLocal  ThreadLocal主要解多線程并發(fā)的問(wèn)題
 // sThreadLocal.get() will return null unless you've called prepare().
 static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
 public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

五.Handler消息機(jī)制整個(gè)流程

1.handler內(nèi)部與Looper關(guān)聯(lián),handler->Looper->MessageQueue,handler發(fā)送消息就是向MessageQueue隊(duì)列發(fā)送消息竭贩。
2.Looper通過(guò)Looper.loop()不斷循環(huán)的方法并把消息(通過(guò)先進(jìn)先出的順序)通過(guò)dispatchMessage方法回傳給handler自己蚜印。

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {//callback在message的構(gòu)造方法中初始化或者使用handler.post(Runnable)時(shí)候才不為空
        handleCallback(msg);
    } else {
        if (mCallback != null) {//mCallback是一個(gè)Callback對(duì)象,通過(guò)無(wú)參的構(gòu)造方法創(chuàng)建出來(lái)的handler留量,該屬性為null窄赋,此段不執(zhí)行
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);//最終執(zhí)行handleMessage方法
    }
}
 private static void handleCallback(Message message) {
        message.callback.run();
    }
流程圖

注意:主線程中的Looper.loop()一直無(wú)限循環(huán)為什么不會(huì)造成ANR?

也許講到這里楼熄,很多人已經(jīng)知道原因了吧忆绰!不過(guò)習(xí)慣使然,我還是要總結(jié)一下可岂。主線程Looper從消息隊(duì)列讀取消息较木,當(dāng)讀完所有消息時(shí),主線程阻塞青柄。子線程往消息隊(duì)列發(fā)送消息伐债,并且往管道文件寫(xiě)數(shù)據(jù),主線程即被喚醒致开,從管道文件讀取數(shù)據(jù)峰锁,主線程被喚醒只是為了讀取消息,當(dāng)消息讀取完畢双戳,再次睡眠虹蒋。因此loop的循環(huán)并不會(huì)對(duì)CPU性能有過(guò)多的消耗。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市魄衅,隨后出現(xiàn)的幾起案子峭竣,更是在濱河造成了極大的恐慌,老刑警劉巖晃虫,帶你破解...
    沈念sama閱讀 219,490評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件皆撩,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡哲银,警方通過(guò)查閱死者的電腦和手機(jī)扛吞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)荆责,“玉大人滥比,你說(shuō)我怎么就攤上這事∽鲈海” “怎么了盲泛?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,830評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)键耕。 經(jīng)常有香客問(wèn)我寺滚,道長(zhǎng),這世上最難降的妖魔是什么郁竟? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,957評(píng)論 1 295
  • 正文 為了忘掉前任玛迄,我火速辦了婚禮,結(jié)果婚禮上棚亩,老公的妹妹穿的比我還像新娘蓖议。我一直安慰自己,他們只是感情好讥蟆,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評(píng)論 6 393
  • 文/花漫 我一把揭開(kāi)白布勒虾。 她就那樣靜靜地躺著,像睡著了一般瘸彤。 火紅的嫁衣襯著肌膚如雪修然。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,754評(píng)論 1 307
  • 那天质况,我揣著相機(jī)與錄音愕宋,去河邊找鬼。 笑死结榄,一個(gè)胖子當(dāng)著我的面吹牛中贝,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播臼朗,決...
    沈念sama閱讀 40,464評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼邻寿,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼蝎土!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起绣否,我...
    開(kāi)封第一講書(shū)人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤誊涯,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后蒜撮,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體暴构,經(jīng)...
    沈念sama閱讀 45,847評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評(píng)論 3 338
  • 正文 我和宋清朗相戀三年淀弹,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了丹壕。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片薇溃。...
    茶點(diǎn)故事閱讀 40,137評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖沐序,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情策幼,我是刑警寧澤,帶...
    沈念sama閱讀 35,819評(píng)論 5 346
  • 正文 年R本政府宣布奴紧,位于F島的核電站,受9級(jí)特大地震影響黍氮,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜沫浆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評(píng)論 3 331
  • 文/蒙蒙 一捷枯、第九天 我趴在偏房一處隱蔽的房頂上張望专执。 院中可真熱鬧,春花似錦本股、人聲如沸攀痊。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,023評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)苟径。三九已至,卻和暖如春凿叠,著一層夾襖步出監(jiān)牢的瞬間涩笤,已是汗流浹背嚼吞。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,149評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蹬碧,地道東北人舱禽。 一個(gè)月前我還...
    沈念sama閱讀 48,409評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像恩沽,于是被迫代替她去往敵國(guó)和親誊稚。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評(píng)論 2 355

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