With Jetpack Compose’s recent Beta release, I took the time to learn about the changes and how some of them work. This article will not go into the specific API, but rather will freely share some of the questions I had and what I learned from the search for answers.
“Compose”?
Android has been around for more than a decade, and the traditional Android UI ToolKit has a lot of legacy issues, some of which are hard to fix officially. For example, view. Java has more than 30,000 lines of code. For example, a Combo box is called a Spinner. Meanwhile, some of the official widget fixes rely on system upgrades, which take too long to reach users.
By adding Compose to Jetpack, the code fixes can reach users faster without Android.
For domestic developers, more uniform code means no vendor customization. The other day, one of my colleagues said, “Does anybody have time to rewrite an editText? A bunch of vendor/system problems.” I think his dream might come true.
In addition, Compose makes coding faster and easier by introducing declarative programming that relies on Kotlin features.
Imagine writing an interface for searching contacts. How much code would a traditional Android developer need to write this interface? Activity an XML, item an XML, package a RecyclerView, and write a Adapter, write so many, may also be thankless, XML into view in the process, IO and reflection affects the performance, interface more complex, go asynchronous layout or X2C? In compose, you might only need the following short piece of code, and have no performance issues with XML.
As shown above, in Compose, the UI is assembled by continuously modulating the methods.
What is declarative programming?
Mention Compose, and you have to know what declarative programming is.
According to Wikipedia, declarative programming is a programming paradigm that expresses logic but does not describe the flow of control. Telling the computer what I want rather than telling it how to do it. That corresponds to What I want the UI to be, not how the UI should change, and of course automatic UI changes need to be supported by the framework.
Declarative programming has been widely used in frameworks like React and Flutter to declare states, state changes, and automatic UI redrawing.
Interestingly, Jim Sproch, founder of Compose and a core developer at React, talked on Slack about some of the problems with VDOM. For example, the memory allocation of VDOM can become a performance bottleneck in complex projects. Compose calls its composable method. Reduce memory allocation. Compared to VDOM, compose hides node in the background to prevent abuse and makes it easier to use if/for to control the process.
How does @Composable work?
@Composable
fun CounterInner() {
val count = remember { (mutableStateOf(0)) }
Button(onClick = { count.value += 1 }) {
Text(text = "${count.value}")
}
}
Copy the code
In the simple example above, when clicking on a button, the text in the button is automatically incremented by 1. Remember is used to record the latest count.
@composable is an annotation, and to automatically update the UI, you must have modified the Class file. Let’s see what the Class file looks like. The compiled Kotlin classes are in the build/ TMP /kotlin-classes directory, but you can’t see the decompiled classes in Android Studio. You can use Jadx instead. The Composable methods of Text() are also changed when compiled into classes. For ease of reading, it is best to use Jadx to read the decompiled source code after compiling APK.
Here is the compiled CountInner method. As you can see, the parameters of the method have been changed. A lot of start/end has been added to the method block, and the Lambda that calls Text() has become a ComposableLamda.
How did these changes come about? If I remember correctly, Kotlin’s coroutine also did something to change the parameters of the method. Are they similar implementations? Class files are dynamically modified by the application layer. Gradle Transform uses ASM to manipulate class files.
A search revealed a new feature of Kotlin Compiler for Compose, which uses IR Extension to modify logic during intermediate code generation. What is IR? Short for intermediate representation, translated as intermediate language. Kotlin has opened up extension capabilities for Compose and consolidated the JVM/JS/Native IR pipeline to provide cross-platform support. The same thing that Kotlin did for coroutines, you can do at the application level by using IR Extension.
Talk is cheap, I will show you the code.
The source code for Compose Compiler is here. 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. The concrete logic can see the source code, the comment describes more clearly.
How did the reorganization work?
If you look at the documentation for Compose, there’s always the word Recomposition, which is automatically updating the UI as the state changes. So how does the reorganization work?
@Composable
fun CounterInner() {
val count = remember { (mutableStateOf(0)) }
Button(onClick = { count.value += 1 }) {
Text(text = "${count.value}")
}
}
Copy the code
Each call to count.getValue() will eventually call back to Composer, which maintains a Map. In this case, state is associated with the current scope, which can be understood as a range that can be regrouped. Where does the current scope come from? Remember that the compiled class has a lot more start and end. 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, updateScope is called to update the scope’s block property, which is a lambda. This lambda is redrawn using the composable method, so that the state and the block are associated. When the state changes later, just get the block and execute it. In this example, the block corresponding to count state is a lambda calling the Button method.
Now let’s look at the process of updating state. Every time you call count.setvalue (), it will finally tune to the recordModificationsOf method in the Composer. Then it will get the scope corresponding to state from the Map in the previous segment and add it to the invalidations. The next time vsync happens, the Invoke method of the lambda in InValidations is called to update the UI.
For example, in Compose, there is a very important data structure called a SlotTable, and this is something that needs to be stored. For example, in Compose, there is a very important data structure called a SlotTable, and this is something that needs 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 TextView?
Does Text correspond to TextView? Isn’t.
Debug took a look. All of the composable UI ended up wrapped in an AndroidComposeView underneath the ContentView, so the top layer of things didn’t change. The traditional View tree in Android UI is now a Node tree, and the functions of view are replaced by Node.
Compatible with older systems, you can add AndroidComposeView directly to the XML, so you can mix.
How to write a custom Layout?
Measure/Layout is measured policy. Measure and Layout are written in one method. MeasureSpec (match_parent) is changed to Modifier. FillMaxWidth (). This Modifier will change the Modifier before the measure. The measure will pass in the modified Constraints.
Draw is achieved through the Modifier, or go canvas that set.
How to handle Touch events?
I thought I was quite familiar with Android touch events, but I also found some interesting things when I looked at Android source code before. For example, down events are handled at the bottom level of Native, not as messages in Java layer looper. Therefore, the setMessageLogging method does not detect the down time. < span style = “color: RGB (74, 74, 74); That’s mostly 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, seeing the emergence of Compose, I must also be curious about the change in 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 Boolean, so how do you tell if it’s consumed? There is a consumption attribute in the wrapped event passed in. Each filter determines whether there are unconsumed events and modifies the consumed events. It seems that there is still room for optimization in this area. It seems that there is no consumption before the event, and the subsequent event will be back to.
The Initial, Main, and Final phases are now defined to be handled in the phases you care about. The first two phases are similar to what they were before, and the Final phase is used to process the cancel event.
At the end
Compose is in the process of continuous optimization, such that the Composable function will recently support concurrent execution.
Two years in the making, there’s no doubt about Google’s determination to promote Compose. Compose also has a lot of real-world things in mind to make things easier for developers, such as support for Compose and traditional UI intermodulation, just like Kotlin supports Java intermodulation. While it’s a lot of money, it’s faster and easier, but it’s a long way to go for the community, because many of Jetpack’s libraries aren’t used yet, and Compose’s journey is destined to be harder than Kotlin’s.
Time is limited, this article can only talk on paper, a glimpse of the leopard, throw a brick to introduce jade, if there is a fallacy, also hope not hesitate to give advice.