Android系統源碼分析:Handler源碼分析及使用細節

博客主頁java

1. Handler源碼分析(API 29)

在分析Handler源碼以前,咱們先來看下下面這條異常android

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
        at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6094)
        at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:824)
        at android.view.View.requestLayout(View.java:16431)

做爲Android開發人員,這樣異常的信息應該並不陌生,產生的緣由就是在子線程操做UI控件了。那麼爲何在子線程操做UI控件,就會拋出異常呢?segmentfault

咱們再來看另外一條異常信息安全

java.lang.RuntimeException: Can't toast on a thread that has not called Looper.prepare()
        at android.widget.Toast$TN.<init>(Toast.java:435)
        at android.widget.Toast.<init>(Toast.java:150)
        at android.widget.Toast.makeText(Toast.java:313)
        at android.widget.Toast.makeText(Toast.java:303)

拋出這條異常信息是由於在子線程中彈toast致使的。爲何跟第一條產生的異常信息不同呢?多線程

讓咱們帶着疑問開始探索源碼之旅吧~~~併發

一、其實第一條異常信息咱們都知道是在哪裏拋出的,跟View的繪製機制有關,也就是爲何不容許在子線程中操做UI控件?
這是由於Android的UI控件不是線程安全的,若是在多線程中併發訪問可能會致使UI控件不可預期的狀態,那麼爲何系統不對UI控件的訪問加上鎖機制呢?app

  1. 首先加上鎖機制會讓UI訪問的邏輯變得複雜
  2. 鎖機制會下降UI訪問效率,由於鎖機制會阻塞某些線程的執行

因此最簡單且高效的方法就是採用單線程模型來處理UI操做,那麼Android中子線程必定不能更新UI控件嗎?async

其實Android系統在更新UI控件時,會調用ViewRootImpl類的checkThread()來檢測當前線程是不是建立UI控件的線程,若是不在同一個線程就會拋出異常。ide

// ViewRootImpl.java
    public ViewRootImpl(Context context, Display display) {
       
        mThread = Thread.currentThread();
        // ... 省略無關代碼
    }

    @Override
    public void requestLayout() {
        checkThread();
        // ... 
    }

    void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }

二、只要咱們執行下面這段代碼就會拋出第二條崩潰日誌oop

new Thread(new Runnable() {
    @Override
    public void run() {
         Toast.makeText(MainActivity.this, "handler", Toast.LENGTH_LONG).show();
    }
}).start();
// 拋出:java.lang.RuntimeException: Can't toast on a thread that has not called

看到這裏不少人感到奇怪,子線程中更新UI控件應該是第一條崩潰日誌啊。

來看下Toast源碼:

Toast.java

    public Toast(@NonNull Context context, @Nullable Looper looper) {
        // 建立TN
        mTN = new TN(context.getPackageName(), looper);
       // ...
    }

    private static class TN extends ITransientNotification.Stub {
      
        final Handler mHandler;

        TN(String packageName, @Nullable Looper looper) {
           // ...
            // 看到這裏,我相信你們知道爲何緣由了。
            if (looper == null) {
                // Use Looper.myLooper() if looper is not specified.
                looper = Looper.myLooper();
                if (looper == null) {
                    throw new RuntimeException(
                            "Can't toast on a thread that has not called Looper.prepare()");
                }
            }
            mHandler = new Handler(looper, null) {
                @Override
                public void handleMessage(Message msg) {
                   
                }
            };
        }
    }

閱讀Toast源碼後發現,彈toast時須要獲取當前線程的Looper,若是當前線程沒有Looper,就會拋出異常。看到這裏會有一個疑問,爲何在main線程中彈toast不會報錯呢?

咱們能夠猜測,既然在main線程沒有報錯,那麼確定main線程中已經建立過Looper對象。是誰建立的呢?

在Android系統中,App啓動入口是在ActivityThread類的main方法。

