Android 消息機制(Handler Looper Message )理解

1.概述

? Android的消息機制主要是指Hanlder的運行機制及其附帶的MessageQueue和Looper的工作過程。三者作為一個整體來實現消息機制。

  • Handler的主要作用是將一個任務切換到某個指定的線程中去執行,最主要的一個場景我們開啟一個子線程來處理一些耗時的任務,在耗時任務結束后返回主線程去更新ui。那為什么我們不能直接在子線程中去更新ui呢,這是因為Android規定ui操作只能在主線程進行,否則會拋出異常,具體判斷邏輯是在ViewRootImpl中通過方法checkThread()進行的驗證。

    void checkThread() {
            if (mThread != Thread.currentThread()) {
                throw new CalledFromWrongThreadException(
                        "Only the original thread that created a view hierarchy can touch its                         views.");
            }
        }
    
  • 為什么系統不允許在子線程訪問UI?

    這是因為Android的UI控件不是線程安全的,如果在多線程中并發訪問控件可能會導致UI控件處于不可預期狀態。so,那為什么系統不對控件加上安全鎖的機制?缺點有兩個:首先控件加上鎖機制會導致UI訪問邏輯復雜;其次鎖機制會降低UI訪問的效率,因為鎖機制會阻塞某些線程的執行。

    因此,比較好的方案就是采用單線程的模型來處理UI,開發者只需要切換一下線程去執行ui操作即可。

  • Handler另外一個作用是可以發送一個延時消息

2.原理概述

? Handler的使用方法大致如下:

private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case 0:
                    mTestTV.setText("This is handleMessage");//更新UI
                    break;
            }
        }
    };
    
  
new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);//在子線程有一段耗時操作,比如請求網絡
                    mHandler.sendEmptyMessage(0);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
  • 大致原理:

    ? Handler與其內部的LooperMessageQueue一起協同工作。首先通過Handler的post方法將一個Runnable投遞到Handler內部的MessageQueue中,也可以用Handler的send方法發送一個Message,其實post方法最終也是通過send方法實現的。

    ? 當Handler的send方法被調用時,會調用MessageQueue的enqueueMessage方法將這個消息放入消息隊列中,然后Looper會一直檢查MessageQueue中的消息,當發現有新消息到來時,就會處理這個消息,最終消息中的Runnable或者Handler中的handleMessage方法會被調用來處理具體的工作。

3.ThreadLocal工作原理

3.1 ThreadLocal的使用場景

? 早在JDK 1.2的版本中就提供java.lang.ThreadLocalThreadLocal為解決多線程程序的并發問題提供了一種新的思路。使用這個工具類可以很簡潔地編寫出優美的多線程程序。

當使用ThreadLocal維護變量時,ThreadLocal為每個使用該變量的線程提供獨立的變量副本,所以每一個線程都可以獨立地改變自己的副本,而不會影響其它線程所對應的副本。

? ThreadLocal是一個線程內部的數據存儲類,通過它可以在指定線程中存儲數據,數據存儲以后只可以在當前線程中獲取到存儲的數據,對于其他線程來說是無法獲取到的。

主要的應用場景是:

  • 以線程為作用域并且不同的線程具有不同的數據副本。這種情況對應與我們要講的Handler和Looper。

  • 在復雜邏輯下的對象傳遞,如監聽器的傳遞。比如監聽器的傳遞,有時候一個線程中的任務過于復雜,可能表現為函數調用棧比較深或者代碼入口的多樣性,在這種情況下,我們一般的作法有大致兩種:

    • 第一種方法是將監聽器通過參數的形式在函數調用棧中進行傳遞,這個方法的局限性在于函數調用棧很深時,通過函數傳遞會讓程序看起來很混亂糟糕。
    • 第二種方法是將監聽器作為靜態變量供線程訪問,這個方法的局限性是缺乏擴充性。比如如果需要有兩個線程并發執行,那就需要提供兩個靜態的監聽器對象。如果有10個監聽器對象,提供10個監聽器對象?這是無法接受的

    而采用ThreadLocal就會很簡單,只需要維護一個ThreadLocal,就可以實現不同線程存儲自己的監聽器對象,互不干擾,代碼邏輯也會很簡潔。

