Android Handler原理源碼淺析

開發(fā)Android一般都用遇到使用handler的情況即硼,現(xiàn)在因為rxjava的時候可能就減少了handler的使用逃片。
使用handler需要注意內存泄漏問題(可以通過弱引用Context解決,或者在不需要使用后調用Handler.removeCallbacksAndMessages(null))只酥,當然rxjava也是會出現(xiàn)該種情況(RxLifecycle和AutoDispose都是為了不打斷rxjva鏈式調用設計的生命周期監(jiān)聽)
說到handler總是離不開looper褥实、message、messageQueue的裂允。但是使用Handler的時候也總是離不開Thread之間的使用损离。

Thread

構建Thread最終會調起init方法。在init方法中僅僅是對thread一些成員變量的初始化而已绝编,并沒有任何線程創(chuàng)建的過程僻澎。

    private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
        Thread parent = currentThread();
        if (g == null) {
            g = parent.getThreadGroup();
        }
        //告訴ThreadGroup中就緒線程+1,但沒調用g.add所以沒添加進ThreadGroup中
        g.addUnstarted();
        this.group = g;

        this.target = target;
        this.priority = parent.getPriority();
        this.daemon = parent.isDaemon();
        setName(name);

        init2(parent);

        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;
        tid = nextThreadID();
    }

    private void init2(Thread parent) {
        this.contextClassLoader = parent.getContextClassLoader();
        this.inheritedAccessControlContext = AccessController.getContext();
        if (parent.inheritableThreadLocals != null) {
            this.inheritableThreadLocals = ThreadLocal.createInheritedMap(
                    parent.inheritableThreadLocals);
        }
    }

線程的使用最后總離不開start()方法十饥。

    public synchronized void start() {
        // threadStatus==0表示線程未start過
        if (threadStatus != 0 || started)
            throw new IllegalThreadStateException();
        //添加線程進ThreadGroup中窟勃,并通知start,減少就緒線程計數器
        group.add(this);

        started = false;
        try {
            //真正創(chuàng)建線程的地方
            nativeCreate(this, stackSize, daemon);
            started = true;
        } finally {
            try {
                if (!started)  group.threadStartFailed(this);
            } catch (Throwable ignore) {
            }
        }
    }

在native方法中去創(chuàng)建線程逗堵,最終通過linux層的pthread方式創(chuàng)建線程并調用Thread中的run的方法指針秉氧。這時候的run方法才真正在的子線程中運行。

Looper

使用looper時很簡單砸捏,短短兩句話就可以實現(xiàn)創(chuàng)建一個當前線程的Looper

Looper.prepare();
Looper.loop();

調用prepare方法時谬运,當前線程中只能有一個looper的存在。當多個時會導致RuntimeException垦藏。代碼中的ThreadLocal是什么呢梆暖?最后會稍微說下的。

 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));
    }

Handler的構造方法有幾個重構方法掂骏。當傳入Looper時轰驳,handler使用傳入的looper去獲取對應message。不傳入Looper時使用當前線程Looper(Looper.myLooper())。而在UI線程中早已創(chuàng)建MainLooper(在ActivityThread中的main方法级解,涉及到activity創(chuàng)建流程冒黑,這里不詳述了),所以可以直接通過空構建方法使用Handler(這時的Looper.myLooper()==Looper.getMainLooper())

public static void main(String[] args) {
        Looper.prepareMainLooper();
        ActivityThread thread = new ActivityThread();
        thread.attach(false);
        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }
        Looper.loop();
    }

OK勤哗,說完Looper.prepare()抡爹。這時說下Looper.loop(),該方法主要是開啟輪詢去獲取MessageQueue中的Message

