我正在做一个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。