【拒絕一問就懵】之從Thread講到Handle


背景介紹

咱們在Android開發過程當中,幾乎都離不開線程。可是你對線程的瞭解有多少呢?它完美運行的背後,究竟隱藏了多少鮮爲人知的祕密呢?線程間互通暗語,傳遞信息到底是如何作到的呢?Looper、Handler、MessageQueue究竟在這背後進行了怎樣的運做。本期,讓咱們一塊兒從Thread開始,逐步探尋這個完美的線程鏈背後的祕密。
注意,大部分分析在代碼中,因此請仔細關注代碼哦!php

從Tread的建立流程開始

在這一個環節,咱們將一塊兒一步步的分析Thread的建立流程。
話很少說,直接代碼裏看。java

線程建立的起始點init()

// 建立Thread的公有構造函數,都調用的都是這個私有的init()方法。咱們看看到底幹什麼了。
/** * * @param 線程組 * @param 就是咱們平時接觸最多的Runnable同窗 * @param 指定線程的名稱 * @param 指定線程堆棧的大小 */
private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
        // 獲取當前正在運行的線程
        // 當前正在運行的線程就是該咱們要建立的線程的父線程
        // 咱們要建立的線程會從父線程那繼承一些參數過來
        // 注意哦,此時仍然是在原來的線程,新線程此時尚未建立的哦!
        Thread parent = currentThread();  
        if (g == null) {
            g = parent.getThreadGroup();            //若是沒有指定ThreadGroup,將獲取父線程的TreadGroup
        }

        g.addUnstarted();                           //將ThreadGroup中的就緒線程計數器增長一。注意,此時線程還並無被真正加入到ThreadGroup中。
        this.group = g;                             //將Thread實例的group賦值。從這裏開始線程就擁有ThreadGroup了。

        this.target = target;                       //給Thread實例設置Runnable。之後start()的時候執行的就是它了。
        this.priority = parent.getPriority();       //設置線程的優先權重爲父線程的權重
        this.daemon = parent.isDaemon();            //根據父線程是不是守護線程來肯定Thread實例是不是守護線程。
        setName(name);                              //設置線程的名稱 

        init2(parent);                              //納尼?又一個初始化,參數仍是父線程。不急,稍後在看。

        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;                 //設置線程的堆棧大小
        tid = nextThreadID();                       //線程的id。這是個靜態變量,調用這個方法會自增,而後做爲線程的id。
    }
複製代碼

在Thread的init()方法中,比較重要的是會經過一個currentThread()這樣的native函數經過底層從虛擬機中獲取到當前運行的線程。android

因此在Thread初始化的時候,仍然是在建立它的線程中。不難猜想出,其實Java層的Thread只是對底層的封裝而已。c++

第二個init2()

private void init2(Thread parent) {
        this.contextClassLoader = parent.getContextClassLoader();           //設置ClassLoader成員變量
        this.inheritedAccessControlContext = AccessController.getContext(); //設置訪問權限控制環境
        if (parent.inheritableThreadLocals != null) {
            this.inheritableThreadLocals = ThreadLocal.createInheritedMap(  //建立Thread實例的ThreadLoacaleMap。須要用到父線程的ThreadLocaleMap,目的是爲了將父線程中的變量副本拷貝一份到當前線程中。
            //ThreadLocaleMap是一個Entry型的數組,Thread實例會將變量副本保存在這裏面。
                    parent.inheritableThreadLocals);        
        }
    }
複製代碼

至此,咱們的Thread就初始化完成了,Thread的幾個重要成員變量都賦值了。數組

啓動線程,開車啦!

一般,咱們這樣了啓動一條線程。緩存

Thread threadDemo = new Thread(() -> {

    });
threadDemo.start();
複製代碼

那麼start()背後究竟隱藏着什麼樣不可告人的祕密呢?是人性的扭曲?仍是道德的淪喪?讓咱們一塊兒點進start()。探尋start()背後的祕密。安全

