Author Archives: Android Developers

Google Play Games on PC brings new features to accelerate growth

Posted by Arjun Dayal, Director, Google Play Games


Since we launched Google Play Games on PC beta last year, the product has seen a tremendous amount of growth. Now offering over 3,000 titles, Google Play Games on PC currently reaches users in more than 120 countries, providing a wide range of games across genres for players to enjoy. Our catalog continues to grow with popular mobile titles including Clash of Clans, Clash Royale, Free Fire MAX, and Angry Birds 2 available today*.

We’ve been engaging with our amazing community of players and have been focused on increasing the quality of the gaming experience by adding new gameplay capabilities. We’ve recently added support for microphones and popular game controllers, as well as provided dynamic display options that let players select a screen resolution up to 4K.

CookieRun: Tower of Adventures with game controller support
Preview of CookieRun: Tower of Adventures with game controller support

We’ve also streamlined the user install journey for Google Play Games on PC to make game discovery easier. When a user visits the Play Store from a Windows PC, the games homepage will now showcase PC optimized games that can be installed directly on their PC.

Play store web version details page

We’re excited to bring more features to Google Play Games on PC to our growing community of players and unlock opportunities for developers to expand their reach. If you’re ready to make Google Play Games on PC a part of your growth strategy, learn more about new features and optimization guides from our developer site.


Windows is a trademark of the Microsoft group of companies.
*Google Play Games on PC is available to download in 122 countries as of Nov 15, 2023.
Please see g.co/googleplaygames for more information. Game titles may vary by region.

Snapchat integrated new camera features 50% faster with the Camera2 Extensions API

Posted by Fred Chung, Android Developer Relations

Snapchat is a visual messaging app that enhances Snapchatters’ relationships with friends, family, and the world. It opens to the camera and offers millions of augmented reality and AI-powered Lenses for self expression, learning, and play. Ensuring Snapchatters can easily capture and share their lives with close friends and family is a priority for Snapchat, and they're always exploring new ways to improve the overall app experience.

As part of this, the Snapchat team added new camera features into the app using Android’s Camera2 Extensions API, which allows developers to access various capabilities that OEMs have implemented on various devices, like Night Mode and Bokeh. Thanks to Android’s intuitive API, the Snapchat team implemented new camera features 50% faster than before.

Camera2 Extensions API gives access to advanced features

The Snapchat team wanted to optimize the application for the expanding selection of Android devices, knowing many OEMs differentiate their devices with their respective camera technologies. As Snapchat is a primarily visual app that works with a device’s camera, the team optimizes the app to take full advantage of each device’s unique hardware.

“We wanted to leverage each OEM’s software to enhance the Snapchat experience on Android,” said Ye Tian, a software engineer at Snapchat. “This would help the app achieve higher-quality Snaps that are comparable to what a device's native camera offers.”

https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiU-F-r5DnCnu0YdvwT1OYmjJinR-gH1LNVq_wmImMPU4rwXDfroLQlvht1k8640kMaTabS8maaYYeRgfDQwBYrjv8Gi5QygnmWMb1nw-X8OfxSxEoSjp3V56uhg3lbdoaXRruZHzHuvscejVS-9dsqeQHzJ9QDytZQuQmmZRQcfLYb42v578M4Ln8OX9g/s1600/image3.gif
Snapchat developers enhanced the app’s zoom and night mode camera capabilities using the Camera2 Extensions API

What started as a goal to improve the app’s low-light capabilities led to much more. The Snapchat team worked on finding new ways to improve the app’s camera capabilities by implementing features like night mode, portrait mode, face retouch, tap-to-focus, zoom, and more.

“Our collaboration with Google Pixel paved the way for collaboration with other OEMs to implement night mode and super-night mode in their devices with very minimal code changes,” said Ye. “The Camera2 Extensions API is flexible and extensive. Snapchat can now use it to build full-fledged applications on demand without negatively impacting performance and stability.”

The implementation via the Camera2 Extension API made it easy for Snapchat developers to add more camera features into the app. And using the extensions made available with Android’s camera API, Snapchat integrated new camera features 50% faster when compared to the typical industry-standard approaches it used in the past.

The Camera2 Extensions API is flexible and extensive. Snapchat can now use it to build full-fledged applications on demand without negatively impacting performance and stability.” — Ye Tian, Software Engineer at Snapchat

More opportunities on more devices

The Snapchat team was happy to give its users a more cohesive experience using the Camera2 Extensions API. Thanks to the extensions provided in the API, developers easily improved the app’s camera on a range of manufacturer devices using the Android platform, and much faster than before.

“I enjoy the diversity of the Android platform and utilizing the unique advantages of each mobile phone manufacturers’ devices,” said Ye. “It helps us bring their cutting-edge innovations into the Snapchat app, allowing Snapchatters to better capture their life moments.”

Snapchat’s team looks forward to working with more OEMs to further improve the app’s processing capabilities across devices using the Camera2 Extensions API. They’re also looking forward to improving the app’s backward compatibility using the new API, which will allow even more users to benefit from the extensions.

“I would recommend using Camera2 Extension API. It provides extensive functionalities and stable performance to improve the velocity that developers can deliver features,” said Ye.

Get started

Learn how to increase your app’s camera capabilities with the Camera2 Extensions API.

Bringing New Input Support to Desktop AVD

Posted by Joshua Hale – Software Engineer

As large screens become increasingly important within the Android app ecosystem, we are committed to enhance tools to help Android developers adapt their apps for these large screen form factors. In doing so, we strive to ensure that we can bring impactful tools to enhance the overall experience for building for all large screens such as foldables, tablets, and Chromebooks.

Over the last year, the team has worked on bringing Android 13 to the Desktop AVD, along with some additional enhancements to input support within the emulator. The Android 13 release of the Desktop AVD is now available within Android Studio. To test using this emulator, create a new virtual device.

What is the Desktop AVD?

Android Studio comes bundled with various virtual devices that run on different API levels and architectures. These emulators help developers test Android apps across a variety of devices, allowing for testing across different screen sizes, form factors, and APIs.

When an Android app runs on a Chromebook, it uses functionality that mirrors desktop behaviors, such as minimizing, maximizing, or resizing to a user-specified size. The Desktop Android Virtual Device (AVD) is an emulator that allows testing in a freeform windowing mode, similar to a Chromebook, to support this functionality.

For a deeper dive into the Desktop AVD, check out Desktop AVD in Android Studio.

Screenshot of the Desktop AVD emulator, rendering a clock app, browser window, and downloads folder in freeform windowing mode

What enhancements come with the Android 13 desktop AVD?

