Launch Screens: From a Tap to Your App

Launch Screens: From a Tap to Your App

The launch screen is a user’s first experience of your application and, hence, should be designed with great care. In this talk, we will deep dive into the concept of launch screen, discover how to measure, debug & optimise them efficiently, and learn more about how to implement them correctly. In other words, this talk is all about discussing for ±45 min about screens displayed less than 5 seconds.

E9bf8f6d5480ea2a2623df7dccfd1f70?s=128

Cyril Mottier

June 17, 2016
Tweet

Transcript

  1. @cyrilmottier Launch screens From a Tap to your App

  2. 38 sec average interaction time

  3. 23 % apps used only once 38 sec average interaction

    time
  4. 13 launches per month 23 % apps used only once

    38 sec average interaction time Source: Mobile Life Research Centre, University of Stockholm & Localytics
  5. Polish your launching experience

  6. None
  7. Placeholder UI /ˈpleɪshəʊldə(r) juː-aɪ/ Display core structural elements (status bar,

    app bar, etc.) without content until the app has loaded.
  8. Branded launch /ˈbrændɪd lɔːntʃ/ Display your logo or other elements

    that improve brand recognition to portray a brand while the app is loading.
  9. Branded launch Google Maps Placeholder UI Settings

  10. Branded launch Google Maps Placeholder UI Settings your side Choose

  11. Branded launch Google Maps Placeholder UI Settings

  12. Yes! A launch screen is intentionally boring

  13. Source: iOS Human Interface Guidelines, Apple If you think that

    following these guidelines will result in a plain, boring launch image, you’re right. Remember, the launch image doesn’t provide you with an opportunity for artistic expression. It’s solely intended to enhance the user’s perception of your app as quick to launch and immediately ready for use. “
  14. Timeline of a launch

  15. Timeline of a launch Slowed down version

  16. User taps on icon Timeline of a launch 1 2

    3 4 5
  17. User taps on icon Timeline of a launch 1 2

    Fit to screen animation starts 3 4 5
  18. User taps on icon Timeline of a launch 1 2

    Fit to screen animation starts 3 4 5 Fit to screen animation ends
  19. User taps on icon Timeline of a launch 1 2

    Fit to screen animation starts 3 4 5 Fit to screen animation ends Window switching starts
  20. User taps on icon Timeline of a launch 1 2

    Fit to screen animation starts 3 4 5 Fit to screen animation ends Window switching starts App content finishes loading
  21. User taps on icon Timeline of a launch 1 2

    Fit to screen animation starts 3 4 5 Fit to screen animation ends Window switching starts App content finishes loading
  22. 1 2 3 4 5 Launcher & Intent triggering Entering

    animation Static launch screen Content switching / fading Application fully drawn 1 2 3 4 5
  23. 1 2 3 5 System App Launcher & Intent triggering

    Entering animation Static launch screen Content switching / fading Application fully drawn 1 2 3 4 5 4
  24. 5 The perfect launch 1 2 3 4

  25. 1 Load and launch the app 2 Display a starting/preview

    window 3 Create the app process 4 Create the app object 5 Launch the main thread 6 Create the main activity 7 Inflate views 8 Lay out the screen 9 Perform the initial draw
  26. 1 Load and launch the app 2 Display a starting/preview

    window 3 Create the app process 4 Create the app object 5 Launch the main thread 6 Create the main activity 7 Inflate views 8 Lay out the screen 9 Perform the initial draw
  27. System 1 Load and launch the app 2 Display a

    starting/preview window 3 Create the app process 4 Create the app object 5 Launch the main thread 6 Create the main activity 7 Inflate views 8 Lay out the screen 9 Perform the initial draw
  28. System App 1 Load and launch the app 2 Display

    a starting/preview window 3 Create the app process 4 Create the app object 5 Launch the main thread 6 Create the main activity 7 Inflate views 8 Lay out the screen 9 Perform the initial draw
  29. System App Cold start 1 Load and launch the app

    2 Display a starting/preview window 3 Create the app process 4 Create the app object 5 Launch the main thread 6 Create the main activity 7 Inflate views 8 Lay out the screen 9 Perform the initial draw
  30. System App Warm start 1 Load and launch the app

    2 Display a starting/preview window 3 Create the app process 4 Create the app object 5 Launch the main thread 6 Create the main activity 7 Inflate views 8 Lay out the screen 9 Perform the initial draw
  31. PhoneWindowManager.java Context context = mContext; try { context = context.createPackageContext(packageName,

    0); context.setTheme(theme); } catch (PackageManager.NameNotFoundException e) { // Ignore } final PhoneWindow win = new PhoneWindow(context); win.setType(WindowManager.LayoutParams.TYPE_APPLICATION_STARTING); win.setFlags(/*...*/); final WindowManager.LayoutParams params = win.getAttributes(); // Tweak params ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)). addView(win.getDecorView(), params);
  32. PhoneWindowManager.java Context context = mContext; try { context = context.createPackageContext(packageName,

    0); context.setTheme(theme); } catch (PackageManager.NameNotFoundException e) { // Ignore } final PhoneWindow win = new PhoneWindow(context); win.setType(WindowManager.LayoutParams.TYPE_APPLICATION_STARTING); win.setFlags(/*...*/); final WindowManager.LayoutParams params = win.getAttributes(); // Tweak params ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)). addView(win.getDecorView(), params); Create Context for the started application context = context.createPackageContext(packageName, 0);
  33. PhoneWindowManager.java Context context = mContext; try { context = context.createPackageContext(packageName,

    0); context.setTheme(theme); } catch (PackageManager.NameNotFoundException e) { // Ignore } final PhoneWindow win = new PhoneWindow(context); win.setType(WindowManager.LayoutParams.TYPE_APPLICATION_STARTING); win.setFlags(/*...*/); final WindowManager.LayoutParams params = win.getAttributes(); // Tweak params ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)). addView(win.getDecorView(), params); Set the target theme context.setTheme(theme);
  34. PhoneWindowManager.java Context context = mContext; try { context = context.createPackageContext(packageName,

    0); context.setTheme(theme); } catch (PackageManager.NameNotFoundException e) { // Ignore } final PhoneWindow win = new PhoneWindow(context); win.setType(WindowManager.LayoutParams.TYPE_APPLICATION_STARTING); win.setFlags(/*...*/); final WindowManager.LayoutParams params = win.getAttributes(); // Tweak params ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)). addView(win.getDecorView(), params); final PhoneWindow win = new PhoneWindow(context); win.setType(WindowManager.LayoutParams.TYPE_APPLICATION_STARTING); win.setFlags(/*...*/); Create new Window and set it up final WindowManager.LayoutParams params = win.getAttributes(); // Tweak params
  35. PhoneWindowManager.java Context context = mContext; try { context = context.createPackageContext(packageName,

    0); context.setTheme(theme); } catch (PackageManager.NameNotFoundException e) { // Ignore } final PhoneWindow win = new PhoneWindow(context); win.setType(WindowManager.LayoutParams.TYPE_APPLICATION_STARTING); win.setFlags(/*...*/); final WindowManager.LayoutParams params = win.getAttributes(); // Tweak params ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)). addView(win.getDecorView(), params); ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)). addView(win.getDecorView(), params); Display the newly created Window
  36. Same look, different process …and that explains a lot

  37. $ adb logcat | grep “ActivityManager” ActivityManager: Displayed com.launchtime/.LaunchTimeActivity: +666ms

    API 19+
  38. None
  39. public class LaunchTimeActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<Void> {
 
 private

    static final int LOADER_YO = 0xcafe;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_launch_time); 
 getSupportLoaderManager().initLoader(LOADER_YO, null, this);
 }
 
 @Override
 public void onLoadFinished(Loader<Void> loader, Void data) {
 switch (loader.getId()) {
 case LOADER_YO:
 // ...
 break;
 }
 reportFullyDrawn();
 }
 // ... }
  40. public class LaunchTimeActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<Void> {
 
 private

    static final int LOADER_YO = 0xcafe;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_launch_time); 
 getSupportLoaderManager().initLoader(LOADER_YO, null, this);
 }
 
 @Override
 public void onLoadFinished(Loader<Void> loader, Void data) {
 switch (loader.getId()) {
 case LOADER_YO:
 // ...
 break;
 }
 reportFullyDrawn();
 }
 // ... } reportFullyDrawn();
  41. public class LaunchTimeActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<Void> {
 
 private

    static final int LOADER_YO = 0xcafe;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_launch_time); 
 getSupportLoaderManager().initLoader(LOADER_YO, null, this);
 }
 
 @Override
 public void onLoadFinished(Loader<Void> loader, Void data) {
 switch (loader.getId()) {
 case LOADER_YO:
 // ...
 break;
 }
 reportFullyDrawn();
 }
 // ... } SecurityException on KitKat… reportFullyDrawn();
  42. public final class ActivityCompatAdditions {
 
 private static final Impl

    IMPL;
 
 static {
 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
 IMPL = new LollipopImpl();
 } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
 IMPL = new KitKatImpl();
 } else {
 IMPL = new BaseImpl();
 }
 }
 
 private ActivityCompatAdditions() {
 // No instances
 } 
 
 public static void reportFullyDrawn(Activity activity) {
 IMPL.reportFullyDrawn(activity);
 } 
 
 private interface Impl {
 void reportFullyDrawn(Activity activity);
 } 

  43. private static final class BaseImpl implements Impl {
 @Override
 public

    void reportFullyDrawn(Activity activity) {
 // No-op
 }
 }
 
 private static final class KitKatImpl implements Impl {
 @Override @TargetApi(Build.VERSION_CODES.KITKAT)
 public void reportFullyDrawn(Activity activity) {
 try {
 activity.reportFullyDrawn();
 } catch (SecurityException se) {
 // Prevent an Exception on KitKat
 }
 }
 }
 
 private static final class LollipopImpl implements Impl {
 @Override @TargetApi(Build.VERSION_CODES.KITKAT)
 public void reportFullyDrawn(Activity activity) {
 activity.reportFullyDrawn();
 }
 }
 
 }
  44. public class LaunchTimeActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<Void> {
 
 private

    static final int LOADER_YO = 0xcafe;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_launch_time); 
 getSupportLoaderManager().initLoader(LOADER_YO, null, this);
 }
 
 @Override
 public void onLoadFinished(Loader<Void> loader, Void data) {
 switch (loader.getId()) {
 case LOADER_YO:
 // ...
 break;
 }
 
 }
 // ... } reportFullyDrawn();
  45. public class LaunchTimeActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<Void> {
 
 private

    static final int LOADER_YO = 0xcafe;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_launch_time); 
 getSupportLoaderManager().initLoader(LOADER_YO, null, this);
 }
 
 @Override
 public void onLoadFinished(Loader<Void> loader, Void data) {
 switch (loader.getId()) {
 case LOADER_YO:
 // ...
 break;
 }
 
 }
 // ... } reportFullyDrawn();
  46. public class LaunchTimeActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<Void> {
 
 private

    static final int LOADER_YO = 0xcafe;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_launch_time); 
 getSupportLoaderManager().initLoader(LOADER_YO, null, this);
 }
 
 @Override
 public void onLoadFinished(Loader<Void> loader, Void data) {
 switch (loader.getId()) {
 case LOADER_YO:
 // ...
 break;
 }
 }
 // ... } ActivityCompatAdditions.reportFullyDrawn(this);
  47. $ adb logcat | grep “ActivityManager” ActivityManager: Displayed com.launchtime/.LaunchTimeActivity: +666ms

    ActivityManager: Fully drawn com.launchtime/.LaunchTimeActivity: +1s440ms API 19+
  48. com.launchtime/.LaunchScreenActivity $ adb shell am start

  49. com.launchtime/.LaunchScreenActivity $ adb shell am start -W

  50. None
  51. None
  52. $ adb shell am start -W com.launchtime/.LaunchScreenActivity Starting: Intent {

    act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.launchtime/.LaunchScreenActivity } Status: ok Activity: com.launchtime/.LaunchScreenActivity ThisTime: 2426 TotalTime: 2426 WaitTime: 2488 Complete
  53. $ adb shell screenrecord /sdcard/launch.mp4 API 19+

  54. $ adb shell screenrecord /sdcard/launch.mp4 --bugreport API 21+

  55. API 21+

  56. API 21+

  57. None
  58. 11:16:46.666 f=26 (0)

  59. 11:16:46.666 f=26 (0) 11:16:46.666 Frame timestamp

  60. 11:16:46.666 f=26 (0) f=26 Frame number

  61. 11:16:46.666 f=26 (0) (0) Dropped frames count

  62. 11:16:46.666 f=26 (0) Frame timestamp Dropped frames count Frame number

  63. SystemClock.sleep(2000L) all the things \o/ in debug only…

  64. SystemClock.sleep(2000L) all the things \o/ in debug only…

  65. com.launchtime/.LaunchScreenActivity $ adb shell am start

  66. com.launchtime/.LaunchScreenActivity $ adb shell am start -S

  67. None
  68. None
  69. Developer options Background process limit

  70. Developer options Background process limit

  71. Force cold launch with “No background processes”

  72. Measure, measure and measure again!

  73. Use Systrace & Traceview extensively

  74. Lazy-load & async all the things

  75. In general, beware of static init & onCreate

  76. public class LazyProvider extends ContentProvider {
 
 private static final

    int MODEL = 101;
 private static final int MODEL_ID = 102;
 
 private static final UriMatcher URI_MATCHER;
 static {
 URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
 URI_MATCHER.addURI(LazyContract.AUTHORITY, "/model", MODEL);
 URI_MATCHER.addURI(LazyContract.AUTHORITY, "/model/#", MODEL_ID);
 // ...
 }
 
 private SQLiteOpenHelper mOpenHelper;
 
 @Override
 public boolean onCreate() {
 mOpenHelper = new LoadOpenHelper(getContext());
 return true;
 }
 // ...
 } LazyProvider.java
  77. public class LazyProvider extends ContentProvider {
 
 private static final

    int MODEL = 101;
 private static final int MODEL_ID = 102;
 
 private static final UriMatcher URI_MATCHER;
 static {
 URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
 URI_MATCHER.addURI(LazyContract.AUTHORITY, "/model", MODEL);
 URI_MATCHER.addURI(LazyContract.AUTHORITY, "/model/#", MODEL_ID);
 // ...
 }
 
 private SQLiteOpenHelper mOpenHelper;
 
 @Override
 public boolean onCreate() {
 mOpenHelper = new LoadOpenHelper(getContext());
 return true;
 }
 // ...
 } LazyProvider.java private static final UriMatcher URI_MATCHER; Created at class loading but used only in CRUD…
  78. public abstract class LazyGetter<T> {
 
 private final AtomicReference<Optional<T>> mCachedValue

    = new AtomicReference<>(Optional.<T>empty());
 
 public final T get() {
 Optional<T> value = mCachedValue.get();
 if (!value.isPresent()) {
 synchronized (mCachedValue) {
 value = mCachedValue.get();
 if (!value.isPresent()) {
 mCachedValue.set(Optional.ofNullable(initialValue()));
 }
 }
 }
 return mCachedValue.get().get();
 }
 
 protected abstract T initialValue();
 
 } LazyGetter.java
  79. public class LazyProvider extends ContentProvider {
 
 private static final

    int MODEL = 101;
 private static final int MODEL_ID = 102;
 
 private static final LazyGetter<UriMatcher> URI_MATCHER = new LazyGetter<UriMatcher>() {
 @Override
 protected UriMatcher initialValue() {
 final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
 uriMatcher.addURI(LazyContract.AUTHORITY, "/model", MODEL);
 uriMatcher.addURI(LazyContract.AUTHORITY, "/model/#", MODEL_ID);
 // ...
 return uriMatcher;
 }
 }; 
 // ...
 } LazyProvider.java
  80. android:windowDisablePreview the (wrong) simple way…

  81. <?xml version="1.0" encoding="utf-8"?>
 <manifest package="com.cyrilmottier.android.launchtime"
 xmlns:android="http://schemas.android.com/apk/res/android">
 
 <application
 android:icon="@mipmap/ic_launcher"
 android:label="@string/app_name"


    android:theme="@style/Theme.LaunchTime">
 
 <activity
 android:name=".LaunchScreenActivity">
 <intent-filter>
 <action android:name="android.intent.action.MAIN" />
 <category android:name="android.intent.category.LAUNCHER" />
 </intent-filter>
 </activity>
 
 </application>
 
 </manifest> AndroidManifest.xml
  82. <?xml version="1.0" encoding="utf-8"?>
 <manifest package="com.cyrilmottier.android.launchtime"
 xmlns:android="http://schemas.android.com/apk/res/android">
 
 <application
 android:icon="@mipmap/ic_launcher"
 android:label="@string/app_name"


    android:theme="@style/Theme.LaunchTime">
 
 <activity
 android:name=".LaunchScreenActivity">
 <intent-filter>
 <action android:name="android.intent.action.MAIN" />
 <category android:name="android.intent.category.LAUNCHER" />
 </intent-filter>
 </activity>
 
 </application>
 
 </manifest> Default theme applies to all Activities AndroidManifest.xml android:theme="@style/Theme.LaunchTime">
  83. <?xml version="1.0" encoding="utf-8"?> <resources>
 
 <style name="Theme.LaunchTime" parent="android:Theme.Material.Light.DarkActionBar">
 <item name="android:colorPrimary">@color/colorPrimary</item>


    <item name="android:colorPrimaryDark">@color/colorPrimaryDark</item>
 <item name="android:colorAccent">@color/colorAccent</item>
 <item name="android:windowBackground">@color/appBackground</item>
 </style>
 
 </resources>
 values/themes.xml
  84. No project on Earth is minSdk 21+ except Android built-in

    apps!
  85. to the rescue… AppCompat

  86. to the rescue… AppCompat (almost)

  87. to the rescue… AppCompat (almost)

  88. UI design is all about

  89. mirrors UI design is all about &mirrors mirrors & smoke

  90. Switch between compile time & runtime themes

  91. <?xml version="1.0" encoding="utf-8"?>
 <resources>
 
 <attr format="reference" name="ltRuntimeTheme" />
 


    </resources> values/attrs.xml
  92. <?xml version="1.0" encoding="utf-8"?>
 <resources>
 
 <style name=“PreviewTheme.LaunchTime" parent=“android:Theme.Holo.Light.DarkActionBar"> 
 <item

    name=“android:actionBarSize"> @dimen/abc_action_bar_default_height_material </item>
 <item name="android:actionBarStyle">@style/PreviewWidget.ActionBar</item> <item name="android:windowBackground">@color/appBackground</item> <item name=“ltRuntimeTheme">@style/Theme.LaunchTime</item> 
 </style>
 
 </resources> values/themes_preview.xml
  93. <?xml version="1.0" encoding="utf-8"?>
 <resources>
 
 <style name=“PreviewTheme.LaunchTime" parent=“android:Theme.Holo.Light.DarkActionBar"> 
 <item

    name=“android:actionBarSize"> @dimen/abc_action_bar_default_height_material </item>
 <item name="android:actionBarStyle">@style/PreviewWidget.ActionBar</item> <item name="android:windowBackground">@color/appBackground</item> <item name=“ltRuntimeTheme">@style/Theme.LaunchTime</item> 
 </style>
 
 </resources> values/themes_preview.xml <item name=“android:actionBarSize"> @dimen/abc_action_bar_default_height_material </item> Force pre-Lollipop ActionBars height to 48 / 56 / 64dp
  94. <?xml version="1.0" encoding="utf-8"?>
 <resources>
 
 <style name=“PreviewTheme.LaunchTime" parent=“android:Theme.Holo.Light.DarkActionBar"> 
 <item

    name=“android:actionBarSize"> @dimen/abc_action_bar_default_height_material </item>
 <item name="android:actionBarStyle">@style/PreviewWidget.ActionBar</item> <item name="android:windowBackground">@color/appBackground</item> <item name=“ltRuntimeTheme">@style/Theme.LaunchTime</item> 
 </style>
 
 </resources> values/themes_preview.xml <item name=“ltRuntimeTheme">@style/Theme.LaunchTime</item> Define the theme to use at runtime
  95. <?xml version="1.0" encoding="utf-8"?>
 <resources>
 
 <style name="PreviewWidget.ActionBar" parent=“android:Widget.Holo.Light.ActionBar.Solid.Inverse"> 
 <item

    name="android:background">@color/colorPrimary</item>
 <item name=“android:displayOptions">showTitle</item> 
 </style>
 
 </resources> values/styles_preview.xml
  96. <?xml version="1.0" encoding="utf-8"?>
 <resources>
 
 <style name="PreviewTheme.LaunchTime" parent=“android:Theme.Material.Light.DarkActionBar"> 
 <item

    name="android:colorPrimary">@color/colorPrimary</item>
 <item name="android:colorPrimaryDark">@color/colorPrimaryDark</item>
 <item name="android:colorAccent">@color/colorAccent</item>
 <item name="android:windowBackground">@color/appBackground</item>
 <item name=“ltRuntimeTheme">@style/Theme.LaunchTime</item> 
 </style>
 
 </resources> values-v21/themes_preview.xml
  97. <?xml version="1.0" encoding="utf-8"?>
 <resources>
 
 <style name="PreviewTheme.LaunchTime" parent=“android:Theme.Material.Light.DarkActionBar"> 
 <item

    name="android:colorPrimary">@color/colorPrimary</item>
 <item name="android:colorPrimaryDark">@color/colorPrimaryDark</item>
 <item name="android:colorAccent">@color/colorAccent</item>
 <item name="android:windowBackground">@color/appBackground</item>
 <item name=“ltRuntimeTheme">@style/Theme.LaunchTime</item> 
 </style>
 
 </resources> values-v21/themes_preview.xml parent=“android:Theme.Material.Light.DarkActionBar" Use Theme.Material on v-21+ devices
  98. <?xml version="1.0" encoding="utf-8"?>
 <resources>
 
 <style name="PreviewTheme.LaunchTime" parent=“android:Theme.Material.Light.DarkActionBar"> 
 <item

    name="android:colorPrimary">@color/colorPrimary</item>
 <item name="android:colorPrimaryDark">@color/colorPrimaryDark</item>
 <item name="android:colorAccent">@color/colorAccent</item>
 <item name="android:windowBackground">@color/appBackground</item>
 <item name=“ltRuntimeTheme">@style/Theme.LaunchTime</item> 
 </style>
 
 </resources> values-v21/themes_preview.xml Define the theme to use at runtime <item name=“ltRuntimeTheme">@style/Theme.LaunchTime</item>
  99. <?xml version="1.0" encoding="utf-8"?>
 <manifest package="com.cyrilmottier.android.launchtime"
 xmlns:android="http://schemas.android.com/apk/res/android">
 
 <application
 android:icon="@mipmap/ic_launcher"
 android:label="@string/app_name"


    android:theme="@style/PreviewTheme.LaunchTime">
 
 <activity android:name=".LaunchScreenActivity">
 <intent-filter>
 <action android:name="android.intent.action.MAIN" />
 <category android:name="android.intent.category.LAUNCHER" />
 </intent-filter>
 </activity>
 
 </application>
 
 </manifest> AndroidManifest.xml
  100. <?xml version="1.0" encoding="utf-8"?>
 <manifest package="com.cyrilmottier.android.launchtime"
 xmlns:android="http://schemas.android.com/apk/res/android">
 
 <application
 android:icon="@mipmap/ic_launcher"
 android:label="@string/app_name"


    android:theme="@style/PreviewTheme.LaunchTime">
 
 <activity android:name=".LaunchScreenActivity">
 <intent-filter>
 <action android:name="android.intent.action.MAIN" />
 <category android:name="android.intent.category.LAUNCHER" />
 </intent-filter>
 </activity>
 
 </application>
 
 </manifest> AndroidManifest.xml android:theme="@style/PreviewTheme.LaunchTime" Tell the system to use a temporary preview theme
  101. public final class ThemeUtils { private ThemeUtils() { // No

    instances } @UiThread public static void ensureRuntimeTheme(Context context) { final TypedValue tv = new TypedValue(); context.getTheme().resolveAttribute(R.attr.ltRuntimeTheme, tv, true); if (tv.resourceId <= 0) { throw new IllegalArgumentException("ltRuntimeTheme " + "not defined in the preview theme"); } context.setTheme(tv.resourceId); } } ThemeUtils.java
  102. public class LaunchScreenActivity extends AppCompatActivity {
 
 @Override
 protected void

    onCreate(Bundle savedInstanceState) {
 ThemeUtils.ensureRuntimeTheme(this);
 super.onCreate(savedInstanceState); 
 setContentView(R.layout.activity_launch_screen);
 // ...
 }
 
 } LaunchScreenActivity.java
  103. public class LaunchScreenActivity extends AppCompatActivity {
 
 @Override
 protected void

    onCreate(Bundle savedInstanceState) {
 ThemeUtils.ensureRuntimeTheme(this);
 super.onCreate(savedInstanceState); 
 setContentView(R.layout.activity_launch_screen);
 // ...
 }
 
 } LaunchScreenActivity.java Inherit AppCompatActivity to get AppCompat behaviour public class LaunchScreenActivity extends AppCompatActivity {
  104. public class LaunchScreenActivity extends AppCompatActivity {
 
 @Override
 protected void

    onCreate(Bundle savedInstanceState) {
 ThemeUtils.ensureRuntimeTheme(this);
 super.onCreate(savedInstanceState); 
 setContentView(R.layout.activity_launch_screen);
 // ...
 }
 
 } LaunchScreenActivity.java ThemeUtils.ensureRuntimeTheme(this); Dynamically switch to the runtime theme
  105. Don’t forget every screen is an entry point

  106. Consider launch screen as boring screen

  107. Don’t try to outsmart Android Embrace it!

  108. That’s all Folks! @cyrilmottier • cyrilmottier.com