帶你深入理解Android Handler機制

分類:IT技術 時間:2016-10-17

帶你深入理解android Handler機制


歡迎轉載請註明來源

說到消息機制,我們一定會想到Handler,由於Android系統規定主線程不能阻塞超過5s,否則會出現”Application Not Responding”。也就是說,你不能在主線程中進行耗時操作(網絡請求,數據庫操作等),只能在子線程中進行。下面先來看一下在子線程中訪問UI會出現什麽情況。

  public void click(View v){
    new Thread(new Runnable() {
        @Override
        public void run() {
            mTextView.setText("2");
        }
    }) .start();
}

結果不出意外的報錯:

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

在ViewRootImpl的checkThread()檢查是否是主線程,如果不是拋異常。

那麽這個時候怎麽解決才能在更新UI呢?其實就是用Handler機制啦!

Handler

先來看一下如何改進代碼,然後詳細分析Handler機制。

   public void click(View v){
    new Thread(new Runnable() {
        @Override
        public void run() {
            //拿到message對象
            Message msg = mHandler.obtainMessage();
            msg.arg1 = 2;
            mHandler.sendMessage(msg);
        }
    }) .start();
}

然後在handleMessage中更新UI

   private Handler mHandler =  new Handler(){
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        mTextView.setText(msg.arg1+"");
    }
};

這樣就成功了。

僅僅知道怎麽用那肯定是不夠的,我們還需要知道其背後到底幹了什麽。

我們就從 mHandler.sendMessage(msg)開始說起吧。當我們調用sendMessage時候,其實最終調用的是sendMessageAtTime(msg,long)。此方法源碼如下:

    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()將Message送到MessageQueue中去。那麽MessageQueue是什麽呢?顧名思義是消息隊列,其實在我們創建Handler的時候,它需要與Looper作關聯,Looper類有一個成員變量
MessageQueue mQueue,它就是消息隊列。用來接收Handler發送Message。MessageQueue內部並不是用數組存儲的,而是用鏈表的數據結構,方便添加和刪除。

下面來看一下Looper.looper()源碼,這個方法就是將Message交給Handler.handleMessage去完成的。

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

第3,4行:判斷Looper對象是否為空,如果是空,拋出”No Looper; Looper.prepare() wasn’t called on this thread.”異常。換句話說,如果我們在子線程中創建Handler,並調用sendMessage()時候,由於沒有Looper對象,就會拋此異常信息。我們可以通過Looper.prepare()將當前線程轉為Looper線程。該源碼會在下面分析。

主要看 for (;;)那段代碼,它是個死循環,不斷地執行next()方法,如果有新消息,就交給 msg.target.dispatchMessage(msg);這裏msg.target其實就是Handler對象。那麽下面我們看一下Handler.dispatchMessage(msg)到底幹了什麽。

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

其實看到這裏就明白了,它調用的就是handleMessage(),所以我們就可以輕松的更新UI界面了!

那麽mCallback.handleMessage(msg)是什麽呢?

接下來我們看一下這個代碼:

    private Handler mHandler =  new Handler(new Handler.Callback() {
    @Override
    public boolean handleMessage(Message msg) {
        Toast.makeText(getApplicationContext(),"1",Toast.LENGTH_SHORT).show();
        return false;
    }
}){
    @Override
    public void handleMessage(Message msg) {
        Toast.makeText(getApplicationContext(),"2",Toast.LENGTH_SHORT).show();
    }
};

註意:在第5行我return false,結果吐司展現1,展現完之後再展示2.
當return true時,結果吐司只展現1。這樣我們就可以知道,這其實是用來攔截處理消息的。

剛剛提到Looper.prepare()可以將當前線程轉為Looper線程。那看一下Looper.prepare()源碼

    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.而一個Looper內部維護者MessageQueue,當有消息時Looper從MessageQueue中取出消息交給Handler處理。這樣它們之間就建立起關系了。

看一下源碼中的這行代碼 sThreadLocal.set(new Looper(quitAllowed)); 關於ThreadLocal可以看一下我的這篇文章

http://blog.csdn.net/qq_31609983/article/details/52094032

要想到Looper不斷的從MessageQueue中取消息,就必須調用Looper.loop()來不斷取消息.

在子線程中發送消息的完整代碼如下:

    public void click(View v){
    final Handler handler =  new Handler();
    new Thread(new Runnable() {
        @Override
        public void run() {
            Looper.prepare();
            handler.sendMessage();
            Looper.loop();
        }
    }) .start();
}

註意 Looper.prepare(); handler.sendMessage();這二個方法順序不能變,我們可以看一下Handler構造方法

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

註意這段代碼 mLooper = Looper.myLooper();如果為空,拋異常,該異常意思是必須調用Looper.prepare().這就是說為什麽順序不能改變!

那麽讀者可能會問,我們在主線程創建Handler對象,並沒有調用Looper.prepare()也出什麽問題啊,的確是這樣的,因為ActivityThread 的main()函數裏面 Looper.prepareMainLooper();已經自動幫我們創建好了Looper對象了。看一下源碼:

 public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}

註意sMainLooper = myLooper();看一下myLooper()方法:

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

講了這麽多,估計對於小白來說有點暈了。哈哈。那麽接下來我盜用一張hyman老師的一張圖分析Handler,MessageQueue,Looper之間的關系吧。。。

0160904160222603)

一天,老板正在和員工開會,員工想上廁所,出於禮貌,對老板說我要去上廁所(sendMessage 到 MessageQueue中) 老板思考了一會兒,回復到”你去吧”(Looper.loop()) , 最後員工去WC了(handleMessage) ,從詢問到最後WC都是員工做的事,這就是它們之間的關系了。。。哈哈哈。

其實Handler發送消息有多種方式

  • msg.sendToTarget();
  • mHandler.sendMessageAtFrontOfQueue();
  • mHandler.sendEmptyMessage()
  • mHandler.sendEmptyMessageDelayed()
  • mHandler.post()
  • mHandler.postDelayed()

雖然有多種方法,但本質都是通過Handler.handleMessage()實現的。

還有幾個這裏就不一一列舉了,有興趣的讀者可以去Android官網吧。。。

聲明

感謝《Android開發藝術探索》,感謝慕課網視頻,感謝郭神。
我來附一下鏈接吧,感興趣的話去看一下吧。。。

視頻:

http://www.imooc.com/learn/267

郭神博客:

http://blog.csdn.net/guolin_blog/article/details/9991569

最後:這是筆者第一次寫分析源碼的文章,(估計也是最後一次了吧。。。哈哈哈。。),寫的不好,比較雜亂,還請讀者多多包涵。寫著寫著就過了24點了,即將迎來大三,希望用這篇文章來為準大三生活開個好頭吧!祝大家生活愉快!


Tags: Android 數據庫操作 original private created

文章來源:


ads
ads

相關文章
ads

相關文章

ad