Most laptops use a keyboard—and it’s a common input device for increased productivity with tablets and foldables. Prior to Android 13, the Desktop AVD relied solely on uncustomizable input mapping built into Android Studio, which can cause friction points for users who rely on physical devices for mapped input and shortcuts. The Android 13 release of the Desktop AVD adds support for common keyboard interactions with Android apps. You can now test shortcuts, support keys, and mouse support to help you adhere to the large screen app quality guidelines.

Keyboard Shortcuts

The majority of apps within Google Play are designed for mobile usage and as such do not always support keyboard interactions. In Android 13, the Desktop AVD adds support for commonly used shortcuts, such as Ctrl+C (Copy) and Ctrl+V (Paste). These shortcuts can be used when copying text from a TextView/Text composable or pasting text into an EditText/TextField. These shortcuts are intercepted by the system and automatically applied.

Custom shortcuts (which are not intercepted by the system) are also included in this release. An example of this type of shortcut: a media player app that uses the Spacebar to play or pause media. You must use the new Hardware Input feature within Android Studio Hedgehog to use custom shortcuts. This will allow Android Studio to pass custom shortcuts directly to the emulator. If this is not enabled, Android Studio may consume the key combination.

Support keys

Android 13 supports additional keymappings for support keys. These keys are mapped to controls that are similar to experiences for keyboard shortcuts on a desktop. Some examples of these support keys include:

    • Esc: Dismisses pop-ups and notifications.
    • Delete / Backspace: Deletes text within an EditText or TextField
    • Arrow Keys: Provides in-app navigation (Arrow Up/Down to scroll).

Mouse support

In addition to enhanced keyboard support, there are additional mouse controls integrated into the Desktop AVD. Using the scroll wheel sends a mouse scroll event to the app that has input focus. Right-clicking the mouse sends a right-click event—which can be used to show context menus if the app supports it.

Where can you start?

Large screen app quality provides guidance around creating high quality large screen apps across all form factors, outlining a comprehensive set of quality requirements for most types of Android apps. Not all requirements need to be met, but it’s best practice for you to adhere to the requirements that make sense for your apps.

Create a desktop emulator today in Android Studio Hedgehog to see how your Android app responds to keyboard and mouse inputs and freeform window resizing.

The secret to Android’s improved memory on 1B+ Devices: The latest Android Runtime update

Posted by Santiago Aboy Solanes - Software Engineer

The Android Runtime (ART) executes Dalvik bytecode produced from apps and system services written in the Java or Kotlin languages. We constantly improve ART to generate smaller and more performant code. Improving ART makes the system and user-experience better as a whole, as it is the common denominator in Android apps. In this blog post we will talk about optimizations that reduce code size without impacting performance.

Code size is one of the key metrics we look at, since smaller generated files are better for memory (both RAM and storage). With the new release of ART, we estimate saving users about 50-100MB per device. This could be just the thing you need to be able to update your favorite app, or download a new one. Since ART has been updateable from Android 12, these optimizations reach 1B+ devices for whom we are saving 47-95 petabytes (47-95 millions of GB!) globally!

All the improvements mentioned in this blog post are open source. They are available through the ART mainline update so you don’t even need a full OS update to reap the benefits. We can have our upside-down cake and eat it too!

Optimizing compiler 101

ART compiles applications from the DEX format to native code using the on-device dex2oat tool. The first step is to parse the DEX code and generate an Intermediate Representation (IR). Using the IR, dex2oat performs a number of code optimizations. The last step of the pipeline is a code generation phase where dex2oat converts the IR into native code (for example, AArch64 assembly).

The optimization pipeline has phases that execute in order that each concentrate on a particular set of optimizations. For example, Constant Folding is an optimization that tries to replace instructions with constant values like folding the addition operation 2 + 3 into a 5.

ART's optimization pipeline overview with an example showing we can combine the addition of 2 plus 3 into a 5

The IR can be printed and visualized, but is very verbose compared to Kotlin language code. For the purposes of this blog post, we will show what the optimizations do using Kotlin language code, but know that they are happening to IR code.

Code size improvements

For all code size optimizations, we tested our optimizations on over half a million APKs present in the Google Play Store and aggregated the results.

Eliminating write barriers

We have a new optimization pass that we named Write Barrier Elimination. Write barriers track modified objects since the last time they were examined by the Garbage Collector (GC) so that the GC can revisit them. For example, if we have:

Example showing that we can eliminate redundant write barriers if there a GC cannot happen between  set instructionsPreviously, we would emit a write barrier for each object modification but we only need a single write barrier because: 1) the mark will be set in o itself (and not in the inner objects), and 2) a garbage collection can't have interacted with the thread between those sets.

If an instruction may trigger a GC (for example, Invokes and SuspendChecks), we wouldn't be able to eliminate write barriers. In the following example, we can't guarantee a GC won't need to examine or modify the tracking information between the modifications:

Example showing that we can't eliminate redundant write barriers because a GC may happen between set instructionsImplementing this new pass contributes to 0.8% code size reduction.

Implicit suspend checks

Let's assume we have several threads running. Suspend checks are safepoints (represented by the houses in the image below) where we can pause the thread execution. Safepoints are used for many reasons, the most important of them being Garbage Collection. When a safepoint call is issued, the threads must go into a safepoint and are blocked until they are released.

The previous implementation was an explicit boolean check. We would load the value, test it, and branch into the safepoint if needed.

Shows the explicit suspend check (load + test + branch) when multiple threads are running

Implicit suspend checks is an optimization that eliminates the need for the test and branch instructions. Instead, we only have one load: if the thread needs to suspend, that load will trap and the signal handler will redirect the code to a suspend check handler as if the method made a call.

Shows the implicit suspend check (two loads: the first one loads null and the second one traps) when multiple threads are running

Going into a bit more detail, a reserved register rX is pre-loaded with an address within the thread where we have a pointer pointing to itself. As long as we don't need to do a suspend check, we keep that self-pointing pointer. When we need to do a suspend check, we clear the pointer and once it becomes visible to the thread the first LDR rX, [rX] will load null and the second will segfault.

The suspend request is essentially asking the thread to suspend some time soon, so the minor delay of having to wait for the second load is okay.

This optimization reduces code size by 1.8%.

Coalescing returns

It is common for compiled methods to have an entry frame. If they have it, those methods have to deconstruct it when they return, which is also known as an exit frame. If a method has several return instructions, it will generate several exit frames, one per return instruction.

By coalescing the return instructions into one, we are able to have one return point and are able to remove the extra exit frames. This is especially useful for switch cases with multiple return statements. Switch case optimized by having one return instead of multiple return instructions