public static void loop() {
        final Looper me = myLooper();
        //略芒划。冬竟。。民逼。
        final MessageQueue queue = me.mQueue;
        //略泵殴。。拼苍。笑诅。
        for (;;) {
            Message msg = queue.next(); // might block(無消息則阻塞)
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            //略。疮鲫。吆你。。
            try {
                //通過Message.target(Handler)分發(fā)處理回調
                msg.target.dispatchMessage(msg);
                end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            //略棚点。早处。。瘫析。
            msg.recycleUnchecked();
        }
    }

上述代碼可以看出,Looper主要是通過MessageQueue.next()去獲取對應Message默责。獲取到Message后贬循,通過Handler.dispatchMessage方法下發(fā)處理Message。最后通過msg.recycleUnchecked回收Message桃序,供Message.obtain復用杖虾。
因為Looper.loop方法的循環(huán)等待,所以Looper.loop方法是會阻塞當前線程的媒熊。所以handler的創(chuàng)建應該房間prepare和loop方法之間奇适。在ActivityThread中也能看到調用Looper.loop()之前有一段代碼sMainThreadHandler = thread.getHandler()。其實在主線程中activity的生命周期處理及其他相關操作都是通過內部的H.class的Handler通訊實現(xiàn)的芦鳍。

MessageQueue

大概邏輯就是當有新的Message插入隊列時會喚醒隊列嚷往,若同步消息處理時間未到則再休眠指定時候后喚醒。
主要看下next方法是怎么獲取對應message的柠衅。

Message next() {
        //檢查loop是否已經為退出狀態(tài)皮仁。mPrt是Native層的MessageQueue的地址。
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }

        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                 //如果不是第一次獲取消息,調用Native的函數贷祈,讓虛擬機刷新所有的IBinder命令趋急,確保進程在執(zhí)行可能阻塞的任務之前,釋放之前的對象势誊。
                Binder.flushPendingCommands();
            }
            //休眠指定時間
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) {//遇到消息屏障時
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    //忽略同步消息
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        //未到時間呜达,繼續(xù)休眠
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a message.
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        //標記使用并返回
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }

                // Process the quit message now that all pending messages have been handled.
                //隊列已退出返回空,looper也相應結束
                if (mQuitting) {
                    dispose();
                    return null;
                }
                //略粟耻。闻丑。。勋颖。嗦嗡。。
            }
            //略饭玲。侥祭。。茄厘。矮冬。。

            // Reset the idle handler count to 0 so we do not run them again.
            pendingIdleHandlerCount = 0;

            // While calling an idle handler, a new message could have been delivered
            // so go back and look again for a pending message without waiting.
            nextPollTimeoutMillis = 0;
        }
    }

上面看到次哈,當msg.target為空(即消息屏障)是會忽略同步消息胎署。那在系統(tǒng)中什么時候會創(chuàng)建一個taget為空的消息呢?ViewRootImpl.scheduleTraversals()方法窑滞,即是在繪圖之前會插入一個消息屏障琼牧,繪制之后移除。

    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            //添加消息屏障
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

Message

平時我們一般發(fā)送的Message一般為同步Message,可以通過Messasge.setAsynchronous(true)設為異步消息哀卫。

Handler.obtain和Message.obtain就簡單了巨坊,主要是Message內部維持著一個Message鏈表,獲取時先在鏈表中獲取對應緩存Message此改。Message使用完后趾撵,在Looper中通過Message.recycleUnchecked()回收

Handler

Looper的創(chuàng)建需要通過Looper.prepare()來調用,但是每個線程只能有一個Looper共啃。那是不是就意味著每次創(chuàng)建子線程Handler都需要new Thread在Thread內使用Looper的prepare和loop后再創(chuàng)建Handler呢占调?Android其實還有HandlerThread可以了解一下(不需要子線程處理后,要自行調用quit方法釋放資源喲~)移剪。但是要記住的是Looper.loop是會阻塞當前進程的究珊。
剩下Handler就只有post、sendMessage挂滓、handleMessage需要說了
post和sendMessage可以合并一塊說

public final boolean post(Runnable r){
       return  sendMessageDelayed(getPostMessage(r), 0);
}
//最終起調方法
    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

是不是很驚喜很意外苦银,其實最終還是當成了Message處理啸胧。通過post方法傳入的Runnable最后轉為了Message.callback成員。所以Handler無論sendEmptyMessage還是post幔虏,最后還是回落到Message.obtain并對Message初始化發(fā)送的流程纺念。
但是前面MessageQueue在next方法中已經休眠了,所以在MessageQueue.enqueueMessage會根據添加Message判斷是否需要立刻喚醒隊列想括。

    boolean enqueueMessage(Message msg, long when) {
       //省略判斷代碼陷谱,message無依附Handler或者還在使用中直接拋出異常
        synchronized (this) {
            if (mQuitting) {
                //隊列已結束,返回
                msg.recycle();
                return false;
            }

            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                //隊列為空瑟蜈,喚醒
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                // 插在中間無需喚醒烟逊,插入隊列頭或者是異步消息則需要喚醒
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);//喚醒隊列
            }
        }
        return true;
    }

