Skip to content

Commit ca29bed

Browse files
author
Chao Zhang
committed
Add LiveData Transformation
1 parent 39cc830 commit ca29bed

File tree

5 files changed

+118
-7
lines changed

5 files changed

+118
-7
lines changed

build.gradle

+16-3
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,13 @@ plugins {
66
}
77

88
repositories {
9-
mavenCentral()
109
google()
11-
jcenter()
10+
mavenCentral()
11+
jcenter {
12+
content {
13+
includeGroup "org.jetbrains.trove4j"
14+
}
15+
}
1216
}
1317

1418
gradlePlugin {
@@ -26,7 +30,16 @@ compileJava {
2630
}
2731

2832
dependencies {
29-
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7"
33+
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.4.31"
34+
constraints {
35+
implementation("org.jetbrains.kotlin:kotlin-reflect:1.4.31") {
36+
because("Android Gradle Plugin 4.1 depends on Kotlin 1.3.72 but we should not mix 1.3 and 1.4.")
37+
}
38+
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.4.31") {
39+
because("Android Gradle Plugin 4.1 depends on Kotlin 1.3.72 but we should not mix 1.3 and 1.4.")
40+
}
41+
}
3042
implementation 'com.android.tools.build:gradle-api:4.1.3'
43+
implementation 'org.ow2.asm:asm:9.1'
3144
compileOnly 'com.android.tools.build:gradle:4.1.3'
3245
}
+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
distributionBase=GRADLE_USER_HOME
22
distributionPath=wrapper/dists
3-
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-all.zip
3+
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-milestone-3-bin.zip
44
zipStoreBase=GRADLE_USER_HOME
55
zipStorePath=wrapper/dists
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package io.github.chao2zhang
2+
3+
import org.objectweb.asm.ClassVisitor
4+
import org.objectweb.asm.MethodVisitor
5+
import org.objectweb.asm.Opcodes.ASM9
6+
7+
class LoggingLiveDataClassVisitor(
8+
private val classVisitor: ClassVisitor
9+
) : ClassVisitor(ASM9, classVisitor) {
10+
11+
override fun visitMethod(
12+
access: Int,
13+
name: String?,
14+
descriptor: String?,
15+
signature: String?,
16+
exceptions: Array<out String>?
17+
): MethodVisitor {
18+
val methodVisitor = classVisitor.visitMethod(access, name, descriptor, signature, exceptions)
19+
return if (name == "considerNotify") {
20+
LoggingLiveDataMethodVisitor(methodVisitor)
21+
} else {
22+
methodVisitor
23+
}
24+
}
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package io.github.chao2zhang
2+
3+
import org.objectweb.asm.MethodVisitor
4+
import org.objectweb.asm.Opcodes
5+
import org.objectweb.asm.Opcodes.ASM9
6+
7+
/**
8+
* Add the following code after invoking `observer.onChanged`:
9+
* ```
10+
* Log.i("LiveData", "considerNotify() called with LiveData = " + this + " Observer = " + observer.mObserver + " Data = " + this.mData);
11+
* ```
12+
*/
13+
class LoggingLiveDataMethodVisitor(
14+
private val methodVisitor: MethodVisitor
15+
) : MethodVisitor(ASM9, methodVisitor) {
16+
17+
override fun visitMethodInsn(
18+
opcode: Int,
19+
owner: String?,
20+
name: String?,
21+
descriptor: String?,
22+
isInterface: Boolean
23+
) {
24+
methodVisitor.visitMethodInsn(opcode, owner, name, descriptor, isInterface)
25+
if (opcode == Opcodes.INVOKEINTERFACE && owner == "androidx/lifecycle/Observer" && name == "onChanged") {
26+
methodVisitor.visitLdcInsn("LiveData")
27+
methodVisitor.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder")
28+
methodVisitor.visitInsn(Opcodes.DUP)
29+
methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false)
30+
methodVisitor.visitLdcInsn("considerNotify() called with LiveData = ")
31+
methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false)
32+
methodVisitor.visitVarInsn(Opcodes.ALOAD, 0)
33+
methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/Object;)Ljava/lang/StringBuilder;", false)
34+
methodVisitor.visitLdcInsn(" Observer = ")
35+
methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false)
36+
methodVisitor.visitVarInsn(Opcodes.ALOAD, 1)
37+
methodVisitor.visitFieldInsn(Opcodes.GETFIELD, "androidx/lifecycle/LiveData\$ObserverWrapper", "mObserver", "Landroidx/lifecycle/Observer;")
38+
methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/Object;)Ljava/lang/StringBuilder;", false)
39+
methodVisitor.visitLdcInsn(" Data = ")
40+
methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false)
41+
methodVisitor.visitVarInsn(Opcodes.ALOAD, 0)
42+
methodVisitor.visitFieldInsn(Opcodes.GETFIELD, "androidx/lifecycle/LiveData", "mData", "Ljava/lang/Object;")
43+
methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/Object;)Ljava/lang/StringBuilder;", false)
44+
methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false)
45+
methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "android/util/Log", "i", "(Ljava/lang/String;Ljava/lang/String;)I", false)
46+
methodVisitor.visitInsn(Opcodes.POP)
47+
}
48+
}
49+
50+
51+
override fun visitMaxs(maxStack: Int, maxLocals: Int) {
52+
methodVisitor.visitMaxs(3, 2)
53+
}
54+
}

