天天看點

初識Android Handler機制

Handler在android中主要用于接受子線程的消息,然後在主線程中更新UI。因為android規定,不能在子線程中更新UI。如果在子線程中進行UI操作就會報以下錯誤:

 android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

但是,android又規定,在UI線程,也就是主線程中不能進行耗時操作,比如網絡請求,否則可能會産生ANR問題,是以引入了handler機制來對UI進行更新操作。

Handler常用的方法有兩種:

<span style="font-size:18px;">public class TestActivity extends AppCompatActivity {

    private static Handler handler = new Handler() {

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);
        new Thread(new Runnable() {
            @Override
            public void run() {
                // 方式一
                handler.sendEmptyMessage(0x001);
            }
        }).start();

        //方式二
        new Handler().post(new Runnable() {
            @Override
            public void run() {

            }
        });

    }

}</span>
           

方式一:就是在主線程中建立handler,通過handler.sendMessage的方法調用主線程中handler的handlerMessage方法處理,進而進行UI更新;

方式二:handler.post(runnable)。來進行一些延時操作。

但是這些方法的使用原理是什麼呢,我們來看一下源碼。

我們從第一步new handler()開始研究。

<span style="font-size:18px;">    public Handler() {
        this(null, false);
    }</span>
           

這是new Handler()的構造方法,就是調用了另外一個構造方法,屬性設為null,false。

再看其調用的自身的構造方法。

<span style="font-size:18px;">    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;
    }</span>
           

這裡就是對handler進行一些初始化操作,其中關鍵的代碼為:

<span style="font-size:18px;">        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }</span>
           

這段話的意思就是,擷取目前線程的Looper對象,如果目前線程沒有建立Looper則會報錯:

java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()

意思就是在調用new Handler()前必須調用Looper.prepare()方法,否則就會報錯。這裡就很奇怪了,我們平常使用過程中也沒有調用這個方法,但是程式也沒報錯啊。這是因為,在Android的入口類:ActivityThread中,main方法已經初始化調用了Looper.prepared()。也就是在主線程中,系統已經預設建立了Looper對象,是以,我們在主線程中new Handler()并不需要自己再去調用一遍。這也就是我們沒有去自己寫Looper.prepared()的原因。但是,當我們在子線程中去new Handler 的時候必須自己去調用Looper.prepared()否則就會出錯。

我們再來順着看一下Looper.prepared()方法:

<span style="font-size:18px;">    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));
    }</span>
           

這個方法也很簡單,要知道的就是ThreadLocal是什麼。

ThreadLocal其實是一個很特殊的類,它可以在不同線程中調用set方法儲存自己的值,并且這些值不會因為在其他線程中進行set操作而改變,目前線程隻能操作目前線程的值。很适合處理多線程時的場景。

而這裡,通過ThreadLocal的get()方法,判斷目前線程的Looper對象是否已經存在,如果存在就回報錯,因為android規定了一個線程隻能調用一次存在一個Looper對象。如果不存在,則建立一個新的looper對象。

我們看一下ThreadLocal的get、set方法。

<span style="font-size:18px;">    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);
    }</span>
           
<span style="font-size:18px;">    public void set(T value) {
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values == null) {
            values = initializeValues(currentThread);
        }
        values.put(this, value);
    }</span>
           

可以看到存取的時候都是通過先擷取目前的線程,是以ThreadLocal可以儲存某個線程的值。

回過頭來,我們再看Looper.myLooper()方法:

public static Looper myLooper() {
        return sThreadLocal.get();
    }
           

就是通過ThreadLocal擷取目前線程的Looper,判斷Looper.prepared()是否調用,這樣後面的邏輯也想的通了。

順着代碼,我們看一下post()方法,這裡就一次性貼出來了,因為post()、postDelayed()等都是層級傳遞的,比較簡單,最終都是調用的sendMessageAtTime()方法:

public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }

    public final boolean postAtTime(Runnable r, long uptimeMillis)
    {
        return sendMessageAtTime(getPostMessage(r), uptimeMillis);
    }

    public final boolean postDelayed(Runnable r, long delayMillis)
    {
        return sendMessageDelayed(getPostMessage(r), 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;
        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);
    }
           

這些方法其實都是順着調用下來的,還是比較清楚的。

我們看一下最終調用的enqueueMessage方法:

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }
           

這裡它講本身指派給Message的target,是以 target是一個handler對象,然後調用了MessageQueue的enqueueMessage方法:

boolean enqueueMessage(Message msg, long when) {
        if (msg.isInUse()) {
            throw new AndroidRuntimeException(msg + " This message is already in use.");
        }
        if (msg.target == null) {
            throw new AndroidRuntimeException("Message must have a target.");
        }

        synchronized (this) {
            if (mQuitting) {
                RuntimeException e = new RuntimeException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w("MessageQueue", e.getMessage(), e);
                return false;
            }

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

這個方法通過無限循環将Message加入到消息隊列,直到沒有Message即msg.next==null為止,這也就是根據先進先出的原則來進行排隊。但是這裡并不是通過隊列來實作,而是通過連結清單,因為我們設定了delay時間,也就是msg.when,這裡要先判斷時間再進行插入,進而排序。到這裡就沒有進行下一級調用了。

接下來,我們看一下Looper.loop()方法,其實它和Looper.prepared()所對應,同樣系統在主線程中幫我們寫好了,是以我們平常不用自己手動調用。

/**
     * 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.recycle();
        }
    }
           

這裡是一個無限循環,首先擷取目前線程的Looper對象,然後再擷取Looper的消息隊列。通過循環将消息隊列依次操作,直到消息隊列為null,跳出循環。那到底是如何調用我們在handler中寫的方法的呢。注意到:

msg.target.dispatchMessage(msg);
           

而target上面寫道,它就是一個目前Looper的handler:

/**
     * 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是否為null,如果是則調用handleCallback方法。

private static void handleCallback(Message message) {
        message.callback.run();
    }
           

callback是一個Runnable對象,而這邊就是先判斷runnable是否為null,不為null則直接調用runnable的run方法,否則調用handlerMessage方法,而這個方法就是我們自己要重寫的一個空方法,到這裡我們也就知道了handler是如何調用到run 、handlerMessage方法的。 同時還有幾個結論: 1、如果handler的runnable不為null,即使後面handler重寫了handlerMessage方法依然不會執行; 2、調用handler,如果是在子線程必須加Looper .prepared()、Looper.loop();(一般也不會這麼使用) 3、隻有調用了Looper.loop()方法,消息才會進行處理。

。。。。。

還有很多不足,歡迎大家指出。