首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >内存泄漏:实现io.reactivex.Observer的匿名类

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

Stack Overflow用户
提问于 2020-03-14 03:15:51
回答 1查看 240关注 0票数 0

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

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

代码语言:javascript
复制
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()并侦听其中继以获取结果。

代码语言:javascript
复制
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
    }
}

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

代码语言:javascript
复制
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的答案,但不知道到底是什么问题。

代码语言:javascript
复制
====================================
    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
    ====================================
EN

回答 1

Stack Overflow用户

发布于 2020-03-19 02:23:31

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

代码语言:javascript
复制
    val weakThis = WeakReference(this@SiteFinderActivity)
    if (weakThis.get() == null) return@createDefaultObserver

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

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

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/60676082

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档