src/main/kotlin/io/github/chao2zhang/LoggingLiveDataTransform.kt

+22-3
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,14 @@ import com.android.build.api.transform.TransformInput
99
import com.android.build.api.transform.TransformInvocation
1010
import com.android.build.api.variant.VariantInfo
1111
import org.gradle.api.logging.Logger
12+
import org.objectweb.asm.ClassReader
13+
import org.objectweb.asm.ClassWriter
14+
import org.objectweb.asm.util.TraceClassVisitor
1215
import java.io.BufferedOutputStream
1316
import java.io.File
1417
import java.io.FileOutputStream
18+
import java.io.PrintWriter
19+
import java.io.StringWriter
1520
import java.util.zip.ZipEntry
1621
import java.util.zip.ZipFile
1722
import java.util.zip.ZipOutputStream
@@ -47,7 +52,6 @@ class LoggingLiveDataTransform(private val logger: Logger) : Transform() {
4752
jarInput.scopes,
4853
Format.JAR
4954
)
50-
logger.lifecycle("Transforming ${jarInput.file}")
5155
if (transformInvocation.isIncremental) {
5256
when (jarInput.status) {
5357
Status.ADDED, Status.CHANGED -> transformJarInput(jarInput, inputJar, outputJar)
@@ -74,10 +78,22 @@ class LoggingLiveDataTransform(private val logger: Logger) : Transform() {
7478
val inputEntries = inputZip.entries()
7579
while (inputEntries.hasMoreElements()) {
7680
val inputEntry = inputEntries.nextElement()
81+
val inputBytes = inputZip.getInputStream(inputEntry).readAllBytes()
82+
var outputBytes = inputBytes
83+
if (inputEntry.isLiveDataClass()) {
84+
logger.lifecycle("Transforming $inputEntry to add logging statements in LiveData")
85+
val stringWriter = StringWriter()
86+
val classReader = ClassReader(inputBytes)
87+
val classWriter = ClassWriter(classReader, 0)
88+
val tracingClassVisitor = TraceClassVisitor(classWriter, PrintWriter(stringWriter))
89+
val classVisitor = LoggingLiveDataClassVisitor(tracingClassVisitor)
90+
classReader.accept(classVisitor, 0)
91+
outputBytes = classWriter.toByteArray()
92+
}
7793
val outputEntry = ZipEntry(inputEntry.name)
7894
with(outputZip) {
7995
putNextEntry(outputEntry)
80-
write(inputZip.getInputStream(inputEntry).readAllBytes())
96+
write(outputBytes)
8197
closeEntry()
8298
}
8399
}
@@ -86,5 +102,8 @@ class LoggingLiveDataTransform(private val logger: Logger) : Transform() {
86102
}
87103

88104
private fun JarInput.isLiveDataJarInput(): Boolean =
89-
name.startsWith("androidx.lifecycle:lifecycle-livedata:")
105+
name.startsWith("androidx.lifecycle:lifecycle-livedata-core:")
106+
107+
private fun ZipEntry.isLiveDataClass(): Boolean =
108+
name == "androidx/lifecycle/LiveData.class"
90109
}

0 commit comments

Comments
 (0)