Posted by the Google and Facebook teams. Authored by Kateryna Semenova from the Google Android team and Tim Trueman, Steven Harris, Subramanian Ramaswamy from the Facebook team.
Introduction
Improving app startup time is not a trivial task and requires a deep understanding of things that affect it. This year, the Google Android team and the Facebook app team have been working together on metrics and sharing approaches to improve app startup. Google Android’s public documentation has a lot of information on app startup optimization. In addition to that we want to share how it applies to the Facebook app and what helped them to improve app startup.
There are now more than 2.9 billion people using Facebook every month. Facebook helps give people the power to build community and bring the world closer together. It is a place for people to share life’s moments, discover and discuss what’s happening, connect and nurture relationships, and help work together to build economic opportunity.
Facebook app developers are committed to ensure that people have the best possible experience and that the app works seamlessly on every device, in any country, and within different network conditions. Working together, the Google Android team and Facebook team aligned on metrics definition for app startup and best practices and shared them in this article.
Where to start
Start by measuring your startup times. This will let you know how good your user’s startup experience is, track any regressions, as well as how much to invest on improving it. At the end of the day, your startup times need to be tied to user satisfaction or engagement or user-base growth in order to prioritize your investments.
Android defines two metrics to measure app startup times: Time-To-Full-Display (TTFD) and Time-To-Initial-Display (TTID). While you can further split it into cold/warm startup times, this post will not disambiguate between them - Facebook's approach is to measure and optimize the startup time that’s experienced across all users interacting with the app (some of them will be cold, some warm).
Time-To-Full-Display
TTFD captures the time when your app has completed rendering and is ready for user interaction and consumption, perhaps including content from disk or the network. This can take a while on slow networks and can depend on what surface your users land on. Thus, it may also be helpful to show something right away and let users see progress is still happening, which brings us to TTID…
Time-To-Initial-Display
TTID captures the time for your app to draw its background, navigation, any fast-loading local content, placeholders for slower local content or content coming from the network. TTID should be when users can navigate around and get to where they want to go.
Don’t change too much: One thing to watch out for is visually shifting your app’s content between TTID and TTFD, like showing cached content then snapping it away once network content comes in. This can be jarring and frustrating for users, so make sure your TTID draws enough meaningful content to show users as much as possible of what to expect for TTFD. |
Focus on user success
Your users are coming to your app for content that might take a while to load, and you want to deliver that content to them as quickly as you can.
Facebook app developers focus on a metric based on Time To Full Display (TTFD), including all content and images, because that represents the full experience of what users came to the app for. If a network call for content or an image takes a long time or fails, developers want to know so that they can improve the entire start to finish startup experience.
What’s a good target for TTID and TTFD?
Facebook’s startup metric is the percentage of app starts that they consider “bad,” which is any start that either has a TTFD longer than 2.5 seconds OR any part of startup that is unsuccessful (e.g. an image fails to load or the app crashes). Facebook focuses on driving this percentage of bad starts down either by improving successful starts that take longer than 2.5 seconds, or by fixing issues causing unsuccessful starts. 2.5 seconds was chosen based on research that showed this was meaningful to Facebook users (this also matches the Largest Contentful Paint (LCP) metric in the Web Vitals recommendations for web sites).
Including the full experience, especially of any network calls to fetch recent content, can make your TTFD startup metrics seem really slow compared to TTID. This is actually a good thing! It represents the real experience people have with your app. Improvements you make to this may drive increased usage and perception of your app’s performance for your users like it has at Facebook.
Measuring TTFD can be tricky depending on your app. If it’s too hard, it’s fine to start with Time To Initial Display (TTID). That may miss the performance of loading some of your content if you have placeholders or images, but it’s good to start somewhere even if it’s just a subset of what your users see interacting with your app every day.
Instrumenting TTID
In Android 4.4 (API level 19) and higher, logcat provides a “Displayed” value capturing the time elapsed between launching the process and the completion of drawing the first frame of the corresponding activity on the screen.
The reported log line looks similar to the following example:
ActivityManager: Displayed com.android.myexample/.StartupTiming: +3s534ms
Instrumenting TTFD
To instrument TTFD, call reportFullyDrawn() in your Activity after all your content is on screen. Be sure to include any content that replaces placeholders, as well as any images you render (be sure to count when the image itself is displayed, not just its placeholder). Once you instrument calling reportFullyDrawn(), you can see it in logcat:
ActivityManager: Fully drawn {package}/.MainActivity: +1s54ms
Recommendations From Facebook App Developers
Facebook app developers have been optimizing the app for billions of users across a multitude of devices, platforms and countries for many years. This section shares some of the key lessons that Facebook app developers applied to optimize their app startup.
- Understand first, then optimize - Once you’ve defined a good startup metric, instrumenting it in your app can allow you to understand and prioritize improving your startup performance to deliver a better experience for your users. By starting with instrumentation, you can prove there is an opportunity, you can identify where to focus your efforts, and you can see how much you’ve improved things as you start optimizing.
- Fix crashes first - After you’ve instrumented your starts, make sure your app starts reliably. Crashes during startup are the most frustrating and quickest way to get users to abandon your app; measure and address these first.
- Don’t forget about functional reliability - Also, don’t forget about functional reliability: did your app show some content quickly, but fail to load all content or take a long time to load images? Your app may be starting fast, but failing to function as a customer wants (e.g., if tapping a button doesn’t work) - this worsens the customer experience.
- Aim for consistency - Inconsistent performance is more frustrating than consistent but slower than average startup performance. Take a look at the long tail of your starts and see if there are any fixes or ways to mitigate these slow starts. Don’t forget to look at your offline and lossy network startup performance starts.
- Parallelize work - Most modern phones have at least 4 CPU cores, so there's room to multitask! Don’t block the main thread unless you have to. Move I/O and non-critical paths work off the main thread.
- Be lazy - Once you’ve got a reliable and consistent startup, take a look through everything you’re doing to display your first visible screen of content—is there any work in there that’s not necessary? Remove, delay, or move to the background any work that’s not directly related to a startup experience until after the app has started (but be careful to watch your app’s responsiveness as a counter-metric). Try to keep your app’s onCreate() as lightweight as possible. You can also benefit from using the Jetpack App Startup library to initialize components at application startup. When doing so, make sure to still load all the required modules for the starting activity, and don’t introduce flickers where the lazily-loaded modules become available.
- Show progress, but don’t shift the UI too much - Try not to shift what’s presented to users around too much during startup. It’s frustrating to try to tap on something, only to have it change and do the wrong thing. This is similar to the Cumulative Layout Shift (CLS) concept from web vitals. For network-based loads with indeterminate durations, dismiss the splash screen and show placeholders for asynchronous loading. Consider applying subtle animations to the content area that reflect the loading state. Make sure that the loaded content structure matches the skeleton structure as closely as possible, to allow for a smooth transition once the content is loaded.
- Cache it - When a user opens your app for the first time, you can show loading indicators for some UI elements. The next time a user comes to your app, you can show this cached content while you load more recent content. Ever seen your FB feed update after your app is loaded as we fetch updated content from the network? Cutting network time out of your startup, if you can, is a great way to speed things up and introduce a more consistent startup performance experience. However, showing cached content may not always be the best approach as the next point suggests, and this is why it is important to measure what works better for the customer.
- Go fast & slow - Slightly slower, fresh & relevant content may be better than fast stale content. Showing fresh content to your users may be more valuable than starting up super fast only to refresh the content soon after startup. Evaluate whether it’s better to optimize for showing fresh content as quickly as possible with a timeout for showing stale content if the network is slow, or to just show what’s available immediately if the network is offline.
- Consistent session start surface - You may find it helpful to reset users to your main content after your app is in the background for a long time. Devices can keep your app in memory for a long time.
- Look at the inner workings - Trace and actually look at what’s executing during startup or attach a debugger—you might be surprised what you find! Once you’ve got a good understanding of the critical path for your starts, you can efficiently optimize your app’s performance. Invest in your biggest opportunities because you’ll know where they are.
- Make it easy to do the right thing - Sometimes developers use bad patterns and architecture because there are too many ways to do things. Don’t be afraid to consolidate the patterns used in your app, and optimize them so it’s easy to pick how to complete a task and for that task to be performant. A good example of this would be eager code execution patterns. If you’re running code for content that appears after the first full screen draw, you’re by definition hurting performance. Lazy code execution is a good pattern. Only run code eagerly when it is blocking the critical path for your startup.
Recommendations From Google Android Team
Google Android team’s recommendations to measure and optimize app startup are available in the public docs: App startup time. This section summarizes some of the key points that ties into Facebook’s recommendations above that all Android app developers should consider.
- TTID and TTFD are important metrics for app startup. Google Android ranks apps with TTID in the Play Console. TTFD is a super-set of TTID, so any improvements in TTID should apply to both metrics.
- Call reportFullyDrawn() to report TTFD and to let the system know that your activity is finished rendering. To improve app startup, the Android system adjusts optimizations to prioritize work that happens before reportFullyDrawn() is called. Calling this method when your app is in fully usable state will improve your app startup time. Every application should be using this API! And don’t forget to measure it.
- Monitoring your app's technical performance with Android vitals will help you improve your app startup. Using the Play Console, you can view data to help you understand and improve your app's startup time and more.
- We know a bug in production is much more expensive to fix compared to a fix at development time. The same applies to performance as well. Setup your application for measuring app startup early with local performance tests by using Jetpack Macrobenchmark: Startup.
- Instrumenting is key to understanding and optimizing startup as we’ve discussed above. Android offers system tracing that can help to dig deep and diagnose app startup problems.
- The Jetpack App startup library provides a straightforward, performant way to initialize components at application startup. Both library developers and app developers can use this library to streamline startup sequences and explicitly set the order of initialization. You can use this library to set which components load at what points during startup.
- A typical issue that affects app startup is doing too much during initialization - for example, inflating large or complex layouts, blocking screen drawing, loading and decoding bitmaps, garbage collection, etc.
Recap
This article captures some key measures of startup and best practices to improve startup experience that helps drive user engagement and adoption for the Facebook Android app. It also shares metrics, libraries and tools recommended by the Google Android team. Any Android app stands to benefit from applying some of the strategies described in the document. Measure and make your app startup delightful and fast for your users!