preface
I wrote an article about how to write Transform increment. This time I want to talk about something else.
Let's start with a few strange questions.
- If you want to write an R file inline, what will you do and what problems may you encounter?
- If the Transform is out of date, it's better to abstract it in that way.
Finally, there are some simple asm related things I recently played.
I'm starting to pretend
If you want to inline the R file, what problems may you encounter?
On Android AGP version 4.1.0, R8 has done this part of the inline operation, so we don't need to write such a conversion at all
Because in the process of Transform operation, we access class files in the form of file path. In this case, it is impossible to ensure whether the access is orderly and syntactic.
Because we first need to get the R file, then collect the contents of the R file, and then go to all classes that use the R file, we may not be able to operate if we use the normal Transform process
Then cache data for incremental compilation through file or class information, because we need to record the last R file during incremental compilation, otherwise there will be a problem of missing in incremental case. This part is especially easy to occur in the process of route table increment.
Secondary scanning
If we split the file scanning operation into two parts, the first time we only collect data, and the second time we make modifications based on the data collected for the first time, is it OK?
In the same way, can we perfectly solve this part of the problem when R files are inlined with such complex asm operations.
In the first scan, we only access asm files without modifying them. In this process, we only collect the data information we need. Of course, this operation will not carry out any asm replacement operation and file writing operation, but will only convert the file into asm syntax related. Of course, it doesn't matter whether we use tree api or core api here.
The tree api is faster than the core api.
api 'org.ow2.asm:asm:9.2' api 'org.ow2.asm:asm-tree:9.2'
In the second asm operation, we will perform file copy operation and class replacement. In the second time, we will restore the original class replacement or method replacement after the first collection of class data.
You can taste one product in detail. Can basically any complex operation be based on this set of logic. But I still don't recommend you to write this. After all, it's still easy to make a black pot.
Incremental cache
In this part, I feel that the increment of routing table can easily explain this situation.
When we want to perform the second incremental compilation, due to the characteristics of incremental compilation, only the changed file needs to be modified, but the previous routing table needs to be restored at this time.
For this part, please refer to the routing Transform or DRouter , I analyzed it before, because the registered class of my routing table is class, not jar. So I read the current class through asm to trace the last routing table. Then I will add, delete and modify this operation on this registration class.
and DRouter A json file is generated to record the last routing table through the json file, and then modify the routing table during incremental compilation, and then overwrite the json file after this compilation.
And we studied it ourselves BRouter The implementation of incremental caching is even more bizarre. We are based on javac task and json file caching.
Transform has expired
If you are interested in trying to upgrade the agp 700 version, you should find that the Transform has been identified and discarded. Gorson can also refer to the previous introduction.
What does it mean that the AGP Transform API is abandoned
TransformAction is an api after version 700, but I'm sorry I haven't learned it yet.
However, booster and bytex, two powerful open source frameworks, are actually isolated from Transform, including the bytecode framework used internally.
The advantage of such abstraction is that when the api of agp is expired and replaced and adjusted, we can avoid all the people who wrote Transform adjusting together.
Only need the bottom layer to do the corresponding adaptation work, and then let the upper layer development students upgrade the lower layer library version.
TODO
In this part, I also intend to abstract and develop it later. Together with spi, it can become a dynamic framework.
Automatic exposure
My previous article introduced about Effective exposure monitoring of View (Part I) . I have also made a small improvement and attempt on this recently. I intend to try to adjust it with ASM.
View.OnAttachStateChangeListener
Before, we need to customize many layouts, then modify the root node in the xml layout, and then find a specific layout through kt's expansion function.
Because it involves two parts of code, my personal opinion is relatively cumbersome to use. Later, my boss pointed out my api. In fact, View itself provides an addOnAttachStateChangeListener. Its role is the same as that of onViewAttachedToWindow and onViewDetachedFromWindow introduced earlier.
In this way, we don't need to write a custom View replacement. We just need to insert a call to itemView.
viewTreeObserver.addOnWindowFocusChangeListener(this) view.addOnAttachStateChangeListener(this) } override fun onViewAttachedToWindow(v: View?) { exposeChecker.updateStartTime() } override fun onViewDetachedFromWindow(v: View?) { onExpose() // exposeChecker.updateStartTime() } override fun onWindowFocusChanged(hasFocus: Boolean) { if (hasFocus) { exposeChecker.updateStartTime() } else { onExpose() } } private fun onExpose() { if (exposeChecker.isViewExpose(view)) { mListener?.onExpose(exposeChecker.exposeTime) } } }
Simply put, the courage code is lost. If you are interested, you can go to the AutoTrack project.
Auto insert exposure monitor
After we have written the above code, we have basically solved the problem of xml, so the rest is that we need to use recyclerview Just insert our exposure monitoring class into the initialization function of viewholder.
This part of logic is relatively simple. I'll just say it briefly.
class RecyclerViewHolderImp(classNode: ClassNode) { init { if (isRecyclerViewHolder(classNode.superName)) { classNode.methods.firstOrNull { it.name == "<init>" }?.apply { instructions.forEach { if (it.methodEnd()) { instructions.insertBefore(it, VarInsnNode(Opcodes.ALOAD, 1)) instructions.insertBefore(it, TypeInsnNode(Opcodes.NEW, "com/wallstreetcn/sample/viewexpose/AutoExposeImp")) instructions.insertBefore(it, InsnNode(Opcodes.DUP)) instructions.insertBefore(it, VarInsnNode(Opcodes.ALOAD, 0)) instructions.insertBefore(it, MethodInsnNode(Opcodes.INVOKESPECIAL, "com/wallstreetcn/sample/viewexpose/AutoExposeImp", "<init>", "(Landroidx/recyclerview/widget/RecyclerView\$ViewHolder;)V", false)) instructions.insertBefore(it, MethodInsnNode(Opcodes.INVOKESTATIC, "com/wallstreetcn/sample/viewexpose/ItemViewExtensionKt", "addExposeListener", "(Landroid/view/View;Lcom/wallstreetcn/sample/viewexpose/OnExposeListener;)V", false)) } } } } } }
class AutoExposeImp(private val viewHolder: RecyclerView.ViewHolder) : OnExposeListener { override fun onExpose(exposeTime: Float) { } } fun View.addExposeListener(listener: OnExposeListener?) { val exposeDelegate = ExposeViewDelegate(this, listener) }
Judge whether the current class is recyclerview For the implementation class of viewholder, if so, insert a static code of the exposure implementation class of AutoExposeImp at the end of the < init > method.
This part of the specific source code is in AndroidAutoTrack There is a sample inside. Interested brothers can refer to it.
summary
In fact, the difficulty of asm is not particularly high. As long as you try more, write more and debug more, you don't need to be smart to master this skill. With the help of reverse tools, in fact, more operations are just like copying and pasting.
In addition, there is the opportunity to learn more about some strong open source frameworks and what their implementation is. It can help us understand and abstract code.
booster The Gradle open source framework written by SENGO is really great. SENGO is the best in the universe
ByteX Byte bytecode open source framework is also a very awesome framework
lancet Our boss's early works are also a byte code framework that is outrageous
BRouter Our BiliBili routing framework is really awesome
DRouter Didi's routing is actually a little interesting. In order to save compilation time, it is all based on Transform