Coalescing returns reduces code size by 1%.

Other optimization improvements

We improved a lot of our existing optimization passes. For this blog post, we will group them up in the same section, but they are independent of each other. All the optimizations in the following section contribute to a 5.7% code size reduction.

Code Sinking

Code sinking is an optimization pass that pushes down instructions to uncommon branches like paths that end with a throw. This is done to reduce wasted cycles on instructions that are likely not going to be used.

We improved code sinking in graphs with try catches: we now allow sinking code as long as we don't sink it inside of a different try than the one it started in (or inside of any try if it wasn't in one to begin with).

Code sinking optimizations in the presence of a try catch

In the first example, we can sink the Object creation since it will only be used in the if(flag) path and not the other and it is within the same try. With this change, at runtime it will only be run if flag is true. Without getting into too much technical detail, what we can sink is the actual object creation, but loading the Object class still remains before the if. This is hard to show with Kotlin code, as the same Kotlin line turns into several instructions at the ART compiler level.

In the second example, we cannot sink the code as we would be moving an instance creation (which may throw) inside of another try.

Code Sinking is mostly a runtime performance optimization, but it can help reduce the register pressure. By moving instructions closer to their uses, we can use fewer registers in some cases. Using fewer registers means fewer move instructions, which ends up helping code size.

Loop optimization

Loop optimization helps eliminate loops at compile time. In the following example, the loop in foo will multiply a by 10, 10 times. This is the same as multiplying by 100. We enabled loop optimization to work in graphs with try catches.

Loop optimization in the presence of a try catch

In foo, we can optimize the loop since the try catch is unrelated.

In bar or baz, however, we don't optimize it. It is not trivial to know the path the loop will take if we have a try in the loop, or if the loop as a whole is inside of a try.

Dead code elimination – Remove unneeded try blocks

We improved our dead code elimination phase by implementing an optimization that removes try blocks that don't contain throwing instructions. We are also able to remove some catch blocks, as long as no live try blocks point to it.

In the following example, we inline bar into foo. After that, we know that the division cannot throw. Later optimization passes can leverage this and improve the code.

We can remove tries which contain no throwing instructions

Just removing the dead code from the try catch is good enough, but even better is the fact that in some cases we allow other optimizations to take place. If you recall, we don't do loop optimization when the loop has a try, or it's inside of one. By eliminating this redundant try/catch, we can loop optimize producing smaller and faster code.

Another example of removing tries which contain no throwing instructions

Dead code elimination – SimplifyAlwaysThrows

During the dead code elimination phase, we have an optimization we call SimplifyAlwaysThrows. If we detect that an invoke will always throw, we can safely discard whatever code we have after that method call since it will never be executed.

We also updated SimplifyAlwaysThrows to work in graphs with try catches, as long as the invoke itself is not inside of a try. If it is inside of a try, we might jump to a catch block, and it gets harder to figure out the exact path that will be executed.

We can use the SimplifyAlwaysThrows optimization as long as the invoke itself is not inside of a try

We also improved:

  • Detection when an invoke will always throw by looking at their parameters. On the left, we will mark divide(1, 0) as always throwing even when the generic method doesn't always throw.
  • SimplifyAlwaysThrows to work on all invokes. Previously we had restrictions for example don't do it for invokes leading to an if, but we could remove all of the restrictions.

We improved detection, and removed some of the restrictions from this optimization

Load Store Elimination – Working with try catch blocks

Load store elimination (LSE) is an optimization pass that removes redundant loads and stores.

We improved this pass to work with try catches in the graph. In foo, we can see that we can do LSE normally if the stores/loads are not directly interacting with the try. In bar, we can see an example where we either go through the normal path and don't throw, in which case we return 1; or we throw, catch it and return 2. Since the value is known for every path, we can remove the redundant load.

Examples showing we can perform Load Store Elimination in graphs with try catches, as long as the instructions are not inside of a try

Load Store Elimination – Working with release/acquire operations

We improved our load store elimination pass to work in graphs with release/acquire operations. These are volatile loads, stores, and monitor operations. To clarify, this means that we allow LSE to work in graphs that have those operations, but we don't remove said operations.

In the example, i and j are regular ints, and vi is a volatile int. In foo, we can skip loading the values since there's not a release/acquire operation between the sets and the loads. In bar, the volatile operation happens between them so we can't eliminate the normal loads. Note that it doesn't matter that the volatile load result is not used—we cannot eliminate the acquire operation.

Examples showing that we can perform LSE in graphs with release/acquire operations. Note that the release/acquire operations themselves are not removed since they are needed for synchronization.

This optimization works similarly with volatile stores and monitor operations (which are synchronized blocks in Kotlin).

New inliner heuristic

Our inliner pass has a wide range of heuristics. Sometimes we decide not to inline a method because it is too big, or sometimes to force inlining of a method because it is too small (for example, empty methods like Object initialization).

We implemented a new inliner heuristic: Don't inline invokes leading to a throw. If we know we are going to throw we will skip inlining those methods, as throwing itself is costly enough that inlining that code path is not worth it.