ActivityThread.java

    public static void main(String[] args) {
        
        // 建立Looper對象和MessageQueue對象,用於處理主線程的消息
        Looper.prepareMainLooper();

        // 建立ActivityThread對象
        ActivityThread thread = new ActivityThread();
        // 建立Binder通道
        thread.attach(false, startSeq);
        
        // 主線程的Handler
        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop(); // 消息循環運行

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

閱讀ActivityThread源碼後,咱們知道main線程的Looper在App啓動時就幫咱們建立了,因此咱們能夠直接在main線程中彈toast了。 Binder 線程會向 H(就是main線程的Handler) 發送消息,H 收到消息後處理Activity的生命週期,因此在主線程中能夠直接更新UI控件了。

主線程的消息循環模型:
ActivityThread 經過 ApplicationThread 和 AMS 進行進程間通信,AMS 以進程間通訊的方式完成 ActivityThread 的請求後會回調 ApplicationThread 中的 Binder 方法,而後 ApplicationThread 會向 H 發送消息,H 收到消息後會將 ApplicationThread 中的邏輯切換到 ActivityThread 中去執行,即切換到主線程中去執行。

1.1 Looper 、Thread之間的關係

先來看這段代碼,在子線程中建立Handler,運行後拋出異常了

new Thread(new Runnable() {
    @Override
    public void run() {
         Handler handler = new Handler();
    }
}).start();

出現崩潰日誌以下,提示說明:調用線程中沒有調用過Looper.prepare(),就不能建立Handler

java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
        at android.os.Handler.<init>(Handler.java:200)
        at android.os.Handler.<init>(Handler.java:114)

爲何Looper.prepare()會影響Handler的建立呢?走進Handler的源碼分析:

//Handler.java

    final Looper mLooper;
    final MessageQueue mQueue;

    public Handler(@Nullable Callback callback, boolean async) {
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
       // ...
    }

---------------------------------
// Looper.java
    // 返回與調用線程相關聯的Looper對象
    // 若是調用的線程沒有關聯Looper(也就是沒調用過Looper.prepare())返回null
    public static @Nullable Looper myLooper() {
        // Looper對象怎麼與調用線程關聯?設計到ThreadLocal知識
        return sThreadLocal.get();
    }

從Handler構造方法中可知:在建立Handler對象時,會檢查調用線程中是否有Looper關聯,若是沒有就拋出異常。

1.2 Handler、Looper、MessageQueue、Message之間的關係

Looper源碼:

//Looper.java

   final MessageQueue mQueue;

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

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

    // 在ActivityThread中的main方法調用
    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類中主要做用:

  1. 提供prepare方法,關聯調用線程與Looper對象
  2. 主線程經過prepareMainLooper方法關聯Looper,能夠經過Looper.getMainLooper()獲取主線程Looper
  3. Looper對象與MessageQueue對象創建關係,一個Looper對象對用一個MessageQueue對象

其它的線程怎麼擁有本身的Looper呢?Android系統爲咱們提供了一個很是方便的類:HandlerThread

// HandlerThread.java

public class HandlerThread extends Thread {
    @Override
    public void run() {
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        onLooperPrepared();
        Looper.loop();
    }
}

HandlerThread繼承Thread類,當Thread.start()線程,調用Looper.prepare()後,就會建立一個Looper與這個Thread關聯。

Handler、Looper、MessageQueue、Message之間的關係:一個Thread持有一個Looper,一個Looper持有一個MessageQueue和多個與之關聯的Handler,一個Handler持有一個Looper,一個MessageQueue維護Message單項鍊表。

1.3 Handler工做機制

用一張圖來描述Handler的工做機制

簡單描述上圖:使用Handler發送消息到MessageQueue,Looper不斷輪詢MessageQueue中消息,分發消息到Handler中處理。

Looper的loop()源碼:

// Looper.java
    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;
        // ...
        for (;;) {
           // 堵塞調用,從MessageQueue中獲取消息
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
           // ...
           // 消息的分發及處理
           msg.target.dispatchMessage(msg);
           // ...
        }
    }

