diff --git a/app/build.gradle b/app/build.gradle
index 203b12f4fe..a12c3b4aad 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -145,6 +145,8 @@ dependencies {
implementation Deps.mozilla_ui_icons
implementation Deps.mozilla_lib_crash
+ debugImplementation Deps.leakcanary
+ releaseImplementation Deps.leakcanary_noop
testImplementation Deps.junit
androidTestImplementation Deps.tools_test_runner
diff --git a/app/src/debug/AndroidManifest.xml b/app/src/debug/AndroidManifest.xml
new file mode 100644
index 0000000000..f3c91aa4e8
--- /dev/null
+++ b/app/src/debug/AndroidManifest.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/debug/java/org/mozilla/fenix/DebugFenixApplication.kt b/app/src/debug/java/org/mozilla/fenix/DebugFenixApplication.kt
new file mode 100644
index 0000000000..b34ab9ae19
--- /dev/null
+++ b/app/src/debug/java/org/mozilla/fenix/DebugFenixApplication.kt
@@ -0,0 +1,43 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ License, v. 2.0. If a copy of the MPL was not distributed with this
+ file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.fenix
+
+import android.content.Context
+import android.content.SharedPreferences
+import androidx.preference.PreferenceManager
+import com.squareup.leakcanary.AndroidHeapDumper
+import com.squareup.leakcanary.HeapDumper
+import com.squareup.leakcanary.LeakCanary
+import com.squareup.leakcanary.internal.LeakCanaryInternals
+import org.mozilla.fenix.R.string.pref_key_leakcanary
+import org.mozilla.fenix.ext.getPreferenceKey
+import java.io.File
+
+class DebugFenixApplication : FenixApplication() {
+
+ private var heapDumper: ToggleableHeapDumper? = null
+
+ override fun setupLeakCanary() {
+ val leakDirectoryProvider = LeakCanaryInternals.getLeakDirectoryProvider(this)
+ val defaultDumper = AndroidHeapDumper(this, leakDirectoryProvider)
+ heapDumper = ToggleableHeapDumper(this, defaultDumper)
+ LeakCanary.refWatcher(this)
+ .heapDumper(heapDumper)
+ .buildAndInstall()
+ }
+
+ override fun toggleLeakCanary(newValue: Boolean) {
+ heapDumper?.enabled = newValue
+ }
+
+ internal class ToggleableHeapDumper(
+ private val context: Context,
+ private val defaultDumper: HeapDumper
+ ) : HeapDumper {
+ var prefs: SharedPreferences? = PreferenceManager.getDefaultSharedPreferences(context)
+ var enabled = prefs?.getBoolean(context.getPreferenceKey(pref_key_leakcanary), true) ?: true
+ override fun dumpHeap(): File = if (enabled) defaultDumper.dumpHeap() else HeapDumper.RETRY_LATER
+ }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/FenixApplication.kt b/app/src/main/java/org/mozilla/fenix/FenixApplication.kt
index d3908dafdf..068399fcec 100644
--- a/app/src/main/java/org/mozilla/fenix/FenixApplication.kt
+++ b/app/src/main/java/org/mozilla/fenix/FenixApplication.kt
@@ -4,8 +4,10 @@
package org.mozilla.fenix
+import android.annotation.SuppressLint
import android.app.Application
import android.content.Context
+import com.squareup.leakcanary.LeakCanary
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
@@ -19,7 +21,8 @@ import mozilla.components.support.base.log.sink.AndroidLogSink
import org.mozilla.fenix.components.Components
import java.io.File
-class FenixApplication : Application() {
+@SuppressLint("Registered")
+open class FenixApplication : Application() {
lateinit var fretboard: Fretboard
val components by lazy { Components(this) }
@@ -28,11 +31,23 @@ class FenixApplication : Application() {
super.onCreate()
Log.addSink(AndroidLogSink())
+ if (LeakCanary.isInAnalyzerProcess(this)) {
+ return // don't perform extra init in analyzer
+ }
+ setupLeakCanary()
setupCrashReporting()
setupGlean(this)
loadExperiments()
}
+ protected open fun setupLeakCanary() {
+ // no-op, LeakCanary is disabled by default
+ }
+
+ open fun toggleLeakCanary(newValue: Boolean) {
+ // no-op, LeakCanary is disabled by default
+ }
+
private fun setupGlean(context: Context) {
Glean.initialize(context)
Glean.setUploadEnabled(BuildConfig.TELEMETRY)
diff --git a/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt
index 220c3cd354..2cc085f388 100644
--- a/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt
+++ b/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt
@@ -13,8 +13,11 @@ import androidx.navigation.Navigation
import androidx.preference.Preference
import androidx.preference.Preference.OnPreferenceClickListener
import androidx.preference.PreferenceFragmentCompat
+import org.mozilla.fenix.BuildConfig
+import org.mozilla.fenix.FenixApplication
import org.mozilla.fenix.R
import org.mozilla.fenix.R.string.pref_key_about
+import org.mozilla.fenix.R.string.pref_key_leakcanary
import org.mozilla.fenix.R.string.pref_key_make_default_browser
import org.mozilla.fenix.ext.getPreferenceKey
import org.mozilla.fenix.ext.requireComponents
@@ -39,13 +42,24 @@ class SettingsFragment : PreferenceFragmentCompat() {
private fun setupPreferences() {
val makeDefaultBrowserKey = context?.getPreferenceKey(pref_key_make_default_browser)
val aboutKey = context?.getPreferenceKey(pref_key_about)
+ val leakKey = context?.getPreferenceKey(pref_key_leakcanary)
val preferenceMakeDefaultBrowser = findPreference(makeDefaultBrowserKey)
val preferenceAbout = findPreference(aboutKey)
+ val preferenceLeakCanary = findPreference(leakKey)
preferenceMakeDefaultBrowser.onPreferenceClickListener =
getClickListenerForMakeDefaultBrowser()
preferenceAbout.onPreferenceClickListener = getAboutPageListener()
+
+ preferenceLeakCanary.isVisible = BuildConfig.DEBUG
+ if (BuildConfig.DEBUG) {
+ preferenceLeakCanary.onPreferenceChangeListener =
+ Preference.OnPreferenceChangeListener { _, newValue ->
+ (context?.applicationContext as FenixApplication).toggleLeakCanary(newValue as Boolean)
+ true
+ }
+ }
}
private val defaultClickListener = OnPreferenceClickListener { preference ->
diff --git a/app/src/main/res/values/preference_keys.xml b/app/src/main/res/values/preference_keys.xml
index 0b90d11c23..6f4a5b0530 100644
--- a/app/src/main/res/values/preference_keys.xml
+++ b/app/src/main/res/values/preference_keys.xml
@@ -18,4 +18,5 @@
pref_key_sign_in
pref_key_private_mode
pref_key_theme
+ pref_key_leakcanary
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index f2802a0f6d..34e7445f57 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -105,6 +105,8 @@
Language
Data choices
+
+ Leak Canary
diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml
index 8e96bfc892..90f96bb705 100644
--- a/app/src/main/res/xml/preferences.xml
+++ b/app/src/main/res/xml/preferences.xml
@@ -64,6 +64,10 @@
android:icon="@drawable/ic_data_collection"
android:key="@string/pref_key_data_choices"
android:title="@string/preferences_data_choices" />
+
entry.value.onComplete() }
buses.remove(owner)
}
@@ -113,8 +113,7 @@ class ActionBusFactory private constructor(val owner: LifecycleOwner) {
}
/**
- * getDestroyObservable observes to Lifecycle owner and fires when
- * lifecycle.currentState == Lifecycle.State.DESTROYED
+ * getDestroyObservable observes to Lifecycle owner and fires during ON_STOP
*/
fun getDestroyObservable(): Observable {
return owner.createDestroyObservable()
@@ -141,8 +140,6 @@ inline fun LifecycleOwner.getManagedEmitter(): Observer
/**
* This method returns a destroy observable that can be passed to [org.mozilla.fenix.mvi.UIView]s as needed.
- * This is deliberately scoped to the attached [LifecycleOwner]'s [Lifecycle.Event.ON_DESTROY]
- * because a viewholder can be reused across adapter destroys.
*/
inline fun LifecycleOwner?.createDestroyObservable(): Observable {
return Observable.create { emitter ->
@@ -152,7 +149,7 @@ inline fun LifecycleOwner?.createDestroyObservable(): Observable {
return@create
}
this.lifecycle.addObserver(object : LifecycleObserver {
- @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
+ @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun emitDestroy() {
if (emitter.isDisposed) {
emitter.onNext(kotlin.Unit)
diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt
index 2378c34e74..841102b191 100644
--- a/buildSrc/src/main/java/Dependencies.kt
+++ b/buildSrc/src/main/java/Dependencies.kt
@@ -10,6 +10,7 @@ private object Versions {
const val rxKotlin = "2.3.0"
const val anko = "0.10.8"
const val sentry = "1.7.10"
+ const val leakcanary = "1.6.3"
const val androidx_appcompat = "1.1.0-alpha02"
const val androidx_constraint_layout = "2.0.0-alpha2"
@@ -90,6 +91,8 @@ object Deps {
const val mozilla_support_ktx = "org.mozilla.components:support-ktx:${Versions.mozilla_android_components}"
const val sentry = "io.sentry:sentry-android:${Versions.sentry}"
+ const val leakcanary = "com.squareup.leakcanary:leakcanary-android:${Versions.leakcanary}"
+ const val leakcanary_noop = "com.squareup.leakcanary:leakcanary-android-no-op:${Versions.leakcanary}"
const val junit = "junit:junit:${Versions.junit}"
const val tools_test_runner = "com.android.support.test:runner:${Versions.test_tools}"