Skip to content

Commit

Permalink
Add LiveData Transformation
Browse files Browse the repository at this point in the history
  • Loading branch information
Chao Zhang committed Apr 6, 2021
1 parent 39cc830 commit ca29bed
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 7 deletions.
19 changes: 16 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,13 @@ plugins {
}

repositories {
mavenCentral()
google()
jcenter()
mavenCentral()
jcenter {
content {
includeGroup "org.jetbrains.trove4j"
}
}
}

gradlePlugin {
Expand All @@ -26,7 +30,16 @@ compileJava {
}

dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.4.31"
constraints {
implementation("org.jetbrains.kotlin:kotlin-reflect:1.4.31") {
because("Android Gradle Plugin 4.1 depends on Kotlin 1.3.72 but we should not mix 1.3 and 1.4.")
}
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.4.31") {
because("Android Gradle Plugin 4.1 depends on Kotlin 1.3.72 but we should not mix 1.3 and 1.4.")
}
}
implementation 'com.android.tools.build:gradle-api:4.1.3'
implementation 'org.ow2.asm:asm:9.1'
compileOnly 'com.android.tools.build:gradle:4.1.3'
}
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-milestone-3-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package io.github.chao2zhang

import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.MethodVisitor
import org.objectweb.asm.Opcodes.ASM9

class LoggingLiveDataClassVisitor(
private val classVisitor: ClassVisitor
) : ClassVisitor(ASM9, classVisitor) {

override fun visitMethod(
access: Int,
name: String?,
descriptor: String?,
signature: String?,
exceptions: Array<out String>?
): MethodVisitor {
val methodVisitor = classVisitor.visitMethod(access, name, descriptor, signature, exceptions)
return if (name == "considerNotify") {
LoggingLiveDataMethodVisitor(methodVisitor)
} else {
methodVisitor
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package io.github.chao2zhang

import org.objectweb.asm.MethodVisitor
import org.objectweb.asm.Opcodes
import org.objectweb.asm.Opcodes.ASM9

/**
* Add the following code after invoking `observer.onChanged`:
* ```
* Log.i("LiveData", "considerNotify() called with LiveData = " + this + " Observer = " + observer.mObserver + " Data = " + this.mData);
* ```
*/
class LoggingLiveDataMethodVisitor(
private val methodVisitor: MethodVisitor
) : MethodVisitor(ASM9, methodVisitor) {

override fun visitMethodInsn(
opcode: Int,
owner: String?,
name: String?,
descriptor: String?,
isInterface: Boolean
) {
methodVisitor.visitMethodInsn(opcode, owner, name, descriptor, isInterface)
if (opcode == Opcodes.INVOKEINTERFACE && owner == "androidx/lifecycle/Observer" && name == "onChanged") {
methodVisitor.visitLdcInsn("LiveData")
methodVisitor.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder")
methodVisitor.visitInsn(Opcodes.DUP)
methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false)
methodVisitor.visitLdcInsn("considerNotify() called with LiveData = ")
methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false)
methodVisitor.visitVarInsn(Opcodes.ALOAD, 0)
methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/Object;)Ljava/lang/StringBuilder;", false)
methodVisitor.visitLdcInsn(" Observer = ")
methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false)
methodVisitor.visitVarInsn(Opcodes.ALOAD, 1)
methodVisitor.visitFieldInsn(Opcodes.GETFIELD, "androidx/lifecycle/LiveData\$ObserverWrapper", "mObserver", "Landroidx/lifecycle/Observer;")
methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/Object;)Ljava/lang/StringBuilder;", false)
methodVisitor.visitLdcInsn(" Data = ")
methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false)
methodVisitor.visitVarInsn(Opcodes.ALOAD, 0)
methodVisitor.visitFieldInsn(Opcodes.GETFIELD, "androidx/lifecycle/LiveData", "mData", "Ljava/lang/Object;")
methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/Object;)Ljava/lang/StringBuilder;", false)
methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false)
methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "android/util/Log", "i", "(Ljava/lang/String;Ljava/lang/String;)I", false)
methodVisitor.visitInsn(Opcodes.POP)
}
}


override fun visitMaxs(maxStack: Int, maxLocals: Int) {
methodVisitor.visitMaxs(3, 2)
}
}
25 changes: 22 additions & 3 deletions src/main/kotlin/io/github/chao2zhang/LoggingLiveDataTransform.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,14 @@ import com.android.build.api.transform.TransformInput
import com.android.build.api.transform.TransformInvocation
import com.android.build.api.variant.VariantInfo
import org.gradle.api.logging.Logger
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassWriter
import org.objectweb.asm.util.TraceClassVisitor
import java.io.BufferedOutputStream
import java.io.File
import java.io.FileOutputStream
import java.io.PrintWriter
import java.io.StringWriter
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
import java.util.zip.ZipOutputStream
Expand Down Expand Up @@ -47,7 +52,6 @@ class LoggingLiveDataTransform(private val logger: Logger) : Transform() {
jarInput.scopes,
Format.JAR
)
logger.lifecycle("Transforming ${jarInput.file}")
if (transformInvocation.isIncremental) {
when (jarInput.status) {
Status.ADDED, Status.CHANGED -> transformJarInput(jarInput, inputJar, outputJar)
Expand All @@ -74,10 +78,22 @@ class LoggingLiveDataTransform(private val logger: Logger) : Transform() {
val inputEntries = inputZip.entries()
while (inputEntries.hasMoreElements()) {
val inputEntry = inputEntries.nextElement()
val inputBytes = inputZip.getInputStream(inputEntry).readAllBytes()
var outputBytes = inputBytes
if (inputEntry.isLiveDataClass()) {
logger.lifecycle("Transforming $inputEntry to add logging statements in LiveData")
val stringWriter = StringWriter()
val classReader = ClassReader(inputBytes)
val classWriter = ClassWriter(classReader, 0)
val tracingClassVisitor = TraceClassVisitor(classWriter, PrintWriter(stringWriter))
val classVisitor = LoggingLiveDataClassVisitor(tracingClassVisitor)
classReader.accept(classVisitor, 0)
outputBytes = classWriter.toByteArray()
}
val outputEntry = ZipEntry(inputEntry.name)
with(outputZip) {
putNextEntry(outputEntry)
write(inputZip.getInputStream(inputEntry).readAllBytes())
write(outputBytes)
closeEntry()
}
}
Expand All @@ -86,5 +102,8 @@ class LoggingLiveDataTransform(private val logger: Logger) : Transform() {
}

private fun JarInput.isLiveDataJarInput(): Boolean =
name.startsWith("androidx.lifecycle:lifecycle-livedata:")
name.startsWith("androidx.lifecycle:lifecycle-livedata-core:")

private fun ZipEntry.isLiveDataClass(): Boolean =
name == "androidx/lifecycle/LiveData.class"
}

0 comments on commit ca29bed

Please sign in to comment.