Looper不斷從MessageQueue中輪詢到消息後,分發給Handler處理。

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

分發消息Message處理的順序:

  1. 若是Message中設置了callback (Runnable),執行回調後直接返回
  2. 若是Handler設置了mCallback (Callback),執行回調;若是處理結果返回true,直接返回,不然執行Handler的handleMessage方法
  3. 若是Message和Handler都沒有設置callback,執行Handler的handleMessage方法

總結:若是想攔截Handler中callback或者Handler中handleMessage方法,能夠給Message設置callback,這樣Handler中Message處理就不會被執行。

接下來分析關鍵的MessageQueue中的next():

Message next() {
        // ...
        int nextPollTimeoutMillis = 0;
        for (;;) {
            // 阻塞在native層
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                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) {
                        // msg定義的when尚未到,讓native繼續等nextPollTimeoutMillis時長
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // 有到點的msg,返回給Looper的loop處理
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        return msg;
                    }
                } else {
                    // 沒有消息,在native層阻塞
                    nextPollTimeoutMillis = -1;
                }

                // 若是調用了quit(),Looper的loop()就會退出無限循環
                if (mQuitting) {
                    dispose();
                    return null;
                }
            }

            // ...
          
            // 當在處理idle handler的時,能夠發送一個新的Message
            // nextPollTimeoutMillis設置爲0,當即查詢掛起的消息,無需等待
            nextPollTimeoutMillis = 0;
        }
    }

1.4 源碼分析後的問題思考

一、 Looper.loop() 死循環爲何不會致使應用卡死?
這裏就涉及到 Linux pipe/epoll機制,簡單說就是在主線程的 MessageQueue 沒有消息時,便阻塞在 loop 的 queue.next() 中的 nativePollOnce() 方法裏,此時主線程會釋放 CPU 資源進入休眠狀態,直到下個消息到達或者有事務發生,經過往 pipe 管道寫端寫入數據來喚醒主線程工做。這裏採用的 epoll 機制,是一種IO多路複用機制,能夠同時監控多個描述符,當某個描述符就緒(讀或寫就緒),則馬上通知相應程序進行讀或寫操做,本質同步I/O,即讀寫是阻塞的。 因此說,主線程大多數時候都是處於休眠狀態,並不會消耗大量CPU資源。

2.Handler的使用細節

2.1 子線程更新UI的方法

一、直接在main線程建立Handler,那麼Looper就會與main線程綁定,子線程就能夠經過該Handler更新UI

// 使用方式
    private static Handler mHandler = new Handler() {
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
        }
    };

二、用Activity的runOnUiThread方法,判斷調用線程是不是main線程,若是不是,使用Handler發送到main線程。原理也是使用Handler機制

// Activity.java源碼

    final Handler mHandler = new Handler();

    public final void runOnUiThread(Runnable action) {
        if (Thread.currentThread() != mUiThread) {
            mHandler.post(action);
        } else {
            action.run();
        }
    }

三、建立Handler時,在Handler構造方法中傳入main Looper。能夠在子線程建立該Handler,而後更新UI。

// 使用方式
  new Thread(new Runnable() {
       @Override
       public void run() {
            Handler handler = new Handler(Looper.getMainLooper());
            handler.post(new Runnable() {
                @Override
                public void run() {
                    //  TODO 更新UI
                    Log.d(TAG, "run: "+Thread.currentThread().getName());
                    // run: main
                }
           });
       }
  }).start();

四、View.post(Runnable action),先看下源碼API 29:

// View.java源碼

    public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.post(action);
        }

        // Postpone the runnable until we know on which thread it needs to run.
        // Assume that the runnable will be successfully placed after attach.
        getRunQueue().post(action);
        return true;
    }

   private HandlerActionQueue getRunQueue() {
        if (mRunQueue == null) {
            mRunQueue = new HandlerActionQueue();
        }
        return mRunQueue;
    }

在來看下HandlerActionQueue的源碼:

