Posted by Niharika Arora, Developer Relations Engineer
Building for Android Go involves paying special attention to performance optimizations and resource usage.
In part-1 of this blog, we discussed why developers should consider building for Android Go, some tips on optimizing the app memory and identified the standard approach to follow while fixing performance issues. In this blog, we will talk about the other vitals to pay attention to while building apps for Android Go.
Optimize your apps for Android Go
Improve startup latency
Improving app startup time requires a deep understanding of things that affect it.
- Avoid eager initialization: Avoid doing eager work that may not be needed in your app’s startup sequence. The user launching your app is the most well-known reason a process starts, but WorkManager, JobScheduler, BroadcastReceiver, bound Services, and ContentProviders can also start your app process in the background. Avoid initializing things eagerly in your Application class like ContentProviders, or androidx initializers if possible (If they are not needed for any possible reason for your app process to start).
Move the tasks from UI thread to background thread
- Identify operations which occupy large time frames and can be optimized.
- Identify operations that consume more time than expected.
- Identify operations which cause the main thread to be blocked.
If there are tasks which are taking longer and blocking the main thread, try to move them to a background thread or prefer using WorkManager.
- Check third party library initialization
Lazy load third party libraries. A lot of libraries do have on demand initialization or disabling auto init options.
- Check rendering time of webp / png images
- Prefer webp images over jpg/png
- Prefer svg for small icons.
- Check if any layouts are invisible, but still spend time for the images to be loaded.
- Explore using a low resolution image according to the memory capabilities of the device.
- Remove unnecessary backgrounds/alpha from views.
- Avoid synchronous IPCs on UI thread: Check for binder transactions keeping the main thread busy: Often there are multiple Inter process communication happening within the app. These could be anything like image / asset loading, third party libs loading, heavy work on application’s main thread like disk or network access etc. StrictMode is a useful developer tool to detect such accident usage.
Identify and measure these transactions to understand :
- How much time does this take and if it is expected & necessary ?
- Is the main thread sleeping / idle / blocked during this time ? If yes, this could be a performance bottleneck.
- Can this be delayed ?
- Wisely use XML and Json parsing: The Gboard app optimized file list parsing by using Java code instead of XML parsing as they were parsing them out into memory as Java objects at runtime. So, GBoard did a work to convert all the XML into Java code at compile time, the latency then become time of class loading, which is much more faster, almost ⅕ of previous. Benchmarking both XML and Json parsing for your app to identify the appropriate library to be used.
- Analyze and fix severe disk read contention: To capture this, use StrictMode in your development environment.
- Detects accidental disk or network access on the application's main thread, where UI operations are received and animations take place.
- Can automatically terminate the app (or log it to logcat), when a violation has occurred by adding different penalties.
Optimize app size
Users often avoid downloading apps that seem too large, particularly in emerging markets where devices connect to spotty 2G and 3G networks with low bandwidth speed or work on pay-by-the-byte plans.
- Remove unnecessary layouts: Gboard and Camera from Google teams validated the layouts which were unused or could be merged with small UI changes and removed the unnecessary layouts reducing overall app code size.
- Migrate to dynamic layouts/views when appropriate: The apps deep dive and find out the layouts and views which can be dynamically rendered. They used merge and viewstub to further optimize their views and layouts.
- Revaluate features with low DAU. Try to disable features which take more memory and make the app less performant: The team further analyzed their apps to specifically optimize for Android Go and disabled features on go devices which were not of much usage but were taking a lot of memory. They removed complex animations, large GIFs etc. to make space for parts of the app.
- Try combine native binaries with common dependencies into one: If the app has different JNI implementations with a lot of common underlying dependencies - all the different binaries add up to the apk size with redundant components. Camera from Google app benefited from combining several JNI binaries into a single JNI binary while keeping the Java and JNI files separate. This helped the app reduce the apk size by several Mbs.
- Try to reduce dalvik code size: Check for code that is never used at runtime, eg) large classes and auto-generated code.
- Code optimizers like ProGuard () could help optimize and shrink code size, but they can't deal with codes guarded by runtime-constants. Replacing the check/flags with compile-time constants to make most usage of the optimization tools.
- Reduce translatable strings size :
a. Don’t translate internal-only UI strings. Mark them as translatable = “false”.
b. Remove unused alternative resources: You can use the Android Gradle plugin's resConfigs property to remove alternative resource files that your app does not need. if you are using a library that includes language resources (such as AppCompat or Google Play Services), then your app includes all translated language strings for the messages in those libraries whether the rest of your app is translated to the same languages or not. If you'd like to keep only the languages that your app officially supports, you can specify those languages using the resConfig property. Any resources for languages not specified are removed.
The following snippet shows how to limit your language resources to just English and French
You can read more here.c. Don’t translate what doesn’t need translation: If the string does not participate in a UI, it should not be translated. Strings for the purpose of debugging, exception messages, URLs and so on should be string literals in code, not resources.i. Don’t translate what’s not shown to the user anywayIt’s possible to have a String resource that’s practically never shown to the user, but is still strongly referenced. One example is in your <activity>, if you have an android:label set and referencing a String resource but the Activity’s label is never actually shown (e.g. it’s not a launcher Activity and it doesn’t have an app bar that shows its own label).d. Don’t translate URLs: Consider this example:
You may recognize < and > - these are escape characters for “<” and “>”. They’re needed here because if you were to put an <a> tag inside a <string> tag, then the Android resource compiler would just drop them (as it does with all tags it doesn’t recognize).
However, this means that you’re translating the HTML tags and the URL to 78 languages. Totally unnecessary.Instead, remove the part with HTML:
Note we don’t define a separate string for “Learn more”, because it’s a common string. To produce the HTML snippet for the link, we define "<a href="https://support.google.com/androidauto/answer/6395843>%s</a>" as a string literals in code, and then drop the value of “Learn more” from resources into the format specifier.
e. Inline untranslated strings: By specifying strings in strings.xml you can leverage the Android framework to automatically change the actual value used at runtime based on the current configuration (e.g. based on the current locale, show a localized value for the string).
f. Remove duplicate strings: If you have the same string multiple times as a literal in code (“Hello World”), it will be shared across all instances. But resources don’t work this way - if you have an identical string under a different name then unless both are translated identically across 78 languages (unlikely) you’ll end up with duplicates.
Don’t have duplicate strings!
g. Don’t have separate strings for ALL CAPS or Title Case: Android has built-in support for case mutations.
Use android:capitalize (since API level 1). If set, specifies that this TextView has a textual input method and should automatically capitalize what the user types. The default is "none".
- Reducing asset size: Be mindful of different target device form factors that your app supports and adjust your assets accordingly.
- Upload your app with Android app bundles: The easiest way to gain immediate app size savings when publishing to Google Play is by uploading your app as an Android App Bundle, which is a new upload format that includes all your app’s compiled code and resources, but defers APK generation and signing to Google Play. Read more here.
- Utilize dynamic delivery feature if applicable: Play Feature Delivery uses advanced capabilities of app bundles, allowing certain features of your app to be delivered conditionally or downloaded on demand. You can use feature modules for custom delivery. A unique benefit of feature modules is the ability to customize how and when different features of your app are downloaded onto devices running Android 5.0 (API level 21) or higher. Learn more here.