We had three families of methods that we are skipping to inline:

  • Calculating and printing debug information before a throw.
  • Inlining the error constructor itself.
  • Finally blocks are duplicated in our optimizing compiler. We have one for the normal case (i.e. the try doesn't throw), and one for the exceptional case. We do this because in the exceptional case we have to: catch, execute the finally block, and rethrow. The methods in the exceptional case will now not be inlined, but the ones in the normal case will.

Examples showing:
* calculating and printing debug information before a throw
* inlining the error constructor itself
* methods in finally blocks

Constant folding

Constant folding is the optimization pass that changes operations into constants if possible. We implemented an optimization that propagates variables known to be constant when used in if guards. Having more constants in the graph allows us to perform more optimizations later.

In foo, we know that a has the value 2 in the if guard. We can propagate that information and deduce that b must be 4. In a similar vein, in bar we know that cond must be true in the if case and false in the else case (simplifying the graphs).

Example showing that if we know that a variable has a constant value within the scope of an 
 `if`, we will propagate that example within said scope

Putting it all together

If we take into account all code size optimizations in this blog post we achieved a 9.3% code size reduction!

To put things into perspective, an average phone can have 500MB-1GB in optimized code (The actual number can be higher or lower, depending on how many apps you have installed, and which particular apps you installed), so these optimizations save about 50-100MB per device. Since these optimizations reach 1B+ devices, we are saving 47-95 petabytes globally!

Further reading

If you are interested in the code changes themselves, feel free to take a look. All the improvements mentioned in this blog post are open source. If you want to help Android users worldwide, consider contributing to the Android Open Source Project!

  • Write barrier elimination: 1
  • Implicit suspend checks: 1
  • Coalescing returns: 1
  • Code sinking: 1, 2
  • Loop optimization: 1
  • Dead code elimination: 1, 2, 3, 4
  • Load store elimination: 1, 2, 3, 4
  • New inlining heuristic: 1
  • Constant folding: 1

Java is a trademark or registered trademark of Oracle and/or its affiliates

Ensuring high-quality apps on Google Play

Posted by Kobi Gluck, Director of Product Management, Google Play

Every day, Google Play helps billions of people around the world discover engaging, helpful, and enriching experiences on their devices. Maintaining consistently high app quality across these experiences is our top priority, which is why we continuously invest in new tools, features, and programs to help developers deliver the best apps and games.

Previously, we’ve highlighted our efforts to amplify the highest-quality apps on Google Play, as well as steer users away from lower-quality ones. Today, we’re sharing an update on this work and introducing some new policies and programs to boost app quality across the platform and connect people with experiences they’ll love, wherever they are, on whatever device they’re using.

Helping existing developers comply with our updated verification requirements

Earlier this year, we announced that all developers must meet an expanded set of verification requirements before publishing apps on Google Play, to help users make informed choices, prevent the spread of malware, and reduce fraud. We’ve already rolled out the requirements for developers creating new Play Console developer accounts.

Today, we're sharing how developers with existing accounts can complete these verifications to comply with the updated Play Console requirements policy. We know that developers of different types and sizes have different priorities, and that it might take some developers longer to verify than others. Because of this, we're allowing you to choose your own deadline by which to complete account verification.

Starting today, you can choose your preferred deadline in Play Console. Deadlines are available on a first-come, first-served basis, so choose your deadline early to guarantee a timeframe that works for you. If you don't choose a deadline before February 29, 2024, we'll assign one for you automatically.

Screenshot of 'choose your preferred account verification deadline now' prompt in Google Play Console

Required app testing for all new personal developer accounts

Developers who regularly use Play’s app testing tools before publishing release higher-quality apps and games, which can lead to higher ratings and more success on Google Play. In fact, apps that use our testing tools have on average 3 times the amount of app installs and user engagement compared to those that don't.

To help developers reap these benefits, developers with newly created personal Play Console accounts will soon be required to test their apps with at least 20 people for a minimum of two weeks before applying for access to production. This will allow developers to test their app, identify issues, get feedback, and ensure that everything is ready before they launch. Developers who create new personal developer accounts will start seeing this requirement in Play Console in the coming days.

Increased investment in app review

As more developers use new technologies in their mobile apps, apps on Play are becoming more sophisticated — but so are abuse methodologies. To ensure we continue to provide a safe and trusted experience, our global review teams now spend more time assessing new apps to make sure they provide a valuable user experience that does not deceive or defraud users, either via the app or off-Play activity, and complies with our policies.

While we do not anticipate significant changes to our overall app review timelines, it may take us longer to review a small portion of apps, such as apps designed for children or that request certain device permissions. These deeper reviews help ensure that users are engaging in safe and trusted experiences through Google Play.

Connecting users to great, trustworthy apps

Given the global reach and regional diversity of the Android ecosystem, it’s important that every Play user can easily discover the best content for their needs and not be limited to a single recommendation. To continue providing great content for users that rewards developer investment in quality, we’ve already begun:

    • Providing users with information on whether an app may not perform as well on their device, including phones, large screens, and wearables and
    • Surfacing more high-quality local and regional content

Next year, we’ll add new signifiers to app listings to help users find what they’re looking for, starting with a badge identifying official government apps.

All of these changes are designed to help connect people with safe, high-quality, useful, and relevant experiences they’ll love, wherever they are and on whatever device they’re using. Thank you for your ongoing investment in the quality of your user experiences. We look forward to continuing to provide you with the platform that helps you supercharge your growth and success.

Order Files in Android

Posted by Aditya Kumar – Software Engineer

Context

Binary layout using a symbol order file (also known as binary order file or linker order file) is a well-known link-time optimization. The linker uses the order of symbols in order file to lay out symbols in the binary. Order file based binary layout improves application launch time as well as other critical user journeys. Order file generation is typically a multi-step process where developers use different tools at every stage. We are providing a unified set of tools and documentation that will allow every native app developer to leverage this optimization. Both Android app developers and the AOSP community can benefit from the tools.

Background

Source code is typically structured to facilitate software development and comprehension. The layout of functions and variables in a binary is also impacted by their relative ordering in the source code. The binary layout impacts application performance as the operating system has no way of knowing which symbols will be required in future and typically uses spatial locality as one of the cost models for prefetching subsequent pages.

But the order of symbols in a binary may not reflect the program execution order. When an application executes, fetching symbols that are not present in memory would result in page faults. For example, consider the following program:

// Test.cpp
int foo() { /* */ } int bar() { /* */ } // Other functions... int main() { bar(); foo();

}

Which gets compiled into:

# Test.app page_x: _foo page_y: _bar # Other symbols page_z:_main

When Test.app starts, its entrypoint _main is fetched first then _bar followed by _foo. Executing Test.app can lead to page faults for fetching each function. Compare this to the following binary layout where all the functions are located in the same page (assuming the functions are small enough).

# Test.app page_1: _main page_1: _bar page_1: _foo # Other symbols

In this case when _main gets fetched, _bar and _foo can get fetched in the memory at the same time. In case these symbols are large and they are located in consecutive pages, there is a high chance the operating system may prefetch those pages resulting in less page faults.

Because execution order of functions during an application lifecycle may depend on various factors it is impossible to have a unique order of symbols that is most efficient. Fortunately, application startup sequence is fairly deterministic and stable in general. And it is also possible to build a binary having a desired symbol order with the help of linkers like lld which is the default linker for Android NDK toolchain.

Order file is a text file containing a list of symbols. The linker uses the order of symbols in order file to lay out symbols in the binary. An order file having functions that get called during the app startup sequence can reduce page faults resulting in improved launch time. Order files can improve the launch time of mobile applications by more than 2%. The benefits of order files are more meaningful on larger apps and lower end devices. A more mature order file generation system can improve other critical user journeys.

Design

The order file generation involves the following steps

    • Collect app startup sequence using compiler instrumentation technique
      • Use compiler instrumentation to report every function invocation
      • Run the instrumented binary to collect launch sequence in a (binary) profraw file
    • Generate order file from the profraw files
    • Validate order file
    • Merge multiple order files into one
    • Recompile the app with the merged order file

