1
0
Fork 0

For #8034: Create a post-visual completeness executor

Create an object that will execute its enqued tasks
when Fenix is visually complete.
master
Will Hawkins 2020-01-23 03:24:48 -05:00 committed by Emma Malysz
parent af19913a16
commit f9b716286a
7 changed files with 192 additions and 1 deletions

View File

@ -37,6 +37,7 @@ import org.mozilla.fenix.components.Components
import org.mozilla.fenix.components.metrics.MetricServiceType import org.mozilla.fenix.components.metrics.MetricServiceType
import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.session.NotificationSessionObserver import org.mozilla.fenix.session.NotificationSessionObserver
import org.mozilla.fenix.session.PerformanceActivityLifecycleCallbacks
import org.mozilla.fenix.session.VisibilityLifecycleCallback import org.mozilla.fenix.session.VisibilityLifecycleCallback
import org.mozilla.fenix.utils.BrowsersCache import org.mozilla.fenix.utils.BrowsersCache
import org.mozilla.fenix.utils.Settings import org.mozilla.fenix.utils.Settings
@ -143,6 +144,10 @@ open class FenixApplication : LocaleAwareApplication() {
// if ((System.currentTimeMillis() - settings().lastPlacesStorageMaintenance) > ONE_DAY_MILLIS) { // if ((System.currentTimeMillis() - settings().lastPlacesStorageMaintenance) > ONE_DAY_MILLIS) {
// runStorageMaintenance() // runStorageMaintenance()
// } // }
registerActivityLifecycleCallbacks(
PerformanceActivityLifecycleCallbacks(components.performance.visualCompletenessTaskManager)
)
} }
// See https://github.com/mozilla-mobile/fenix/issues/7227 for context. // See https://github.com/mozilla-mobile/fenix/issues/7227 for context.

View File