//如咱們所見,這個方法是加了鎖的。緣由是避免開發者在其它線程調用同一個Thread實例的這個方法,從而儘可能避免拋出異常。
//這個方法之因此可以執行咱們傳入的Runnable裏的run()方法,是應爲JVM調用了Thread實例的run()方法。
public synchronized void start() {
        //檢查線程狀態是否爲0,爲0表示是一個新狀態,即還沒被start()過。不爲0就拋出異常。
        //就是說,咱們一個Thread實例,咱們只能調用一次start()方法。
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        //從這裏開始才真正的線程加入到ThreadGroup組裏。再重複一次,前面只是把nUnstartedThreads這個計數器進行了增量,並無添加線程。
        //同時,當線程啓動了以後,nUnstartedThreads計數器會-1。由於就緒狀態的線程少了一條啊!
        group.add(this);

        started = false;
        try {
            //又是個Native方法。這裏交由JVM處理,會調用Thread實例的run()方法。
            nativeCreate(this, stackSize, daemon);  
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);  //若是沒有被啓動成功,Thread將會被移除ThreadGroup,同時,nUnstartedThreads計數器又增量1了。
                }
            } catch (Throwable ignore) {
               
            }
        }
    }
複製代碼

好把,最精華的函數是nativeCreate(this, stackSize, daemon),會去調用底層的JNI函數Thread_nativeCreate(),進一步的會調用底層的Thread類的Thread::CreateNativeThread()函數。數據結構

Thread::CreateNativeThread()函數在/art/runtime/thread.cc文件中(注:CoorChice用的是6.0.0-r1的源碼)。它會在去建立一個c/c++層的Thread對象,而且會關聯Java層的Thread對象(其實就是保存一個Java層Thread對象的引用而已)。接着,會經過c/c++層的pthread_create()函數去建立並啓動一個新線程。這條代碼必需要看看了:多線程

pthread_create_result = pthread_create(&new_pthread, &attr, 
    Thread::CreateCallback, child_thread);
複製代碼

這裏咱們須要注意第三個參數位置的Thread::CreateCallback,它會返回一個Java層Thread類的run()方法指針,在Linux層的pthread線程建立成功後,將會調用這個run()方法。這就是爲何咱們調用start()方法後,run()方法會被調用的緣由。併發

從上面的分析咱們能夠知道,其實Java的線程Thread仍是用的Linux那一套 pthread 的東西,而且一條線程真正建立並運行在虛擬機中時,是在調用start()方法以後。因此,若是你建立了一條線程,可是從沒調用過它的start()方法,就不會有條新線程生成,此時的Thread對象和主線程裏的一個普通對象沒什麼區別。若是你企圖調用 run() 方法去試圖啓動你的線程,那真是大錯特錯了!這樣不過至關於在主線程中調用了一個Java方法而已。

因此,Java中的線程在Android中實際上走的仍是Linux的pthread那一套。

//沒錯,就是這麼簡單!僅僅調用了Runnable類型的成員變量target的run()方法。至此,咱們須要執行的代碼就執行起來了。
//至於這個@Overrid的存在,徹底是由於Thread自己也是一個Runnable!就是說,咱們的Thread也能夠做爲一個Runnable來使用。
@Override
public void run() {
        if (target != null) {
            target.run();
        }
    }
複製代碼

看,若是不調用start()方法,你能夠把Thread看成一個Handler去使用!!

public void test_1() {
    Thread thread1 = new Thread(() -> {
      System.out.println(Thread.currentThread().getName());
    }, "Thread_1");


    Thread thread2 = new Thread(thread1, "Thread_2");
    thread2.start();
  }
  

---
輸出:
Thread_2
複製代碼

幾個常見的線程手段(操做)

Thread.sleep()那不可告人的祕密

咱們平時使用Thread.sleep()的頻率也比較高,因此咱們在一塊兒研究研究Thread.sleep()被調用的時候發生了什麼。
在開始以前,先介紹一個概念——納秒。1納秒=十億分之一秒。可見用它計時將會很是的精準。可是因爲設備限制,這個值有時候並非那麼準確,但仍是比毫秒的控制粒度小不少。