Overview

The order file generation is based on LLVM’s compiler instrumentation process. LLVM has a stage to generate the order file then recompile the source code using the order file.ALT TEXT


Collect app startup sequence

The source code is instrumented by passing -forder-file-instrumentation to the compiler. Additionally, the -orderfile-write-mapping flag is also required for the compiler to generate a mapping file. The mapping file is generated during compilation and it is used while processing the profraw file. The mapping file shows the mapping from MD5 hash to function symbol (as shown below).

# Mapping file MD5 db956436e78dd5fa main MD5 83bff1e88ac48f32 _GLOBAL__sub_I_main.cpp MD5 c943255f95351375 _Z5mergePiiii MD5 d2d2238cf08db816 _Z9mergeSortPiii MD5 11ed18006e729e73 _Z4partPiii MD5 3e897b5ee8bebbd1 _Z9quickSortPiii

The profile (profraw file) is generated every time the instrumented application is executed. The profile data in the profraw file contains the MD5 hash of the functions executed in chronological order. The profraw file does not have duplicate entries because each function only outputs its MD5 hash on first invocation. A typical run of binary containing the functions listed in the mapping file above can have the following profraw entries.

# Profraw file 00000000 32 8f c4 8a e8 f1 bf 83 fa d5 8d e7 36 64 95 db |2...........6d..| 00000010 16 b8 8d f0 8c 23 d2 d2 75 13 35 95 5f 25 43 c9 |.....#..u.5._%C.| 00000020 d1 bb be e8 5e 7b 89 3e 00 00 00 00 00 00 00 00 |....^{.>........| 00000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|

In order to find the function names corresponding to the MD5 hashes in a profraw file, a corresponding mapping file is used.

Note: The compiler instrumentation for order files (-forder-file-instrumentation) only works when an optimization flag (01, 02, 03, 0s, 0z) is passed. So, if -O0 (compiler flag typically used for debug builds) is passed, the compiler will not instrument the binary. In principle, one should use the same optimization flag for instrumentation that is used in shipping release binaries.

The Android NDK repository has scripts that automate the order file generation given a mapping file and an order file.


Recompiling with Order File

Once you have an order file, you provide the path of the order file to the linker using the --symbol-ordering-file flag.


Detailed design

Creating Order File Build Property

The Android Open Source Project (AOSP) uses a build system called soong so we can leverage this build system to pass the flags as necessary. The order file build property has four main fields:

    • instrumentation
    • load_order_file
    • order_file_path
    • cflags

The cflags are meant to add other necessary flags (like mapping flags) during compilation. The load_order_file and order_file_path tells the build system to recompile with the order file rather than set it to the profiling stage. The order files must be in saved in one of two paths:

    • toolchain/pgo-profiles/orderfiles
    • vendor/google_data/pgo_profile/orderfiles

# Profiling orderfile: { instrumentation: true, load_order_file: false, order_file_path: "", cflags: [ "-mllvm", "-orderfile-write-mapping=<filename>-mapping.txt", ], } #Recompiling with Order File orderfile: { instrumentation: true, load_order_file: true, order_file_path: "<filename>.orderfile", }

Creating order files

We provide a python script to create an order file from a mapping file and a profraw file. The script also allows removing a particular symbol or creating an order file until a particular symbol.

Script Flags:

        • Profile file (--profile-file):
                • Description: The profile file generated by running a binary compiled with -forder-file-instrumentation
        • Mapping file (--mapping-file):
                • Description: The mapping file generated during compilation that maps MD5 hashes to symbol names
        • Output file (--output):
                • Description: The output file name for the order file. Default Name: default.orderfile
        • Deny List (--denylist):
                • Description: Symbols that you want to exclude from the order file
        • Last symbol (--last-symbol):
                • Description: The order file will end at the passed last symbol and ignore the symbols after it. If you want an order file only for startup, you should pass the last startup symbol. Last-symbol has priority over leftover so we will output until the last symbol and ignore the leftover flag.
        • Leftover symbols (--leftover):
                • Description: Some symbols (functions) might not have been executed so they will not appear in the profile file. If you want these symbols in your order file, you can use this flag and it will add them at the end.

Validating order files

Once we get an order file for a library or binary, we need to check if it is valid based on a set of criteria. Some order files may not be of good quality so they are better discarded. This can happen due to several reasons like application terminated unexpectedly, the runtime could not write the complete profraw file before exiting, an undesired code-sequence was collected in the profile, etc. To automate this process, we provide a python script that can help developers check for:

    • Partial order that needs to be in the order file
    • Symbols that have to be present in order file
    • Symbols that should not be present in order file
    • Minimum number of symbols to make an order file

Script Flags:

        • Order file (--order-file):
                • Description: The order file you are validating on the below criteria.
        • Partial Order (--partial):
                • Description: A partial order of symbols that must be held in the order file.
        • Allowed Lists (--allowlist):
                • Description: Symbols that must be present in the order file.
        • Denied Lists (--denylist):
                • Description: Symbols that should not be in the order file. Denylist flag has priority over allowlist.
        • Minimum Number of Entries (--min):
                • Description: Minimum number of symbols needed for an order file

Merging orderfiles

At a higher level, the order file symbols in a collection of order files approximate a partial order (poset) of function names with order defined by time of execution. Across different runs of an application, the order files might have variations. These variations could be due to OS, device class, build version, user configurations etc. However, the linker can only take one order file to build an application. In order to have one order file that provides the desired benefits, we need to merge these order files into a single order file. The merging algorithm also needs to be efficient so as to not slow down the build time. There are non-linear clustering algorithms that may not scale well for merging large numbers of order files, each having many symbols. We provide an efficient merging algorithm that converges quickly. The algorithm allows for customizable parameters, such that developers can tune the outcome.

Merging N partial order sets can be done either pessimistically (merging a selection of order files all the way until there is one order file left) or optimistically (merging all of them at once). The pessimistic approach can be inefficient as well as sub-optimal. As a result, it is better to work with all N partial order sets at once. In order to have an efficient implementation it helps to represent all N posets with a weighted directed Graph (V,E) where:

    • V: Elements of partial order sets (symbols) and the number of times it appears in different partial order sets. Note that the frequency of vertices may be greater than the sum of all incoming edges because of invocations from uninstrumented parts of binary, dependency injection etc.
    • E (V1 -> V2): An edge occurs if the element of V2 immediately succeeds V1 in any partial order set with its weight being the number of times this happens.

