内存泄漏:实现io.reactivex.Observer的匿名类



我正在做一个Android项目。我的一些活动正在扩展SiteFinderActivity。这个类负责验证当前会话,并通过一些抽象函数将其传递给它的子级。

我使用JakeWharton的RxRelay将结果从ViewModels传递给订阅者,在本例中为SiteFinderActivity。这是我的ViewModel的简化版本。

sealed class AuthState {
object AuthOnError: AuthState()
class AuthOnSuccessToken(val accessToken: String): AuthState()
}
class AuthViewModel(
val context: Context,
val logger: Logger
) {
/**
* Subscribe to this observer in order to be notified when the result is ready.
*/
val relay: PublishRelay<AuthState> = PublishRelay.create()
fun validateToken(): {
// Validation...
// Once it is done then
relay.accept(AuthOnSuccessToken(it))
}
}

活动调用validateToken()并侦听其中继以获取结果。

abstract class SiteFinderActivity : AppCompatActivity() {
abstract fun onAuthenticationError()
abstract fun onAzureAccessTokenReceived(azureAccessToken: String)
private var azureAuthVM: AuthViewModel? = getAzureAuthVM()
private var authObserver: Observer<AuthState>? = createDefaultObserver(logger) {
val weakThis = WeakReference(this@SiteFinderActivity)
if (weakThis.get() == null) return@createDefaultObserver
when (this) {
is AuthOnSuccessToken -> {
//...
onAzureAccessTokenReceived(azureAccessToken)
}
else -> {
onAuthenticationError()
}
}
}
override fun onStart() {
super.onStart()
azureAuthVM?.relay?.safeSubscribe(authObserver)
}
override fun onStop() {
super.onStop()
// We need to nullify it here otherwise it leaks the context
azureAuthVM = null
authObserver = null
}
}

由于我在项目中经常使用这种方法,所以我创建了这个实用函数。在我看来,这就是内存泄漏的根本原因。这在项目中的某个文件中(在活动之外(。

import com.atco.logger.Logger
import io.reactivex.Observer
import io.reactivex.disposables.Disposable
inline fun <T : Any> createDefaultObserver(logger: Logger, crossinline onNext: T.() -> Unit) = object : Observer<T> {
override fun onComplete() {}
override fun onSubscribe(d: Disposable) {}
override fun onNext(t: T) {
onNext(t)
}
override fun onError(e: Throwable) {
logger.logException(e)
}
}

最后,这是Leakanary为我记录的内容。它表明888正在泄漏。我读了很多文件,在Stackoverflow上看了很多答案,但没有意识到到底是什么问题。

====================================
HEAP ANALYSIS RESULT
====================================
1 APPLICATION LEAKS
References underlined with "~~~" are likely causes.
Learn more at https://squ.re/leaks.
204736 bytes retained by leaking objects
Signature: d477dd791e60b0167ba58d241bd7f6ce875a33d4
┬───
│ GC Root: System class
│
├─ android.provider.FontsContract class
│    Leaking: NO (SiteFinderApplication↓ is not leaking and a class is never leaking)
│    ↓ static FontsContract.sContext
├─ com.atco.forsite.app.SiteFinderApplication instance
│    Leaking: NO (SiteFinderApplication↓ is not leaking and Application is a singleton)
│    SiteFinderApplication does not wrap an activity context
│    ↓ SiteFinderApplication.shadow$_klass_
├─ com.atco.forsite.app.SiteFinderApplication class
│    Leaking: NO (a class is never leaking)
│    ↓ static SiteFinderApplication.appComponent
│                                   ~~~~~~~~~~~~
├─ com.atco.forsite.di.DaggerAppComponent instance
│    Leaking: UNKNOWN
│    ↓ DaggerAppComponent.provideAzureAuthTokenProvider
│                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
├─ dagger.internal.DoubleCheck instance
│    Leaking: UNKNOWN
│    ↓ DoubleCheck.instance
│                  ~~~~~~~~
├─ com.atco.auth.AuthViewModel instance
│    Leaking: UNKNOWN
│    ↓ AuthViewModel.relay
│                    ~~~~~
├─ com.jakewharton.rxrelay2.PublishRelay instance
│    Leaking: UNKNOWN
│    ↓ PublishRelay.subscribers
│                   ~~~~~~~~~~~
├─ java.util.concurrent.atomic.AtomicReference instance
│    Leaking: UNKNOWN
│    ↓ AtomicReference.value
│                      ~~~~~
├─ com.jakewharton.rxrelay2.PublishRelay$PublishDisposable[] array
│    Leaking: UNKNOWN
│    ↓ PublishRelay$PublishDisposable[].[0]
│                                       ~~~
├─ com.jakewharton.rxrelay2.PublishRelay$PublishDisposable instance
│    Leaking: UNKNOWN
│    ↓ PublishRelay$PublishDisposable.downstream
│                                     ~~~~~~~~~~
├─ io.reactivex.observers.SafeObserver instance
│    Leaking: UNKNOWN
│    ↓ SafeObserver.downstream
│                   ~~~~~~~~~~
├─ com.atco.forsite.app.activity.SiteFinderActivity$$special$$inlined$createDefaultObserver$1 instance
│    Leaking: UNKNOWN
│    Anonymous class implementing io.reactivex.Observer
│    ↓ SiteFinderActivity$$special$$inlined$createDefaultObserver$1.this$0
│                                                                   ~~~~~~
╰→ com.atco.forsite.screens.splash.StartupActivity instance
​     Leaking: YES (ObjectWatcher was watching this because com.atco.forsite.screens.splash.StartupActivity received Activity#onDestroy() callback and Activity#mDestroyed is true)
​     key = b7c3c771-d399-475a-ab22-a7985eaec020
​     watchDurationMillis = 9874
​     retainedDurationMillis = 4873
====================================
0 LIBRARY LEAKS
Library Leaks are leaks coming from the Android Framework or Google libraries.
====================================
METADATA
Please include this in bug reports and Stack Overflow questions.
Build.VERSION.SDK_INT: 29
Build.MANUFACTURER: Google
LeakCanary version: 2.2
App process name: com.atco.forsite
Analysis duration: 13179 ms
Heap dump file path: /data/user/0/com.atco.forsite/files/leakcanary/2020-03-13_11-58-39_932.hprof
Heap dump timestamp: 1584122335006
====================================

这段代码绝对不正确:

val weakThis = WeakReference(this@SiteFinderActivity)
if (weakThis.get() == null) return@createDefaultObserver

因为创建一个实例的弱引用并立即检查null是没有意义的。如果这里的解决方案是弱引用,那么应该提前创建它,以便回调只保留对弱引用的引用,而不是对活动的引用。

话虽如此,但我认为没有必要提及薄弱之处。这里的关键问题是,在onStop中,您应该清除订阅,而不是仅将observer设置为null。

相关内容

最新更新