//平時咱們調用的Thread.sleep(long)最後調用到這個方法來,後一個陌生一點的參數就是納秒。
//你能夠在納秒級控制線程。
public static void sleep(long millis, int nanos) throws InterruptedException {
        //下面三個檢測毫秒和納秒的設置是否合法。
        if (millis < 0) {
            throw new IllegalArgumentException("millis < 0: " + millis);
        }
        if (nanos < 0) {
            throw new IllegalArgumentException("nanos < 0: " + nanos);
        }
        if (nanos > 999999) {
            throw new IllegalArgumentException("nanos > 999999: " + nanos);
        }

    
        if (millis == 0 && nanos == 0) {
            if (Thread.interrupted()) {   //當睡眠時間爲0時,檢測線程是否中斷,並清除線程的中斷狀態標記。這是個Native的方法。
              throw new InterruptedException();  //若是線程被設置了中斷狀態爲true了(調用Thread.interrupt())。那麼他將拋出異常。若是在catch住這個異常以後return線程,那麼線程就中止了。 
              //須要注意,在調用了Thread.sleep()以後,再調用isInterrupted()獲得的結果永遠是False。別忘了Thread.interrupted()在檢測的同時還會清除標記位置哦!
            }
            return;
        }

        long start = System.nanoTime();  //相似System.currentTimeMillis()。可是獲取的是納秒,可能不許。
        long duration = (millis * NANOS_PER_MILLI) + nanos;  

        Object lock = currentThread().lock;  //得到當前線程的鎖。

        synchronized (lock) {   //對當前線程的鎖對象進行同步操做
            while (true) {
                sleep(lock, millis, nanos);  //這裏又是一個Native的方法,而且也會拋出InterruptedException異常。
                //據我估計,調用這個函數睡眠的時長是不肯定的。

                long now = System.nanoTime();
                long elapsed = now - start;  //計算線程睡了多久了

                if (elapsed >= duration) {   //若是當前睡眠時長,已經知足咱們的需求,就退出循環,睡眠結束。
                    break;
                }

                duration -= elapsed;   //減去已經睡眠的時間,從新計算須要睡眠的時長。
                start = now;
                millis = duration / NANOS_PER_MILLI;  //從新計算毫秒部分
                nanos = (int) (duration % NANOS_PER_MILLI); //從新計算微秒部分
            }
        }
    }
複製代碼

經過上面的分析能夠知道,使線程休眠的核心方法就是一個Native函數sleep(lock, millis, nanos)。這個sleep()對應底層的一個JNI函數,這個JNI函數最終會調用到c/c++中對應的Thread的條件變量的 TimedWait()函數。這個條件變量是應該是Android中本身定義的條件變量,固然,這裏的TimedWait()函數天然也是Android本身實現的。在這個函數裏,Android直接使用了Linux的futex()函數。這個futex()函數會調用syscall()函數,經過一種名爲【快速用戶區互斥鎖】的鎖去執行鎖定的。futex()的效率比phtread_cond_wait()要高不少。

Android爲了確保休眠的準確性,在這裏還使用了一個while()循環,在每次線程從底層被喚醒後,檢查一下是否休眠夠了足夠的時長。若是不夠就讓它繼續休眠。

同時,須要注意一點,若是線程的interruted狀態在調用sleep()方法時被設置爲true,那麼在開始休眠循環前會拋出InterruptedException異常。

Thread.yield()究竟隱藏了什麼?

這個方法是Native的。調用這個方法能夠提示cpu,當前線程將放棄目前cpu的使用權,和其它線程從新一塊兒爭奪新的cpu使用權限。當前線程可能再次得到執行,也可能沒得到。就醬。

無處不在的wait()到底是什麼?

你們必定常常見到,不管是哪個對象的實例,都會在最下面出現幾個名爲wait()的方法。等待?它們到底是怎樣的一種存在,讓咱們一塊兒點擊去看看。
哎喲我去,都是Native函數啊。

image

