1. 程式人生 > >Handler原始碼詳解及導致記憶體洩漏的分析

Handler原始碼詳解及導致記憶體洩漏的分析

[TOC]

簡介

android的訊息處理有三個核心類:Looper,Handler和Message,
主要接受子執行緒傳送的資料, 並用此資料配合主執行緒更新UI。
部分圖片來至CodingMyWorld部落格,3Q

使用方法

public class LooperThread extends Thread {
    @Override
    public void run() {
        // 將當前執行緒初始化為Looper執行緒
        Looper.prepare();

        // ...其他處理,如例項化handler

        // 開始迴圈處理訊息佇列
Looper.loop(); } }

通過上面兩行核心程式碼,你的執行緒就升級為Looper執行緒了,就具備訊息處理的功能!

Looper.prepare()

這裡寫圖片描述
通過上圖可以看到,現在你的執行緒中有一個Looper物件,它的內部維護了一個訊息佇列MQ。注意,一個Thread只能有一個Looper物件,以下原始碼使用到ThreadLoacal,可以想象成一個執行緒的屬性/變數,想了解更多請點選連結

    public static void prepare() {
        prepare(true);
    }

    private static
void prepare(boolean quitAllowed) { //獲取當前執行緒對應執行緒變數:Looper,重複執行此方法會有如下報錯提示 // //"Only one Looper may be created per thread" if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } //將當前初始化的Looper物件儲存到當前執行緒變數中
sThreadLocal.set(new Looper(quitAllowed)); }

Looper.loop()

這裡寫圖片描述
呼叫loop方法後,Looper執行緒就開始真正工作了,它不斷從自己的MQ中取出隊頭的訊息(也叫任務)執行。

 /**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the loop.
     */
    public static void loop() {

        //獲得當前執行緒的Looper物件
        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();
        }
    }

msg.target.dispatchMessage(msg)解釋

Message類中查詢可以發現

    /*package*/ Handler target;

其實target就是handler物件,那handler是如何和一個Message發生聯絡的,稍等?下面移步Handler原始碼分析

Handler的建立就已經獲取了當前執行緒的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());
            }
        }

        //獲取同一執行緒的Looper
        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;
    }

真正Message和Handler關聯的地方來了

在我們sendMessage()的時候都是先用obtainMessage 來獲取一個Message

    public final Message obtainMessage()
    {
        return Message.obtain(this);
    }

移步Message的方法看詳細

    public static Message obtain(Handler h) {
        Message m = obtain();

        //是不是so easy,真正的聯絡在這裡
        m.target = h;

        return m;
    }

如果你是使用post(Runnable r) 來發送訊息的,那應該構造一個Message來發出去,不信可以看原始碼
Handler類中:

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

    /**
    *構造一個Message
    */
    private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();

        //與PostMessage不同的是這個runable是給了callback屬性
        m.callback = r;
        return m;
    }

具體訊息處理:Handler處理訊息

    /**
     * Handle system messages here.
     */
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {

            //處理Runable訊息
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }

            //處理具體Message,這個是你在訊息處理的時候重寫的方法實現
            handleMessage(msg);
        }
    }

總結

  1. Lopper–訊息的集合和訊息的迴圈
  2. Handler–訊息的管理介面和訊息的處理
  3. 主執行緒已經持有Looper,所以不需要Looper.prepare()
    Activity原始碼檢視:
    /*package*/ ActivityThread mMainThread;

ActivityThread原始碼中檢視:

    final Looper mLooper = Looper.myLooper();

導致記憶體洩漏的分析

記憶體洩漏場景

  1. 在一個activity中post已個message
  2. 關閉這個activity
  3. 由於某些原因這個message開始執行或者正在執行(如上一個message比較耗時/當前message比較耗時),
    更嚴重的是你傳送一個延時訊息前把activity關閉
    參考程式碼
private Handler mLeakHandler=new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            Logger.d(msg.toString());
        }
    };
    @Override
    protected void onResume() {
        super.onResume();

        //延遲10s來模擬場景
        mLeakHandler.sendEmptyMessageDelayed(0x1,10000);
        finish();
    }
    //省略其他程式碼

分析及修改方法

由於這個Handler作為內部類宣告在Activity內部,普通的內部類物件隱式地儲存了一個指向外部類物件的引用,所以這個Handler物件儲存了一個指向Activity物件的引用。而這個Handler物件的生命週期可能比Activity生命週期長,比如當有一個後臺執行緒持有該Handler,且該執行緒在執行一個長時間任務。Handler通過傳送Message與主執行緒互動,Message發出之後是儲存在MessageQueue中的,有些Message也不是馬上就被處理的。在Message中存在一個 target,是Handler的一個引用,如果Message在Queue中存在的時間越長,就會導致Handler無法被回收。如果Handler是非靜態的,則會導致Activity不會被回收。但是注意這個洩漏時臨時的!當這個訊息處理完引用關係也就不存在了,下次GC的時候也就能回收啦
修改方法:

private static class MyHandler extends Handler {
        private WeakReference<Activity> reference;

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

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            LeakActivity activity = (LeakActivity) reference.get();
            if (activity != null) {
                Logger.d("activity != null"+activity.toString());
            } else {
                Logger.d("activity = null");
            }
        }
    }

    private final Handler mHandler = new MyHandler(this);

同時你需要在呼叫一下方法,避免不必要的回撥(雖然不會報錯了)

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mHandler.removeCallbacksAndMessages(null);
    }

參考文獻: