Room 返回的 Livedata GC 后不会触发
Livedata returned by Room won't be triggered after GC
我们的团队正在构建一个受益于 Android Jetpack 的项目。
有演示代码展示了我们面临的问题。这些代码可以在 https://github.com/viseator/TestRoomLivedata
找到
我创建一个 UserDao
:
@Dao
interface UserDao {
@Query("SELECT * FROM user WHERE uid = :uid LIMIT 1")
fun findUserById(uid: String?): Single<User>
@Query("SELECT * FROM user WHERE state = 1 LIMIT 1")
fun findLoginUserWithObserve(): LiveData<User>
@Query("SELECT * FROM user WHERE state =1 LIMIT 1")
fun findLoginUser(): Single<User>
@Update
fun update(vararg user: User)
}
我还创建了一个 kotlin 对象来管理用户状态。
我正在观察 findLoginUserWithObserve()
返回的实时数据,以便在登录用户更改时收到通知:
object AccountManager {
private const val DATA_BASE_NAME = "users"
val TAG = "AccountManager"
fun init(context: Application) {
sDb = databaseBuilder(context, UserDataBase::class.java, DATA_BASE_NAME).build()
sDao = sDb.userDao()
sDao.findLoginUserWithObserve().observeForever {
Log.d(TAG, "notified: $it")
}
}
private lateinit var sDb: UserDataBase
private lateinit var sDao: UserDao
fun findLoginUserWithObserve() = sDao.findLoginUserWithObserve()
fun logoutFlowable(): Single<Boolean> = sDao.findLoginUser().subscribeOn(
Schedulers.io()).map { user ->
user.state = User.STATE_NOT_LOGIN
sDao.update(user)
true
}
fun login(user: User) = logoutFlowable().subscribe({ doLogin(user) }, { doLogin(user) })
private fun doLogin(user: User) = sDao.findUserById(user.uid).subscribeOn(
Schedulers.io()).subscribe({ origin ->
origin.userName = user.userName
origin.state = User.STATE_HAVE_LOGIN
sDao.update(origin)
user.state = User.STATE_HAVE_LOGIN
}, {
user.state = User.STATE_HAVE_LOGIN
sDao.insert(user)
})
}
我通过调用它的 init
方法在 Applicaiton
中初始化 AccountManager
并创建一个演示 activity:
class MainActivity : AppCompatActivity() {
private var i = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
AccountManager.login(User().apply {
userName = "user1"
uid = System.currentTimeMillis().toString()
})
button.setOnClickListener {
AccountManager.login(User().apply {
userName = "user${i++}"
uid = System.currentTimeMillis().toString()
})
}
}
}
我想一旦 AccountManager.login()
被调用,我就会收到通知并打印一条日志消息。但是我们发现GC之后就不会再通知了。 (我们通过 Android Studio Profiler 触发 GC)
Log message
在探索房间生成的 UserDao_Impl
class 之后,我们发现它通过调用 addWeakObserver()
:
创建了一个观察者和 link 数据库
@Override
public LiveData<User> findLoginUserWithObserve() {
final String _sql = "SELECT * FROM user WHERE state = 1 LIMIT 1";
final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 0);
return new ComputableLiveData<User>(__db.getQueryExecutor()) {
private Observer _observer;
@Override
protected User compute() {
if (_observer == null) {
_observer = new Observer("user") {
@Override
public void onInvalidated(@NonNull Set<String> tables) {
invalidate();
}
};
__db.getInvalidationTracker().addWeakObserver(_observer);
}
所以我们想知道为什么房间在这里使用WeakObserver
,这使得房间返回的实时数据不可靠?
PS:我们正在使用 Flowable
在它的 onNext()
中发射实时数据来解决这个问题,onNext()
将按预期每次触发。
在 post 这个问题到 google 问题跟踪器(https://issuetracker.google.com/issues/114833188)之后,我得到了回复:
We don't want to leak the LiveData if it is not used anymore. We could
technically keep adding and removing the observer when LiveData is in
use / not in use; but that might mean missing some events that happens
when LiveData is inactive. We used to do that in the initial
prototypes but became harder to maintain. You should keep a reference
to the LiveData to keep using it. This is the patter we use in all
examples.
因此,只需保留对 room 返回的实时数据的引用,而不仅仅是观察它,现在一切正常。
我们的团队正在构建一个受益于 Android Jetpack 的项目。
有演示代码展示了我们面临的问题。这些代码可以在 https://github.com/viseator/TestRoomLivedata
找到我创建一个 UserDao
:
@Dao
interface UserDao {
@Query("SELECT * FROM user WHERE uid = :uid LIMIT 1")
fun findUserById(uid: String?): Single<User>
@Query("SELECT * FROM user WHERE state = 1 LIMIT 1")
fun findLoginUserWithObserve(): LiveData<User>
@Query("SELECT * FROM user WHERE state =1 LIMIT 1")
fun findLoginUser(): Single<User>
@Update
fun update(vararg user: User)
}
我还创建了一个 kotlin 对象来管理用户状态。
我正在观察 findLoginUserWithObserve()
返回的实时数据,以便在登录用户更改时收到通知:
object AccountManager {
private const val DATA_BASE_NAME = "users"
val TAG = "AccountManager"
fun init(context: Application) {
sDb = databaseBuilder(context, UserDataBase::class.java, DATA_BASE_NAME).build()
sDao = sDb.userDao()
sDao.findLoginUserWithObserve().observeForever {
Log.d(TAG, "notified: $it")
}
}
private lateinit var sDb: UserDataBase
private lateinit var sDao: UserDao
fun findLoginUserWithObserve() = sDao.findLoginUserWithObserve()
fun logoutFlowable(): Single<Boolean> = sDao.findLoginUser().subscribeOn(
Schedulers.io()).map { user ->
user.state = User.STATE_NOT_LOGIN
sDao.update(user)
true
}
fun login(user: User) = logoutFlowable().subscribe({ doLogin(user) }, { doLogin(user) })
private fun doLogin(user: User) = sDao.findUserById(user.uid).subscribeOn(
Schedulers.io()).subscribe({ origin ->
origin.userName = user.userName
origin.state = User.STATE_HAVE_LOGIN
sDao.update(origin)
user.state = User.STATE_HAVE_LOGIN
}, {
user.state = User.STATE_HAVE_LOGIN
sDao.insert(user)
})
}
我通过调用它的 init
方法在 Applicaiton
中初始化 AccountManager
并创建一个演示 activity:
class MainActivity : AppCompatActivity() {
private var i = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
AccountManager.login(User().apply {
userName = "user1"
uid = System.currentTimeMillis().toString()
})
button.setOnClickListener {
AccountManager.login(User().apply {
userName = "user${i++}"
uid = System.currentTimeMillis().toString()
})
}
}
}
我想一旦 AccountManager.login()
被调用,我就会收到通知并打印一条日志消息。但是我们发现GC之后就不会再通知了。 (我们通过 Android Studio Profiler 触发 GC)
Log message
在探索房间生成的 UserDao_Impl
class 之后,我们发现它通过调用 addWeakObserver()
:
@Override
public LiveData<User> findLoginUserWithObserve() {
final String _sql = "SELECT * FROM user WHERE state = 1 LIMIT 1";
final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 0);
return new ComputableLiveData<User>(__db.getQueryExecutor()) {
private Observer _observer;
@Override
protected User compute() {
if (_observer == null) {
_observer = new Observer("user") {
@Override
public void onInvalidated(@NonNull Set<String> tables) {
invalidate();
}
};
__db.getInvalidationTracker().addWeakObserver(_observer);
}
所以我们想知道为什么房间在这里使用WeakObserver
,这使得房间返回的实时数据不可靠?
PS:我们正在使用 Flowable
在它的 onNext()
中发射实时数据来解决这个问题,onNext()
将按预期每次触发。
在 post 这个问题到 google 问题跟踪器(https://issuetracker.google.com/issues/114833188)之后,我得到了回复:
We don't want to leak the LiveData if it is not used anymore. We could technically keep adding and removing the observer when LiveData is in use / not in use; but that might mean missing some events that happens when LiveData is inactive. We used to do that in the initial prototypes but became harder to maintain. You should keep a reference to the LiveData to keep using it. This is the patter we use in all examples.
因此,只需保留对 room 返回的实时数据的引用,而不仅仅是观察它,现在一切正常。