@ -15,13 +15,14 @@ import androidx.annotation.VisibleForTesting
import androidx.annotation.VisibleForTesting.PROTECTED import androidx.annotation.VisibleForTesting.PROTECTED
import androidx.appcompat.app.ActionBar import androidx.appcompat.app.ActionBar
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.core.view.doOnPreDraw
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavDestination import androidx.navigation.NavDestination
import androidx.navigation.NavDirections import androidx.navigation.NavDirections
import androidx.navigation.fragment.NavHostFragment import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.AppBarConfiguration import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.NavigationUI import androidx.navigation.ui.NavigationUI
import kotlinx.android.synthetic.main.activity_home.navigationToolbarStub import kotlinx.android.synthetic.main.activity_home.*
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import mozilla.components.browser.search.SearchEngine import mozilla.components.browser.search.SearchEngine
@ -69,6 +70,7 @@ import org.mozilla.fenix.settings.logins.SavedLoginsFragmentDirections
import org.mozilla.fenix.theme.DefaultThemeManager import org.mozilla.fenix.theme.DefaultThemeManager
import org.mozilla.fenix.theme.ThemeManager import org.mozilla.fenix.theme.ThemeManager
import org.mozilla.fenix.utils.BrowsersCache import org.mozilla.fenix.utils.BrowsersCache
import org.mozilla.fenix.utils.StartupTaskManager
@SuppressWarnings("TooManyFunctions", "LargeClass") @SuppressWarnings("TooManyFunctions", "LargeClass")
open class HomeActivity : LocaleAwareAppCompatActivity() { open class HomeActivity : LocaleAwareAppCompatActivity() {
@ -77,6 +79,10 @@ open class HomeActivity : LocaleAwareAppCompatActivity() {
lateinit var themeManager: ThemeManager lateinit var themeManager: ThemeManager
lateinit var browsingModeManager: BrowsingModeManager lateinit var browsingModeManager: BrowsingModeManager
private var isVisuallyComplete = false
private var visualCompletenessTaskManager: StartupTaskManager? = null
private var sessionObserver: SessionManager.Observer? = null private var sessionObserver: SessionManager.Observer? = null
private val hotStartMonitor = HotStartPerformanceMonitor() private val hotStartMonitor = HotStartPerformanceMonitor()
@ -107,6 +113,18 @@ open class HomeActivity : LocaleAwareAppCompatActivity() {
setupThemeAndBrowsingMode(getModeFromIntentOrLastKnown(intent)) setupThemeAndBrowsingMode(getModeFromIntentOrLastKnown(intent))
setContentView(R.layout.activity_home) setContentView(R.layout.activity_home)
// Must be after we set the content view
if (isVisuallyComplete) {
rootContainer.doOnPreDraw {
// This delay is temporary. We are delaying 5 seconds until the performance
// team can locate the real point of visual completeness.
it.postDelayed({
visualCompletenessTaskManager!!.start()
}, delay)
}
}
Performance.instrumentColdStartupToHomescreenTime(this) Performance.instrumentColdStartupToHomescreenTime(this)
externalSourceIntentProcessors.any { it.process(intent, navHost.navController, this.intent) } externalSourceIntentProcessors.any { it.process(intent, navHost.navController, this.intent) }
@ -390,6 +408,15 @@ open class HomeActivity : LocaleAwareAppCompatActivity() {
navHost.navController.navigate(action) navHost.navController.navigate(action)
} }
/**
* The root container is null at this point, so let the HomeActivity know that
* we are visually complete.
*/
fun postVisualCompletenessQueue(visualCompletenessTaskManager: StartupTaskManager) {
isVisuallyComplete = true
this.visualCompletenessTaskManager = visualCompletenessTaskManager
}
companion object { companion object {
const val OPEN_TO_BROWSER = "open_to_browser" const val OPEN_TO_BROWSER = "open_to_browser"
const val OPEN_TO_BROWSER_AND_LOAD = "open_to_browser_and_load" const val OPEN_TO_BROWSER_AND_LOAD = "open_to_browser_and_load"
@ -397,5 +424,6 @@ open class HomeActivity : LocaleAwareAppCompatActivity() {
const val PRIVATE_BROWSING_MODE = "private_browsing_mode" const val PRIVATE_BROWSING_MODE = "private_browsing_mode"
const val EXTRA_DELETE_PRIVATE_TABS = "notification_delete_and_open" const val EXTRA_DELETE_PRIVATE_TABS = "notification_delete_and_open"
const val EXTRA_OPENED_FROM_NOTIFICATION = "notification_open" const val EXTRA_OPENED_FROM_NOTIFICATION = "notification_open"
const val delay = 5000L
} }
} }

View File

@ -78,4 +78,5 @@ class Components(private val context: Context) {
val publicSuffixList by lazy { PublicSuffixList(context) } val publicSuffixList by lazy { PublicSuffixList(context) }
val clipboardHandler by lazy { ClipboardHandler(context) } val clipboardHandler by lazy { ClipboardHandler(context) }
val migrationStore by lazy { MigrationStore() } val migrationStore by lazy { MigrationStore() }
val performance by lazy { PerformanceComponent() }
} }

View File

@ -0,0 +1,14 @@
/* 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.components
import org.mozilla.fenix.utils.StartupTaskManager
/**
* Component group for all functionality related to performance.
*/
class PerformanceComponent {
val visualCompletenessTaskManager by lazy { StartupTaskManager() }
}

View File

@ -0,0 +1,56 @@
/* 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.utils
import org.mozilla.gecko.util.ThreadUtils
import java.lang.IllegalStateException
typealias StartupTask = () -> Unit
/**
* A queue of tasks that are performed at specific points during Fenix startup.
*
* This queue contains a list of startup tasks. Each task in the queue will be started once Fenix
* is visually complete.
*
* This class is not thread safe and should only be called from the main thread.
*/
class StartupTaskManager {
private var tasks = mutableListOf<StartupTask>()
private var hasStarted = false
private set
/**
* Add a task to the queue.
* Each task will execute on the main thread.
*
* @param task: The task to add to the queue.
*/
@Synchronized
fun add(task: StartupTask) {
ThreadUtils.assertOnUiThread()
if (hasStarted) {
throw IllegalStateException("New tasks should not be added because queue already " +
"started, and these newly added tasks will not execute.")
}
tasks.add(task)
}
/**
* Start all tasks in the queue. When all the tasks have been started,
* clear the queue.
*/
fun start() {
ThreadUtils.assertOnUiThread()
hasStarted = true
tasks.forEach { it.invoke() }
// Anything captured by the lambda will remain captured if we hold on to these tasks,
// which takes up more memory than we need to.
tasks.clear()
}
}

View File

@ -0,0 +1,79 @@
/* 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.session
import android.app.Activity
import android.app.Application
import android.os.Bundle
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.IntentReceiverActivity
import org.mozilla.fenix.browser.BrowserPerformanceTestActivity
import org.mozilla.fenix.settings.account.AuthIntentReceiverActivity
import org.mozilla.fenix.utils.StartupTaskManager
import org.mozilla.fenix.widget.VoiceSearchActivity
/**
* These callbacks handle binding performance code to the activity lifecycle.
*/
@SuppressWarnings("EmptyFunctionBlock")
class PerformanceActivityLifecycleCallbacks(
private val visualCompletenessTaskManager: StartupTaskManager
) : Application.ActivityLifecycleCallbacks {
override fun onActivityCreated(activity: Activity, bundle: Bundle?) {
ifNecessaryPostVisualCompleteness(activity)
}
/**
* Returns true if it is a terminal activity, or false if it
* an activity used in transition.
*/
private fun isTransientActivity(activity: Activity): Boolean {
// These are the current list of non terminal activites.
// They have been whitelisted in case new activities are added to the application
// to ensure these new activities would not crash the application.
return isTransientActivityInMigrationVariant(activity) ||
(activity is IntentReceiverActivity) ||
(activity is VoiceSearchActivity) ||
(activity is AuthIntentReceiverActivity) ||
(activity is BrowserPerformanceTestActivity)
}
/**
* This starts the StartupTaskManager, eitther delayed or right away, if the activity is a
* terminal activity. If not, do nothing.
*/
private fun ifNecessaryPostVisualCompleteness(activity: Activity) {
fun shouldStartVisualCompletenessQueueImmediately(): Boolean {
return !isTransientActivity(activity)
}
if (activity is HomeActivity) {
// We should delay the visualCompletenessTaskManager when reaching the HomeActivity
// to ensure all tasks are delayed until after visual completeness
activity.postVisualCompletenessQueue(visualCompletenessTaskManager)
} else if (shouldStartVisualCompletenessQueueImmediately()) {
// If we do not go through the home activity, we have to start the tasks
// immediately to avoid spending time implementing it.
visualCompletenessTaskManager.start()
}
}
override fun onActivityStarted(activity: Activity?) {}
override fun onActivityStopped(activity: Activity?) {}
override fun onActivityResumed(activity: Activity?) {}
override fun onActivityPaused(activity: Activity?) {}
override fun onActivitySaveInstanceState(activity: Activity?, bundle: Bundle?) {}
override fun onActivityDestroyed(activity: Activity?) {}
companion object {
/**
* The source files are different from migration and non migration variants.
* We use this property to extend the [isTransientActivity] implementation for the
* migration variants.
*/
var isTransientActivityInMigrationVariant: (Activity) -> Boolean = { false }
}
}

View File

@ -7,11 +7,19 @@ package org.mozilla.fenix
import android.content.Context import android.content.Context
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import mozilla.components.support.migration.FennecMigrator import mozilla.components.support.migration.FennecMigrator
import org.mozilla.fenix.session.PerformanceActivityLifecycleCallbacks
/** /**
* An application class which knows how to migrate Fennec data. * An application class which knows how to migrate Fennec data.
*/ */
class MigratingFenixApplication : FenixApplication() { class MigratingFenixApplication : FenixApplication() {
init {
PerformanceActivityLifecycleCallbacks.isTransientActivityInMigrationVariant = {
if (it is MigrationDecisionActivity) true else false
}
}
val migrator by lazy { val migrator by lazy {
FennecMigrator.Builder(this, this.components.analytics.crashReporter) FennecMigrator.Builder(this, this.components.analytics.crashReporter)
.migrateOpenTabs(this.components.core.sessionManager) .migrateOpenTabs(this.components.core.sessionManager)