On Android, there is a common issue that developers must solve: Deal with heavy load at application start.
The easier solution is to put the initialization on the Application
class MainApplication: Application() {
override fun onCreate() {
super.onCreate()
heavyInitialization()
// Here the initialization is done
ApplicationGraph.initialize(this) // Light initialization
}
}
class MainActivity: Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Here the initialization is done
}
}
This solution is perfecly fine for "light" initializations. Initialization is done synchronously on the MainThread so at the Activity.onCreate()
step, that's fine, your task will be completed.
For heavy job, this solution will slow down your app performances on cold starts (time between Application.onCreate()
and the end of you Activity.onCreate()
and the views inflate. As your initialization is on the mainThread, impossible for you to have a loader.
Moreover, with this solution, anything that wake-up you app will do the job:
- Notification display
- Any BroadCastReceiver
To avoid that, multiple solutions exist.
This solution consists of keeping light in the Application.onCreate()
but to move heavy job and manage it on a WorkerThread
via a LoadingActivity
as first activity of your application.
interface HeavyInitializerManager {
fun initialize()
fun getStatus(): Status
// Crash if not initialized.
fun getResult(): Result
fun addListener(listener: Listener)
fun removeListener(listener: Listener)
interface Listener {
fun onStatusChanged()
}
enum class Status {
IDLE,
INITIALIZING,
INITIALIZED
}
}
class MainApplication: Application() {
override fun onCreate() {
super.onCreate()
ApplicationGraph.initialize(this) // Light initialization
}
}
class LoadingActivity: Activity() {
private val heavyInitializerManager by lazy { ApplicationGraph.getHeavyInitializerManager() }
private val heavyInitializerListener = createHeavyInitializerListener()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
heavyInitializerManager.addListener(heavyInitializerListener)
updateProgress()
}
override fun onDestroy() {
heavyInitializerManager.removeListener(heavyInitializerListener)
}
private fun updateProgress() {
when (heavyInitializerManager.getStatus()) {
HeavyInitializerManager.Status.IDLE,
HeavyInitializerManager.Status.INITIALIZING -> /* Nothing */
HeavyInitializerManager.Status.INITIALIZED -> MainActvity.start(this)
}
}
private fun createHeavyInitializerListener() = object : HeavyInitializerManager.Listener {
override fun onStatusChanged() {
updateProgress()
}
}
}
This solution is perfeclty viable and inside your MainActivity
you will be able to use heavy initialization results.
But be carefull, on Android, when a user go back to the AppLauncher, if your last activity is the MainActivity
, the Android system may kill your app JVM and you will lose your HeavyInitializerManager
initialization.
To deal with that, you will be forced to have this kind of code:
class MainActivity: Activity() {
private val heavyInitializerManager by lazy { ApplicationGraph.getHeavyInitializerManager() }
override fun onCreate(savedInstanceState: Bundle?) {
if (heavyInitializerManager.getStatus() != HeavyInitializerManager.Status.INITIALIZED) {
super.onCreate(null)
LoadingActivity.start(this)
finish() // Will skip onStart onResume onPause onStop BUT will call onDestroy
return
}
super.onCreate(savedInstanceState)
setContentView(/* layout */)
}
}
The super.onCreate(null)
is required to avoid android.app.SuperNotCalledException: Activity did not call through to super.onCreate()
and with null
to avoid your views/fragments DOM to be restored and potentially use headyInitialization results on your views/fragments.
- Loading view full screen on top
- Views/Fragment that are able to work without
HeavyInitializerManager
result and that listen status to update there UI.