3.2 ThreadLocal的使用方法

private ThreadLoacl<Boolean> mBooleanThreadLocal = new ThreadLocal<Boolean>;

mBooleanThreadLocal.set(true);
Log.d(TAG,"[ThreadMain] mBooleanThreadLocal = :" + mBooleanThreadLocal.get());

new Thread("Thread#1") {
  @Override
  public void run () {
    mBooleanThreadLocal.set(false);
    Log.d(TAG,"[Thread#1] mBooleanThreadLocal = :" + mBooleanThreadLocal.get());
  }
}

new Thread("Thread#2") {
  @Override
  public void run () {
    Log.d(TAG,"[Thread#2] mBooleanThreadLocal = :" + mBooleanThreadLocal.get());
  }
}

運行結果:

[ThreadMain] mBooleanThreadLocal = : true
[Thread#1] mBooleanThreadLocal = : false
[Thread#2] mBooleanThreadLocal = : null

? 沒有為ThreadLocal設置的情況下默認get()到的會是null。

3.3 ThreadLocal原理

? ThreadLocal 是一個泛型類,定義為 public class ThreadLocal<T>

? 在Java源碼中有一個ThreadLocal.java,在Android源碼中也有一個ThreadLocal.java。兩者的主要區別在于他內部的實現方式不同。

3.3.1 java中的ThreadLocal

? ThreadLocal是如何做到為每一個線程維護變量的副本的呢?其實實現的思路很簡單:在ThreadLocal類中有一個Map,用于存儲每一個線程的變量副本,Map中元素的鍵為線程對象,而值對應線程的變量副本。

? ThreadLocal的set方法:

ThreadLocal.java

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

? 在set方法中首先獲取到當前Thread的對象。然后去獲取ThreadLocalMap,這個ThreadLocalMap它是在每一個Thread中都有一份,可以看下getMap(t).

ThreadLocalMap getMap(Thread t) {
  return t.threadLocals;
}

? 代碼很簡單就是返回當前線程中的threadLocals 對象。可以看下Thread.java中的定義。

/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;

? 接著看set()方法,如果得到的map不為空就會把當前的ThreadLocal本身作為Key值,需要保存的value值作為Value

? 到這里可以大致理解如下:在每一個Thread中都維護一個ThreadLocalMap,這個ThreadLocalMap 的Key為ThreadLocal對象,Value為保存的值,這樣一個線程中就存了多個ThreadLocal對應的值。

? 如果得到的ThreadLocalMap為空,就去createMap,實現如下:

void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

? 可以看下ThreadLocalMap的構造方法:

ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
    table = new Entry[INITIAL_CAPACITY];
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    table[i] = new Entry(firstKey, firstValue);
    size = 1;
    setThreshold(INITIAL_CAPACITY);
}

? 通過變量命名及實現,可以知道這個構造方法就是一個初始化并put一個鍵值對。

? 接著看下get()方法:

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null)
            return (T)e.value;
    }
    return setInitialValue();
}

? 首先去獲取到線程的ThreadLocalMap,如果ThreadLocalMap不為空,那么就去獲取當前線程對應的value值。否則化會去調用setInitialValue().

private T setInitialValue() {
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
    map.set(this, value);
    else
    createMap(t, value);
    return value;
}

? 首先initialValue()方法是一個空的實現,默認返回null,可以通過重寫該方法來返回默認的value。后面的步驟就是去初始化了。

public class TestThreadLocal {
    private static final ThreadLocal<Integer> value = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            return Integer.valueOf(1);//默認返回1
        }
    };
}
  • 內存泄露問題

    下圖是本文介紹到的一些對象之間的引用關系圖,實線表示強引用,虛線表示弱引用:

