While I developing my app, I just noticed lately there's GAP "too long time" between start run the app and displaying the full first view AKA Home fragment It looks like there's something like blocking UI thread causes Lagging / White page when the app starts between 3 to 6 seconds in the real device and sometimes until 10 seconds in the emulator! at the average
The following GIF shows the problem

so I decided to measure the app launch time with two methods
1. The First method is from google at the bottom of the documentation "How to retrieve TTID" section I filtered LogCat for Displayed
and here is the results test of the emulator PIXEL 6 PRO API 30
2022-10-04 17:03:19.684 573-629 ActivityTaskManager system_process I Displayed com.mml.dummyapp_kotlin/.ui.MainActivity: +17s522ms
2022-10-04 17:05:12.091 573-629 ActivityTaskManager system_process I Displayed com.mml.dummyapp_kotlin/.ui.MainActivity: +12s611ms
2022-10-04 17:32:58.893 573-629 ActivityTaskManager system_process I Displayed com.mml.dummyapp_kotlin/.ui.MainActivity: +14s860ms
the result of my real device API 30
2022-10-04 18:04:56.596 1619-2245 ActivityTaskManager pid-1619 I Displayed com.mml.dummyapp_kotlin/.ui.MainActivity: +13s218ms
2022-10-04 18:05:34.236 1619-2245 ActivityTaskManager pid-1619 I Displayed com.mml.dummyapp_kotlin/.ui.MainActivity: +9s882ms
2022-10-04 18:06:53.985 1619-2245 ActivityTaskManager pid-1619 I Displayed com.mml.dummyapp_kotlin/.ui.MainActivity: +23s329ms
the fully drawn results test of the emulator PIXEL 6 PRO API 30
2022-10-04 17:03:19.684 573-629 ActivityTaskManager system_process I Fully drawn com.mml.dummyapp_kotlin/.ui.MainActivity: +17s522ms
2022-10-04 17:05:12.091 573-629 ActivityTaskManager system_process I Fully drawn com.mml.dummyapp_kotlin/.ui.MainActivity: +12s611ms
2022-10-04 17:32:58.894 573-629 ActivityTaskManager system_process I Fully drawn com.mml.dummyapp_kotlin/.ui.MainActivity: +14s860ms
the fully drawn results test of my device
2022-10-04 18:33:24.921 1619-2245 ActivityTaskManager pid-1619 I Fully drawn com.mml.dummyapp_kotlin/.ui.MainActivity: +4s403ms
2022-10-04 18:33:46.506 1619-2245 ActivityTaskManager pid-1619 I Fully drawn com.mml.dummyapp_kotlin/.ui.MainActivity: +3s577ms
2022-10-04 18:33:57.086 1619-2245 ActivityTaskManager pid-1619 I Fully drawn com.mml.dummyapp_kotlin/.ui.MainActivity: +3s459ms
2. The second method I created a var in onCreate of MainActivity var APP_START_TIME: Long = 0
then I put the following code after onViewCreated
of any fragment in the app
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.root.viewTreeObserver.addOnGlobalLayoutListener(object :
ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
binding.root.viewTreeObserver.removeOnGlobalLayoutListener(this)
val launchTime: Long =
System.currentTimeMillis()
.minus((requireActivity() as MainActivity).APP_START_TIME)
Log.e(TAG, "App launch time = $launchTime")
}
})
Log.d(TAG, "onViewCreated: ${postViewModel.finalURL.value.toString()}")
and I got this result
E App launch time = 2161
E App launch time = 2572
E App launch time = 2516
NOTE: This result measures from the onCreate till onViewCreated
the first lagging (White Page time) is not counted
My app contains 2 Activities and 7 fragments, I converted all images from PNG to lightweight, and recommended version Webp, I have also some AdMob components, and I tried to wrap the Adview and other components related to it inside CoroutineScope
with Dispatchers.Unconfined
but this doesn't solve the issue
Here's The full MainActivity code
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
var APP_START_TIME: Long = 0
private lateinit var appBarConfiguration: AppBarConfiguration
private var _binding: ActivityMainBinding? = null
private val binding get() = _binding!!
private lateinit var navController: NavController
private lateinit var postViewModel: PostViewModel
private lateinit var settingsViewModel: SettingsViewModel
private var _navGraph: NavGraph? = null
private val navGraph get() = _navGraph!!
lateinit var adView: AdView
private var adRequest: AdRequest? = null
private val applicationScope = CoroutineScope(Dispatchers.Unconfined)
override fun onDestroy() {
super.onDestroy()
adView.destroy()
adRequest = null
_binding = null
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
APP_START_TIME = System.currentTimeMillis()
adView = AdView(this)
_binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
postViewModel = ViewModelProvider(this)[PostViewModel::
class.java]
settingsViewModel = ViewModelProvider(this)[SettingsViewModel::
class.java]
checkAndApplySelectedMode()
setSupportActionBar(binding.toolbar)
val drawerLayout: DrawerLayout = binding.drawerLayout
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment?
if (navHostFragment != null) {
navController = navHostFragment.navController
}
appBarConfiguration = AppBarConfiguration(
setOf(
R.id.nav_home, R.id.nav_accessory,
R.id.nav_arcade, R.id.nav_fashion,
R.id.nav_food, R.id.nav_heath,
R.id.nav_lifestyle, R.id.nav_sports, R.id.nav_favorites, R.id.settingsFragment
), drawerLayout
)
setupActionBarWithNavController(this, navController, appBarConfiguration)
setupWithNavController(binding.navView, navController)
_navGraph = navController.navInflater.inflate(R.navigation.mobile_navigation)
}
override fun onStart() {
super.onStart()
delayedInit()
}
override fun onPause() {
super.onPause()
adView.pause()
}
override fun onResume() {
super.onResume()
adView.resume()
postViewModel.currentDestination.observe(this) { currentDestination ->
Log.w(TAG, "currentDestination: at first run is $currentDestination")
navGraph.setStartDestination(currentDestination)
navController.graph = navGraph
}
navController.addOnDestinationChangedListener { _, destination, _ ->
Log.d(TAG, "addOnDestinationChangedListener: " + destination.id)
if (destination.id != R.id.settingsFragment
&& destination.id != R.id.aboutFragment
&& destination.id != R.id.privacyPolicyFragment
) {
postViewModel.saveCurrentDestination(destination.id)
}
}
}
private fun requestHomeBanner() {
adRequest = Constants.callAndBuildAdRequest()
adView.adListener = object : AdListener() {
override fun onAdFailedToLoad(adError: LoadAdError) {
Log.e(TAG, "onAdFailedToLoad: ${adError.cause.toString()}")
Log.e(TAG, "onAdFailedToLoad: ${adError.responseInfo.toString()}")
}
}
adRequest?.let { adView.loadAd(it) }
}
private fun checkAndApplySelectedMode() {
lifecycleScope.launch {
settingsViewModel.getSelectedDayNightMode.observe(this@MainActivity) {
Constants.applySelectedDayNightMode(it)
}
}
}
private fun delayedInit() = applicationScope.launch {
binding.adViewContainer.addView(adView)
adView.adUnitId = "ca-app-pub-3940256099942544/6300978111"
adView.setAdSize(Constants.GET_AD_SIZE(this@MainActivity))
val testDeviceIds = listOf("41A475886257209D2D2A19E53879CDBF")
val configuration = RequestConfiguration.Builder().setTestDeviceIds(testDeviceIds).build()
MobileAds.setRequestConfiguration(configuration)
if (Utils.hasInternetConnection(this@MainActivity)) {
requestTheLatestConsentInformation(this@MainActivity)
MobileAds.initialize(this@MainActivity) {
Log.d(TAG, "onInitCompleted")
}
requestHomeBanner()
}
}
override fun onSupportNavigateUp(): Boolean {
return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
}
}