重識Handler

關於Handler,最開始的使用就是子線程想要刷新UI,必須用Handler來實現,那爲什麼Handler能夠異步更新UI?Handler的內部機制是什麼樣的?Handler跟Looper,Message,MessageQueue之間是什麼關係?等等,本文記錄Handler需要了解的一些知識。

瞭解一些概念。

Android的消息機制:一個線程開啓一個無限循環模式,不斷遍歷自己的消息列表,如果有消息就挨個拿出來做處理,如果列表沒消息,自己就堵塞(相當於wait,讓出cpu資源給其他線程),其他線程如果想讓該線程做什麼事,就往該線程的消息隊列插入消息,該線程會不斷從隊列裏拿出消息做處理。
(摘自知乎的回答)

什麼是Handler

官方解釋爲:Handler可以發送一個消息到與當前線程關聯的MessageQueue中,每一個Handler實例都與當前線程及當前線程的MessageQueue有關,當你創建一個新的Handler,這個Handler會與當前線程及當前線程的MessageQueue關聯,這個時候你通過Handler發送消息到當前的MessageQueue中的時候,會在當前的MessageQueue中處理它。

調度消息使用

  • post(Runnable)
  • postAtTime(Runnable, long)
  • postDelayed(Runnable, long)
  • sendEmptyMessage(int)
  • sendMessage(Message)
  • sendMessageAtTime(Message, long)
  • sendMessageDelayed(Message, long)

處理消息有兩種辦法

第一種:通過重寫Handler的handleMessage方法來處理

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

第二種:實現Handler的CallBack接口

    Handler mHandler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            //如果return false則會繼續調用handleMessage方法
            //return true則不繼續調用handleMessage方法
            return false;
        }
    });

什麼是MessageQueue

官方解釋:MessageQueue是Message的存儲列表(單鏈表),供Looper使用,Message並不是直接添加到MessageQueue中的,而是通過與Looper關聯的Handler來完成的。

什麼是Looper

官方解釋:Looper用於運行線程的消息循環,線程默認是沒有消息循環的,必須通過Looper.prepare();然後Looper.loop()開啓線程的消息循環,example:

 class LooperThread extends Thread {
      public Handler mHandler;

      public void run() {
          Looper.prepare();

          mHandler = new Handler() {
              public void handleMessage(Message msg) {
                  // process incoming messages here
              }
          };

          Looper.loop();
      }
  }

什麼是Message

官方解釋:定義一個包含描述和任意數據對象的消息,Message可以被髮送到Handler,然後由Handler處理。
雖然Message的構造方法是public,但是創建Message最好的方法是通過

Message mMessage = Message.obtain();

或者

Handler mHandler = new Handler();
//其實調用的也是Message.obtain();
Message mMessage = mHandler.obtainMessage();

Handler跟MessageQueue、Looper、Message的關係。

  • Handler: 用於發送Message跟處理Message。
  • MessageQueue: Message隊列。
  • Looper: 負責從 MessageQueue中取出 Message,並分發給對應的 Handler 進行處理。
  • Message: 具體的消息內容(包含一個int的what,一個int的arg1,一個int的arg2跟一個任意對象的obj。)
    一張圖展示他們之間的聯繫:
    none

Handler爲什麼能夠異步刷新UI

public class MainActivity extends AppCompatActivity {
    private final int REFRESH_UI_TAG = 0x1;
    private TextView mTextView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTextView = (TextView) findViewById(R.id.tv_main);
        new Thread(new Runnable() {
            @Override
            public void run() {
                mHandler.sendEmptyMessage(REFRESH_UI_TAG);
            }
        }).start();
    }
    private final Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            if (msg.what == REFRESH_UI_TAG) {
                mTextView.setText("子線程更新UI");
            }
        }
    };
}

由於Android的UI操作是非線程安全的,爲了防止子線程更新UI出現一些不可預見的結果,所以Android只允許在主線程中更新UI。
這是一段最簡單的例子,子線程需要更新UI,通過Handler來實現,爲何Handler能夠異步更新UI?
new Handler()的時候,初始化了LooperMessageQueue

 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());
            }
        }
        //初始化Looper,如果在主線程實例化,Looper.myLooer()獲取的就是Looper.getMainLooper();(這個mainLooper在什麼時候賦值的?往下看。)
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        //初始化MessageQueue
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

Looper.myLooper()獲取到的是什麼?
看源碼之前瞭解一下
ThreadLocal

線程本地存儲區(Thread Local Storage,簡稱爲TLS),每個線程都有自己的私有的本地存儲區域,不同線程之間彼此不能訪問對方的TLS區域。這裏線程自己的本地存儲區域存放是線程自己的Looper

public static @Nullable Looper myLooper() {
        //返回的是Looper中ThreadLocal保存的內容
        return sThreadLocal.get();
    }

那sThreadLocal什麼時候set的內容?在Looper.prepare();

 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");
        }
        //在這裏設置了Looper
        sThreadLocal.set(new Looper(quitAllowed));
    }