For a binary executable, there is one root (e.g., main) vertex, but shared libraries might have many roots based on which functions are called in the binary using them. The graph gets complicated if the application has threads as they frequently result in cycles. To have a topological order, cycles are removed by preferring the highest probability path over others. A Depth-First traversal that selects the highest weighted edge serves the purpose.

Removing Cycles:

- Mark back edges during a Depth-First traversal - For each Cycle (c):      - Add the weights of all in-edges of each vertex (v) in the cycle excluding the edges in the cycle      - Remove the cycle edge pointing **to** the vertex with highest sum

After cycles are removed, the same depth first traversal gives a topological order (the order file) when all the forward edges are removed. Essentially, the algorithm computes a minimum-spanning-tree of maximal weights and traverses the tree in topological order.

Producing an order:

printOrderUtil(G, n, order):    - If n was visited:         - return    - Add n to the end of order    - Sort all out edges based on weight    - For every out_edge (n, v):        - printOrderUtil(G, v, order) printOrder(G):    - Get all roots    - order = []    - For each root r:        - printOrderUtil(G, r, order)    - return order

Example:

Given the following order files:

    • main -> b -> c -> d
    • main -> a -> c
    • main -> e -> f
    • main -> b
    • main -> b
    • main -> c -> b
Flow diagram of orderfiles

The graph to the right is obtained by removing cycles.

    • DFS: main -> b-> c -> b
    • Back edge: c -> b
    • Cycle: b -> c-> b
    • Cycle edges: [b -> c, c -> b]
    • b’s sum of in-edges is 3
    • c’s sum of in-edges is 2
    • This implies b will be traversed from a higher frequency edge, so c -> b is removed
    • Ignore forward edges a->c, main->c
    • The DFS of the acyclic graph on the right will produce an order file main -> b -> c -> d -> a -> e -> f after ignoring the forward edges.

Collecting order files for Android Apps (Java, Kotlin)

The order file instrumentation and profile data collection is only enabled for C/C++ applications. As a result, it cannot benefit Java or Kotlin applications. However, Android apps that ship compiled C/C++ libraries can benefit from order file.

To generate order file for libraries that are used by Java/Kotlin applications, we need to invoke the runtime methods (called as part of order file instrumentation) at the right places. There are three functions that users have to call:

    • __llvm_profile_set_filename(char *f): Set the name of the file where profraw data will be dumped.
    • __llvm_profile_initialize_file: Initialize the file set by __llvm_profile_set_filename
    • __llvm_orderfile_dump: Dumps the profile(order file data) collected while running instrumented binary

Similarly, the compiler and linker flags should be added to build configurations. We provide template build system files e.g, CMakeLists.txt to compile with the correct flags and add a function to dump the order files when the Java/Kotlin application calls it.

# CMakeLists.txt set(GENERATE_PROFILES ON) #set(USE_PROFILE "${CMAKE_SOURCE_DIR}/demo.orderfile") add_library(orderfiledemo SHARED orderfile.cpp) target_link_libraries(orderfiledemo log) if(GENERATE_PROFILES) # Generating profiles require any optimization flag aside from -O0. # The mapping file will not generate and the profile instrumentation does not work without an optimization flag. target_compile_options( orderfiledemo PRIVATE -forder-file-instrumentation -O2 -mllvm -orderfile-write-mapping=mapping.txt ) target_link_options( orderfiledemo PRIVATE -forder-file-instrumentation ) target_compile_definitions(orderfiledemo PRIVATE GENERATE_PROFILES) elseif(USE_PROFILE) target_compile_options( orderfiledemo PRIVATE -Wl,--symbol-ordering-file=${USE_PROFILE} -Wl,--no-warn-symbol-ordering ) target_link_options( orderfiledemo PRIVATE -Wl,--symbol-ordering-file=${USE_PROFILE} -Wl,--no-warn-symbol-ordering ) endif()

We also provide a sample app to dump order files from a Kotlin application. The sample app creates a shared library called “orderfiledemo” and invokes the DumpProfileDataIfNeeded function to dump the order file. This library can be taken out of this sample app and can be repurposed for other applications.

// Order File Library #if defined(GENERATE_PROFILES) extern "C" int __llvm_profile_set_filename(const char *); extern "C" int __llvm_profile_initialize_file(void); extern "C" int __llvm_orderfile_dump(void); #endif void DumpProfileDataIfNeeded(const char *temp_dir) { #if defined(GENERATE_PROFILES) char profile_location[PATH_MAX] = {}; snprintf(profile_location, sizeof(profile_location), "%s/demo.output", temp_dir); __llvm_profile_set_filename(profile_location); __llvm_profile_initialize_file(); __llvm_orderfile_dump(); __android_log_print(ANDROID_LOG_DEBUG, kLogTag, "Wrote profile data to %s", profile_location); #else __android_log_print(ANDROID_LOG_DEBUG, kLogTag, "Did not write profile data because the app was not " "built for profile generation"); #endif } extern "C" JNIEXPORT void JNICALL Java_com_example_orderfiledemo_MainActivity_runWorkload(JNIEnv *env, jobject /* this */, jstring temp_dir) { DumpProfileDataIfNeeded(env->GetStringUTFChars(temp_dir, 0)); }

# Kotlin Application class MainActivity : AppCompatActivity() { private lateinit var binding: ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) runWorkload(applicationContext.cacheDir.toString()) binding.sampleText.text = "Hello, world!" } /** * A native method that is implemented by the 'orderfiledemo' native library, * which is packaged with this application. */ external fun runWorkload(tempDir: String) companion object { // Used to load the 'orderfiledemo' library on application startup. init { System.loadLibrary("orderfiledemo") } } }

Limitation

order file generation only works for native binaries. The validation and merging scripts will work for any set of order files.

References

External References

Alpha Release of Telecom Library

Posted by Luke Hopkins - Developer Relations Engineer

Today we’re thrilled to announce that the Telecom jetpack library is now in alpha for developers who already have or are interested in creating voice and/or video calling applications. Our aim with this library is to simplify the developer integration process and improve VoIP calling across Android surfaces.

androidx.core:core-telecom:1.0.0-alpha02

What’s in the Public Alpha release

This release supports a variety of Telecom features, including:

Platform synchronization

For surfaces like watches, this library allows the user to answer, decline, hang up and mute your call through a simple API, as well as displaying useful information such as who the caller is.

ALT TEXT

This is also beneficial because if the device is aware of your call, should other calls such a PTSN/SIM based call come through, you can give the user a chance to hold the call they are currently on.

Dedicated foreground support

With the changes to Android 14, which require applications to specify foreground service types, this library takes care of the requirements for you. For more information, please refer to the foreground service documentation.