? 然后網上就傳言,ThreadLocal會引發內存泄露,他們的理由是這樣的:
如上圖,ThreadLocalMap使用ThreadLocal的弱引用作為key,如果一個ThreadLocal沒有外部強引用引用他,那么系統gc的時候,這個ThreadLocal勢必會被回收,這樣一來,ThreadLocalMap中就會出現key為null的Entry,就沒有辦法訪問這些key為null的Entry的value,如果當前線程再遲遲不結束的話,這些key為null的Entry的value就會一直存在一條強引用鏈:
Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value
永遠無法回收,造成內存泄露。

? 首先從ThreadLocal的直接索引位置(通過ThreadLocal.threadLocalHashCode & (len-1)運算得到)獲取Entry e,如果e不為null并且key相同則返回e;
? 如果e為null或者key不一致則向下一個位置查詢,如果下一個位置的key和當前需要查詢的key相等,則返回對應的Entry,否則,如果key值為null,則擦除該位置的Entry,否則繼續向下一個位置查詢
? 在這個過程中遇到的key為null的Entry都會被擦除,那么Entry內的value也就沒有強引用鏈,自然會被回收。仔細研究代碼可以發現,set操作也有類似的思想,將key為null的這些Entry都刪除,防止內存泄露。
? 但是光這樣還是不夠的,上面的設計思路依賴一個前提條件:要調用ThreadLocalMap的getEntry函數或者set函數。這當然是不可能任何情況都成立的,所以很多情況下需要使用者手動調用ThreadLocal的remove函數,手動刪除不再需要的ThreadLocal,防止內存泄露。所以JDK建議將ThreadLocal變量定義成private static的,這樣的話ThreadLocal的生命周期就更長,由于一直存在ThreadLocal的強引用,所以ThreadLocal也就不會被回收,也就能保證任何時候都能根據ThreadLocal的弱引用訪問到Entry的value值,然后remove它,防止內存泄露。

3.3.2 Android 中的ThreadLocal

? set()方法:

public void set(T value) {
    Thread currentThread = Thread.currentThread();
    Values values = values(currentThread);
    if (values == null) {
        values = initializeValues(currentThread);
    }
    values.put(this, value);
}

? 這里新增了一個Values的對象。這個對象定義在ThreadLocal.Values。 每一個Thread中都有一個這樣的變量localValues。

Values values(Thread current) {
        return current.localValues;
    }
Values initializeValues(Thread current) {
        return current.localValues = new Values();
    }
void put(ThreadLocal<?> key, Object value) {
    cleanUp();

    // Keep track of first tombstone. That's where we want to go back
    // and add an entry if necessary.
    int firstTombstone = -1;

    for (int index = key.hash & mask;; index = next(index)) {
        Object k = table[index];

        if (k == key.reference) {
            // Replace existing entry.
            table[index + 1] = value;
            return;
        }

        if (k == null) {
            if (firstTombstone == -1) {
                // Fill in null slot.
                table[index] = key.reference;
                table[index + 1] = value;
                size++;
                return;
            }

            // Go back and replace first tombstone.
            table[firstTombstone] = key.reference;
            table[firstTombstone + 1] = value;
            tombstones--;
            size++;
            return;
        }

        // Remember first tombstone.
        if (firstTombstone == -1 && k == TOMBSTONE) {
            firstTombstone = index;
        }
    }
}

? 這里的算法不去深入探索,但是可以看到,在table數組中存儲value的地方。

table[index] = key.reference;
table[index + 1] = value;

? 接著看下get()方法

 public T get() {
        // Optimized for the fast path.
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values != null) {
            Object[] table = values.table;
            int index = hash & values.mask;
            if (this.reference == table[index]) {
                return (T) table[index + 1];
            }
        } else {
            values = initializeValues(currentThread);
        }

        return (T) values.getAfterMiss(this);
    }

? 這里就比較好理解了,返回index+1位置的值。

3.4Handler中的ThreadLocal