// HandlerActionQueue.java源碼

   private HandlerAction[] mActions;
   private int mCount;
   public void post(Runnable action) {
        postDelayed(action, 0);
    }

    public void postDelayed(Runnable action, long delayMillis) {
        final HandlerAction handlerAction = new HandlerAction(action, delayMillis);

        synchronized (this) {
            if (mActions == null) {
                mActions = new HandlerAction[4];
            }
            mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
            mCount++;
        }
    }

   // 關鍵:executeActions調用時機不一樣,可能致使不會被執行
    public void executeActions(Handler handler) {
        synchronized (this) {
            final HandlerAction[] actions = mActions;
            for (int i = 0, count = mCount; i < count; i++) {
                final HandlerAction handlerAction = actions[i];
                handler.postDelayed(handlerAction.action, handlerAction.delay);
            }

            mActions = null;
            mCount = 0;
        }
    }

這種方式有不靠譜的地方,區別在於API 24(Android7.0)以上,可能post()的Runnable,永遠不能運行。

根本緣由在於,API 24(Android7.0)以上 executeActions() 方法的調用時機不一樣,致使 View 在沒有 mAttachInfo 對象的時候,表現不同了。而executeActions()執行只會在View.dispatchAttachedToWindow()方法中調用

若是你只是經過 new 或者使用 LayoutInflater 建立了一個 View ,而沒有將它經過 addView() 加入到 佈局視圖中去,你經過這個 View.post() 出去的 Runnable ,將永遠不會被執行到。

舉一個例子說明問題:

private ViewGroup mRootLayout;

    private Handler handler = new Handler(Looper.getMainLooper());

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mRootLayout = findViewById(R.id.rootLayout);

        final View view = new View(this);

        Log.e("View.post()>>>>", "當前設備的SDK的版本:" + Build.VERSION.SDK_INT);
        view.post(new Runnable() {
            @Override
            public void run() {
                Log.e("View.post()>>>>", "直接new一個View,而後post的Runnable被執行了");
            }
        });

        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                Log.e("View.post()>>>>", "delay addView被執行了");
                mRootLayout.addView(view);
            }
        }, 1000);
    }
}

在API 24(Android7.0)如下運行結果。從執行結果中的時間能夠看出,new出來的View,post的Runnable當即被執行了。

11-04 14:40:20.614 View.post()>>>>: 當前設備的SDK的版本:19
11-04 14:40:20.664 View.post()>>>>: 直接new一個View,而後post的Runnable被執行了
11-04 14:40:22.614 View.post()>>>>: delay addView被執行了

而在API 24(Android7.0)以上(包括7.0)運行的結果。從執行時間上能夠看出,post的Runnable沒有當即被執行,而是addView後才被執行。

2019-11-04 14:44:36.240 View.post()>>>>: 當前設備的SDK的版本:28
2019-11-04 14:44:38.243 View.post()>>>>: delay addView被執行了
2019-11-04 14:44:38.262 View.post()>>>>: 直接new一個View,而後post的Runnable被執行了

2.2 sendMessage發送消息

經過Handler能夠發送的Message的方法以下:

public final boolean sendEmptyMessage(int what)
public final boolean sendEmptyMessageDelayed(int what, long delayMillis)
public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis)

public final boolean sendMessage(@NonNull Message msg)
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis)
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis)
public final boolean sendMessageAtFrontOfQueue(@NonNull Message msg)

public final boolean post(@NonNull Runnable r)
public final boolean postAtTime(@NonNull Runnable r, long uptimeMillis)
public final boolean postDelayed(@NonNull Runnable r, long delayMillis)
public final boolean postAtFrontOfQueue(@NonNull Runnable r)

這些方法最終調用的是enqueueMessage方法

// Handler.java源碼

    private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

Handler源碼分析到這了,可是它的精髓遠遠不止這些,還有不少鮮爲人知的祕密,須要咱們去探索。
下一篇:Handler擴展知識探索~~~

若是個人文章對您有幫助,不妨點個贊鼓勵一下(^_^)

相關文章
相關標籤/搜索