Foreground support allows users to stay connected to their calls even after the user has navigated away from your app... You won’t need to build your own foreground services or worry about the background state of your application.

Audio Routing

Instead of using the audio manager to track state, focus and obtain a list of audio devices, this Telecom library will list all available endpoints to your application for streaming audio to/from Bluetooth hearables, hearing aids, wired headphones, and other surfaces, thus giving users access and control to a wide range of hearable devices.

Backwards Compatibility

Backwards compatibility works all the way down to Android O (API Level 26) on devices which support the Telecom stack which means implementing the simple API surface below supports a wide range of devices.

callsManager.addCall(
        attributes,
        onIsCallAnswered,
        onIsCallDisconnected,
        onIsCallActive,
        onIsCallInactive
       ){


        val callScope=this

}

You can also query the packagemanager to know if the device supports Telecom.

packagemanager.hasSystemFeature(PackageManager.FEATURE_TELECOM)

Why use this library over Platform API

You might be thinking, why make this move to a new library when I could just similarly migrate the deprecated APIs to the new APIs added in Android 14. Well this library offers:

New Features

We will have more exciting additions coming to the Telecom library in the coming months which are exclusive to the jetpack library. These included expanded support for VoIP Call actions such as being able to locally mute the VoIP app for the specific call and being able to display the name of the speaker on another surface such as Android Auto. We also have a new feature coming soon that will allow users to transfer their ongoing VoIP calls between their phones and tablets.

Backward Compatibility

As previously stated, this library supports backward compatibility, which means that your app will not only be supported on a wider range of devices, but we can resolve interoperability issues with older Android versions.

A simple API surface and a large coverage of devices, means this library is the goto solution for calling applications.

Migrating from ConnectionService to CallsManager

Even if you already have an existing ConnectionService integration for your VoIP app, you should consider migrating to CallsManager, as mentioned above we have a lot of exciting features coming to this library and using the jetpack library will give you a simple and complete solution moving forward.

Migrating from ConnectionService to CallManager is fairly straightforward to implement but is not a simple case of changing namespace. You can think of CallManager representing ConnectionService and CallControlScope representing ConnectionService.

Below shows the difference between how to switch audio using connection service to CallControlScope.

You can also query the packagemanager to know if the device supports Telecom.

cconnectionService.setAudioRoute (int route)

InCallService- Android 13

when (requestEndpointChange(newEndpoint)) { 

     is CallControlResult.Success -> { 
                                      // Device changed 
          } 

          is CallControlResult.Error -> { 

          } 
  }

Jetpack Library

Another example showing how simple this API is to use, you can add a call to the platform and define you call attributes with the code below:

val attributes = CallAttributesCompat( 
       displayName = displayName, 
              address = address,
       direction = CallAttributesCompat.DIRECTION_INCOMING,
       callType = CallAttributesCompat.CALL_TYPE_AUDIO_CALL,
       callCapabilities = (CallAttributesCompat.SUPPORTS_SET_INACTIVE
                or CallAttributesCompat.SUPPORTS_STREAM 
                               or CallAttributesCompat.SUPPORTS_TRANSFER), 
)
callsManager.addCall(
        attributes
      ) { 
                // Call control scope 

}


From here you will have a call control scope and this scope you can answer, hold, disconnect and get a list of hearable devices.

//call control scope 
launch { 
            availableEndpoints.collect { 

             ..... 

            }

          }

Getting started

Head over to our updated developer guide to get started and try out the Public Alpha release of the Telecom library. Make sure to check out our sample app found on GitHub for a demonstration on how to work with the various APIs.

This sample application implements all the latest features for the Telecom library showing how to do:

  • Audio Routing
  • Foreground Services
  • Accept, Disconnect, Reject and Hold calls
  • Watch integration
  • CallStyle notification

Feedback

We’d love to hear from you during this Alpha launch to help us shape the library and influence future roadmapping, so please share your feedback and let us know your experience with the library!

Increasing trust for embedded media

Posted by the Android team

Android WebView is a powerful and flexible API that Android developers can use to embed media in their apps, and continually improving its security and privacy protections is a top priority for our team. For example, embedded media providers should be able to verify that their media is playing in a trusted and safe environment. Android app developers and SDK providers already have solutions for this, including attestation services like the Play Integrity API and Firebase App Check, which preserve user privacy while enabling developers to verify their apps’ server requests. Today, app developers are able to pass information from these attestation services to embedded content providers; however, the current process is neither simple nor scalable. That’s why we’re piloting an experimental Android WebView Media Integrity API with select embedded media providers early next year.

How does this relate to the Web Environment Integrity API proposal?

We’ve heard your feedback, and the Web Environment Integrity proposal is no longer being considered by the Chrome team. In contrast, the Android WebView Media Integrity API is narrowly scoped, and only targets WebViews embedded in apps. It simply extends existing functionality on Android devices that have Google Mobile Services (GMS) and there are no plans to offer it beyond embedded media, such as streaming video and audio, or beyond Android WebViews.

What is the challenge with Android WebViews?

The Android WebView API lets app developers display web pages which embed media, with increased control over the UI and advanced configuration options to allow a seamless integration in the app. This brings a lot of flexibility, but it can be used as a means for fraud and abuse, because it allows app developers to access web content, and intercept or modify user interactions with it. While this has its benefits when apps embed their own web content, it does not prohibit bad actors from modifying content and, by proxy, misrepresenting its source.

What functionality are we bringing to embedded Android WebView media?

This sequence diagram shows a user requesting media in an Android app and the Android app returning the media in a manipulated WebView that could be used to alter the media and defraud the user.

The new Android WebView Media Integrity API will give embedded media providers access to a tailored integrity response that contains a device and app integrity verdict so that they can ensure their streams are running in a safe and trusted environment, regardless of which app store the embedding app was installed from. These verdicts are simple, low entropy metadata about the app and device and don’t contain any user or device identifiers. Unlike apps and games using Play Integrity API, media providers will not obtain the app’s Play licensing status and apps will also be able to exclude their package name from the verdict if they choose. Our goal for the API is to help sustain a thriving and diverse ecosystem of media content in Android apps, and we’re inviting media content providers to express interest in joining an early access program early next year.

#WeArePlay | Meet Geraldo from Utah. More stories from around the world.

Posted by Leticia Lago, Developer Marketing

Another month, another series of #WeArePlay stories from apps and games we all love. From a Salt Lake City-based music editing app to successful game studios from Indonesia, Uruguay and Türkiye - discover the inspiring founders behind them.