? 具體到Handler中來說,對于每一個Handler它都需要一個唯一對應的Looper。通過ThreadLocal就可以實現在Looper中維護一份ThreadLocal就可以滿足不同的線程對應不同的Looper.

 Looper.java
 
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
public static void prepare() {
        prepare(true);
   }

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,這就勢必會增加一個類似于LooperManager的類了,這種方法繁瑣復雜。系統沒有采用這種方案而是采用了ThreadLocal,這就是ThreadLocal的好處。

4. MessageQueue

? MessageQueue主要包含兩個操作enqueneMessage() 和 next()。enqueneMessage作用是向消息隊列中插入一條消息,而next則是從消息隊列中取出一條消息,同時將其從消息隊列中移除。

? 盡管MessageQueue 叫消息隊列,但是它的實現并不是隊列,而是一個單鏈表的數據結構。

  • enqueneMessage

       boolean enqueueMessage(Message msg, long when) {
            if (msg.target == null) {
                throw new IllegalArgumentException("Message must have a target.");
            }
            if (msg.isInUse()) {
                throw new IllegalStateException(msg + " This message is already in use.");
            }
    
            synchronized (this) {
                if (mQuitting) {
                    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();
                msg.when = when;
                Message p = mMessages;
                boolean needWake;
                if (p == null || when == 0 || when < p.when) {
                    // New head, wake up the event queue if blocked.
                    msg.next = p;
                    mMessages = msg;
                    needWake = mBlocked;
                } else {
                    // Inserted within the middle of the queue.  Usually we don't have to wake
                    // up the event queue unless there is a barrier at the head of the queue
                    // and the message is the earliest asynchronous message in the queue.
                    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;
        }
    

    ? 主要實現就是一個鏈表的插入操作。

  • next

        Message next() {
            // Return here if the message loop has already quit and been disposed.
            // This can happen if the application tries to restart a looper after quit
            // which is not supported.
            final long ptr = mPtr;
            if (ptr == 0) {
                return null;
            }
    
            int pendingIdleHandlerCount = -1; // -1 only during first iteration
            int nextPollTimeoutMillis = 0;
            for (;;) {
                if (nextPollTimeoutMillis != 0) {
                    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.
                            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.
                    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;
            }
        }
    

    ? next方法是一個無限循環的方法,如果消息隊列中沒有消息,那么next方法就會一直堵塞在這里,只有新消息到來時,next方法才會返回這條消息并將其從單鏈表中移除、

5 Looper

  • Looper的構造方法:
    Looper.java
    
    public static void prepare() {
        prepare(true);
    }

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

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

? 在Prepare中會檢查當前ThreadLocal是否已經有了looper對象,如果有了就會拋出異常,也就是說prepare()方法一個線程中一個生命周期內只能調用一次。

? 在構造方法內會new一個MessageQueue,用作消息隊列。

? Looper除了prepare方法外,還提供了prepareMainLooper()方法,這個方法主要是用來給ActivityThread創建Looper使用的,其本質也是調用prepare方法來實現的。但是這個Looper不可以退出。

  • Looper是可以退出的,可以通過兩種方式:

    • quit: 直接退出Looper

    • quitSafely:會設定一個退出的標記,然后把消息隊列中的已有消息都處理完畢后才會安全的退出。但是delay的消息不會被保證結束前執行。

      MessageQueue
        
      void quit(boolean safe) {
          if (!mQuitAllowed) {
              throw new IllegalStateException("Main thread not allowed to quit.");
          }
      
          synchronized (this) {
              if (mQuitting) {
                  return;
              }
              mQuitting = true;
      
              if (safe) {
                  removeAllFutureMessagesLocked();
              } else {
                  removeAllMessagesLocked();
              }
      
              // We can assume mPtr != 0 because mQuitting was previously false.
              nativeWake(mPtr);
          }
      }
      

    在子線程中,我們手動創建了Looper后,在所有任務結束后應該調用quit方法來終止消息循環,否則這個子線程就會一直處于消息等待的狀態。而如果退出Looper之后,這個線程也會立刻終止。因此在不需要Looper的時候應該手動終止它。

  • loop方法

       /**
         * Run the message queue in this thread. Be sure to call
         * {@link #quit()} to end the loop.
         */
        public static void loop() {
            final Looper me = myLooper();
            if (me == null) {
                throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
            }
            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 (;;) {
                Message msg = queue.next(); // might block
                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
                Printer logging = me.mLogging;
                if (logging != null) {
                    logging.println(">>>>> Dispatching to " + msg.target + " " +
                            msg.callback + ": " + msg.what);
                }
    
                msg.target.dispatchMessage(msg);
    
                if (logging != null) {
                    logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
                }
    
                // Make sure that during the course of dispatching the
                // identity of the thread wasn't corrupted.
                final long newIdent = Binder.clearCallingIdentity();
                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方法是一個死循環,唯一跳出循環的方式是MessageQueue返回了null,也就是表明MessageQueue正在quit。當Looper調用了quit或者quitsafely時,會調用MessageQueue的quit或者quitsafely。當消息隊列MessageQueue被標記為退出狀態時,也就是isQuitting 標志,enqueueMessage 不在接受新消息,并返回false。此時當當前隊列中的消息處理完畢后,會判斷標志位,如果是即將退出的標志,那么直接返回null,Looper也會停止死循環。
    • 當正常的接收消息時,會調用msg.target.dispatchMessage(msg);來分發消息,但是Handler中dispatchMessage方法是在創建Handler時所使用的線程中執行的。這樣就完成切換邏輯。

6.Handler

  • 常用的新消息發送方法

    Handler.java
     
    public final boolean post(Runnable r) {
      return  sendMessageDelayed(getPostMessage(r), 0);
    }
    
    public final boolean postDelayed(Runnable r, long delayMillis) {
      return sendMessageDelayed(getPostMessage(r), delayMillis);
    }
    
    private static Message getPostMessage(Runnable r) {
      Message m = Message.obtain();
      m.callback = r;
      return m;
    }
    
    ----------
    
    public final boolean sendMessage(Message msg){
      return sendMessageDelayed(msg, 0);
    }
    
    public final boolean sendMessageDelayed(Message msg, long delayMillis){
      if (delayMillis < 0) {
        delayMillis = 0;
      }
      return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }
    
    public final boolean sendEmptyMessage(int what){
      return sendEmptyMessageDelayed(what, 0);
    }
    
    public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
      Message msg = Message.obtain();
      msg.what = what;
      return sendMessageDelayed(msg, delayMillis);
    }
    
    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
      MessageQueue queue = mQueue;
      if (queue == null) {
        RuntimeException e = new RuntimeException(
          this + " sendMessageAtTime() called with no mQueue");
        Log.w("Looper", e.getMessage(), e);
        return false;
      }
      return enqueueMessage(queue, msg, uptimeMillis);
    }
    
    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
      msg.target = this;
      if (mAsynchronous) {
        msg.setAsynchronous(true);
      }
      return queue.enqueueMessage(msg, uptimeMillis);
    }
    
  • 以上post類方法允許你排列一個Runnable對象到主線程隊列中,最終會調用到sendMessage

  • sendMessage類方法, 會發送一個帶數據的Message對象到隊列中。

  • Looper會通過messageQueue的next方法拿到消息進行處理,最后交給Handler處理,即Handler的dispatchMessage方法

      /**
         * Handle system messages here.
         */
        public void dispatchMessage(Message msg) {
            if (msg.callback != null) {
                handleCallback(msg);
            } else {
                if (mCallback != null) {
                    if (mCallback.handleMessage(msg)) {
                        return;
                    }
                }
                handleMessage(msg);
            }
        }
    
  • 會首先判斷Message的callBack是否存在,如果存在,就會調用callback

   private static void handleCallback(Message message) {
        message.callback.run();
    }
  • 接著會去判斷mCallBack,mCallBack是我們在構造函數傳入的

    public Handler(Looper looper, Callback callback, boolean async) {
            mLooper = looper;
            mQueue = looper.mQueue;
            mCallback = callback;
            mAsynchronous = async;
        }
    

這里的CallBack對象是什么:

/**
     * Callback interface you can use when instantiating a Handler to avoid
     * having to implement your own subclass of Handler.
     *
     * @param msg A {@link android.os.Message Message} object
     * @return True if no further handling is desired
     */
    public interface Callback {
        public boolean handleMessage(Message msg);
    }

這里提示了一種新的構造方法:

Handler handler = new Handler(Looper.getMainLooper(), new Handler.Callback() {
      @Override
      public boolean handleMessage(Message msg) {
        return false;
      }
    });

Handler smsHandler = new Handler(){
      public void handleMessage(Message msg) {
        return false;
      };
    };

Message m = Message.obtain(h, new Runnable() {  
        @Override  
        public void run() {  
                //做一些事情,這個run方法是主線程調用的  
        }  
});  
Handler Handler = new Handler();
handler.sendMessage(m);  

? 如果沒有mCallBack,那么最后就會調用handleMessage方法.

  • 這里的優先級

run(callback) > mCallBack > handleMessage

7.主線程的消息循環

? Android的主線程是ActivityThread,主線程的入口方法為main方法。在main方法系統會通過Looper。prepareMainLooper來創建主線程的Looper及MessageQueue,并通過Looper.loop來開啟循環。

public static void main(String[] args) {
  
        .....

        Looper.prepareMainLooper();

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

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

MainLooper也只能prepare一次。

  /**
     * Initialize the current thread as a looper, marking it as an
     * application's main looper. The main looper for your application
     * is created by the Android environment, so you should never need
     * to call this function yourself.  See also: {@link #prepare()}
     */
    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

因為主線程的Looper比較特殊,所有Looper提供了一個getMainLooper的方法來獲取。

/**
  * Returns the application's main looper, which lives in the main thread of the application.
 */
public static Looper getMainLooper() {
    synchronized (Looper.class) {
        return sMainLooper;
    }
}

上述會在ActivityThread中開啟Looper,并在Looper中初始化MessAgeQueue,這個時候還缺少一個Handler.

Handler是定義在ActivityThread.H中。

private class H extends Handler {
    public static final int LAUNCH_ACTIVITY         = 100;
    public static final int PAUSE_ACTIVITY          = 101;
    public static final int PAUSE_ACTIVITY_FINISHING= 102;
    public static final int STOP_ACTIVITY_SHOW      = 103;
    public static final int STOP_ACTIVITY_HIDE      = 104;
    public static final int SHOW_WINDOW             = 105;
    public static final int HIDE_WINDOW             = 106;
    public static final int RESUME_ACTIVITY         = 107;
    public static final int SEND_RESULT             = 108;
    public static final int DESTROY_ACTIVITY        = 109;
    public static final int BIND_APPLICATION        = 110;
    public static final int EXIT_APPLICATION        = 111;
    public static final int NEW_INTENT              = 112;
    public static final int RECEIVER                = 113;
    public static final int CREATE_SERVICE          = 114;
    public static final int SERVICE_ARGS            = 115;
    public static final int STOP_SERVICE            = 116;
    ...
    
     String codeToString(int code) {
            if (DEBUG_MESSAGES) {
                switch (code) {
                    case LAUNCH_ACTIVITY: return "LAUNCH_ACTIVITY";
                    case PAUSE_ACTIVITY: return "PAUSE_ACTIVITY";
                    case PAUSE_ACTIVITY_FINISHING: return "PAUSE_ACTIVITY_FINISHING";
                    case STOP_ACTIVITY_SHOW: return "STOP_ACTIVITY_SHOW";
                    ......
                    }
           }
 }

? ActivityThread 通過ApplicationThread與AMS進程通信,AMS以進程間通信方試完成ActivityThread的請求后會回調ApplicationThread的Binder方法,然后ApplicationThread會通過H發送消息,H收到消息后會將邏輯切換到ActivityThread中即主線程中進行。這就是主線程的消息循環模型。

8. PS

  • 在開發的過程中會碰到一個棘手的問題,調用Activity.finish函數Acitivity沒有執行生命周期的ondestory函數,是因為有一個handler成員,因為它有一個delay消息沒有處理,調用Activity.finish,Activity不會馬上destory,所以記得在Ativity finish前清理一下handle中的未處理的消息,這樣Activity才會順利的destory。

  • 主線程的Handler和 Looper為什么不會阻塞卡死

    1. epoll模型
      當沒有消息的時候會epoll.wait,等待句柄寫的時候再喚醒,這個時候其實是阻塞的。
    2. 所有的ui操作都通過handler來發消息操作。
      比如屏幕刷新16ms一個消息,你的各種點擊事件,所以就會有句柄寫操作,喚醒上文的wait操作,所以不會被卡死了。
  • 內存泄漏

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

    ? 它默認是可以使用外部類的成員變量的,這樣也佐證了我們所說的它會隱式的持有外部類的引用;這時候如果子線程使用handler將message消息發送到messageQueue中并等待執行的過程過長,這時候activity已經執行finish方法;那么我們希望的是activity被執行onDestory方法,然后activity相關的各種資源,組件都被銷毀掉,但是由于handler隱式的持有activity的引用,那么activity就無法被回收,activity相關的資源與組件也無法被回收--即內存已經泄露。

    ? 解決方案:

    • 使用static 靜態變量定義handler

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

      這樣的話,handler對象由于是靜態類型無法持有外部activity的引用,但是這樣Handler不再持有外部類的引用,導致程序不允許在Handler中操作activity中的對象了,這時候我們需要在Handler對象中增加一個隊activity的弱引用;

      static class MyHandler extends Handler {  
          WeakReference<Activity > mActivityReference;  
          MyHandler(Activity activity) {  
              mActivityReference= new WeakReference<Activity>(activity);  
          }  
          @Override  
          public void handleMessage(Message msg) {  
              final Activity activity = mActivityReference.get();  
              if (activity != null) {  
                  mImageView.setImageBitmap(mBitmap);  
              }  
          }  
      }  
      
    • 通過代碼邏輯判斷

      在activity執行onDestory時,判斷是否有handler已經執行完成,否則做相關邏輯操作。

  • 主線程往子線程發消息

    在子線程中創建Handler,需要手動初始化Looper。

  • Handler常見的兩個異常

    在使用Handler時,經常會出現以下兩個異常:

    (1)CalledFromWrongThreadException:這種異常是因為嘗試在子線程中去更新UI,進而產生異常。

    (2)Can't create handle inside thread that ha not called Looper.prepared:是因為我們在子線程中去創建Handler,而產生的異常。

  • 為什么在有些時候子線程中是可以直接更新UI的:

    為了回答這個問題,我們需要先通過看源碼去了解下面這三個問題:

    (1)Android是如何檢測非UI線程去更新UI的

    (2)ViewRootImp是什么?

    (3)ViewRootImp是在哪里創建的?

    源碼我就不貼出來了,這里我只是總結一下。

    答案:

    非UI線程真的不能更新UI嗎? 是可以的。

    解釋:

    在線程中更新UI時會調用ViewParent.invalidateChild()方法檢查當前的thread是否是Mainthread。

    ? 但是,ViewRootImpl這個類是在activity的onResume()方法中創建的。就算在子線程中更新UI,只要在ViewRootImpl創建之前更新UI(比如,程序在執行onCreate方法時,我就去執行setText方法區更新UI),就可以逃避掉checkThread()的檢查。

    關于本題,給出以下鏈接大家去細讀一下源碼吧:

    Android更新Ui進階精解(一):

    http://www.lxweimin.com/p/6de0a42a44d6

    為什么我們可以在非UI線程中更新UI:

    http://blog.csdn.net/aigestudio/article/details/43449123

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容