那就看看文檔它究竟是什麼吧。
根據文檔的描述,wait()配合notify()和notifyAll()可以實現線程間通信,即同步。在線程中調用wait()必須在同步代碼塊中調用,不然會拋出IllegalMonitorStateException異常。由於wait()函數須要釋放相應對象的鎖。當線程執行到wait()時,對象會把當前線程放入本身的線程池中,而且釋放鎖,而後阻塞在這個地方。直到該對象調用了notify()或者notifyAll()後,該線程才能從新得到,或者有可能得到對象的鎖,而後繼續執行後面的語句。
呃。。。好吧,在說明一下notify()和notifyAll()的區別。

  • notify()
    調用notify()後,對象會從本身的線程池中(也就是對該對象調用了wait()函數的線程)隨機挑選一條線程去喚醒它。也就是一次只能喚醒一條線程。若是在多線程狀況下,只調用一次notify(),那麼只有一條線程能被喚醒,其它線程會一直在
  • notifyAll()
    調用notifyAll()後,對象會喚醒本身的線程池中的全部線程,而後這些線程就會一塊兒搶奪對象的鎖。

扒一扒Looper、Handler、MessageQueue之間的愛恨情仇

咱們可能過去都寫過形如這樣的代碼:

new Thread(()->{

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

}).start()
複製代碼

不少同窗知道,在線程中使用Handler時(除了Android主線程)必須把它放在Looper.prepare()和Looper.loop()之間。不然會拋出RuntimeException異常。可是爲何要這麼作呢?下面咱們一塊兒來扒一扒這其中的內幕。

image

從Looper.prepare()開始

當Looper.prepare()被調用時,發生了什麼?

public static void prepare() {
        prepare(true);  //最終其實執行的是私有方法prepare(boolean quitAllowed)中的邏輯
    }

    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {   //先嚐試獲取是否已經存在一個Looper在當前線程中,若是有就拋個異常。
        //這就是爲何咱們不能在一個Thread中調用兩次Looper.prepare()的緣由。
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));  //首次調用的話,就建立一個新的Looper。
    }
    
    //Looper的私有構造函數
    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);   //建立新的MessageQueue,稍後在來扒它。
        mThread = Thread.currentThread();         //把當前的線程賦值給mThread。
    }
複製代碼

通過上面的分析,咱們已經知道Looper.prepare()調用以後發生了什麼。
可是問題來了!sThreadLocal是個靜態的ThreadLocal<Looper> 實例(在Android中ThreadLocal的範型固定爲Looper)。那麼,Looper.prepare()既然是個靜態方法,Looper是如何肯定如今應該和哪個線程創建綁定關係的呢?咱們接着往裏扒。
來看看ThreadLocal的get()、set()方法。

public void set(T value) {
        Thread t = Thread.currentThread();  //一樣先獲取到當前的線程
        ThreadLocalMap map = getMap(t);     //獲取線程的ThreadLocalMap
        if (map != null)
            map.set(this, value);           //儲存鍵值對
        else
            createMap(t, value);
    }    

public T get() {
        Thread t = Thread.currentThread();   //重點啊!獲取到了當前運行的線程。
        ThreadLocalMap map = getMap(t);      //取出當前線程的ThreadLocalMap。這個東西是個重點,前面已經提到過。忘了的同窗在前面再看看。
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);  
            //能夠看出,每條線程的ThreadLocalMap中都有一個<ThreadLocal,Looper>鍵值對。綁定關係就是經過這個鍵值對創建的。
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
    }
複製代碼

ThreadLocal是Looper類中的靜態常量,因此它對全部線程來講都是可見的。從上面代碼也能夠看出,調用ThreadLocal的set/get方法,實際操做的是Thread的ThreadLocalMap,也就是說每一個Thread的ThreadLocalMap是Thread私有的。這樣的設計,使得即便在併發的狀況下,每一個線程都invoke ThreadLocal的get/set方法,可是因爲每一個線程實際操做的都是本身的ThreadLocalMap,互不影響,因此是線程安全的。關於線程的內存你能夠在CoorChice的這兩篇文章中找到點線索:【拒絕一問就懵】之有必要單獨講講線程:https://juejin.im/post/5cdc164af265da0353790245》【拒絕一問就懵】之你多少要懂點內存回收機制:https://juejin.im/post/5cdc0faf51882568666dfe2f

思考一下:即然TheadLocalMap是每一個線程本身持有的,爲何每次使用的是時候不直接取得Thread以後,而後再取得它的ThreadLocalMap來操做,而是要經過ThreadLocal去間接的操做呢?

