天天看點

重識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最好的方法是通過

或者

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。)

    一張圖展示他們之間的聯系:

    重識Handler

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

的時候,初始化了

Looper

MessageQueue

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為null,則抛出異常,
        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;
    }
           

到這裡都沒有看到任何代碼觸發

Handler

handleMessage()

方法。

其實

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。