接下來
mHandler.sendEmptyMessage(REFRESH_UI_TAG)做了什麼操作?

    //第一步
    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 final boolean sendMessageDelayed(Message msg, long delayMillis){
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }
    //第四步
     public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        //如果MessageQueue爲空,則拋出異常,
        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,這個在Looper.loop()的時候會用到
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

    //該方法在Looper.loop()中調用,是用來處理發出去的消息,如果沒有采用callBack來處理消息的話,最後會調用handleMessage()。
    public void dispatchMessage(Message msg) {
        //msg的callBack是一個runnable
        if (msg.callback != null) {
            //調用runnable的run方法
            //handleCallback(msg)其實就是調用了Message.callback的run方法,這個在Handler#post(Runnable)會被使用到
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            //這個纔是重點
            handleMessage(msg);
        }
    }

當調用sentEmptyMessage(int what)之後最終調用了MessageQueue.enqueueMessage(msg,uptimeMillis);
點開MessageQueue

 boolean enqueueMessage(Message msg, long when) {
        //msg.target指的就是Handler
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        //msg的一個標誌位,用於判斷當前msg是否正在被使用
        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正在被使用
            msg.markInUse();
            //when 表示這個消息執行的時間,隊列是按照消息執行時間排序的
        //SystemClock.uptimeMillis()獲取到的是系統從開機到現在的毫秒數
            //如果handler 調用的是postDelay 那麼when=SystemClock.uptimeMillis()+delayMillis
            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.
             //如果需要喚醒Looper線程,這裏調用native的方法實現epoll機制喚醒線程
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

到這裏都沒有看到任何代碼觸發HandlerhandleMessage()方法。
其實handleMessage()被調用實在Looper.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
            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            final long traceTag = me.mTraceTag;
            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }
            try {
                //到這裏才調用了Handler的dispatchMessage() 
                //dispatchMessage()最後調用了handleMessage();
                msg.target.dispatchMessage(msg);
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }

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

因爲Handler在主線程實例化的,所以當在其他線程發送消息之後,最後都會調用在主線程實例化的Handler中的dispatchMessage方法。

主線程的Looper在什麼時候實例化的?

如果在子線程中直接new Handler的時候

  @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new Thread(new Runnable() {
            @Override
            public void run() {
                Handler mHandler = new Handler();
            }
        }).start();
    }

會拋出異常

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)
                                                     at com.qfxl.handlersample.MainActivity$1.run(MainActivity.java:18)
                                                     at java.lang.Thread.run(Thread.java:818)

這是因爲在當前線程沒有獲取到Looper對象,報錯位置在於

 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();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

但是在主線程中直接new Handler()並不會拋出這個異常。
因爲主線程中的Looper在APP啓動的時候就已經創建好了,位置在於
ActivityThread#main方法中

 public static void main(String[] args) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
        SamplingProfilerIntegration.start();

        // CloseGuard defaults to true and can be quite spammy.  We
        // disable it here, but selectively enable it later (via
        // StrictMode) on debug builds, but using DropBox, not logs.
        CloseGuard.setEnabled(false);

        Environment.initForCurrentUser();

        // Set the reporter for event logging in libcore
        EventLogger.setReporter(new EventLoggingReporter());

        // Make sure TrustedCertificateStore looks in the right place for CA certificates
        final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
        TrustedCertificateStore.setDefaultUserDirectory(configDir);

        Process.setArgV0("<pre-initialized>");

        //這裏已經創建好了Looper
        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循環
        Looper.loop();

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

所以如果想要在子線程中使用Handler的話,得先創建當前線程的Looper對象

  @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new Thread(new Runnable() {
            @Override
            public void run() {
                //創建當前線程的Looper
                Looper.prepare();
                Handler mHandler = new Handler();
                //開啓Looper循環
                Looper.loop():
            }
        }).start();
    }

Looper.prepare()Looper.prepareMainLooper()的區別在於

Looper.prepareMainLooper()創建的MessageQueue是不允許退出的。

Looper.loop爲什麼不會造成線程的死循環?

Looper.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
            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            final long traceTag = me.mTraceTag;
            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }
            try {
                msg.target.dispatchMessage(msg);
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }

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

之所以這個死循環不會導致線程卡死,是因爲Android的消息機制採用了Linux的pipe機制。簡單一句話是:Android應用程序的主線程在進入消息循環過程前,會在內部創建一個Linux管道(Pipe),這個管道的作用是使得Android應用程序主線程在消息隊列爲空時可以進入空閒等待狀態,並且使得當應用程序的消息隊列有消息需要處理時喚醒應用程序的主線程。所以線程只是出於阻塞並不會卡死。另外Activity的生命週期回調也是AMS通過Binder發送ipc調用給app進程,app進程裏的binder stub接收到調用後,給main looper插了條runnable。

發佈了75 篇原創文章 · 獲贊 127 · 訪問量 16萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章