這樣設計主要仍是爲了將邏輯分離出去,由於實現方案可能會改變。若是之後修改了ThreadLocalMap的管理邏輯,只要接口功能沒變,Looper和Thread就不會受到影響。而這套邏輯也能夠直接拿來給其它方案使用。便於修改,便於複用。

建立Handler

Handler能夠用來實現線程間的通行。在Android中咱們在子線程做完數據處理工做時,就經常須要經過Handler來通知主線程更新UI。平時咱們都使用new Handler()來在一個線程中建立Handler實例,可是它是如何知道本身應該處理那個線程的任務呢。下面就一塊兒扒一扒Handler。

public Handler() {
        this(null, false); 
}
    
public Handler(Callback callback, boolean async) {      //能夠看到,最終調用了這個方法。
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }

        mLooper = Looper.myLooper();                    //重點啊!在這裏Handler和當前Thread的Looper綁定了。Looper.myLooper()就是從ThreadLocale中取出當前線程的Looper。
        if (mLooper == null) {
            //若是子線程中new Handler()以前沒有調用Looper.prepare(),那麼當前線程的Looper就還沒建立。就會拋出這個異常。
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;  //賦值Looper的MessageQueue給Handler。
        mCallback = callback;
        mAsynchronous = async;
    }

複製代碼

Looper.loop()

咱們都知道,在Handler建立以後,還須要調用一下Looper.loop(),否則發送消息到Handler沒有用!接下來,扒一扒Looper究竟有什麼樣的魔力,可以把消息準確的送到Handler中處理。

public static void loop() {
        final Looper me = myLooper();   //這個方法前面已經提到過了,就是獲取到當前線程中的Looper對象。
        if (me == null) { 
            //沒有Looper.prepare()是要報錯的!
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;       //獲取到Looper的MessageQueue成員變量,這是在Looper建立的時候new的。

        //這是個Native方法,做用就是檢測一下當前線程是否屬於當前進程。而且會持續跟蹤其真實的身份。
        //在IPC機制中,這個方法用來清除IPCThreadState的pid和uid信息。而且返回一個身份,便於使用restoreCallingIdentity()來恢復。
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

        for (;;) {  //重點(敲黑板)!這裏是個死循環,一直等待抽取消息、發送消息。
            Message msg = queue.next(); // 從MessageQueue中抽取一條消息。至於怎麼取的,咱們稍後再看。
            if (msg == null) {
                // 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
            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            final long traceTag = me.mTraceTag;   //取得MessageQueue的跟蹤標記
            if (traceTag != 0) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));  //開始跟蹤本線程的MessageQueue中的當前消息,是Native的方法。
            }
            try {
                msg.target.dispatchMessage(msg);   //嘗試分派消息到和Message綁定的Handler中
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);      //這個和Trace.traceBegin()配套使用。
                }
            }

            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

            
            final long newIdent = Binder.clearCallingIdentity();   //what?又調用這個Native方法了。這裏主要是爲了再次驗證,線程所在的進程是否發生改變。
            if (ident != newIdent) {
                Log.wtf(TAG, "Thread identity changed from 0x"
                        + Long.toHexString(ident) + " to 0x"
                        + Long.toHexString(newIdent) + " while dispatching to "
                        + msg.target.getClass().getName() + " "
                        + msg.callback + " what=" + msg.what);
            }

            msg.recycleUnchecked();   //回收釋放消息。
        }
    }
複製代碼

從上面的分析能夠知道,當調用了Looper.loop()以後,線程就就會被一個for(;;)死循環阻塞,每次等待MessageQueue的next()方法取出一條Message纔開始往下繼續執行。而後經過Message獲取到相應的Handler (就是target成員變量),Handler再經過dispatchMessage()方法,把Message派發到handleMessage()中處理。

這裏須要注意,當線程loop起來是時,線程就一直在循環中。就是說Looper.loop()後面的代碼就不能被執行了。想要執行,須要先退出loop。

Looper myLooper = Looper.myLoop();
myLooper.quit();        //普通退出方式。
myLooper.quitSafely();  //安全的退出方式。 
複製代碼

