在使用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();
}
}