With the recent Beta release of Jetpack Compose, we took the time to take a look at some of the changes that Compose is bringing and how they work. This article will not go into specific apis, but will just casually share some of the questions I have and some of the things I learned in the search for answers.

Why Compose?


Android has been around for more than 10 years, and the traditional Android UI ToolKit has a lot of historical problems, and some of them are difficult to modify officially. For example, view.java has over 30,000 lines of code, for example, the Combo Box is called Spinner, and for example, Button inherits from Textview. Meanwhile, some official widget fixes rely on system upgrades and take too long to reach users.

By adding Compose to Jetpack, and moving away from Android, code fixes can reach users faster.

For domestic developers, a more uniform code means no vendor customization. A friend of mine recently complained to me about “who has time to rewrite editText? Vendor/system issues” and I think his dream may be coming true.

Compose, meanwhile, relies on Kotlin features to make code writing faster and easier by introducing declarative programming.

Imagine writing an interface to search contacts. How much code does a traditional Android developer need to write that interface? Activity an XML, an ITEM an XML, packaging a recyclerView, and then write a Adapter, write so much, may also be laborious and not to please, XML into view process, IO and reflection affect the performance, interface more complex, go asynchronous layout or X2C? In Compose, the following snippet of code might be required, with no PERFORMANCE issues with XML.

For example, in Compose, the user interface is assembled by constantly tuning methods.

What is declarative programming?

When it comes to Compose, you have to understand what declarative programming is.

To take a look at wikipedia, declarative programming is a programming paradigm that expresses logic but does not describe specific control flows. It’s telling the computer what I want, not telling it how to do it. That corresponds to What I want the UI to be, not how the UI should be changed, and of course the UI needs to be automatically changed by the framework.

Declarative programming has been widely used in frameworks like React and Flutter to declare states, change states, and automatically redraw the UI.

Interestingly, Compose’s founder Jim Sproch, a former React core developer, talked about some of the problems with VDOM in Slack. For example, VDOM allocation can be a performance bottleneck in complex projects, and Compose uses the Composable method as a call. Reduce memory allocation. Compose hides node behind vDOM to prevent abuse and makes it easier to control the flow using if/for.

What is the principle of @composable?

In this simple example, when a button is clicked, the text in the button is automatically incremented by 1. Remember is used to record the latest count value.

@composable is an annotation and to automatically update the UI we must modify the Class file. Let’s see what the Class file looks like. The compiled Kotlin class is in build/ TMP /kotlin-classes, but you can’t see it in Android Studio. You can use Jadx. The Composable methods of Text() are also modified after being compiled into class. For ease of reading, it is best to compile APK and then decomcompile the source code using Jadx.

This is the compiled CountInner method. We can see that the method parameters have been changed, the method blocks have added a lot of start/end, and the lambdas calling Text() have become ComposableLamda.

How did these changes come about? If I remember correctly, Kotlin’s coroutine also does something to change the parameters of the method. Are they similar implementations? Gradle Transform is a Gradle Transform that uses ASM to manipulate classes.

A quick search revealed that Compose applies a new feature in Kotlin Compiler that allows you to modify the logic during intermediate code generation via IR Extension. What is IR? An abbreviation for intermediate representation, translated into intermediate language. Kotlin opens up the extensibility capabilities for Compose and consolidates the JVM/JS/Native IR pipeline for cross-platform support. You can think of what Kotlin did with coroutines that you can do at the application layer by using IR Extension.

Talk is cheap, I will show you the code.

Compose Compiler source code. The registered ComposeIrGenerationextension ComposePlugin. Kt. Again in ComposeIrGenerationExtension ComposableFunctionBodyTransformer realized the methods described above to add the start/end, Change into ComposerLambda ComposerLambdaMemoization implementation described above. Specific logic can see the source code, annotation description is more clear.

How did the reorganization come about?

Looking at the Compose document, there’s always the word Recomposition, which is automatically updating the UI when the state changes. So how does that happen?

Each time count.getValue() is called, it is eventually called back to Composer, where a Map is maintained, and state is associated with the current scope, which can be understood as a range that can be reorganized. Where does the current scope come from? When the start method is called, a scope is generated and placed at the top of the stack. So when calling count.getValue(), just grab the scope at the top of the stack. When end is called, the updateScope is called to update the scope’s block property, which is a lambda. Executing the lambda calls the corresponding Composable method to redraw, so that state and block are associated. When the state changes later, you just take the block and execute it. In this case, the block corresponding to count state is a lambda that calls the Button method.

Let’s look at the process of updating state. The recordModificationsOf method in Composer will be called each time count.setValue () is called. The scope of state will be retrieved from Map and added to invalidations. The next time vsync is performed, the invoke method of lambda in Invalidations is called to update the UI.

Note that “a scope is generated when the start method is called”, but in fact it is enough to generate the scope for the first time and use the old one when updating the UI. There are too many similar things that need to be stored. Just said that the scope of reuse and the example is the remember in the use of SlotTable, specific can see further explanation Jetpack Compose | implementation principle.

Does Text correspond to a TextView?

Does Text correspond to a TextView? Isn’t.

Debug checks that all the Composable UI ends up wrapped in an AndroidComposeView underneath the ContentView, so the top layer is unchanged. The view tree in the traditional Android UI is now a Node tree, and node has replaced the functions of the View.

Compatible with older architectures, you can add AndroidComposeView directly to the XML so it can be mixed.

How to customize Layout?

Measure/Layout follows measurePolicy. Write measure and Layout in one method. MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec When measure, the modified Constraints are passed in.

Draw is achieved through the Modifier, or go canvas that set.

How to handle the Touch event?

I think I have a good understanding of Android touch events. I also found some interesting points when I read Android source code before, for example, down events are processed in native layer, not as message in Java layer Looper. Therefore, setMessageLogging does not detect the time in Down. A callback for Input/animation/layout. That’s primarily used to handle move events. You think only a coordinate point in the move events, see MotionEvent. GetHistorySize method, then the size and the sampling rate and the touch screen sampling rate is what relation?

Anyway, with Compose coming up, I’m sure you’re also curious about the changes to the way Touch events are handled.

DispatchTouchEvent onInterceptTouchEvent/onTouchEvent disappeared, these methods need to implement PointerInputFilter intercept events, main logic is written in the onPointerEvent method, This method doesn’t even return a Boolean, so how can you tell if it was consumed? The wrapped event passed in has an attribute whether it is consumed or not. Each filter determines whether there are unconsumed events to modify the consumed events. It feels like there is room for optimization in this section, as if there are no previous events to consume, and subsequent events will be called back to.

The Initial, Main, and Final phases are now defined and handled in the phase you care about. The first two phases are similar to the previous phase, and the Final phase is similar to the previous cancel event.

At the end

Compose is still in the process of being optimized, for example, the Composable function has recently been added to support concurrent execution.

After two years, there’s no doubting Google’s determination to bring Compose to market. Compose has many practical considerations for developers, such as support for Compose and traditional UI intermodulation in the same way that Kotlin supports Java intermodulation. While the cost is huge, it’s certainly faster and easier, but adoption in the community will take time to prove. Many of Jetpack’s libraries aren’t used yet, and Compose’s journey is bound to be more difficult than Kotlin’s.

Time is limited, this article can only talk on paper, catch a glimpse of what is inside, cast a brick to attract jade, if have fallacy, also hope not stingy give advice.