0

我正在开发一个 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)
    }
}

最后,这是 Leakcanary 为我记录的内容。它表明 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
    ====================================
4

1 回答 1

0

这段代码肯定是不正确的:

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

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

话虽如此,我认为弱引用是不必要的。这里的关键问题是在 onStop 你应该清除订阅而不是仅仅将观察者设置为空。

于 2020-03-18T18:23:31.423 回答