剩下Handler.dispatchMessage(Message msg)需要說明下Handler處理消息流程而已了。在Looper中可以看到dispatchMessage的起調是在Looper.loop方法里铺根。Looper在獲取到需要處理的Message之后宪躯,調用Message.target.dispatchMessage處起調的。

public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

代碼簡單易懂位迂。

  • Message有callback访雪,回調message.callback(所以post方法Runable會優(yōu)先執(zhí)行)
  • Handler有callback,回調Handler.callback
  • 最后回落handleMessage

最后在稍微說下Looper中說到的ThreadLocal

ThreadLocal

最后稍微介紹下ThreadLocal掂林。這個類就是保證每個線程只有一個Looper的關鍵臣缀。
這個類主要作用就是線程內數據共享,不同線程為不同副本泻帮。

class Looper

 public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
 }

 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));
 }

ThreadLocal.get方法主要是通過當前線程的ThreadLocalMap去獲取當前ThreadLocal的值精置。

class ThreadLocal
public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

ThreadLocalMap是ThreadLocal定義的一個內部類,以ThreadLocal為key值锣杂。

  • 每個線程Thread內部有一個ThreadLocal.ThreadLocalMap類型的成員變量threadLocals(存儲實際的變量副本的脂倦,鍵值為當前ThreadLocal變量,value為變量副本(即T類型的變量))蹲堂。
  • 初始時狼讨,在Thread里面,threadLocals為空柒竞,當通過ThreadLocal變量調用get()方法或者set()方法,就會對Thread類中的threadLocals進行初始化播聪,并且以當前ThreadLocal變量為鍵值朽基,以ThreadLocal要保存的副本變量為value,存到threadLocals离陶。
  • 然后在當前線程里面稼虎,如果要使用副本變量,就可以通過get方法在threadLocals里面查找招刨。

所以在不同線程中調用ThreadLocal.set()實際上調用的是當前線程中的ThreadLocalMap霎俩,從而保證線程安全
而Looper只有通過靜態(tài)的Looper.prepare()方法去創(chuàng)建Looper,從而保證每個線程只有一個Looper

參考資料

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市打却,隨后出現(xiàn)的幾起案子杉适,更是在濱河造成了極大的恐慌,老刑警劉巖柳击,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件猿推,死亡現(xiàn)場離奇詭異,居然都是意外死亡捌肴,警方通過查閱死者的電腦和手機蹬叭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來状知,“玉大人秽五,你說我怎么就攤上這事〖玻” “怎么了坦喘?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長铺坞。 經常有香客問我起宽,道長,這世上最難降的妖魔是什么济榨? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任坯沪,我火速辦了婚禮,結果婚禮上擒滑,老公的妹妹穿的比我還像新娘腐晾。我一直安慰自己,他們只是感情好丐一,可當我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布藻糖。 她就那樣靜靜地躺著,像睡著了一般库车。 火紅的嫁衣襯著肌膚如雪巨柒。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天柠衍,我揣著相機與錄音洋满,去河邊找鬼。 笑死珍坊,一個胖子當著我的面吹牛牺勾,可吹牛的內容都是我干的。 我是一名探鬼主播阵漏,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼驻民,長吁一口氣:“原來是場噩夢啊……” “哼翻具!你這毒婦竟也來了?” 一聲冷哼從身側響起回还,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤裆泳,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后懦趋,有當地人在樹林里發(fā)現(xiàn)了一具尸體晾虑,經...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年仅叫,在試婚紗的時候發(fā)現(xiàn)自己被綠了帜篇。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡诫咱,死狀恐怖笙隙,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情坎缭,我是刑警寧澤竟痰,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站掏呼,受9級特大地震影響坏快,放射性物質發(fā)生泄漏。R本人自食惡果不足惜憎夷,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一莽鸿、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧拾给,春花似錦祥得、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至额衙,卻和暖如春饮焦,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背窍侧。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工追驴, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人疏之。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像暇咆,于是被迫代替她去往敵國和親锋爪。 傳聞我的和親對象是個殘疾皇子丙曙,可洞房花燭夜當晚...
    茶點故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內容