0

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()
    }

}
Dr Mido
  • 2,414
  • 4
  • 32
  • 72
  • Not sure if helps but you may want to use a boolean check in `onResume()` to unsure it run once and once only. Consider putting a counter to debug it or better debug the time for each method took to load. – Darkman Oct 04 '22 at 18:44
  • What is the content of `Utils.hasInternetConnection()`? Looks suspicious since you're calling it on the main thread. – Tenfour04 Oct 04 '22 at 19:59
  • @darkman `onResume()` of activity or fragment? do you think it's called twice? can you please show an example of what you mean? – Dr Mido Oct 04 '22 at 20:05
  • @tenfour04 nothing suspicious it's just a method to check internet availability and load the adview like [this one](https://stackoverflow.com/a/59774931/7639296) – Dr Mido Oct 04 '22 at 20:08

0 Answers0