Android原始碼解析Handler系列第(四)篇 --- 打破Handler那些困惑事兒

yangxi_001發表於2016-12-29

Handler這個總共有4篇,這是最後一篇,前面三篇部落格,我們從原始碼的層面將Handler訊息機制梳理了一遍,現在回頭再看當時作為小白使用Hander所遇到的一些問題,顯然,這些問題基本都可以做一個解釋了。

Android原始碼解析Handler系列第(一)篇 --- Message全域性池
Android原始碼解析Handler系列第(二)篇 --- ThreadLocal詳解
Android原始碼解析Handler系列第(三)篇 --- 深入瞭解Android的訊息機制

-1、為什麼不能在子執行緒執行緒中更新UI?
這個答案可以在ViewRootImpl中找到,ViewRootImpl對UI的操作做了驗證,如果不是主執行緒,就會丟擲CalledFromWrongThreadException。

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

進一步思考,為什麼ViewRootImpl要做checkThread的操作呢,因為多執行緒併發訪問UI是不安全的,但是如果用同步鎖不僅會降低效能,也會使邏輯變得複雜,所以就採用單執行緒模型,這活交給了主執行緒來幹!

-2 、下面這段程式碼為什麼會報錯?

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

建立Handler的時候,就會呼叫Looper.myLooper()去獲取Looper物件。myLooper方法中會通過sThreadLocal.get()獲取返回,沒有獲取到就丟擲RuntimeException。

 public Handler(Callback callback, boolean async) {
        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;
    }

每一個執行緒都需要有自己的Looper,主執行緒在ActivityThread的main方法中預設給我們建立了Looper。對於子執行緒需要我們自己建立。解決方式如下:

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

這樣子執行緒就有了自己的Looper,也有了自己的MessageQueue。但是這樣還是有問題的,當你用handler傳送一個訊息,並不能收到訊息的回撥,因為你沒有啟動訊息迴圈,只有呼叫Looper.loop()之後,Looper才能不斷的從訊息佇列中取出訊息交給Handler分發處理。即最終的解決方案是這樣的。

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

-3、使用Handler為什麼會容易記憶體洩露?
下面是一段“常規”寫法

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

因為內部類會有一個指向外部類的引用(這個Handler又持有Activity的引用,就導致該Activity無法被回收)。垃圾回收機制中約定,當記憶體中的一個物件的引用計數為0時,將會被回收Handler作為Android上的非同步訊息處理機制(好吧,我大多用來進行worker thread與UI執行緒同步),它的工作是需要Looper和MessageQueue配合的。簡單的說,要維護一個迴圈體(Looper)處理訊息佇列(MessageQueue)。每迴圈一次就從MessageQueue中取出一個Message,然後回撥相應的訊息處理函式。如果,迴圈體中有訊息未處理(Message排隊中),那麼Handler會一直存在,那麼Handler的外部類(通常是Activity)的引用計數一直不會是0,所以那個外部類就不能被垃圾回收。很多人會遇到activity的onDestroy方法一直不執行就是這個原因。

解決方案

static class MyHandler extends Handler {
    WeakReference<Activity > mActivityReference;

    MyHandler(Activity activity) {
        mActivityReference= new WeakReference<Activity>(activity);
    }

    @Override
    public void handleMessage(Message msg) {
        final Activity activity = mActivityReference.get();
        if (activity != null) {
            mImageView.setImageBitmap(mBitmap);
        }
    }
}

Please accept mybest wishes for your happiness and success

參考連結:http://blog.csdn.net/lincyang/article/details/46875157



文/Looper景(簡書作者)
原文連結:http://www.jianshu.com/p/338cce832cc9
著作權歸作者所有,轉載請聯絡作者獲得授權,並標註“簡書作者”。

相關文章