This time we’re starting in the US with Geraldo. Inspired by his mom’s studies in computer engineering, he decided to start his own tech company at just 16 years of age. But he was also a keen musician and merged both his passions in Moises, alongside childhood friend and co-founder Eddie. The app uses artificial intelligence to remove vocals and instruments from any song. Geraldo describes the process as like “getting a smoothie and removing only the banana” – complex, to say the least, but Moises makes it easy. He hopes “to democratize access to cutting edge audio tools for everyday musicians.”

#WeArePlay Diori & Agung MINIMO South Tangerang Indonesia g.co/play/weareplay Google Play

Next up, we’re crossing the Pacific over to Indonesia where colleagues and game enthusiasts Diori and Agung decided to collaborate outside of the office on their own independent project. This culminated in the launch of their studio, Minimo, with their most successful game, Mini Racing Adventures, accumulating over 38 million downloads to date. The pair channeled Agung’s passion for cars and mechanics into this particular release, but next they’re shifting genres and working on a new shooter game.

#WeArePlay Pablo & Gonzalo Ironhide Game Studio Montevideo Uruguay g.co/play/weareplay Google Play

Now we’re heading down to Uruguay where friends Pablo, Gonzalo and Alvaro had a dream of making games for a living and created Ironhide Game Studio in 2010, learning how to code for mobiles from scratch. As Pablo puts it: “Over the years we’ve realized that what we have is special, because we have the passion, but we also work really hard. This has allowed us to create something great.” Their popular title, Kingdom Rush: Tower Defence, is a strategy game set in a medieval settlement and chock-full of action-filled battles. Looking to the future, they’re hoping to branch into multiplayer games and expand their Kingdom Rush saga.

#WeArePlay Remi, Mithat, Rina, Fuat & Barkin SPYKE GAMES Instanbul Türkiye g.co/play/weareplay Google Play

And finally we’re crossing over to Europe to meet Rina. While working in private equity and meeting an array of business heads, she was inspired to pursue an entrepreneurial path herself. Seeing how popular gaming was becoming, Rina delved into creating titles for a Turkish audience. She struck gold with her first studio becoming a tech unicorn, and soon followed it up with Spyke Games, launched alongside her brother Remi and friends Mithat, Barkin and Fuat. Their title Tile Busters combine social multiplayer fun and skill-based puzzle solving. Soon, they’re releasing a follow-up, Blitz Busters, keeping their goal of being “great content developers creating games that people crave more of.”

Discover more global #WeArePlay stories and share your favorites.



How useful did you find this blog post?

Meta built threads in only 5 months using Jetpack Compose

Posted by Yasmine Evjen – Product Manager, and Florina Muntenescu – Developer Relations Engineer

Following its release in July of 2023, Meta’s Threads became the most rapidly downloaded app ever with over 100 million downloads in its first week. Meta created the new text-based social media platform as a place to build connections and have meaningful conversations. To ensure the app was set up for success at its release and into the future, Threads developers used Jetpack Compose, Android’s modern declarative toolkit for building UI.

An easier way to build UI with Jetpack Compose

Threads is built on top of existing code from its sister app Instagram, which uses Views for its UI development. After positive reports from other Android developers about Compose, and following internal testing and an assessment of the toolkit’s benefits, Threads engineers opted to build the all-new app from scratch using Compose. By using Compose, the team could move faster and better prepare the app for any future updates.

“We decided Jetpack Compose would be our target UI framework going forward,” said Richard Zadorozny, a software engineer at Threads. “We wanted to build the new app UI from scratch using Compose because it would enable us to move faster than refactoring a large application like Instagram.”

Even though most of Threads’ engineers had no prior experience using Compose, they found it easy to get started and learn the new toolkit. With Compose, Threads engineers built and shipped the app in only five months. This greatly exceeded the team’s speed expectations for developing a high-quality Android application — especially of this complexity and scale. The team attributes much of this speed to the flexibility and decoupling Compose provided.

Compose helped Threads engineers streamline the development of new product features. The modular nature of the toolkit let Threads developers iterate on the app as it evolved and teed up the app’s architecture for future development. Compose also helped engineers build user-friendly features that adhered to Material Design guidelines.

Threads was built and shipped in five months, exceeding our speed expectations for building a high-quality Android app of this complexity and scale.”  — Richard Zadorozny, software engineer at Threads

Going all in with Compose

Threads engineers developed almost all of the app’s surfaces using Compose. In the end, they built over 90% of Threads using Compose, including the app’s activity feed, navigation, search, profiles, onboarding page, shared element transitions, media viewer, settings, and more.

While Compose did mostly everything Threads engineers needed it to, it was still easy for them to interoperate with Views as necessary. They used Views for Threads’ videos and the media picker that’s available when creating a new post.

Compose provides modern APIs that ship directly with an app. Because of this, Threads engineers spent less time worrying about backward compatibility, missing features, or differing functionality between different versions of Android. Instead, they could focus their energy on developing a high-quality application.

“Compose’s design encourages a modular, plug-in approach to development,” said Richard. “Modifiers make all sorts of functionality inherently reusable, so you no longer have to subclass complicated ViewGroups or lump all sorts of logic into one place.”

Moving image shows Jetpack Compose/Modifiers code appears on screen

The Threads team used Modifiers for the app’s custom click behaviors and its thread line illustration that appears on the left side of posts. Modifiers also allowed Threads developers to easily add the app’s branding to any elements and ensured they were properly aligned on-screen.

Threads engineers also ensured the app was ready for users across platforms at launch. That meant making sure Threads resizes to work on different devices, like large screens and foldables. The adaptive layouts Compose offers ensure an app responds properly to different screen sizes, orientations, and form factors. This made it easier for the Threads app to “just work” for configuration changes, according to Richard.

For developers who are building new apps, I would definitely recommend using Compose. Not only is it enjoyable… it sets your team up for success in the future.” — Richard Zadorozny, software engineer at Threads

Compose is the ‘future’ of Android UI

Compose offered Threads developers an easier way to design and create UI while preparing the app’s architecture for the future. With its intuitive composables and modern declarative framework, Compose made end-to-end development smooth and gave Threads developers confidence that updating the app would be easy.

Given the positive results the team saw with the release of Threads, Meta plans to expand its use of Compose to some of Instagram’s most important surfaces, like the app’s main feed.

“It’s reached a point where Jetpack Compose can do almost everything you’ll need, and its modular nature makes it easy to make most of the changes you would need to fill the gaps,” said Richard. “I believe Compose is the future of Android UI development, and it’s just fun!”

Get started

Optimize your UI development with Jetpack Compose.