Wednesday, May 16, 2012

Monodroid application initialization and lifecycle in MvvmCross

The startup/setup lifecycle pattern for Android (Monodroid) within MvvmCross was one of the features that I loved and borrowed from within MonoCross.

Basically, the idea was to use a Intent.ActionMain startup Activity as a SplashScreen - and to use the OnCreate and OnResume handlers within that screen to schedule the initialisation of the framework and the "Core" MonoDroid application .

Within MvvmCross this idea is embodied in two classes within every Android UI project:

  • a SplashScreen Activity which inherits from MvxBaseSplashScreenActivity,which kicks off platform initialization in its initial creation and which then calls IMvxStartNavigation when it detects that initialization is complete.
  • Setup class which owns and controls the actual initialization of the application, including registration of all necessary models, services, plugins, viewmodels and views.

This structure has worked well for lots of simple Android apps...

The bad news..

However, I've always known that something wasn't quite right... and yesterday on StackOverflow there was a question about how an Android BroadcastReceiver might live within an MvvmCross application - "How do i initialize the mvvmcross framework without a splash activity?"

The problem here is one of understanding the lifecycle of the Process in which an Android application is hosted. The simple model used initially in MvvmCross is not correct. An Android application is not always started by being called with a "ActionMain" Intent.

As well as this "Main" route:

  • applications can be started on a secondary Intent declared for an Activity within the application manifest
  • applications can be started because a Service, BroadcastReceiver or ContentProvider component has been requested by some other application (often this is also done by Intent)
  • applications can be restarted on any Activity because the Android OS has previously purged them out of memory, but the user has now requested them back in (a bit like hydration after tombstoning in WP7)

Because of these challenges, the SplashScreen model of startup in MvvmCross was a little bit broken.

The good news :)

The good news is that this startup code was genuinely "only a little bit" broken - it wasn't a big change that was needed.

Looking at the startup scenarios above I realised that what was needed was a decoupling of framework startup from the SplashScreen Activity - so that any Activity or other component could request initialization without requiring the SplashScreen to show, and without requiring IMvxStartNavigation to run.

To achieve this:

- I've added a singleton manager for setup to every Android application - the MvxAndroidSetupSingleton  https://github.com/slodge/MvvmCross/blob/master/Cirrious/Cirrious.MvvmCross/Android/Platform/MvxAndroidSetupSingleton.cs

- I've modified the way the application Setup is located - it is now located by convention - so your setup class must be called Setup; it must be derived from MvxBaseAndroidSetup; and it must have a public constructor which takes a Context as a parameter.

- This also means that the old code which provides a Setup creation method within SplashScreenActivity must now be removed from existing applications.

- I've added a check to the OnCreate of every MvxActivityView - so that now every view (with the exception of special SplashScreen views) checks that the Setup/platform is initialized before OnCreate completes:


    var setup = MvxAndroidSetupSingleton.GetOrCreateSetup(activity.ApplicationContext);
    setup.EnsureInitialized(androidView.GetType());


- This same code can now also be added to other Components which require the platform to be initialized - e.g. a Service or a BroadcastReceiver

- The Type parameter to EnsureInitialized does provide developers with a mechanism to override application initialization if they want to. For example, they could choose to initialize only small parts of their app for some Activities, and larger parts for others. The details of exactly how they do this is down to them...

- The current SplashScreen is modified just slightly - but it remains in place to allow you to provide a visually pleasant loading screen while your app loads through "Main".

- The opportunity is also there now for additional SplashScreens to be used for "secondary" Activities - for other Activities which may also loaded using external Intents.

Current state...

The new code is checked in to Master and seems to work well across all samples.

It's not yet tested thoroughly on all the scenarios - so there may be problems. I can see these especially happening in any situations where multiple requests to start components within an application might happen simultaneously.

As we get a few more people building multi-Intent applications, BroadcastReceivers, Services and ContentProviders then we may discover other gaps, problems and opportunities.


Key design consideration - your startup time is important

Regardless of these changes, when you design your application setup code, then please pay careful attention to the time it takes for startup to execute. While the Intent.ActionMain route for your app is "protected" by a SplashScreen, the other startup scenarios for your application will not have the same protection.

And, of course, even when you do have a SplashScreen in place, then startup time - or perceived startup time - can still be a key factor for usability, and for how much your users love your apps.

No comments:

Post a Comment