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.ext.settings
import org.mozilla.fenix.session.NotificationSessionObserver
import org.mozilla.fenix.session.PerformanceActivityLifecycleCallbacks
import org.mozilla.fenix.session.VisibilityLifecycleCallback
import org.mozilla.fenix.utils.BrowsersCache
import org.mozilla.fenix.utils.Settings
@ -143,6 +144,10 @@ open class FenixApplication : LocaleAwareApplication() {
// if ((System.currentTimeMillis() - settings().lastPlacesStorageMaintenance) > ONE_DAY_MILLIS) {
// runStorageMaintenance()
// }
registerActivityLifecycleCallbacks(
PerformanceActivityLifecycleCallbacks(components.performance.visualCompletenessTaskManager)
)
}
// 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.appcompat.app.ActionBar
import androidx.appcompat.widget.Toolbar
import androidx.core.view.doOnPreDraw
import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavDestination
import androidx.navigation.NavDirections
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.AppBarConfiguration
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.launch
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.ThemeManager
import org.mozilla.fenix.utils.BrowsersCache
import org.mozilla.fenix.utils.StartupTaskManager
@SuppressWarnings("TooManyFunctions", "LargeClass")
open class HomeActivity : LocaleAwareAppCompatActivity() {
@ -77,6 +79,10 @@ open class HomeActivity : LocaleAwareAppCompatActivity() {
lateinit var themeManager: ThemeManager
lateinit var browsingModeManager: BrowsingModeManager
private var isVisuallyComplete = false
private var visualCompletenessTaskManager: StartupTaskManager? = null
private var sessionObserver: SessionManager.Observer? = null
private val hotStartMonitor = HotStartPerformanceMonitor()
@ -107,6 +113,18 @@ open class HomeActivity : LocaleAwareAppCompatActivity() {
setupThemeAndBrowsingMode(getModeFromIntentOrLastKnown(intent))
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)
externalSourceIntentProcessors.any { it.process(intent, navHost.navController, this.intent) }
@ -390,6 +408,15 @@ open class HomeActivity : LocaleAwareAppCompatActivity() {
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 {
const val OPEN_TO_BROWSER = "open_to_browser"
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 EXTRA_DELETE_PRIVATE_TABS = "notification_delete_and_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 clipboardHandler by lazy { ClipboardHandler(context) }
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 kotlinx.coroutines.runBlocking
import mozilla.components.support.migration.FennecMigrator
import org.mozilla.fenix.session.PerformanceActivityLifecycleCallbacks
/**
* An application class which knows how to migrate Fennec data.
*/
class MigratingFenixApplication : FenixApplication() {
init {
PerformanceActivityLifecycleCallbacks.isTransientActivityInMigrationVariant = {
if (it is MigrationDecisionActivity) true else false
}
}
val migrator by lazy {
FennecMigrator.Builder(this, this.components.analytics.crashReporter)
.migrateOpenTabs(this.components.core.sessionManager)