Room RoomTrackingLiveData导致的内存泄漏

在使用Room RoomTrackingLiveData的时候,发现一个内存泄漏,为了解决RoomTrackingLiveData在一直写数据库的时候,不回调的问题,我自己重写了一个SHRoomTrackingLiveData,正因为重新后再里面加log发现了内存泄漏,Activity/Fragment已经退出了,SHRoomTrackingLiveData还活着,还在一直查数据库,导致一系列的对象泄漏,而且一直运行代码手机发热。
内存泄漏,我们得发现引用关系,才能知道怎么泄漏的,可以用Android profiler抓一下,看下面

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
泄漏原因是一个Runable mRefreshRunable在子线程中执行,它引用了外部类对象SHRoomTrackingLiveData,图中编号1,SHRoomTrackingLiveData中通过mObserver引用了InvalidationTracker.Observer对象,编号2,同时InvalidationTracker.Observer对象被弱引用着。

在这种状态下,我们退出activity,子线程还在执行,上图的引用关系就还在,由于编号2引用的存储,导致编号3引用释放不掉,现在又不断的更新数据库InvalidationTracker.Observer重置mInvalid变量,又导致子线程结束不了,陷入到了循环里,导致内存泄漏。

要解决这个内存泄漏,就得让子线程退出,子线程退出了,SHRoomTrackingLiveData被回收了,编号2引用断了,编号3弱引用自然也就断了。这个是google设计者就这么设计的。
理论上,不密集的写数据库,子线程执行完会自动退出的。
在这里插入图片描述
这个就是编号3弱引用的地方

但是,我们的业务场景是密集的写数据库,所以导致线程退出不了。为了解决这个问题,我们在退出activity的时候,把编号3引用干掉,那么线程最终会退出的。
在这里插入图片描述
在这里插入图片描述
这是改写的地方,可以对照着RoomTrackingLiveData源码看就理解了。
这里说一点,本来想在onInactive中,把编号3引用干掉,结果,导致了新问题。跳转到一个新activity再回来,onInactive是执行的。看注释。其实我们只是想退出activity的时候才干掉引用。

SHRoomTrackingLiveData完整代码

package androidx.room;

import android.annotation.SuppressLint;

import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.WorkerThread;
import androidx.arch.core.executor.ArchTaskExecutor;
import androidx.lifecycle.LiveData;
import androidx.room.InvalidationTracker;
import androidx.room.RoomDatabase;

import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * @Author zhongyili
 * @Date 2022/6/21
 *
 * 模仿RoomTrackingLiveData,Callable执行完都通知LiveData更新数据
 * RoomTrackingLiveData原有逻辑是:Callable执行完,如果是有效的(mInvalid false)才通知LiveData更新数据
 */
public class SHRoomTrackingLiveData<T> extends LiveData<T> {
    @SuppressWarnings("WeakerAccess")
    final RoomDatabase mDatabase;

    @SuppressWarnings("WeakerAccess")
    final Callable<T> mComputeFunction;

    @SuppressWarnings("WeakerAccess")
    final InvalidationTracker.Observer mObserver;

    @SuppressWarnings("WeakerAccess")
    final AtomicBoolean mInvalid = new AtomicBoolean(true);

    @SuppressWarnings("WeakerAccess")
    final AtomicBoolean mComputing = new AtomicBoolean(false);

    @SuppressWarnings("WeakerAccess")
    final AtomicBoolean mRegisteredObserver = new AtomicBoolean(false);

    @SuppressWarnings("WeakerAccess")
    final Runnable mRefreshRunnable = new Runnable() {
        @SuppressLint("RestrictedApi")
        @WorkerThread
        @Override
        public void run() {
            if (mRegisteredObserver.compareAndSet(false, true)) {
                mDatabase.getInvalidationTracker().addObserver(mObserver);
            }
            boolean computed;
            do {
                computed = false;
                // compute can happen only in 1 thread but no reason to lock others.
                if (mComputing.compareAndSet(false, true)) {
                    // as long as it is invalid, keep computing.
                    try {
                        T value = null;
                        while (mInvalid.compareAndSet(true, false)) {
                            computed = true;
                            try {
                                value = mComputeFunction.call();
                            } catch (Exception e) {
                                throw new RuntimeException("Exception while computing database"
                                        + " live data.", e);
                            }
                            if (computed) {
                                postValue(value);
                            }
                        }
                    } finally {
                        // release compute lock
                        mComputing.set(false);
                    }
                }
                // check invalid after releasing compute lock to avoid the following scenario.
                // Thread A runs compute()
                // Thread A checks invalid, it is false
                // Main thread sets invalid to true
                // Thread B runs, fails to acquire compute lock and skips
                // Thread A releases compute lock
                // We've left invalid in set state. The check below recovers.
            } while (computed && mInvalid.get());
        }
    };

    @SuppressWarnings("WeakerAccess")
    final Runnable mInvalidationRunnable = new Runnable() {
        @MainThread
        @Override
        public void run() {
            boolean isActive = hasActiveObservers();
            if (mInvalid.compareAndSet(false, true)) {
                if (isActive) {
                    getQueryExecutor().execute(mRefreshRunnable);
                }
            }
        }
    };
    @SuppressLint("RestrictedApi")
    public SHRoomTrackingLiveData(
            RoomDatabase database,
            Callable<T> computeFunction,
            String[] tableNames) {
        mDatabase = database;
        mComputeFunction = computeFunction;
        mObserver = new InvalidationTracker.Observer(tableNames) {
            @Override
            public void onInvalidated(@NonNull Set<String> tables) {
                ArchTaskExecutor.getInstance().executeOnMainThread(mInvalidationRunnable);
            }
        };
    }

    @Override
    protected void onActive() {
        super.onActive();
        getQueryExecutor().execute(mRefreshRunnable);
    }

    /**
     * !!!!!!!!
     * 这个表示是Inactive 不活跃,并不能代表Activity/Fragment destroy
     * stop的时候,也是inactive
     */
    @Override
    protected void onInactive() {
        super.onInactive();
    }

    public void onDestroy() {
        mDatabase.getInvalidationTracker().removeObserver(mObserver);
        mInvalid.set(false);
    }

    Executor getQueryExecutor() {
        return mDatabase.getQueryExecutor();
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值