如今又產生一個疑問,MessageQueue的next()方法是如何阻塞住線程的呢?接下來,扒一扒這個幕後黑手MessageQueue。

幕後黑手MessageQueue

MessageQueue是一個用單鏈的數據結構來維護消息列表。

Message next() {
        //檢查loop是否已經爲退出狀態。mPrt是Native層的MessageQueue的地址。經過這個地址能夠和Native層的MessageQueue互動。
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }

        int pendingIdleHandlerCount = -1;
        int nextPollTimeoutMillis = 0;      //時間標記,當且僅當第一次獲取消息時才爲0。由於它在死循環外面啊!
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();     
                //若是不是第一次獲取消息,調用Native的函數,讓虛擬機刷新全部的餓Binder命令,確保進程在執行可能阻塞的任務以前,釋放以前的對象。
            }

            //這是一個Native的方法。
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {       //鎖住MessageQueue
                //獲取當前的系統時間,用於後面和msg.when進行比較。
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;        //得到當前MessageQueue中的第一條消息
                if (msg != null && msg.target == null) {
                
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {  //這個判斷的意義在於只有到了Message應該被髮送的時刻纔去發送,不然繼續循環。
                        //計算下一條消息的時間。注意最大就是Integer.MAX_VALUE。
                        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;         //返回一條消息給Looper。
                    }
                } else {
                    // 若是取到的Message爲null,將時間標記設置爲-1。
                    nextPollTimeoutMillis = -1;
                }

                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {
                    dispose();
                    return null;
                }

                // If first time idle, then get the number of idlers to run.
                // Idle handles only run if the queue is empty or if the first message
                // in the queue (possibly a barrier) is due to be handled in the future.
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run. Loop and wait some more.
                    mBlocked = true;
                    continue;
                }

                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            // Run the idle handlers.
            // We only ever reach this code block during the first iteration.
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // release the reference to the handler

                boolean keep = false;
                try {
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }

                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }

            // 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;
        }
    }
複製代碼

能夠看到。MessageQueue在取消息(調用next())時,會進入一個死循環,直到取出一條Message返回。這就是爲何Looper.loop()會在queue.next()處等待的緣由。

在這個方法中須要注意一個參數mPtr,它是底層的MessageQueue對象的地址。就是說Android的c/c++層也有一套與Java層對應的Handler機制,而咱們的MessageQueue因爲持有了一個底層的引用,天然就成了Java層的Handler機制和底層的溝通橋樑了。

上面方法中出現了一個nativePollOnce(ptr, nextPollTimeoutMillis);函數的調用。線程會被阻塞在這個地方。這個native方法會調用到底層的JNI函數android_os_MessageQueue_nativePollOnce(),進一步調用c/c++層的nativeMessageQueuepollOnce()函數,在這個函數中又會經過本線程在底層的Looper的pollOnce()函數,進而調用pollInner()函數。在pollInner()函數中會調用epoll_wait()函數,這個函數會將線程阻塞在這,直到被超時或者檢測到pipe中有事件發生。那麼阻塞在這怎麼喚醒呢,咱們下面在說。

那麼,一條Message是如何添加到MessageQueue中呢?要弄明白最後的真相,咱們須要調查一下mHandler.post()這個方法。

Handler究竟對Message作了什麼?

Handler的post()系列方法,最終調用的都是下面這個方法:

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;      //在這裏給Message的target賦值。
        if (mAsynchronous) {
            msg.setAsynchronous(true);      //若是是異步,就標記爲異步
        }
        return queue.enqueueMessage(msg, uptimeMillis);     //就是這個方法把Message添加到線程的MessageQueue中的。
    }
複製代碼

接下來就看看MessageQueue的enqueueMessage()做了什麼。

boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {   //沒Handler調用是會拋異常的啊
            throw new IllegalArgumentException("Message must have a target.");
        }
        if (msg.isInUse()) {        //不能使用一條正在使用中的Message。
            throw new IllegalStateException(msg + " This message is already in use.");
        }

        synchronized (this) {       //鎖住MessageQueue再往裏添加消息。
            if (mQuitting) {        //若是MessageQueue被標記爲退出,就返回。
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false;
            }

            msg.markInUse();        //切換Message的使用狀態爲未使用。
            msg.when = when;        //咱們設置的延遲發送的時間。
            //通過下面的邏輯,Message將會被「儲存」在MessageQueue中。實際上,Message在MessageQueue中的儲存方式,
            //是使用Message.next逐個向後指向的單鏈表結構來儲存的。好比:A.next = B, B.next = C...
            Message p = mMessages;  //嘗試獲取當前Message
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // 若是爲null,說明是第一條。
                msg.next = p;   
                mMessages = msg;    //設置當前的Message爲傳入的Message,也就是做爲第一條。
                needWake = mBlocked;
            } else {
            
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                //不知足做爲第一條Message的條件時,經過下面的逐步變換,將它放在最後面。這樣便把Message「儲存」到MessageQueue中了。
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; 
                prev.next = msg;
            }

          
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }
複製代碼

上一節面CoorChice說過,MessageQueue在next()方法中會阻塞在nativePollOnce()這個地方,其實是阻塞在了底層的Looper的epoll_wait()這個地方等待喚醒呢。看到上面這段代碼的最後面沒?nativeWake(),赤裸裸的代表就是喚醒。實際上這個nativeWake()函數代表pipe寫端有write事件發生,從而讓epoll_wait()退出等待。

至此,咱們已經揭露了Looper、Handler、MessageQueue隱藏的祕密。

另外一個疑問?

也許你已經注意到在主線程中能夠直接使用Handler,而不須要Looper.prepare()和Looper.loop()。爲何能夠作到這樣呢?根據以前的分析能夠知道,主線程中必然存在Looper.prepare()和Looper.loop()。既然如此,爲何主線程沒有被loop()阻塞呢?看一下ActivityThread來弄清楚究竟是怎麼回事。

//這個main()方法能夠認爲是Android應用的起點
public static void main(String[] args) {
                。
                。
                。
        Looper.prepareMainLooper();                             //主要做用和咱們平時調用的Looper.prepare()差很少

        ActivityThread thread = new ActivityThread();           //建立本類實例
        thread.attach(false);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();           //重點啊!這裏取得了處理主線程事物的Handler。
        }

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();                                          //開始循環。能夠看到,主線程本質上是阻塞的!
                。
                。
                。
        }
複製代碼

注意ActivityThread並無繼承Thread,它的Handler是繼承Handler的私有內部類H.class。在H.class的handleMessage()中,它接受並執行主線程中的各類生命週期狀態消息。UI的16ms的繪製也是經過Handler來實現的。也就是說,主線程中的全部操做都是在Looper.prepareMainLooper()和Looper.loop()之間進行的。進一步說是在主Handler中進行的。

總結

  1. Android中Thread在建立時進行初始化,會使用當前線程做爲父線程,並繼承它的一些配置。
  2. Thread初始化時會被添加到指定/父線程的ThreadGroup中進行管理。
  3. Thread正真啓動是一個native函數完成的。
  4. 在Android的線程間通訊中,須要先建立Looper,就是調用Looper.prepare()。這個過程當中會自動依賴當前Thread,而且建立MessageQueue。通過上一步,就能夠建立Handler了,默認狀況下,Handler會自動依賴當前線程的Looper,從而依賴相應的MessageQueue,也就知道該把消息放在哪一個地方了。MessageQueue經過Message.next實現了一個單鏈表結構來緩存Message。消息須要送達Handler處理,還必須調用Looper.loop()啓動線程的消息泵送循環。loop()內部是無限循環,阻塞在MessageQueue的next()方法上,由於next()方法內部也是一個無限循環,直到成功從鏈表中抽取一條消息返回爲止。而後,在loop()方法中繼續進行處理,主要就是把消息派送到目標Handler中。接着進入下一次循環,等待下一條消息。因爲這個機制,線程就至關於阻塞在loop()這了。

通過上面的揭露,咱們已經對線程及其相互之間通信的祕密有所瞭解。掌握了這些之後,相信在之後的開發過程當中咱們能夠思路清晰的進行線程的使用,而且可以吸取Android在設計過程當中的精華思想。

感受不錯就關注我,都不幹就點個贊!😘

相關文章
相關標籤/搜索