This blogpost is a collaboration between Google and Twitter. Authored by Jingwei Hao with support from César Puerta, Fred Lohner from Twitter, and developed with Jingyu Shi from Google.

Push notifications are an important way to keep Twitter users informed about what's happening. However, they can be a significant and often overlooked source of battery drain. For example: high priority notifications can wake a phone from Doze, and fetching data upon push notification delivery via the network can drain the battery quickly.

As app developers at Twitter, we know that battery life is an important aspect of the mobile experience for our users. Over time we've taken several steps to optimize our app to work with the power saving features, particularly around push notifications. In this article, we'll share what we did to save battery life on our users' devices in the hope this will help other developers optimize their apps as well.

Firebase Cloud Messaging migration

Earlier this year, we upgraded our notifications messaging library to the next evolution of GCM: Firebase Cloud Messaging (FCM). This gave us the ability to use the latest APIs and get access to the additional features Firebase has to offer.

This was a very unique migration for us for multiple reasons:

Push notifications are an important part of Twitter's mobile engagement strategy. Notifications help our users stay informed and connected with the world, and helped a man get his nuggs. Therefore, we couldn't afford having unreliable delivery of notifications during and after the migration, which would negatively impact the platform.

Since it's not possible to support both GCM and FCM in the application, we were not able to use typical A/B testing techniques during the migration.

We mitigated the risks by thoroughly testing across dogfood, alpha, and beta users for both migration and rollback paths, with real time metrics monitoring, and a staged app roll out. During the migration, we were pleased to find that replacing GCM with FCM worked smoothly in all our use cases.

Migrating to FCM proved valuable to us when testing against the power saving features on Android. With APIs like: getPriority() and getOriginalPriority(), FCM gave us insight into if the priority of FCM messages are downgraded.

Set the right FCM message priority

On the backend at Twitter, we always try to make sure that notifications are assigned with the appropriate priority, making sure that high priority FCM messages are only used to generate a user visible notification. In fact, a very small fraction of notifications we send are classified as high priority.

App Standby Buckets was introduced in Android 9 Pie, this feature impose restrictions on the number of high priority messages the app will receive based on which bucket the app belongs to. As a result, high priority messages should be reserved for the notifications users are more likely to interact with. Using high priority FCM messages for actions which do not involve user interactions can lead to negative consequences, for example: once an app exhausts its app standby bucket quota, the following genuinely urgent FCM messages will be downgraded to normal priority and delayed when device is in Doze.

To understand how our app performs with App Standby Buckets, we gathered statistics on the notification priorities at both send and delivery time for the Twitter App:

During our test, we observed that none of the high priority FCM messages were downgraded, especially for the 2% of devices which are bucketed in the frequent or lower bucket when the notifications are delivered. This is particularly worth noting since the system could potentially impose restrictions on high priority FCM messages for devices in low active buckets.

86% of the devices were bucketed in the active bucket when high priority FCM messages are delivered. This is another positive signal that our priority assignment of the messages is consistent with the use pattern from the users.

This was very encouraging to see, as it means all users would receive the important notifications without any delay, and this is true no matter the app is considered active or not by the Android system. This also confirms that we are correctly categorizing high priority FCM messages based on our users usage engagement patterns.

Avoid follow-up data prefetch for notifications

Prefetching data is a popular practice to enrich the user experience around notifications. This entails including a piece of metadata within the payload of a notification. When the notification is delivered, the app leverages the payload data to start one or multiple network calls to download more data before the rendering of the notification. FCM payload has a 4KB max limit, and when more data is needed to create rich notifications, this prefetch practice is used. But doing this has a trade-off, and will increase both device power consumption and notification latency. At Twitter, among all the types of notifications we push to our users, there's only one type which does prefetching which makes up to less than 1% of the volume. In addition, in the cases where data prefetching for notification is unavoidable, it should be scheduled with JobScheduler or WorkManager tasks in order to avoid issues with the background execution limits in Oreo+.

Create notification channels

In addition, Notification Channels were introduced in the Android 8 Oreo release. We designed the importance level of the channels with multiple factors in mind, user experience being the most important and power-saving being another. Currently, Twitter for Android has nine notification channels, among which only direct messaging, emergency, and security are designed as high importance, leaving most of the channels with a lower importance level, making it less intrusive.

Summary

We set out to improve our user experience by migrating to FCM, prioritizing notifications cautiously, limiting prefetching, and carefully designing notification channels. We were glad to find that these changes had a positive impact on battery performance and enabled our app to take advantage of the power-saving features introduced in recent versions of Android.

Designing for power optimization in a large evolving application is a complex and ongoing process, particularly as the Android platform grows and provides more granular controls. At Twitter we strongly believe in continuous refinement to improve application performance and resource consumption. We hope this discussion is useful in your own quest to optimize your app's performance and use of resources 💙