性能优化 -- 如何优雅的防止Handler引发的内存泄漏

性能优化 – 如何优雅的防止Handler引发的内存泄漏

目录

  • 性能优化 – 如何优雅的防止Handler引发的内存泄漏
    • 目录
    • Handler内存泄漏问题
    • 解决方案
      • 书中的解决方案
      • 优化版解决方案
        • * 然而此方法其实有一个坑 *
      • 最终优化方案

Handler内存泄漏问题

在《Android高级进阶》一书的性能优化篇中提到若Handler为匿名内部类,则此内部类会持有外部类的引用,如果Message还没有被处理完成,那么Handler对象和外部类就都不会被垃圾回收,进而导致内存泄漏(详见《Android高级进阶》 Handler和内部类的正确用法).

解决方案

书中的解决方案

在《Android高级进阶》一书中给出的解决方案如下

private static class InnerHandler extends Handler {
    private final WeakReference mActivity;

    public InnerHandler(HandlerActivity activity) {
        mActivity = new WeakReference(activity);
    }

    @Override
    public void handleMessage(Message msg) {
        HandlerActivity activity = mActivity.get();
        if (activity != null) {
            // ...
        }
    }
}

private static final Runnable sRunnable = new Runnable() {
    @Override
    public void run() {

    }
};

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    InnerHandler mHandler = new InnerHandler(this);
    mHandler.postDelayed(sRunnable, 1000 * 60 * 5);
}

此方法解决了引用问题导致垃圾回收失败,然而使用过程中会发现几个问题

  • 针对不同的环境每次使用Handler都需要类似这样的创建一个类非常麻烦
  • 由于Handler是静态类,故而使用时只能使用activity实例的方法,也是非常麻烦
  • sRunnable也是静态类,无法很好的和外部类交互

优化版解决方案

先继承Handler创建一个MyHandler类如下

import android.os.Handler;
import android.os.Looper;
import android.os.Message;

import java.lang.ref.WeakReference;


public class MyHandler extends Handler {

    private WeakReference reference;

    public MyHandler(HandlerCallback callback) {
        super();
        init(callback);
    }

    public MyHandler(HandlerCallback callback, Looper looper) {
        super(looper);
        init(callback);
    }

    private void init(HandlerCallback callback) {
        reference = new WeakReference(callback);
    }

    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        HandlerCallback callback = reference.get();
        if (callback != null) {
            callback.handleMessage(msg);
        }
    }

    public static class HandlerRunnable implements Runnable {
        WeakReference reference;

        public HandlerRunnable(Runnable runnable) {
            reference = new WeakReference(runnable);
        }

        @Override
        public void run() {
            Runnable runnable = reference.get();
            if (runnable != null) {
                runnable.run();
            }
        }
    }

    interface HandlerCallback {
        void handleMessage(Message msg);
    }

}

然后在需要的地方使用这个Handler,以Activity为例

import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity implements MyHandler.HandlerCallback {

    private Runnable task = new Runnable() {
        @Override
        public void run() {
            //................
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        MyHandler handler = new MyHandler(this);
        MyHandler.HandlerRunnable runnable = new MyHandler.HandlerRunnable(task);
        handler.postDelayed(runnable, 1000 * 60 * 5);
    }

    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            //....................
        }
    }
}

如此,相对于第一个解决方案就不需要每次使用时都创建一个静态内部Handler类,也解决了第一个解决方案中由于静态类导致的无法很好的和外部类交互的问题.

* 然而此方法其实有一个坑 *

便是若以匿名内部类作为参数传递时

MyHandler handler = new MyHandler(new MyHandler.HandlerCallback() {
        @Override
        public void handleMessage(Message msg) {
            //....................
        }
    });

或者

MyHandler.HandlerRunnable runnable = new MyHandler.HandlerRunnable(new Runnable() {
        @Override
        public void run() {
            //....................
        }
    });
handler.postDelayed(runnable, 1000 * 60 * 5);

将会导致由于匿名内部类没有强引用而被垃圾回收,进而导致接口回调失效,所以必须在外部类加一个强引用

private Runnable task = new Runnable() {
        @Override
        public void run() {
            //................
        }
    };

然后调用

MyHandler.HandlerRunnable runnable = new MyHandler.HandlerRunnable(task);
handler.postDelayed(runnable, 1000 * 60 * 5);

或者外部类继承于接口,将外部类作为参数传入构造方法中

MyHandler handler = new MyHandler(this);

最终优化方案

原本仅停留在第二解决方案的,然而最近却意外的在github的示例项目中发现了一个可以完美解决方案二中缺陷的方法,而LZ是即兴奋又惆怅啊,兴奋有如此妙的方法,惆怅自己木鱼脑袋想不出来,感慨之余,附上此示例项目的github地址https://github.com/vsona/RxJava2RetrofitDemo

WeakHandler下载地址

贴出代码

/*
 * Copyright (c) 2014 Badoo Trading Limited
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 * Portions of documentation in this code are modifications based on work created and
 * shared by Android Open Source Project and used according to terms described in the
 * Apache License, Version 2.0
 */
package net.vsona.common.utils;

import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;

import java.lang.ref.WeakReference;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Memory safer implementation of android.os.Handler
 * 

* Original implementation of Handlers always keeps hard reference to handler in queue of execution. * If you create anonymous handler and post delayed message into it, it will keep all parent class * for that time in memory even if it could be cleaned. *

* This implementation is trickier, it will keep WeakReferences to runnables and messages, * and GC could collect them once WeakHandler instance is not referenced any more *

* * @see Handler * * Created by Dmytro Voronkevych on 17/06/2014. */ @SuppressWarnings("unused") public class WeakHandler { private final Handler.Callback mCallback; // hard reference to Callback. We need to keep callback in memory private final ExecHandler mExec; private Lock mLock = new ReentrantLock(); @SuppressWarnings("ConstantConditions") @VisibleForTesting final ChainedRef mRunnables = new ChainedRef(mLock, null); /** * Default constructor associates this handler bindDisposable the {@link Looper} for the * current thread. * * If this thread does not have a looper, this handler won't be able to receive messages * so an exception is thrown. */ public WeakHandler() { mCallback = null; mExec = new ExecHandler(); } /** * Constructor associates this handler bindDisposable the {@link Looper} for the * current thread and takes a callback interface in which you can handle * messages. * * If this thread does not have a looper, this handler won't be able to receive messages * so an exception is thrown. * * @param callback The callback interface in which to handle messages, or null. */ public WeakHandler(@Nullable Handler.Callback callback) { mCallback = callback; // Hard referencing body mExec = new ExecHandler(new WeakReference<>(callback)); // Weak referencing inside ExecHandler } /** * Use the provided {@link Looper} instead of the default one. * * @param looper The looper, must not be null. */ public WeakHandler(@NonNull Looper looper) { mCallback = null; mExec = new ExecHandler(looper); } /** * Use the provided {@link Looper} instead of the default one and take a callback * interface in which to handle messages. * * @param looper The looper, must not be null. * @param callback The callback interface in which to handle messages, or null. */ public WeakHandler(@NonNull Looper looper, @NonNull Handler.Callback callback) { mCallback = callback; mExec = new ExecHandler(looper, new WeakReference<>(callback)); } /** * Causes the Runnable r to be added to the message queue. * The runnable will be run on the thread to which this handler is * attached. * * @param r The Runnable that will be executed. * * @return Returns true if the Runnable was successfully placed in to the * message queue. Returns false on failure, usually because the * looper processing the message queue is exiting. */ public final boolean post(@NonNull Runnable r) { return mExec.post(wrapRunnable(r)); } /** * Causes the Runnable r to be added to the message queue, to be run * at a specific time given by uptimeMillis. * The time-base is {@link android.os.SystemClock#uptimeMillis}. * The runnable will be run on the thread to which this handler is attached. * * @param r The Runnable that will be executed. * @param uptimeMillis The absolute time at which the callback should run, * using the {@link android.os.SystemClock#uptimeMillis} time-base. * * @return Returns true if the Runnable was successfully placed in to the * message queue. Returns false on failure, usually because the * looper processing the message queue is exiting. Note that a * result of true does not mean the Runnable will be processed -- if * the looper is quit before the delivery time of the message * occurs then the message will be dropped. */ public final boolean postAtTime(@NonNull Runnable r, long uptimeMillis) { return mExec.postAtTime(wrapRunnable(r), uptimeMillis); } /** * Causes the Runnable r to be added to the message queue, to be run * at a specific time given by uptimeMillis. * The time-base is {@link android.os.SystemClock#uptimeMillis}. * The runnable will be run on the thread to which this handler is attached. * * @param r The Runnable that will be executed. * @param uptimeMillis The absolute time at which the callback should run, * using the {@link android.os.SystemClock#uptimeMillis} time-base. * * @return Returns true if the Runnable was successfully placed in to the * message queue. Returns false on failure, usually because the * looper processing the message queue is exiting. Note that a * result of true does not mean the Runnable will be processed -- if * the looper is quit before the delivery time of the message * occurs then the message will be dropped. * * @see android.os.SystemClock#uptimeMillis */ public final boolean postAtTime(Runnable r, Object token, long uptimeMillis) { return mExec.postAtTime(wrapRunnable(r), token, uptimeMillis); } /** * Causes the Runnable r to be added to the message queue, to be run * after the specified amount of time elapses. * The runnable will be run on the thread to which this handler * is attached. * * @param r The Runnable that will be executed. * @param delayMillis The delay (in milliseconds) until the Runnable * will be executed. * * @return Returns true if the Runnable was successfully placed in to the * message queue. Returns false on failure, usually because the * looper processing the message queue is exiting. Note that a * result of true does not mean the Runnable will be processed -- * if the looper is quit before the delivery time of the message * occurs then the message will be dropped. */ public final boolean postDelayed(Runnable r, long delayMillis) { return mExec.postDelayed(wrapRunnable(r), delayMillis); } /** * Posts a message to an object that implements Runnable. * Causes the Runnable r to executed on the next iteration through the * message queue. The runnable will be run on the thread to which this * handler is attached. * This method is only for use in very special circumstances -- it * can easily starve the message queue, cause ordering problems, or have * other unexpected side-effects. * * @param r The Runnable that will be executed. * * @return Returns true if the message was successfully placed in to the * message queue. Returns false on failure, usually because the * looper processing the message queue is exiting. */ public final boolean postAtFrontOfQueue(Runnable r) { return mExec.postAtFrontOfQueue(wrapRunnable(r)); } /** * Remove any pending posts of Runnable r that are in the message queue. */ public final void removeCallbacks(Runnable r) { final WeakRunnable runnable = mRunnables.remove(r); if (runnable != null) { mExec.removeCallbacks(runnable); } } /** * Remove any pending posts of Runnable r bindDisposable Object * token that are in the message queue. If token is null, * all callbacks will be removed. */ public final void removeCallbacks(Runnable r, Object token) { final WeakRunnable runnable = mRunnables.remove(r); if (runnable != null) { mExec.removeCallbacks(runnable, token); } } /** * Pushes a message onto the end of the message queue after all pending messages * before the current time. It will be received in callback, * in the thread attached to this handler. * * @return Returns true if the message was successfully placed in to the * message queue. Returns false on failure, usually because the * looper processing the message queue is exiting. */ public final boolean sendMessage(Message msg) { return mExec.sendMessage(msg); } /** * Sends a Message containing only the what value. * * @return Returns true if the message was successfully placed in to the * message queue. Returns false on failure, usually because the * looper processing the message queue is exiting. */ public final boolean sendEmptyMessage(int what) { return mExec.sendEmptyMessage(what); } /** * Sends a Message containing only the what value, to be delivered * after the specified amount of time elapses. * @see #sendMessageDelayed(Message, long) * * @return Returns true if the message was successfully placed in to the * message queue. Returns false on failure, usually because the * looper processing the message queue is exiting. */ public final boolean sendEmptyMessageDelayed(int what, long delayMillis) { return mExec.sendEmptyMessageDelayed(what, delayMillis); } /** * Sends a Message containing only the what value, to be delivered * at a specific time. * @see #sendMessageAtTime(Message, long) * * @return Returns true if the message was successfully placed in to the * message queue. Returns false on failure, usually because the * looper processing the message queue is exiting. */ public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis) { return mExec.sendEmptyMessageAtTime(what, uptimeMillis); } /** * Enqueue a message into the message queue after all pending messages * before (current time + delayMillis). You will receive it in * callback, in the thread attached to this handler. * * @return Returns true if the message was successfully placed in to the * message queue. Returns false on failure, usually because the * looper processing the message queue is exiting. Note that a * result of true does not mean the message will be processed -- if * the looper is quit before the delivery time of the message * occurs then the message will be dropped. */ public final boolean sendMessageDelayed(Message msg, long delayMillis) { return mExec.sendMessageDelayed(msg, delayMillis); } /** * Enqueue a message into the message queue after all pending messages * before the absolute time (in milliseconds) uptimeMillis. * The time-base is {@link android.os.SystemClock#uptimeMillis}. * You will receive it in callback, in the thread attached * to this handler. * * @param uptimeMillis The absolute time at which the message should be * delivered, using the * {@link android.os.SystemClock#uptimeMillis} time-base. * * @return Returns true if the message was successfully placed in to the * message queue. Returns false on failure, usually because the * looper processing the message queue is exiting. Note that a * result of true does not mean the message will be processed -- if * the looper is quit before the delivery time of the message * occurs then the message will be dropped. */ public boolean sendMessageAtTime(Message msg, long uptimeMillis) { return mExec.sendMessageAtTime(msg, uptimeMillis); } /** * Enqueue a message at the front of the message queue, to be processed on * the next iteration of the message loop. You will receive it in * callback, in the thread attached to this handler. * This method is only for use in very special circumstances -- it * can easily starve the message queue, cause ordering problems, or have * other unexpected side-effects. * * @return Returns true if the message was successfully placed in to the * message queue. Returns false on failure, usually because the * looper processing the message queue is exiting. */ public final boolean sendMessageAtFrontOfQueue(Message msg) { return mExec.sendMessageAtFrontOfQueue(msg); } /** * Remove any pending posts of messages bindDisposable code 'what' that are in the * message queue. */ public final void removeMessages(int what) { mExec.removeMessages(what); } /** * Remove any pending posts of messages bindDisposable code 'what' and whose obj is * 'object' that are in the message queue. If object is null, * all messages will be removed. */ public final void removeMessages(int what, Object object) { mExec.removeMessages(what, object); } /** * Remove any pending posts of callbacks and sent messages whose * obj is token. If token is null, * all callbacks and messages will be removed. */ public final void removeCallbacksAndMessages(Object token) { mExec.removeCallbacksAndMessages(token); } /** * Check if there are any pending posts of messages bindDisposable code 'what' in * the message queue. */ public final boolean hasMessages(int what) { return mExec.hasMessages(what); } /** * Check if there are any pending posts of messages bindDisposable code 'what' and * whose obj is 'object' in the message queue. */ public final boolean hasMessages(int what, Object object) { return mExec.hasMessages(what, object); } public final Looper getLooper() { return mExec.getLooper(); } private WeakRunnable wrapRunnable(@NonNull Runnable r) { //noinspection ConstantConditions if (r == null) { throw new NullPointerException("Runnable can't be null"); } final ChainedRef hardRef = new ChainedRef(mLock, r); mRunnables.insertAfter(hardRef); return hardRef.wrapper; } private static class ExecHandler extends Handler { private final WeakReference mCallback; ExecHandler() { mCallback = null; } ExecHandler(WeakReference callback) { mCallback = callback; } ExecHandler(Looper looper) { super(looper); mCallback = null; } ExecHandler(Looper looper, WeakReference callback) { super(looper); mCallback = callback; } @Override public void handleMessage(@NonNull Message msg) { if (mCallback == null) { return; } final Callback callback = mCallback.get(); if (callback == null) { // Already disposed return; } callback.handleMessage(msg); } } static class WeakRunnable implements Runnable { private final WeakReference mDelegate; private final WeakReference mReference; WeakRunnable(WeakReference delegate, WeakReference reference) { mDelegate = delegate; mReference = reference; } @Override public void run() { final Runnable delegate = mDelegate.get(); final ChainedRef reference = mReference.get(); if (reference != null) { reference.remove(); } if (delegate != null) { delegate.run(); } } } static class ChainedRef { @Nullable ChainedRef next; @Nullable ChainedRef prev; @NonNull final Runnable runnable; @NonNull final WeakRunnable wrapper; @NonNull Lock lock; public ChainedRef(@NonNull Lock lock, @NonNull Runnable r) { this.runnable = r; this.lock = lock; this.wrapper = new WeakRunnable(new WeakReference<>(r), new WeakReference<>(this)); } public WeakRunnable remove() { lock.lock(); try { if (prev != null) { prev.next = next; } if (next != null) { next.prev = prev; } prev = null; next = null; } finally { lock.unlock(); } return wrapper; } public void insertAfter(@NonNull ChainedRef candidate) { lock.lock(); try { if (this.next != null) { this.next.prev = candidate; } candidate.next = this.next; this.next = candidate; candidate.prev = this; } finally { lock.unlock(); } } @Nullable public WeakRunnable remove(Runnable obj) { lock.lock(); try { ChainedRef curr = this.next; // Skipping head while (curr != null) { if (curr.runnable == obj) { // We do comparison exactly how Handler does inside return curr.remove(); } curr = curr.next; } } finally { lock.unlock(); } return null; } } }

你可能感兴趣的:(Android,性能优化)