如何防止内存泄露 - 1835434698/1835434698.github.io GitHub Wiki

内存泄露多种多样,举例说明一下如何防治内存泄露。

1、Handler内存泄露

还是老生常谈的Handler内存泄露,有些人觉得创建一个Handler没有post或者send消息,或者没有循环处理的消息,只是调用一次就销毁了,不会造成内存泄露的,其实这种想法是不正确的。你没有给Handler写死循环就不代表它执行完你的任务就会销毁。别忘了Handler本身就是一个“死循环”,只是MessageQueue为空的时候looper就休眠了,所以,大部分情况下Handler是不会自动销毁的。举一个例子,某下拉刷新控件的早期版本就有这么一个类PtrFrameLayout(后来作者修复了,直接去掉了Handler),源码


private Handler mHandler = new Handler();

    public void autoRefresh(final boolean atOnce, final int duration) {
     
      	 mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                if (mStatus != PTR_STATUS_INIT) {
                    return;
                }

                mFlag |= atOnce ? FLAG_AUTO_REFRESH_AT_ONCE : FLAG_AUTO_REFRESH_BUT_LATER;

                mStatus = PTR_STATUS_PREPARE;
                if (mPtrUIHandlerHolder.hasHandler()) {
                    mPtrUIHandlerHolder.onUIRefreshPrepare(PtrFrameLayout.this);
                    if (DEBUG) {
                        PtrCLog.i(LOG_TAG, "PtrUIHandler: onUIRefreshPrepare, mFlag %s", mFlag);
                    }
                }
                mScrollChecker.tryToScrollTo(mPtrIndicator.getOffsetToRefresh(), duration);
                if (atOnce) {
                    mStatus = PTR_STATUS_LOADING;
                    performRefresh();
                }
            }
        }, 200);
    }

没有写销毁Handler的方法,因此内存泄露了。在实际项目中用刷新的页面是很多的,如果都用了这个,每一个页面都会内存泄露,一个App就会很严重的泄露。

修复方法:

首先想到的肯定是无侵入性修复方法。

1、view不是有postDelayed方法吗?是的如果是view的话用这个替换Handler即可,如果是ViewGroup的话就不行了。

2、viewGroup不是有一个onDetachedFromWindow方法吗?在这个里面直接销毁Handler多好了。

但是,onDetachedFromWindow方法解释是 This is called when the view is detached from a window.当视图与窗口分离时,将调用此命令。也就是当前view从视图窗口分离出去了,一般情况下分离出去就等于销毁了,但是,如何是有缓存的控件呢比如listView、ViewPage等,他们从视图窗口分离并不等于销毁。因此在onDetachedFromWindow方法里面销毁Handler的时候需要在调用Handler的地方判断一下是否为空。

2、自定义View内存泄露

有些自定义view会需要开启一个线程不停的运行,但是设计者没有考虑到页面关闭时销毁线程,并且很多地方都在调用这个View,如果添加销毁方法需要改动的方法太多。例子

    mThread = new Thread(new Runnable {

        @Override
        public void run() {
            for (float i = 0; i < Integer.MAX_VALUE; ) { // 创建一个死循环,每循环一次i+0.1
                try {
                    for (int j = 0; j < pointers.size(); j++) { // 循环改变每个指针高度
                        float rate = (float) Math.abs(Math.sin(i + j)); // 利用正弦有规律的获取0~1的数。
                        pointers.get(j).setHeight((basePointY - getPaddingTop()) * rate); // rate 乘以 可绘制高度,来改变每个指针的高度
                    }
                    Thread.sleep(pointerSpeed); // 休眠一下下,可自行调节
                    if (isPlaying) { ///这个只会暂停不会停止
                        AndroidThreadExecutor.getInstance().postToMainThread(VoicePlayingView.this::invalidate);
                        i += 0.1;
                    }
                } catch (InterruptedException ignored) {
                }
            }
        }
    });
    mThread.start();

    /**
     * 开始
     */
    public void start() {
        if (!isPlaying) {
            if (mThread == null) {
                mThread = new Thread(new MyRunnable());
                mThread.start();
            }
            isPlaying = true; // 控制子线程中的循环
            isDestory = false;
        }
    }

    /**
     * 暂停
     */
    public void stop() {
        isPlaying = false;
        if (mThread != null) {
            mThread.interrupt();
        }
        invalidate();
    }

修复方法:

首先想到的肯定是无侵入性修复方法。

1、首先考虑到onDetachedFromWindow方法调用的时候可以停止runnable丫,但是不要忘了This is called when the view is detached from a window.当视图与窗口分离时,将调用此命令。也就是当前view从视图窗口分离出去了,一般情况下分离出去就等于销毁了,但是,如何是有缓存的控件呢比如listView、ViewPage等,他们从视图窗口分离并不等于销毁。因此此方法不可用。

然后想到可以模仿AutoDispose,拿到LifecycleOwner来监听生命周期,通过构造方法里面可以拿到context,强转Activity,监听生命周期Activity,当销毁的时候可以停止runnable。很高兴的去实现了。但是,完成后发现不行啊,因为Activity有fragment,fragment销毁了的时候,Activity并没有销毁,还是内存泄露了。。。。

继续找方法,Glide不就可以通过View获取生命周期防止泄露吗?开始撸代码。

private LifecyObserver lifecyObserver;
private void removeObserver() {
    if (lifecyObserver != null){
        ((AppCompatActivity)contextSoftReference.get()).getLifecycle().removeObserver(lifecyObserver);
    }
}

class LifecyObserver implements DefaultLifecycleObserver{
    private SoftReference<VoicePlayingView> softReference;
    public LifecyObserver(VoicePlayingView view){
        softReference = new SoftReference<>(view);
    }

    @Override
    public void onDestroy(@NonNull LifecycleOwner owner) {
        softReference.get().isDestory = true;
        softReference.get().removeObserver();
    }
}

    public VoicePlayingView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        contextSoftReference = new SoftReference<>(context);
        if (lifecyObserver == null){
            if (contextSoftReference.get() instanceof AppCompatActivity){
                lifecyObserver = new LifecyObserver(this);
                Fragment fragment = findSupportFragment(this, (FragmentActivity) contextSoftReference.get());
                if (fragment != null){
                    fragment.getLifecycle().addObserver(lifecyObserver);
                }else {
                    ((AppCompatActivity)contextSoftReference.get()).getLifecycle().addObserver(lifecyObserver);
                }
            }
        }
  }
 

可是发现Fragment为null啊,什么鬼?为什么?静心思考。这构造方法,这个时候View可能还没有绑定到Fragment。那就不在构造方法去设置,继续撸代码。

public void start() {
    if (lifecyObserver == null){
        if (contextSoftReference.get() instanceof AppCompatActivity){
            lifecyObserver = new LifecyObserver(this);
            Fragment fragment = findSupportFragment(this, (FragmentActivity) contextSoftReference.get());
            if (fragment != null){
                fragment.getLifecycle().addObserver(lifecyObserver);
            }else {
                ((AppCompatActivity)contextSoftReference.get()).getLifecycle().addObserver(lifecyObserver);
            }
        }
    }

    if (!isPlaying) {
        if (mThread == null) {
            mThread = new Thread(new MyRunnable());
            mThread.start();
        }
        isPlaying = true; // 控制子线程中的循环
        isDestory = false;
    }
}