From e738fe270129f6353d5c91ccd37ced89a9384bca Mon Sep 17 00:00:00 2001 From: jackjintai Date: Fri, 7 Aug 2020 10:31:04 +0800 Subject: [PATCH] =?UTF-8?q?android:=E6=9B=B4=E6=96=B0=E6=89=93=E5=8C=85?= =?UTF-8?q?=E8=A7=84=E5=88=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../doraemonkit/plugin/DoKitExt.kt | 114 ++++++ .../doraemonkit/plugin/DoKitExtUtil.kt | 174 +++++++++ .../doraemonkit/plugin/DoKitPlugin.kt | 141 +++++++ .../plugin/DoKitTransformInvocation.kt | 226 +++++++++++ .../DoKitTransformTaskExecutionListener.kt | 23 ++ .../doraemonkit/plugin/ServiceLoader.kt | 76 ++++ .../asmtransformer/BaseDoKitAsmTransformer.kt | 82 ++++ .../asmtransformer/DoKitAsmTransformer.kt | 16 + .../classtransformer/BigImgTransformer.kt | 152 ++++++++ .../classtransformer/CommTransformer.kt | 362 ++++++++++++++++++ .../EnterMethodStackTransformer.kt | 169 ++++++++ .../GlobalSlowMethodTransformer.kt | 137 +++++++ .../MethodStackDepTransformer.kt | 149 +++++++ .../UrlConnectionTransformer.kt | 67 ++++ .../doraemonkit/plugin/extension/CommExt.kt | 34 ++ .../doraemonkit/plugin/extension/DoKitExt.kt | 42 ++ .../plugin/extension/SlowMethodExt.kt | 120 ++++++ .../processor/DoKitPluginConfigProcessor.kt | 68 ++++ .../plugin/stack_method/MethodStackNode.kt | 18 + .../stack_method/MethodStackNodeUtil.kt | 30 ++ .../plugin/transform/DoKitBaseTransform.kt | 86 +++++ .../plugin/transform/DoKitCommTransform.kt | 12 + .../plugin/transform/DoKitDependTransform.kt | 19 + 23 files changed, 2317 insertions(+) create mode 100644 Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/DoKitExt.kt create mode 100644 Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/DoKitExtUtil.kt create mode 100644 Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/DoKitPlugin.kt create mode 100644 Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/DoKitTransformInvocation.kt create mode 100644 Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/DoKitTransformTaskExecutionListener.kt create mode 100644 Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/ServiceLoader.kt create mode 100644 Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/asmtransformer/BaseDoKitAsmTransformer.kt create mode 100644 Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/asmtransformer/DoKitAsmTransformer.kt create mode 100644 Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/classtransformer/BigImgTransformer.kt create mode 100644 Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/classtransformer/CommTransformer.kt create mode 100644 Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/classtransformer/EnterMethodStackTransformer.kt create mode 100644 Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/classtransformer/GlobalSlowMethodTransformer.kt create mode 100644 Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/classtransformer/MethodStackDepTransformer.kt create mode 100644 Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/classtransformer/UrlConnectionTransformer.kt create mode 100644 Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/extension/CommExt.kt create mode 100644 Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/extension/DoKitExt.kt create mode 100644 Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/extension/SlowMethodExt.kt create mode 100644 Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/processor/DoKitPluginConfigProcessor.kt create mode 100644 Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/stack_method/MethodStackNode.kt create mode 100644 Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/stack_method/MethodStackNodeUtil.kt create mode 100644 Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/transform/DoKitBaseTransform.kt create mode 100644 Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/transform/DoKitCommTransform.kt create mode 100644 Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/transform/DoKitDependTransform.kt diff --git a/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/DoKitExt.kt b/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/DoKitExt.kt new file mode 100644 index 00000000..eb3c343b --- /dev/null +++ b/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/DoKitExt.kt @@ -0,0 +1,114 @@ +package com.didichuxing.doraemonkit.plugin + +import com.android.build.gradle.api.BaseVariant +import com.didiglobal.booster.transform.TransformContext +import org.objectweb.asm.Opcodes.* +import org.objectweb.asm.tree.InsnList +import org.objectweb.asm.tree.InsnNode +import org.objectweb.asm.tree.MethodInsnNode +import org.objectweb.asm.tree.MethodNode +import java.io.File + +/** + * ================================================ + * 作 者:jint(金台) + * 版 本:1.0 + * 创建日期:2020/5/19-18:00 + * 描 述:dokit 对象扩展 + * 修订历史: + * ================================================ + */ + +fun MethodNode.isGetSetMethod(): Boolean { + var ignoreCount = 0 + val iterator = instructions.iterator() + while (iterator.hasNext()) { + val insnNode = iterator.next() + val opcode = insnNode.opcode + if (-1 == opcode) { + continue + } + if (opcode != GETFIELD && opcode != GETSTATIC && opcode != H_GETFIELD && opcode != H_GETSTATIC && opcode != RETURN && opcode != ARETURN && opcode != DRETURN && opcode != FRETURN && opcode != LRETURN && opcode != IRETURN && opcode != PUTFIELD && opcode != PUTSTATIC && opcode != H_PUTFIELD && opcode != H_PUTSTATIC && opcode > SALOAD) { + if (name.equals("") && opcode == INVOKESPECIAL) { + ignoreCount++ + if (ignoreCount > 1) { + return false + } + continue + } + return false + } + } + return true +} + +fun MethodNode.isSingleMethod(): Boolean { + val iterator = instructions.iterator() + while (iterator.hasNext()) { + val insnNode = iterator.next() + val opcode = insnNode.opcode + if (-1 == opcode) { + continue + } else if (INVOKEVIRTUAL <= opcode && opcode <= INVOKEDYNAMIC) { + return false + } + } + return true +} + +fun MethodNode.isEmptyMethod(): Boolean { + val iterator = instructions.iterator() + while (iterator.hasNext()) { + val insnNode = iterator.next() + val opcode = insnNode.opcode + return if (-1 == opcode) { + continue + } else { + false + } + } + return true +} + + +fun InsnList.getMethodExitInsnNodes(): Sequence? { + return this.iterator()?.asSequence()?.filterIsInstance(InsnNode::class.java)?.filter { + it.opcode == RETURN || + it.opcode == IRETURN || + it.opcode == FRETURN || + it.opcode == ARETURN || + it.opcode == LRETURN || + it.opcode == DRETURN || + it.opcode == ATHROW + } +} + +fun BaseVariant.isRelease(): Boolean { + if (this.name.contains("release") || this.name.contains("Release")) { + return true + } + return false +} + + +fun TransformContext.isRelease(): Boolean { + if (this.name.contains("release") || this.name.contains("Release")) { + return true + } + return false +} + + +fun String.println() { + if (DoKitExtUtil.dokitLogSwitchOpen()) { + println("[dokit plugin]===>$this") + } +} + +fun File.lastPath(): String { + return this.path.split("/").last() + +} + +val MethodInsnNode.ownerClassName: String + get() = owner.replace('/', '.') \ No newline at end of file diff --git a/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/DoKitExtUtil.kt b/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/DoKitExtUtil.kt new file mode 100644 index 00000000..7e54ef64 --- /dev/null +++ b/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/DoKitExtUtil.kt @@ -0,0 +1,174 @@ +package com.didichuxing.doraemonkit.plugin + +import com.didichuxing.doraemonkit.plugin.extension.CommExt +import com.didichuxing.doraemonkit.plugin.extension.DoKitExt +import com.didichuxing.doraemonkit.plugin.extension.SlowMethodExt +import org.gradle.api.Project + +/** + * ================================================ + * 作 者:jint(金台) + * 版 本:1.0 + * 创建日期:2020/3/24-14:58 + * 描 述: + * 修订历史: + * ================================================ + */ +object DoKitExtUtil { + //private var mApplicationId: String = "" + + /** + * dokit 插件开关 字段权限必须为public 否则无法进行赋值 + */ + var DOKIT_PLUGIN_SWITCH = true + var DOKIT_LOG_SWITCH = false + + /** + * 默认函数调用为5级 + */ + var STACK_METHOD_LEVEL = 5 + + /** + * 慢函数默认关闭 + */ + var SLOW_METHOD_SWITCH = false + + + /** + * 慢函数策略 默认为函数调用栈策略 + */ + var SLOW_METHOD_STRATEGY = SlowMethodExt.STRATEGY_STACK + + private val applications: MutableSet = mutableSetOf() + var commExt = CommExt() + private set + val slowMethodExt = SlowMethodExt() + + + fun dokitPluginSwitchOpen(): Boolean { + return DOKIT_PLUGIN_SWITCH + } + + + fun dokitLogSwitchOpen(): Boolean { + return DOKIT_LOG_SWITCH + } + + fun dokitSlowMethodSwitchOpen(): Boolean { + return SLOW_METHOD_SWITCH + } + + /** + * 初始化 + * + * @param dokitEx dokitExtension + * @param appExtension appExtension + */ + fun init(dokitEx: DoKitExt, applicationId: String) { + //设置普通的配置 + commExt = dokitEx.comm + //slowMethodExt.strategy = dokitEx.slowMethod.strategy + //slowMethodExt.methodSwitch = dokitEx.slowMethod.methodSwitch + /** + * ============慢函数普通策略的配置 start========== + */ + slowMethodExt.normalMethod.thresholdTime = dokitEx.slowMethod.normalMethod.thresholdTime + //设置慢函数普通策略插装包名 + slowMethodExt.normalMethod.packageNames.clear() + for (packageName in dokitEx.slowMethod.normalMethod.packageNames) { + slowMethodExt.normalMethod.packageNames.add(packageName) + } + //添加默认的包名 + if (applicationId.isNotEmpty()) { + if (slowMethodExt.normalMethod.packageNames.isEmpty()) { + slowMethodExt.normalMethod.packageNames.add(applicationId) + } + } + + + //设置慢函数普通策略插装包名黑名单 + slowMethodExt.normalMethod.methodBlacklist.clear() + for (blackStr in dokitEx.slowMethod.normalMethod.methodBlacklist) { + slowMethodExt.normalMethod.methodBlacklist.add(blackStr) + } + /** + * ============慢函数普通策略的配置end========== + */ + /** + * ============慢函数stack策略的配置 start========== + */ + slowMethodExt.stackMethod.thresholdTime = dokitEx.slowMethod.stackMethod.thresholdTime + slowMethodExt.stackMethod.enterMethods.clear() + //添加默认的入口函数 + for (application in applications) { + val attachBaseContextMethodName = "$application.attachBaseContext" + val onCreateMethodName = "$application.onCreate" + slowMethodExt.stackMethod.enterMethods.add(attachBaseContextMethodName) + slowMethodExt.stackMethod.enterMethods.add(onCreateMethodName) + } + for (methodName in dokitEx.slowMethod.stackMethod.enterMethods) { + slowMethodExt.stackMethod.enterMethods.add(methodName) + } + /** + * ============慢函数stack策略的配置 end========== + */ + + } + + + fun setApplications(applications: MutableSet) { + if (applications.isEmpty()) { + return + } + this.applications.clear() + for (application in applications) { + this.applications.add(application) + } + } + + fun ignorePackageNames(className: String): Boolean { + //命中白名单返回false + for (packageName in whitePackageNames) { + if (className.startsWith(packageName, true)) { + return false + } + } + + //命中黑名单返回true + for (packageName in blackPackageNames) { + if (className.startsWith(packageName, true)) { + return true + } + } + + return false + } + + + /** + * 白名单 + */ + private val whitePackageNames = arrayOf( + "com.didichuxing.doraemonkit.DoraemonKit", + "com.didichuxing.doraemonkit.DoraemonKitReal" + ) + + + /** + * 黑名单 + */ + private val blackPackageNames = arrayOf( + "com.didichuxing.doraemonkit.", + "kotlin.", + "java.", + "android.", + "androidx." + ) + + fun log(tag: String, className: String, methodName: String, access: Int, desc: String, signature: String, thresholdTime: Int) { + if (DOKIT_LOG_SWITCH) { + println("$tag===matched====> className===$className methodName===$methodName access===$access desc===$desc signature===$signature thresholdTime===$thresholdTime") + } + } + +} \ No newline at end of file diff --git a/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/DoKitPlugin.kt b/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/DoKitPlugin.kt new file mode 100644 index 00000000..1777e676 --- /dev/null +++ b/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/DoKitPlugin.kt @@ -0,0 +1,141 @@ +package com.didichuxing.doraemonkit.plugin + +import com.android.build.gradle.AppExtension +import com.android.build.gradle.LibraryExtension +import com.didichuxing.doraemonkit.plugin.extension.DoKitExt +import com.didichuxing.doraemonkit.plugin.extension.SlowMethodExt +import com.didichuxing.doraemonkit.plugin.stack_method.MethodStackNodeUtil +import com.didichuxing.doraemonkit.plugin.transform.DoKitCommTransform +import com.didichuxing.doraemonkit.plugin.transform.DoKitDependTransform +import com.didiglobal.booster.gradle.getAndroid +import com.didiglobal.booster.gradle.getProperty +import org.gradle.api.Plugin +import org.gradle.api.Project + +/** + * ================================================ + * 作 者:jint(金台) + * 版 本:1.0 + * 创建日期:2020/5/14-10:01 + * 描 述: + * 修订历史: + * ================================================ + */ +class DoKitPlugin : Plugin { + override fun apply(project: Project) { + //创建指定扩展 并将project 传入构造函数 + val doKitExt = project.extensions.create("dokitExt", DoKitExt::class.java) + +// val debug = project.gradle.startParameter.taskNames.any { +// it.contains("debug") || it.contains("Debug") +// } +// if (!debug) { +// return +// } + project.gradle.addListener(DoKitTransformTaskExecutionListener(project)) + + //println("project.plugins===>${project.plugins}") + /** + * when 也可以用来取代 if-else if链。 + * 如果不提供参数,所有的分支条件都是简单的布尔表达式,而当一个分支的条件为真时则执行该分支: + */ + + /** + * 作用域函数:let、run、with、apply 以及 also + * 它们的唯一目的是在对象的上下文中执行代码块 + * 由于作用域函数本质上都非常相似,因此了解它们之间的区别很重要。每个作用域函数之间有两个主要区别: + * 引用上下文对象的方式: + * 作为 lambda 表达式的接收者(this)或者作为 lambda 表达式的参数(it) + * run、with 以及 apply 通过关键字 this 引用上下文对象 + * let 及 also 将上下文对象作为 lambda 表达式参数 + * + * 返回值: + * apply 及 also 返回上下文对象。 + * let、run 及 with 返回 lambda 表达式结果. + */ + /** + * 函数 对象引用 返回值 是否是扩展函数 + * let it Lambda 表达式结果 是 + * run this Lambda 表达式结果 是 + * run - Lambda 表达式结果 不是:调用无需上下文对象 + * with this Lambda 表达式结果 不是:把上下文对象当做参数 + * apply this 上下文对象 是 + * also it 上下文对象 是 + */ + + /** + *对一个非空(non-null)对象执行 lambda 表达式:let + *将表达式作为变量引入为局部作用域中:let + *对象配置:apply + *对象配置并且计算结果:run + *在需要表达式的地方运行语句:非扩展的 run + *附加效果:also + *一个对象的一组函数调用:with + */ + when { + project.plugins.hasPlugin("com.android.application") || project.plugins.hasPlugin("com.android.dynamic-feature") -> { + project.getAndroid().let { androidExt -> + + val pluginSwitch = project.getProperty("DOKIT_PLUGIN_SWITCH", true) + val logSwitch = project.getProperty("DOKIT_LOG_SWITCH", false) + val slowMethodSwitch = project.getProperty("DOKIT_METHOD_SWITCH", false) + val slowMethodStrategy = project.getProperty("DOKIT_METHOD_STRATEGY", 0) + val methodStackLevel = project.getProperty("DOKIT_METHOD_STACK_LEVEL", 5) + DoKitExtUtil.DOKIT_PLUGIN_SWITCH = pluginSwitch + DoKitExtUtil.DOKIT_LOG_SWITCH = logSwitch + DoKitExtUtil.SLOW_METHOD_SWITCH = slowMethodSwitch + DoKitExtUtil.SLOW_METHOD_STRATEGY = slowMethodStrategy + DoKitExtUtil.STACK_METHOD_LEVEL = methodStackLevel + + "application module ${project.name} is executing...".println() + + MethodStackNodeUtil.METHOD_STACK_KEYS.clear() + if (DoKitExtUtil.DOKIT_PLUGIN_SWITCH) { + //注册transform + androidExt.registerTransform(DoKitCommTransform(project)) + if (slowMethodSwitch && slowMethodStrategy == SlowMethodExt.STRATEGY_STACK) { + MethodStackNodeUtil.METHOD_STACK_KEYS.add(0, mutableSetOf()) + val methodStackRange = 1 until methodStackLevel + if (methodStackLevel > 1) { + for (index in methodStackRange) { + MethodStackNodeUtil.METHOD_STACK_KEYS.add(index, mutableSetOf()) + androidExt.registerTransform(DoKitDependTransform(project, index)) + } + } + } + } + + //项目评估完毕回调 + project.afterEvaluate { + loadVariantProcessors(project).let { processors -> + androidExt.applicationVariants.forEach { variant -> + processors.forEach { processor -> + processor.process(variant) + } + } + } + } + } + } + + project.plugins.hasPlugin("com.android.library") -> { + project.getAndroid().let { libraryExt -> + "library module ${project.name} is executing...".println() + if (DoKitExtUtil.DOKIT_PLUGIN_SWITCH) { + libraryExt.registerTransform(DoKitCommTransform(project)) + } + project.afterEvaluate { + loadVariantProcessors(project).let { processors -> + libraryExt.libraryVariants.forEach { variant -> + processors.forEach { processor -> + processor.process(variant) + } + } + } + } + } + } + } + } + +} \ No newline at end of file diff --git a/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/DoKitTransformInvocation.kt b/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/DoKitTransformInvocation.kt new file mode 100644 index 00000000..25b8b984 --- /dev/null +++ b/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/DoKitTransformInvocation.kt @@ -0,0 +1,226 @@ +package com.didichuxing.doraemonkit.plugin + +import com.android.build.api.transform.Context +import com.android.build.api.transform.DirectoryInput +import com.android.build.api.transform.Format +import com.android.build.api.transform.JarInput +import com.android.build.api.transform.QualifiedContent +import com.android.build.api.transform.SecondaryInput +import com.android.build.api.transform.Status.ADDED +import com.android.build.api.transform.Status.CHANGED +import com.android.build.api.transform.Status.NOTCHANGED +import com.android.build.api.transform.Status.REMOVED +import com.android.build.api.transform.TransformInput +import com.android.build.api.transform.TransformInvocation +import com.android.build.api.transform.TransformOutputProvider +import com.android.dx.command.dexer.Main +import com.didichuxing.doraemonkit.plugin.transform.DoKitBaseTransform +import com.didiglobal.booster.gradle.* +import com.didiglobal.booster.kotlinx.NCPU +import com.didiglobal.booster.kotlinx.file +import com.didiglobal.booster.kotlinx.green +import com.didiglobal.booster.kotlinx.red +import com.didiglobal.booster.transform.AbstractKlassPool +import com.didiglobal.booster.transform.ArtifactManager +import com.didiglobal.booster.transform.TransformContext +import com.didiglobal.booster.transform.artifacts +import com.didiglobal.booster.transform.util.transform +import java.io.File +import java.net.URI +import java.util.concurrent.* + +/** + * Represents a delegate of TransformInvocation + * + * @author johnsonlee + */ +internal class DoKitTransformInvocation( + private val delegate: TransformInvocation, + internal val transform: DoKitBaseTransform +) : TransformInvocation by delegate, TransformContext, ArtifactManager { + + private val project = transform.project + + private val outputs = CopyOnWriteArrayList() + + override val name: String = delegate.context.variantName + + override val projectDir: File = project.projectDir + + override val buildDir: File = project.buildDir + + override val temporaryDir: File = delegate.context.temporaryDir + + override val reportsDir: File = File(buildDir, "reports").also { it.mkdirs() } + + override val bootClasspath = delegate.bootClasspath + + override val compileClasspath = delegate.compileClasspath + + override val runtimeClasspath = delegate.runtimeClasspath + + override val artifacts = this + + override val klassPool: AbstractKlassPool = object : AbstractKlassPool(compileClasspath, transform.bootKlassPool) {} + + override val applicationId = delegate.applicationId + + override val originalApplicationId = delegate.originalApplicationId + + override val isDebuggable = variant.buildType.isDebuggable + + override val isDataBindingEnabled = delegate.isDataBindingEnabled + + override fun hasProperty(name: String) = project.hasProperty(name) + + @Suppress("UNCHECKED_CAST") + override fun getProperty(name: String, default: T): T = project.properties[name] as? T + ?: default + + override fun get(type: String) = variant.artifacts.get(type) + + internal fun doFullTransform() = doTransform(this::transformFully) + + internal fun doIncrementalTransform() = doTransform(this::transformIncrementally) + + private fun onPreTransform() { + transform.transformers.forEach { + it.onPreTransform(this) + } + } + + private fun onPostTransform() { + transform.transformers.forEach { + it.onPostTransform(this) + } + } + + private fun doTransform(block: (ExecutorService) -> Iterable>) { + this.outputs.clear() + this.onPreTransform() + + val executor = Executors.newFixedThreadPool(NCPU) + try { + block(executor).forEach { + it.get() + } + } finally { + executor.shutdown() + executor.awaitTermination(1, TimeUnit.HOURS) + } + + this.onPostTransform() + + if (transform.verifyEnabled) { + this.doVerify() + } + } + + private fun transformFully(executor: ExecutorService) = this.inputs.map { + it.jarInputs + it.directoryInputs + }.flatten().map { input -> + executor.submit { + val format = if (input is DirectoryInput) Format.DIRECTORY else Format.JAR + outputProvider?.let { provider -> + project.logger.info("Transforming ${input.file}") + input.transform(provider.getContentLocation(input.name, input.contentTypes, input.scopes, format)) + } + } + } + + private fun transformIncrementally(executor: ExecutorService) = this.inputs.map { input -> + input.jarInputs.filter { it.status != NOTCHANGED }.map { jarInput -> + executor.submit { + doIncrementalTransform(jarInput) + } + } + input.directoryInputs.filter { it.changedFiles.isNotEmpty() }.map { dirInput -> + val base = dirInput.file.toURI() + executor.submit { + doIncrementalTransform(dirInput, base) + } + } + }.flatten() + + @Suppress("NON_EXHAUSTIVE_WHEN") + private fun doIncrementalTransform(jarInput: JarInput) { + when (jarInput.status) { + REMOVED -> jarInput.file.delete() + CHANGED, ADDED -> { + project.logger.info("Transforming ${jarInput.file}") + outputProvider?.let { provider -> + jarInput.transform(provider.getContentLocation(jarInput.name, jarInput.contentTypes, jarInput.scopes, Format.JAR)) + } + } + } + } + + @Suppress("NON_EXHAUSTIVE_WHEN") + private fun doIncrementalTransform(dirInput: DirectoryInput, base: URI) { + dirInput.changedFiles.forEach { (file, status) -> + when (status) { + REMOVED -> { + project.logger.info("Deleting $file") + outputProvider?.let { provider -> + provider.getContentLocation(dirInput.name, dirInput.contentTypes, dirInput.scopes, Format.DIRECTORY).parentFile.listFiles()?.asSequence() + ?.filter { it.isDirectory } + ?.map { File(it, dirInput.file.toURI().relativize(file.toURI()).path) } + ?.filter { it.exists() } + ?.forEach { it.delete() } + } + file.delete() + } + ADDED, CHANGED -> { + project.logger.info("Transforming $file") + outputProvider?.let { provider -> + val root = provider.getContentLocation(dirInput.name, dirInput.contentTypes, dirInput.scopes, Format.DIRECTORY) + val output = File(root, base.relativize(file.toURI()).path) + outputs += output + file.transform(output) { bytecode -> + bytecode.transform() + } + } + } + } + } + } + + private fun doVerify() { + outputs.sortedBy(File::nameWithoutExtension).forEach { output -> + val dex = temporaryDir.file(output.name) + val args = Main.Arguments().apply { + numThreads = NCPU + debug = true + warnings = true + emptyOk = true + multiDex = true + jarOutput = true + optimize = false + minSdkVersion = variant.extension.defaultConfig.targetSdkVersion.apiLevel + fileNames = arrayOf(output.absolutePath) + outName = dex.absolutePath + } + val rc = try { + Main.run(args) + } catch (t: Throwable) { + t.printStackTrace() + -1 + } + + println("${if (rc != 0) red("✗") else green("✓")} $output") + dex.deleteRecursively() + } + } + + private fun QualifiedContent.transform(output: File) { + outputs += output + this.file.transform(output) { bytecode -> + bytecode.transform() + } + } + + private fun ByteArray.transform(): ByteArray { + return transform.transformers.fold(this) { bytes, transformer -> + transformer.transform(this@DoKitTransformInvocation, bytes) + } + } +} \ No newline at end of file diff --git a/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/DoKitTransformTaskExecutionListener.kt b/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/DoKitTransformTaskExecutionListener.kt new file mode 100644 index 00000000..43579768 --- /dev/null +++ b/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/DoKitTransformTaskExecutionListener.kt @@ -0,0 +1,23 @@ +package com.didichuxing.doraemonkit.plugin + +import com.android.build.gradle.internal.pipeline.TransformTask +import com.didiglobal.booster.kotlinx.call +import com.didiglobal.booster.kotlinx.get +import org.gradle.api.Project +import org.gradle.api.Task +import org.gradle.api.execution.TaskExecutionAdapter + +/** + * @author neighbWang + */ +class DoKitTransformTaskExecutionListener(private val project: Project) : TaskExecutionAdapter() { + + override fun beforeExecute(task: Task) { + task.takeIf { + it.project == project && it is TransformTask && it.transform.scopes.isNotEmpty() + }?.run { + task["outputStream"]?.call("init") + } + } + +} \ No newline at end of file diff --git a/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/ServiceLoader.kt b/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/ServiceLoader.kt new file mode 100644 index 00000000..206ae6a2 --- /dev/null +++ b/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/ServiceLoader.kt @@ -0,0 +1,76 @@ +package com.didichuxing.doraemonkit.plugin + +import com.didiglobal.booster.task.spi.VariantProcessor +import com.didiglobal.booster.transform.Transformer +import org.gradle.api.Project +import java.net.URL +import java.nio.charset.StandardCharsets +import java.util.ServiceConfigurationError + +internal interface ServiceLoader { + fun load(vararg args: Any): List +} + +internal class ServiceLoaderFactory(private val classLoader: ClassLoader, private val service: Class) { + + fun newServiceLoader(vararg types: Class<*>) = object : ServiceLoader { + + @Suppress("UNCHECKED_CAST") + override fun load(vararg args: Any) = classLoader.getResources("META-INF/services/${service.name}")?.asSequence()?.map(::parse)?.flatten()?.toSet()?.map { provider -> + try { + val providerClass = Class.forName(provider, false, classLoader) + if (!service.isAssignableFrom(providerClass)) { + throw ServiceConfigurationError("Provider $provider not a subtype") + } + + try { + providerClass.getConstructor(*types).newInstance(*args) as T + } catch (e: NoSuchMethodException) { + providerClass.newInstance() as T + } + } catch (e: ClassNotFoundException) { + throw ServiceConfigurationError("Provider $provider not found") + } + } ?: emptyList() + + } + +} + +internal inline fun newServiceLoader(classLoader: ClassLoader, vararg types: Class<*>) = ServiceLoaderFactory(classLoader, T::class.java).newServiceLoader(*types) + +/** + * Load [Transformer]s with the specified [classLoader] + */ +@Throws(ServiceConfigurationError::class) +internal fun loadTransformers(classLoader: ClassLoader) = newServiceLoader(classLoader, ClassLoader::class.java).load(classLoader) + +/** + * Load [VariantProcessor]s with the specified [classLoader] + */ +@Throws(ServiceConfigurationError::class) +internal fun loadVariantProcessors(project: Project) = newServiceLoader(project.buildscript.classLoader, Project::class.java).load(project) + +@Throws(ServiceConfigurationError::class) +private fun parse(u: URL) = try { + u.openStream().bufferedReader(StandardCharsets.UTF_8).readLines().filter { + it.isNotEmpty() && it.isNotBlank() && !it.startsWith('#') + }.map(String::trim).filter(::isJavaClassName) +} catch (e: Throwable) { + emptyList() +} + +private fun isJavaClassName(text: String): Boolean { + if (!Character.isJavaIdentifierStart(text[0])) { + throw ServiceConfigurationError("Illegal provider-class name: $text") + } + + for (i in 1 until text.length) { + val cp = text.codePointAt(i) + if (!Character.isJavaIdentifierPart(cp) && cp != '.'.toInt()) { + throw ServiceConfigurationError("Illegal provider-class name: $text") + } + } + + return true +} diff --git a/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/asmtransformer/BaseDoKitAsmTransformer.kt b/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/asmtransformer/BaseDoKitAsmTransformer.kt new file mode 100644 index 00000000..a8a55663 --- /dev/null +++ b/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/asmtransformer/BaseDoKitAsmTransformer.kt @@ -0,0 +1,82 @@ +package com.didichuxing.doraemonkit.plugin.asmtransformer + +import com.didiglobal.booster.annotations.Priority +import com.didiglobal.booster.transform.TransformContext +import com.didiglobal.booster.transform.Transformer +import com.didiglobal.booster.transform.asm.ClassTransformer +import org.objectweb.asm.ClassReader +import org.objectweb.asm.ClassWriter +import org.objectweb.asm.tree.ClassNode +import java.lang.management.ManagementFactory +import java.lang.management.ThreadMXBean + +/** + * ================================================ + * 作 者:jint(金台) + * 版 本:1.0 + * 创建日期:2020/5/21-16:44 + * 描 述: + * 修订历史: + * ================================================ + */ +open class BaseDoKitAsmTransformer : Transformer { + private val threadMxBean = ManagementFactory.getThreadMXBean() + + private val durations = mutableMapOf() + + internal val transformers: Collection + + /** + * For unit test only + */ + constructor(vararg transformers: ClassTransformer) { + this.transformers = transformers.sortedBy { + it.javaClass.getAnnotation(Priority::class.java)?.value ?: 0 + } + } + + override fun onPreTransform(context: TransformContext) { + this.transformers.forEach { transformer -> + this.threadMxBean.sumCpuTime(transformer) { + transformer.onPreTransform(context) + } + } + } + + override fun transform(context: TransformContext, bytecode: ByteArray): ByteArray { + return ClassWriter(ClassWriter.COMPUTE_MAXS).also { writer -> + this.transformers.fold(ClassNode().also { klass -> + ClassReader(bytecode).accept(klass, 0) + }) { klass, transformer -> + this.threadMxBean.sumCpuTime(transformer) { + transformer.transform(context, klass) + } + }.accept(writer) + }.toByteArray() + } + + override fun onPostTransform(context: TransformContext) { + this.transformers.forEach { transformer -> + this.threadMxBean.sumCpuTime(transformer) { + transformer.onPostTransform(context) + } + } + + val w1 = this.durations.keys.map { + it.javaClass.name.length + }.max() ?: 20 + this.durations.forEach { (transformer, ns) -> + println("${transformer.javaClass.name.padEnd(w1 + 1)}: ${ns / 1000000} ms") + } + } + + private fun ThreadMXBean.sumCpuTime(transformer: ClassTransformer, action: () -> R): R { + val ct0 = this.currentThreadCpuTime + val result = action() + val ct1 = this.currentThreadCpuTime + durations[transformer] = durations.getOrDefault(transformer, 0) + (ct1 - ct0) + return result + } + +} + diff --git a/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/asmtransformer/DoKitAsmTransformer.kt b/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/asmtransformer/DoKitAsmTransformer.kt new file mode 100644 index 00000000..5c89dd82 --- /dev/null +++ b/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/asmtransformer/DoKitAsmTransformer.kt @@ -0,0 +1,16 @@ +package com.didichuxing.doraemonkit.plugin.asmtransformer + +import com.didichuxing.doraemonkit.plugin.classtransformer.MethodStackDepTransformer + +/** + * ================================================ + * 作 者:jint(金台) + * 版 本:1.0 + * 创建日期:2020/5/21-16:44 + * 描 述: + * 修订历史: + * ================================================ + */ +class DoKitAsmTransformer(private val level: Int) : BaseDoKitAsmTransformer(MethodStackDepTransformer(level)) { + +} diff --git a/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/classtransformer/BigImgTransformer.kt b/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/classtransformer/BigImgTransformer.kt new file mode 100644 index 00000000..8ae4858d --- /dev/null +++ b/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/classtransformer/BigImgTransformer.kt @@ -0,0 +1,152 @@ +package com.didichuxing.doraemonkit.plugin.classtransformer + +import com.didichuxing.doraemonkit.plugin.* +import com.didiglobal.booster.annotations.Priority +import com.didiglobal.booster.transform.TransformContext +import com.didiglobal.booster.transform.asm.ClassTransformer +import com.didiglobal.booster.transform.asm.className +import com.google.auto.service.AutoService +import org.objectweb.asm.Opcodes.* +import org.objectweb.asm.tree.ClassNode +import org.objectweb.asm.tree.InsnList +import org.objectweb.asm.tree.MethodInsnNode +import org.objectweb.asm.tree.VarInsnNode + +/** + * ================================================ + * 作 者:jint(金台) + * 版 本:1.0 + * 创建日期:2020/5/14-18:07 + * 描 述:wiki:https://juejin.im/post/5e8d87c4f265da47ad218e6b + * 修订历史: + * ================================================ + */ +@Priority(1) +@AutoService(ClassTransformer::class) +class BigImgTransformer : ClassTransformer { + + override fun transform(context: TransformContext, klass: ClassNode): ClassNode { + if (context.isRelease()) { + return klass + } + + if (!DoKitExtUtil.dokitPluginSwitchOpen()) { + return klass + } + + if (!DoKitExtUtil.commExt.bigImgSwitch) { + return klass + } + + if (DoKitExtUtil.ignorePackageNames(klass.className)) { + return klass + } + + + val className = klass.className + //glide + if (className == "com.bumptech.glide.request.SingleRequest") { + klass.methods.find { methodNode -> + (methodNode.name == "init" || methodNode.name == "") && methodNode.desc != null + }.let { methodNode -> + //函数结束的地方插入 + methodNode?.instructions?.getMethodExitInsnNodes()?.forEach { + "${context.projectDir.lastPath()}->hook glide succeed: ${className}_${methodNode.name}_${methodNode.desc}".println() + methodNode.instructions?.insertBefore(it, createGlideInsnList()) + } + } + } + + //picasso + if (className == "com.squareup.picasso.Request") { + klass.methods.find { methodNode -> + methodNode.name == "" && methodNode.desc != null + }.let { methodNode -> + //函数结束的地方插入 + methodNode?.instructions?.getMethodExitInsnNodes()?.forEach { + "${context.projectDir.lastPath()}->hook picasso succeed: ${className}_${methodNode.name}_${methodNode.desc}".println() + methodNode.instructions?.insertBefore(it, createPicassoInsnList()) + } + } + } + + //Fresco + if (className == "com.facebook.imagepipeline.request.ImageRequest") { + klass.methods.find { methodNode -> + methodNode.name == "" && methodNode.desc != null + }.let { methodNode -> + "${context.projectDir.lastPath()}->hook Fresco succeed: ${className}_${methodNode?.name}_${methodNode?.desc}".println() + //函数开始的地方插入 + methodNode?.instructions?.insert(createFrescoInsnList()) + } + } + + //ImageLoader + if (className == "com.nostra13.universalimageloader.core.ImageLoadingInfo") { + klass.methods.find { methodNode -> + methodNode.name == "" && methodNode.desc != null + }.let { methodNode -> + "${context.projectDir.lastPath()}->hook ImageLoader succeed: ${className}_${methodNode?.name}_${methodNode?.desc}".println() + methodNode?.instructions?.insert(createImageLoaderInsnList()) + } + } + + + return klass + } + + /** + * 创建Glide Aop代码指令 + */ + private fun createGlideInsnList(): InsnList { + return with(InsnList()) { + add(VarInsnNode(ALOAD, 0)) + add(MethodInsnNode(INVOKESTATIC, "com/didichuxing/doraemonkit/aop/bigimg/glide/GlideHook", "proxy", "(Ljava/lang/Object;)V", false)) + this + } + } + + /** + * 创建Picasso Aop代码指令 + */ + private fun createPicassoInsnList(): InsnList { + return with(InsnList()) { + add(VarInsnNode(ALOAD, 0)) + add(MethodInsnNode(INVOKESTATIC, "com/didichuxing/doraemonkit/aop/bigimg/picasso/PicassoHook", "proxy", "(Ljava/lang/Object;)V", false)) + this + } + + } + + + /** + * 创建Fresco Aop代码指令 + */ + private fun createFrescoInsnList(): InsnList { + return with(InsnList()) { + add(VarInsnNode(ALOAD, 1)) + add(VarInsnNode(ALOAD, 1)) + add(MethodInsnNode(INVOKEVIRTUAL, "com/facebook/imagepipeline/request/ImageRequestBuilder", "getSourceUri", "()Landroid/net/Uri;", false)) + add(VarInsnNode(ALOAD, 1)) + add(MethodInsnNode(INVOKEVIRTUAL, "com/facebook/imagepipeline/request/ImageRequestBuilder", "getPostprocessor", "()Lcom/facebook/imagepipeline/request/Postprocessor;", false)) + add(MethodInsnNode(INVOKESTATIC, "com/didichuxing/doraemonkit/aop/bigimg/fresco/FrescoHook", "proxy", "(Landroid/net/Uri;Lcom/facebook/imagepipeline/request/Postprocessor;)Lcom/facebook/imagepipeline/request/Postprocessor;", false)) + add(MethodInsnNode(INVOKEVIRTUAL, "com/facebook/imagepipeline/request/ImageRequestBuilder", "setPostprocessor", "(Lcom/facebook/imagepipeline/request/Postprocessor;)Lcom/facebook/imagepipeline/request/ImageRequestBuilder;", false)) + this + } + + } + + + /** + * 创建ImageLoader Aop代码指令 + */ + private fun createImageLoaderInsnList(): InsnList { + return with(InsnList()) { + add(VarInsnNode(ALOAD, 6)) + add(MethodInsnNode(INVOKESTATIC, "com/didichuxing/doraemonkit/aop/bigimg/imageloader/ImageLoaderHook", "proxy", "(Lcom/nostra13/universalimageloader/core/listener/ImageLoadingListener;)Lcom/nostra13/universalimageloader/core/listener/ImageLoadingListener;", false)) + add(VarInsnNode(ASTORE, 6)) + this + } + } +} + diff --git a/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/classtransformer/CommTransformer.kt b/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/classtransformer/CommTransformer.kt new file mode 100644 index 00000000..4d4bff97 --- /dev/null +++ b/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/classtransformer/CommTransformer.kt @@ -0,0 +1,362 @@ +package com.didichuxing.doraemonkit.plugin.classtransformer + +import com.didichuxing.doraemonkit.plugin.* +import com.didichuxing.doraemonkit.plugin.extension.SlowMethodExt +import com.didiglobal.booster.annotations.Priority +import com.didiglobal.booster.kotlinx.asIterable +import com.didiglobal.booster.transform.TransformContext +import com.didiglobal.booster.transform.asm.ClassTransformer +import com.didiglobal.booster.transform.asm.className +import com.google.auto.service.AutoService +import org.objectweb.asm.Opcodes.* +import org.objectweb.asm.tree.* + +/** + * ================================================ + * 作 者:jint(金台) + * 版 本:1.0 + * 创建日期:2020/5/14-18:07 + * 描 述:wiki:https://juejin.im/post/5e8d87c4f265da47ad218e6b + * 修订历史: + * ================================================ + */ +@Priority(0) +@AutoService(ClassTransformer::class) +class CommTransformer : ClassTransformer { + private val SHADOW_URL = "com/didichuxing/doraemonkit/aop/urlconnection/HttpUrlConnectionProxyUtil" + private val DESC = "(Ljava/net/URLConnection;)Ljava/net/URLConnection;" + + override fun transform(context: TransformContext, klass: ClassNode): ClassNode { + if (context.isRelease()) { + return klass + } + + if (!DoKitExtUtil.dokitPluginSwitchOpen()) { + return klass + } + + val className = klass.className + + if (className.contains("didihttp")) { + "${context.projectDir.lastPath()}==className===>$className".println() + } + + //查找DoraemonKitReal&pluginConfig方法并插入指定字节码 + if (className == "com.didichuxing.doraemonkit.DoraemonKitReal") { + klass.methods?.find { + it.name == "pluginConfig" + }.let { methodNode -> + "${context.projectDir.lastPath()}->insert map to the DoraemonKitReal pluginConfig succeed".println() + methodNode?.instructions?.insert(createPluginConfigInsnList()) + } + } + + //gps字节码操作 + if (DoKitExtUtil.commExt.gpsSwitch) { + //插入高德地图相关字节码 + if (className == "com.amap.api.location.AMapLocationClient") { + klass.methods?.find { + it.name == "setLocationListener" + }.let { methodNode -> + "${context.projectDir.lastPath()}->hook amap succeed: ${className}_${methodNode?.name}_${methodNode?.desc}".println() + methodNode?.instructions?.insert(createAmapLocationInsnList()) + } + + } + + //插入腾讯地图相关字节码 + if (className == "com.tencent.map.geolocation.TencentLocationManager") { + //持续定位和单次定位 + klass.methods?.filter { + it.name == "requestSingleFreshLocation" || it.name == "requestLocationUpdates" + }?.forEach { methodNode -> + "${context.projectDir.lastPath()}->hook tencent map succeed: ${className}_${methodNode?.name}_${methodNode?.desc}".println() + methodNode?.instructions?.insert(createTencentLocationInsnList()) + } + } + + //插入百度地图相关字节码 + klass.methods?.find { + it.name == "onReceiveLocation" && it.desc == "(Lcom/baidu/location/BDLocation;)V" + }.let { methodNode -> + methodNode?.name?.let { + "${context.projectDir.lastPath()}->hook baidu map succeed: ${className}_${methodNode.name}_${methodNode.desc}".println() + methodNode.instructions?.insert(createBaiduLocationInsnList()) + } + } + } + + + //网络 OkHttp&didi platform aop + if (DoKitExtUtil.commExt.networkSwitch) { + //okhttp + if (className == "okhttp3.OkHttpClient\$Builder") { + //空参数的构造方法 + klass.methods?.find { + it.name == "" && it.desc == "()V" + }.let { zeroConsMethodNode -> + "${context.projectDir.lastPath()}->hook OkHttp succeed: ${className}_${zeroConsMethodNode?.name}_${zeroConsMethodNode?.desc}".println() + zeroConsMethodNode?.instructions?.getMethodExitInsnNodes()?.forEach { + zeroConsMethodNode.instructions.insertBefore(it, createOkHttpZeroConsInsnList()) + } + } + + + //一个参数的构造方法 + klass.methods?.find { + it.name == "" && it.desc == "(Lokhttp3/OkHttpClient;)V" + }.let { oneConsMethodNode -> + "${context.projectDir.lastPath()}->hook OkHttp succeed: ${className}_${oneConsMethodNode?.name}_${oneConsMethodNode?.desc}".println() + oneConsMethodNode?.instructions?.getMethodExitInsnNodes()?.forEach { + oneConsMethodNode.instructions.insertBefore(it, createOkHttpOneConsInsnList()) + } + } + + } + + //didi platform + if (className == "didihttp.DidiHttpClient\$Builder") { + "find DidiHttpClient succeed: ${className}".println() + //空参数的构造方法 + klass.methods?.find { + it.name == "" && it.desc == "()V" + }.let { zeroConsMethodNode -> + "${context.projectDir.lastPath()}->hook didi http succeed: ${className}_${zeroConsMethodNode?.name}_${zeroConsMethodNode?.desc}".println() + zeroConsMethodNode?.instructions?.getMethodExitInsnNodes()?.forEach { + zeroConsMethodNode.instructions.insertBefore(it, createDidiHttpZeroConsInsnList()) + } + } + + + //一个参数的构造方法 + klass.methods?.find { + it.name == "" && it.desc == "(Ldidihttp/DidiHttpClient;)V" + }.let { oneConsMethodNode -> + "${context.projectDir.lastPath()}->hook didi http succeed: ${className}_${oneConsMethodNode?.name}_${oneConsMethodNode?.desc}".println() + oneConsMethodNode?.instructions?.getMethodExitInsnNodes()?.forEach { + oneConsMethodNode.instructions.insertBefore(it, createDidiHttpOneConsInsnList()) + } + } + } + + // url connection + klass.methods.forEach { method -> + method.instructions?.iterator()?.asIterable()?.filterIsInstance(MethodInsnNode::class.java)?.filter { + it.opcode == INVOKEVIRTUAL && + it.owner == "java/net/URL" && + it.name == "openConnection" && + it.desc == "()Ljava/net/URLConnection;" + }?.forEach { + method.instructions.insert(it, MethodInsnNode(INVOKESTATIC, SHADOW_URL, "proxy", DESC, false)) + } + } + + } + + return klass + } + + + /** + * 创建pluginConfig代码指令 + */ + private fun createPluginConfigInsnList(): InsnList { + //val insnList = InsnList() + return with(InsnList()) { + //new HashMap + add(TypeInsnNode(NEW, "java/util/HashMap")) + add(InsnNode(DUP)) + add(MethodInsnNode(INVOKESPECIAL, "java/util/HashMap", "", "()V", false)) + //保存变量 + add(VarInsnNode(ASTORE, 0)) + //获取第一个变量 + //put("dokitPluginSwitch",true) + add(VarInsnNode(ALOAD, 0)) + add(LdcInsnNode("dokitPluginSwitch")) + add(InsnNode(if (DoKitExtUtil.dokitPluginSwitchOpen()) ICONST_1 else ICONST_0)) + add(MethodInsnNode(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", false)) + add(MethodInsnNode(INVOKEINTERFACE, "java/util/Map", "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", true)) + add(InsnNode(POP)) + + //put("gpsSwitch",true) + add(VarInsnNode(ALOAD, 0)) + add(LdcInsnNode("gpsSwitch")) + add(InsnNode(if (DoKitExtUtil.commExt.gpsSwitch) ICONST_1 else ICONST_0)) + add(MethodInsnNode(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", false)) + add(MethodInsnNode(INVOKEINTERFACE, "java/util/Map", "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", true)) + add(InsnNode(POP)) + + //put("networkSwitch",true) + add(VarInsnNode(ALOAD, 0)) + add(LdcInsnNode("networkSwitch")) + add(InsnNode(if (DoKitExtUtil.commExt.networkSwitch) ICONST_1 else ICONST_0)) + add(MethodInsnNode(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", false)) + add(MethodInsnNode(INVOKEINTERFACE, "java/util/Map", "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", true)) + add(InsnNode(POP)) + + //put("bigImgSwitch",true) + add(VarInsnNode(ALOAD, 0)) + add(LdcInsnNode("bigImgSwitch")) + add(InsnNode(if (DoKitExtUtil.commExt.bigImgSwitch) ICONST_1 else ICONST_0)) + add(MethodInsnNode(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", false)) + add(MethodInsnNode(INVOKEINTERFACE, "java/util/Map", "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", true)) + add(InsnNode(POP)) + + //put("methodSwitch",true) + add(VarInsnNode(ALOAD, 0)) + add(LdcInsnNode("methodSwitch")) + add(InsnNode(if (DoKitExtUtil.dokitSlowMethodSwitchOpen()) ICONST_1 else ICONST_0)) + add(MethodInsnNode(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", false)) + add(MethodInsnNode(INVOKEINTERFACE, "java/util/Map", "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", true)) + add(InsnNode(POP)) + + + //put("methodStrategy",0) + add(VarInsnNode(ALOAD, 0)) + add(LdcInsnNode("methodStrategy")) + add(InsnNode(if (DoKitExtUtil.SLOW_METHOD_STRATEGY == SlowMethodExt.STRATEGY_STACK) ICONST_0 else ICONST_1)) + add(MethodInsnNode(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false)) + add(MethodInsnNode(INVOKEINTERFACE, "java/util/Map", "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", true)) + add(InsnNode(POP)) + + //将HashMap注入到DokitPluginConfig中 + add(VarInsnNode(ALOAD, 0)) + add(MethodInsnNode(INVOKESTATIC, "com/didichuxing/doraemonkit/aop/DokitPluginConfig", "inject", "(Ljava/util/Map;)V", false)) + + this + } + + //return insnList + + } + + + /** + * 创建Amap地图代码指令 + */ + private fun createAmapLocationInsnList(): InsnList { + return with(InsnList()) { + //在AMapLocationClient的setLocationListener方法之中插入自定义代理回调类 + add(TypeInsnNode(NEW, "com/didichuxing/doraemonkit/aop/AMapLocationListenerProxy")) + add(InsnNode(DUP)) + //访问第一个参数 + add(VarInsnNode(ALOAD, 1)) + add(MethodInsnNode(INVOKESPECIAL, "com/didichuxing/doraemonkit/aop/AMapLocationListenerProxy", "", "(Lcom/amap/api/location/AMapLocationListener;)V", false)) + //对第一个参数进行重新赋值 + add(VarInsnNode(ASTORE, 1)) + this + } + + } + + + /** + * 创建tencent地图代码指令 + */ + private fun createTencentLocationInsnList(): InsnList { + return with(InsnList()) { + //在AMapLocationClient的setLocationListener方法之中插入自定义代理回调类 + add(TypeInsnNode(NEW, "com/didichuxing/doraemonkit/aop/TencentLocationListenerProxy")) + add(InsnNode(DUP)) + //访问第一个参数 + add(VarInsnNode(ALOAD, 2)) + add(MethodInsnNode(INVOKESPECIAL, "com/didichuxing/doraemonkit/aop/TencentLocationListenerProxy", "", "(Lcom/tencent/map/geolocation/TencentLocationListener;)V", false)) + //对第一个参数进行重新赋值 + add(VarInsnNode(ASTORE, 2)) + + this + } + + } + + + /** + * 创建百度地图代码指令 + */ + private fun createBaiduLocationInsnList(): InsnList { + return with(InsnList()) { + //在AMapLocationClient的setLocationListener方法之中插入自定义代理回调类 + add(VarInsnNode(ALOAD, 1)) + add(MethodInsnNode(INVOKESTATIC, "com/didichuxing/doraemonkit/aop/BDLocationUtil", "proxy", "(Lcom/baidu/location/BDLocation;)Lcom/baidu/location/BDLocation;", false)) + //对第一个参数进行重新赋值 + add(VarInsnNode(ASTORE, 1)) + this + } + + } + + + /** + * 创建Okhttp Build 空参数构造函数指令 + */ + private fun createOkHttpZeroConsInsnList(): InsnList { + return with(InsnList()) { + //插入application 拦截器 + add(VarInsnNode(ALOAD, 0)) + add(FieldInsnNode(GETFIELD, "okhttp3/OkHttpClient\$Builder", "interceptors", "Ljava/util/List;")) + add(FieldInsnNode(GETSTATIC, "com/didichuxing/doraemonkit/aop/OkHttpHook", "globalInterceptors", "Ljava/util/List;")) + add(MethodInsnNode(INVOKEINTERFACE, "java/util/List", "addAll", "(Ljava/util/Collection;)Z", true)) + add(InsnNode(POP)) + + //插入NetworkInterceptor 拦截器 + add(VarInsnNode(ALOAD, 0)) + add(FieldInsnNode(GETFIELD, "okhttp3/OkHttpClient\$Builder", "networkInterceptors", "Ljava/util/List;")) + add(FieldInsnNode(GETSTATIC, "com/didichuxing/doraemonkit/aop/OkHttpHook", "globalNetworkInterceptors", "Ljava/util/List;")) + add(MethodInsnNode(INVOKEINTERFACE, "java/util/List", "addAll", "(Ljava/util/Collection;)Z", true)) + add(InsnNode(POP)) + this + } + + } + + + /** + * 创建Okhttp Build 一个参数构造函数指令 + */ + private fun createOkHttpOneConsInsnList(): InsnList { + return with(InsnList()) { + add(VarInsnNode(ALOAD, 0)) + add(VarInsnNode(ALOAD, 1)) + add(MethodInsnNode(INVOKESTATIC, "com/didichuxing/doraemonkit/aop/OkHttpHook", "performOkhttpOneParamBuilderInit", "(Ljava/lang/Object;Ljava/lang/Object;)V", false)) + this + } + + } + + + /** + * 创建didiClient Build 空参数构造函数指令 + */ + private fun createDidiHttpZeroConsInsnList(): InsnList { + return with(InsnList()) { + //插入application 拦截器 + add(VarInsnNode(ALOAD, 0)) + add(FieldInsnNode(GETFIELD, "didihttp/DidiHttpClient\$Builder", "interceptors", "Ljava/util/List;")) + add(FieldInsnNode(GETSTATIC, "com/didichuxing/foundation/net/rpc/http/PlatformHttpHook", "globalInterceptors", "Ljava/util/List;")) + add(MethodInsnNode(INVOKEINTERFACE, "java/util/List", "addAll", "(Ljava/util/Collection;)Z", true)) + add(InsnNode(POP)) + + //插入NetworkInterceptor 拦截器 + add(VarInsnNode(ALOAD, 0)) + add(FieldInsnNode(GETFIELD, "didihttp/DidiHttpClient\$Builder", "networkInterceptors", "Ljava/util/List;")) + add(FieldInsnNode(GETSTATIC, "com/didichuxing/foundation/net/rpc/http/PlatformHttpHook", "globalNetworkInterceptors", "Ljava/util/List;")) + add(MethodInsnNode(INVOKEINTERFACE, "java/util/List", "addAll", "(Ljava/util/Collection;)Z", true)) + add(InsnNode(POP)) + this + } + + } + + + /** + * 创建didiClient Build 一个参数构造函数指令 + */ + private fun createDidiHttpOneConsInsnList(): InsnList { + return with(InsnList()) { + add(VarInsnNode(ALOAD, 0)) + add(VarInsnNode(ALOAD, 1)) + add(MethodInsnNode(INVOKESTATIC, "com/didichuxing/foundation/net/rpc/http/PlatformHttpHook", "performDidiHttpOneParamBuilderInit", "(Ljava/lang/Object;Ljava/lang/Object;)V", false)) + this + } + } +} \ No newline at end of file diff --git a/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/classtransformer/EnterMethodStackTransformer.kt b/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/classtransformer/EnterMethodStackTransformer.kt new file mode 100644 index 00000000..ad93ad6c --- /dev/null +++ b/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/classtransformer/EnterMethodStackTransformer.kt @@ -0,0 +1,169 @@ +package com.didichuxing.doraemonkit.plugin.classtransformer + +import com.didichuxing.doraemonkit.plugin.* +import com.didichuxing.doraemonkit.plugin.extension.SlowMethodExt +import com.didichuxing.doraemonkit.plugin.stack_method.MethodStackNode +import com.didichuxing.doraemonkit.plugin.stack_method.MethodStackNodeUtil +import com.didiglobal.booster.annotations.Priority +import com.didiglobal.booster.transform.TransformContext +import com.didiglobal.booster.transform.asm.ClassTransformer +import com.didiglobal.booster.transform.asm.asIterable +import com.didiglobal.booster.transform.asm.className +import com.google.auto.service.AutoService +import org.objectweb.asm.Opcodes.* +import org.objectweb.asm.tree.* + +/** + * ================================================ + * 作 者:jint(金台) + * 版 本:1.0 + * 创建日期:2020/5/14-18:07 + * 描 述:入口函数 慢函数调用栈 wiki:https://juejin.im/post/5e8d87c4f265da47ad218e6b + * 修订历史:不要指定自动注入 需要手动在DoKitAsmTransformer中通过配置创建 + * 原理:transform()方法的调用是无序的 原因:哪一个class会先被transformer执行是不确定的 但是每一个class被transformer执行顺序是遵循transformer的Priority规则的 + * ================================================ + */ +@Priority(3) +@AutoService(ClassTransformer::class) +class EnterMethodStackTransformer : ClassTransformer { + + private val thresholdTime = DoKitExtUtil.slowMethodExt.stackMethod.thresholdTime + private val level = 0 + override fun transform(context: TransformContext, klass: ClassNode): ClassNode { + if (context.isRelease()) { + return klass + } + + if (!DoKitExtUtil.dokitPluginSwitchOpen()) { + return klass + } + + if (!DoKitExtUtil.dokitSlowMethodSwitchOpen()) { + return klass + } + + if (DoKitExtUtil.SLOW_METHOD_STRATEGY == SlowMethodExt.STRATEGY_NORMAL) { + return klass + } + + if (DoKitExtUtil.ignorePackageNames(klass.className)) { + return klass + } + + //默认为Application onCreate 和attachBaseContext + val enterMethods = DoKitExtUtil.slowMethodExt.stackMethod.enterMethods + //找不到配置的Application + if (enterMethods.isEmpty()) { + val superName = klass.superName + //先判断父类 + if (superName.isNotEmpty() && (superName == "android/app/Application" || + superName == "android/support/multidex/MultiDexApplication" || + superName == "androidx/multidex/MultiDexApplication")) { + klass.methods.filter { methodNode -> + (methodNode.name == "onCreate" && methodNode.desc == "()V") || (methodNode.name == "attachBaseContext" && methodNode.desc == "(Landroid/content/Context;)V") + }.let { methodNodes -> + //读取全是函数调用的Insn + methodNodes.forEach { methodNode -> + operateMethodInsn(klass, methodNode) + } + } + } + } else { + enterMethods.forEach { enterMethodName -> + klass.methods.forEach { methodNode -> + val allMethodName = "${klass.className}.${methodNode.name}" + if (allMethodName == enterMethodName) { + "${context.projectDir.lastPath()}->level-->$level mathched enterMethod===>$allMethodName".println() + operateMethodInsn(klass, methodNode) + } + } + } + } + + return klass + } + + + private fun operateMethodInsn(klass: ClassNode, methodNode: MethodNode) { + //读取全是函数调用的指令 + methodNode.instructions.asIterable().filterIsInstance(MethodInsnNode::class.java).filter { methodInsnNode -> + methodInsnNode.name != "" + }.forEach { methodInsnNode -> + val methodStackNode = MethodStackNode(level, methodInsnNode.ownerClassName, methodInsnNode.name, methodInsnNode.desc, klass.className, methodNode.name, methodNode.desc) + MethodStackNodeUtil.addMethodStackNode(level, methodStackNode) + } + //函数出入口插入耗时统计代码 + //方法入口插入 + methodNode.instructions.insert(createMethodEnterInsnList(level, klass.className, methodNode.name, methodNode.desc, methodNode.access)) + //方法出口插入 + methodNode.instructions.getMethodExitInsnNodes()?.forEach { methodExitInsnNode -> + methodNode.instructions.insertBefore(methodExitInsnNode, createMethodExitInsnList(level, klass.className, methodNode.name, methodNode.desc, methodNode.access)) + } + } + + + /** + * 创建慢函数入口指令集 + */ + private fun createMethodEnterInsnList(level: Int, className: String, methodName: String, desc: String, access: Int): InsnList { + val isStaticMethod = access and ACC_STATIC != 0 + return with(InsnList()) { + if (isStaticMethod) { + add(FieldInsnNode(GETSTATIC, "com/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil", "INSTANCE", "Lcom/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil;")) + add(IntInsnNode(BIPUSH, DoKitExtUtil.STACK_METHOD_LEVEL)) + add(IntInsnNode(BIPUSH, thresholdTime)) + add(IntInsnNode(BIPUSH, level)) + add(LdcInsnNode(className)) + add(LdcInsnNode(methodName)) + add(LdcInsnNode(desc)) + add(MethodInsnNode(INVOKEVIRTUAL, "com/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil", "recodeStaticMethodCostStart", "(IIILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", false)) + } else { + add(FieldInsnNode(GETSTATIC, "com/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil", "INSTANCE", "Lcom/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil;")) + add(IntInsnNode(BIPUSH, DoKitExtUtil.STACK_METHOD_LEVEL)) + add(IntInsnNode(BIPUSH, thresholdTime)) + add(IntInsnNode(BIPUSH, level)) + add(LdcInsnNode(className)) + add(LdcInsnNode(methodName)) + add(LdcInsnNode(desc)) + add(VarInsnNode(ALOAD, 0)) + add(MethodInsnNode(INVOKEVIRTUAL, "com/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil", "recodeObjectMethodCostStart", "(IIILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;)V", false)) + } + this + } + + + } + + + /** + * 创建慢函数退出时的指令集 + */ + private fun createMethodExitInsnList(level: Int, className: String, methodName: String, desc: String, access: Int): InsnList { + val isStaticMethod = access and ACC_STATIC != 0 + + return with(InsnList()) { + if (isStaticMethod) { + add(FieldInsnNode(GETSTATIC, "com/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil", "INSTANCE", "Lcom/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil;")) + add(IntInsnNode(BIPUSH, thresholdTime)) + add(IntInsnNode(BIPUSH, level)) + add(LdcInsnNode(className)) + add(LdcInsnNode(methodName)) + add(LdcInsnNode(desc)) + add(MethodInsnNode(INVOKEVIRTUAL, "com/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil", "recodeStaticMethodCostEnd", "(IILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", false)) + } else { + add(FieldInsnNode(GETSTATIC, "com/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil", "INSTANCE", "Lcom/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil;")) + add(IntInsnNode(BIPUSH, thresholdTime)) + add(IntInsnNode(BIPUSH, level)) + add(LdcInsnNode(className)) + add(LdcInsnNode(methodName)) + add(LdcInsnNode(desc)) + add(VarInsnNode(ALOAD, 0)) + add(MethodInsnNode(INVOKEVIRTUAL, "com/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil", "recodeObjectMethodCostEnd", "(IILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;)V", false)) + } + this + } + + } + +} + diff --git a/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/classtransformer/GlobalSlowMethodTransformer.kt b/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/classtransformer/GlobalSlowMethodTransformer.kt new file mode 100644 index 00000000..a926d009 --- /dev/null +++ b/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/classtransformer/GlobalSlowMethodTransformer.kt @@ -0,0 +1,137 @@ +package com.didichuxing.doraemonkit.plugin.classtransformer + +import com.didichuxing.doraemonkit.plugin.* +import com.didichuxing.doraemonkit.plugin.extension.SlowMethodExt +import com.didiglobal.booster.annotations.Priority +import com.didiglobal.booster.transform.TransformContext +import com.didiglobal.booster.transform.asm.ClassTransformer +import com.didiglobal.booster.transform.asm.asIterable +import com.didiglobal.booster.transform.asm.className +import com.google.auto.service.AutoService +import org.objectweb.asm.Opcodes.* +import org.objectweb.asm.tree.* + +/** + * ================================================ + * 作 者:jint(金台) + * 版 本:1.0 + * 创建日期:2020/5/14-18:07 + * 描 述:全局业务代码慢函数 wiki:https://juejin.im/post/5e8d87c4f265da47ad218e6b + * 修订历史: + * ================================================ + */ +@Priority(2) +@AutoService(ClassTransformer::class) +class GlobalSlowMethodTransformer : ClassTransformer { + val thresholdTime = DoKitExtUtil.slowMethodExt.normalMethod.thresholdTime + + override fun transform(context: TransformContext, klass: ClassNode): ClassNode { + if (context.isRelease()) { + return klass + } + + if (!DoKitExtUtil.dokitPluginSwitchOpen()) { + return klass + } + + if (!DoKitExtUtil.dokitSlowMethodSwitchOpen()) { + return klass + } + + if (DoKitExtUtil.SLOW_METHOD_STRATEGY == SlowMethodExt.STRATEGY_STACK) { + return klass + } + + if (DoKitExtUtil.ignorePackageNames(klass.className)) { + return klass + } + + + val className = klass.className + //没有自定义设置插装包名 默认是以applicationId为包名 即全局业务代码插桩 + DoKitExtUtil.slowMethodExt.normalMethod.packageNames.forEach { packageName -> + //包含在白名单中且不在黑名单中 + if (className.contains(packageName) && notMatchedBlackList(className)) { + klass.methods.filter { methodNode -> + methodNode.name != "" && + !methodNode.isEmptyMethod() && + !methodNode.isSingleMethod() && + !methodNode.isGetSetMethod() + }.forEach { methodNode -> + methodNode.instructions.asIterable().filterIsInstance(MethodInsnNode::class.java).let { methodInsnNodes -> + if (methodInsnNodes.isNotEmpty()) { + //方法入口插入 + methodNode.instructions.insert(createMethodEnterInsnList(className, methodNode.name, methodNode.access)) + //方法出口插入 + methodNode.instructions.getMethodExitInsnNodes()?.forEach { methodExitInsnNode -> + methodNode.instructions.insertBefore(methodExitInsnNode, createMethodExitInsnList(className, methodNode.name, methodNode.access)) + } + } + } + } + } + } + return klass + } + + + private fun notMatchedBlackList(className: String): Boolean { + for (strBlack in DoKitExtUtil.slowMethodExt.normalMethod.methodBlacklist) { + if (className.contains(strBlack)) { + return false + } + } + + return true + } + + /** + * 创建慢函数入口指令集 + */ + private fun createMethodEnterInsnList(className: String, methodName: String, access: Int): InsnList { + val isStaticMethod = access and ACC_STATIC != 0 + return with(InsnList()) { + if (isStaticMethod) { + add(FieldInsnNode(GETSTATIC, "com/didichuxing/doraemonkit/aop/MethodCostUtil", "INSTANCE", "Lcom/didichuxing/doraemonkit/aop/MethodCostUtil;")) + add(IntInsnNode(SIPUSH, thresholdTime)) + add(LdcInsnNode("$className&$methodName")) + add(MethodInsnNode(INVOKEVIRTUAL, "com/didichuxing/doraemonkit/aop/MethodCostUtil", "recodeStaticMethodCostStart", "(ILjava/lang/String;)V", false)) + } else { + add(FieldInsnNode(GETSTATIC, "com/didichuxing/doraemonkit/aop/MethodCostUtil", "INSTANCE", "Lcom/didichuxing/doraemonkit/aop/MethodCostUtil;")) + add(IntInsnNode(SIPUSH, thresholdTime)) + add(LdcInsnNode("$className&$methodName")) + add(VarInsnNode(ALOAD, 0)) + add(MethodInsnNode(INVOKEVIRTUAL, "com/didichuxing/doraemonkit/aop/MethodCostUtil", "recodeObjectMethodCostStart", "(ILjava/lang/String;Ljava/lang/Object;)V", false)) + } + + this + } + + } + + + /** + * 创建慢函数退出时的指令集 + */ + private fun createMethodExitInsnList(className: String, methodName: String, access: Int): InsnList { + val isStaticMethod = access and ACC_STATIC != 0 + return with(InsnList()) { + if (isStaticMethod) { + add(FieldInsnNode(GETSTATIC, "com/didichuxing/doraemonkit/aop/MethodCostUtil", "INSTANCE", "Lcom/didichuxing/doraemonkit/aop/MethodCostUtil;")) + add(IntInsnNode(SIPUSH, thresholdTime)) + add(LdcInsnNode("$className&$methodName")) + add(MethodInsnNode(INVOKEVIRTUAL, "com/didichuxing/doraemonkit/aop/MethodCostUtil", "recodeStaticMethodCostEnd", "(ILjava/lang/String;)V", false)) + } else { + add(FieldInsnNode(GETSTATIC, "com/didichuxing/doraemonkit/aop/MethodCostUtil", "INSTANCE", "Lcom/didichuxing/doraemonkit/aop/MethodCostUtil;")) + add(IntInsnNode(SIPUSH, thresholdTime)) + add(LdcInsnNode("$className&$methodName")) + add(VarInsnNode(ALOAD, 0)) + add(MethodInsnNode(INVOKEVIRTUAL, "com/didichuxing/doraemonkit/aop/MethodCostUtil", "recodeObjectMethodCostEnd", "(ILjava/lang/String;Ljava/lang/Object;)V", false)) + } + this + } + } + + +} + diff --git a/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/classtransformer/MethodStackDepTransformer.kt b/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/classtransformer/MethodStackDepTransformer.kt new file mode 100644 index 00000000..39aad9b2 --- /dev/null +++ b/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/classtransformer/MethodStackDepTransformer.kt @@ -0,0 +1,149 @@ +package com.didichuxing.doraemonkit.plugin.classtransformer + +import com.didichuxing.doraemonkit.plugin.* +import com.didichuxing.doraemonkit.plugin.extension.SlowMethodExt +import com.didichuxing.doraemonkit.plugin.stack_method.MethodStackNode +import com.didichuxing.doraemonkit.plugin.stack_method.MethodStackNodeUtil +import com.didiglobal.booster.transform.TransformContext +import com.didiglobal.booster.transform.asm.ClassTransformer +import com.didiglobal.booster.transform.asm.asIterable +import com.didiglobal.booster.transform.asm.className +import org.objectweb.asm.Opcodes.* +import org.objectweb.asm.tree.* + +/** + * ================================================ + * 作 者:jint(金台) + * 版 本:1.0 + * 创建日期:2020/5/14-18:07 + * 描 述:入口函数 慢函数调用栈 wiki:https://juejin.im/post/5e8d87c4f265da47ad218e6b + * 修订历史:不要指定自动注入 需要手动在DoKitAsmTransformer中通过配置创建 + * 原理:transform()方法的调用是无序的 原因:哪一个class会先被transformer执行是不确定的 但是每一个class被transformer执行顺序是遵循transformer的Priority规则的 + * ================================================ + */ +class MethodStackDepTransformer(private val level: Int = 1) : ClassTransformer { + + private val thresholdTime = DoKitExtUtil.slowMethodExt.stackMethod.thresholdTime + override fun transform(context: TransformContext, klass: ClassNode): ClassNode { + if (context.isRelease()) { + return klass + } + + if (!DoKitExtUtil.dokitPluginSwitchOpen()) { + return klass + } + + if (!DoKitExtUtil.dokitSlowMethodSwitchOpen()) { + return klass + } + + if (DoKitExtUtil.SLOW_METHOD_STRATEGY == SlowMethodExt.STRATEGY_NORMAL) { + return klass + } + + if (DoKitExtUtil.ignorePackageNames(klass.className)) { + return klass + } + + + val methodStackKeys: MutableSet = MethodStackNodeUtil.METHOD_STACK_KEYS[level - 1] + + klass.methods.filter { methodNode -> + methodNode.name != "" && + !methodNode.isEmptyMethod() && + !methodNode.isSingleMethod() && + !methodNode.isGetSetMethod() + }.forEach { methodNode -> + val key = "${klass.className}&${methodNode.name}&${methodNode.desc}" + if (methodStackKeys.contains(key)) { + "${context.projectDir.lastPath()}->level-->$level mathched key===>$key".println() + operateMethodInsn(klass, methodNode) + } + + } + + return klass + } + + + private fun operateMethodInsn(klass: ClassNode, methodNode: MethodNode) { + //读取全是函数调用的指令 + methodNode.instructions.asIterable().filterIsInstance(MethodInsnNode::class.java).filter { methodInsnNode -> + methodInsnNode.name != "" + }.forEach { methodInsnNode -> + val methodStackNode = MethodStackNode(level, methodInsnNode.ownerClassName, methodInsnNode.name, methodInsnNode.desc, klass.className, methodNode.name, methodNode.desc) + MethodStackNodeUtil.addMethodStackNode(level, methodStackNode) + } + //函数出入口插入耗时统计代码 + //方法入口插入 + methodNode.instructions.insert(createMethodEnterInsnList(level, klass.className, methodNode.name, methodNode.desc, methodNode.access)) + //方法出口插入 + methodNode.instructions.getMethodExitInsnNodes()?.forEach { methodExitInsnNode -> + methodNode.instructions.insertBefore(methodExitInsnNode, createMethodExitInsnList(level, klass.className, methodNode.name, methodNode.desc, methodNode.access)) + } + } + + + /** + * 创建慢函数入口指令集 + */ + private fun createMethodEnterInsnList(level: Int, className: String, methodName: String, desc: String, access: Int): InsnList { + val isStaticMethod = access and ACC_STATIC != 0 + return with(InsnList()) { + if (isStaticMethod) { + add(FieldInsnNode(GETSTATIC, "com/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil", "INSTANCE", "Lcom/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil;")) + add(IntInsnNode(BIPUSH, DoKitExtUtil.STACK_METHOD_LEVEL)) + add(IntInsnNode(BIPUSH, thresholdTime)) + add(IntInsnNode(BIPUSH, level)) + add(LdcInsnNode(className)) + add(LdcInsnNode(methodName)) + add(LdcInsnNode(desc)) + add(MethodInsnNode(INVOKEVIRTUAL, "com/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil", "recodeStaticMethodCostStart", "(IIILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", false)) + } else { + add(FieldInsnNode(GETSTATIC, "com/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil", "INSTANCE", "Lcom/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil;")) + add(IntInsnNode(BIPUSH, DoKitExtUtil.STACK_METHOD_LEVEL)) + add(IntInsnNode(BIPUSH, thresholdTime)) + add(IntInsnNode(BIPUSH, level)) + add(LdcInsnNode(className)) + add(LdcInsnNode(methodName)) + add(LdcInsnNode(desc)) + add(VarInsnNode(ALOAD, 0)) + add(MethodInsnNode(INVOKEVIRTUAL, "com/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil", "recodeObjectMethodCostStart", "(IIILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;)V", false)) + } + this + } + + } + + + /** + * 创建慢函数退出时的指令集 + */ + private fun createMethodExitInsnList(level: Int, className: String, methodName: String, desc: String, access: Int): InsnList { + val isStaticMethod = access and ACC_STATIC != 0 + return with(InsnList()) { + if (isStaticMethod) { + add(FieldInsnNode(GETSTATIC, "com/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil", "INSTANCE", "Lcom/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil;")) + add(IntInsnNode(BIPUSH, thresholdTime)) + add(IntInsnNode(BIPUSH, level)) + add(LdcInsnNode(className)) + add(LdcInsnNode(methodName)) + add(LdcInsnNode(desc)) + add(MethodInsnNode(INVOKEVIRTUAL, "com/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil", "recodeStaticMethodCostEnd", "(IILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", false)) + } else { + add(FieldInsnNode(GETSTATIC, "com/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil", "INSTANCE", "Lcom/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil;")) + add(IntInsnNode(BIPUSH, thresholdTime)) + add(IntInsnNode(BIPUSH, level)) + add(LdcInsnNode(className)) + add(LdcInsnNode(methodName)) + add(LdcInsnNode(desc)) + add(VarInsnNode(ALOAD, 0)) + add(MethodInsnNode(INVOKEVIRTUAL, "com/didichuxing/doraemonkit/aop/method_stack/MethodStackUtil", "recodeObjectMethodCostEnd", "(IILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;)V", false)) + } + this + } + + } + +} + diff --git a/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/classtransformer/UrlConnectionTransformer.kt b/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/classtransformer/UrlConnectionTransformer.kt new file mode 100644 index 00000000..86b54810 --- /dev/null +++ b/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/classtransformer/UrlConnectionTransformer.kt @@ -0,0 +1,67 @@ +package com.didichuxing.doraemonkit.plugin.classtransformer + +import com.didichuxing.doraemonkit.plugin.DoKitExtUtil +import com.didichuxing.doraemonkit.plugin.isRelease +import com.didichuxing.doraemonkit.plugin.lastPath +import com.didichuxing.doraemonkit.plugin.println +import com.didiglobal.booster.annotations.Priority +import com.didiglobal.booster.kotlinx.asIterable +import com.didiglobal.booster.transform.TransformContext +import com.didiglobal.booster.transform.asm.ClassTransformer +import com.didiglobal.booster.transform.asm.className +import com.google.auto.service.AutoService +import org.objectweb.asm.Opcodes.INVOKESTATIC +import org.objectweb.asm.Opcodes.INVOKEVIRTUAL +import org.objectweb.asm.tree.ClassNode +import org.objectweb.asm.tree.MethodInsnNode + +/** + * ================================================ + * 作 者:jint(金台) + * 版 本:1.0 + * 创建日期:2020/5/14-18:07 + * 描 述:wiki:https://juejin.im/post/5e8d87c4f265da47ad218e6b + * 修订历史: + * ================================================ + */ +//@Priority(1) +//@AutoService(ClassTransformer::class) +class UrlConnectionTransformer : ClassTransformer { + private val SHADOW_URL = "com/didichuxing/doraemonkit/aop/urlconnection/HttpUrlConnectionProxyUtil" + private val DESC = "(Ljava/net/URLConnection;)Ljava/net/URLConnection;" + + override fun transform(context: TransformContext, klass: ClassNode): ClassNode { + if (context.isRelease()) { + return klass + } + + if (!DoKitExtUtil.dokitPluginSwitchOpen()) { + return klass + } + + if (!DoKitExtUtil.commExt.networkSwitch) { + return klass + } + + if (DoKitExtUtil.ignorePackageNames(klass.className)) { + return klass + } + + klass.methods.forEach { method -> + method.instructions?.iterator()?.asIterable()?.filterIsInstance(MethodInsnNode::class.java)?.filter { + it.opcode == INVOKEVIRTUAL && + it.owner == "java/net/URL" && + it.name == "openConnection" && + it.desc == "()Ljava/net/URLConnection;" + }?.forEach { + "${context.projectDir.lastPath()}-> hook urlconnection succeed:${klass.name}_${it.name}_${it.desc}".println() + method.instructions.insert(it, MethodInsnNode(INVOKESTATIC, SHADOW_URL, "proxy", DESC, false)) + } + } + + return klass + } + + +} + diff --git a/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/extension/CommExt.kt b/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/extension/CommExt.kt new file mode 100644 index 00000000..51836e01 --- /dev/null +++ b/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/extension/CommExt.kt @@ -0,0 +1,34 @@ +package com.didichuxing.doraemonkit.plugin.extension + +/** + * ================================================ + * 作 者:jint(金台) + * 版 本:1.0 + * 创建日期:2020/4/28-14:56 + * 描 述: + * 修订历史: + * ================================================ + */ +open class CommExt(var gpsSwitch: Boolean = true, + var networkSwitch: Boolean = true, + var bigImgSwitch: Boolean = true) { + + fun gpsSwitch(gpsSwitch: Boolean) { + this.gpsSwitch = gpsSwitch + } + + fun networkSwitch(networkSwitch: Boolean) { + this.networkSwitch = networkSwitch + } + + + fun bigImgSwitch(bigImgSwitch: Boolean) { + this.bigImgSwitch = bigImgSwitch + } + + override fun toString(): String { + return "CommExt(gpsSwitch=$gpsSwitch, networkSwitch=$networkSwitch, bigImgSwitch=$bigImgSwitch)" + } + + +} \ No newline at end of file diff --git a/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/extension/DoKitExt.kt b/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/extension/DoKitExt.kt new file mode 100644 index 00000000..b88b95f2 --- /dev/null +++ b/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/extension/DoKitExt.kt @@ -0,0 +1,42 @@ +package com.didichuxing.doraemonkit.plugin.extension + +import org.gradle.api.Action + +/** + * Created by jint on 07/10/2018. + */ +open class DoKitExt( + //var dokitPluginSwitch: Boolean = true, + var comm: CommExt = CommExt(), + var slowMethod: SlowMethodExt = SlowMethodExt()) { + + + //方法名必须和插件配置一直才能进行反射注入 +// fun dokitPluginSwitch(dokitPluginSwitch: Boolean) { +// this.dokitPluginSwitch = dokitPluginSwitch +// } + + /** + * 让comm 支持 DSL 语法 + * + * @param action + */ + fun comm(action: Action) { + action.execute(comm) + } + + /** + * 让slowMethod 支持 DSL 语法 + * + * @param action + */ + fun slowMethod(action: Action) { + action.execute(slowMethod) + } + + override fun toString(): String { + return "DoKitExt(comm=$comm, slowMethod=$slowMethod)" + } + + +} \ No newline at end of file diff --git a/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/extension/SlowMethodExt.kt b/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/extension/SlowMethodExt.kt new file mode 100644 index 00000000..deb6d6d1 --- /dev/null +++ b/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/extension/SlowMethodExt.kt @@ -0,0 +1,120 @@ +package com.didichuxing.doraemonkit.plugin.extension + +import groovy.lang.Closure +import org.gradle.util.ConfigureUtil +import java.util.* + +/** + * ================================================ + * 作 者:jint(金台) + * 版 本:1.0 + * 创建日期:2020/4/28-14:56 + * 描 述: + * 修订历史: + * ================================================ + */ + +open class SlowMethodExt( + //0:打印函数调用栈 1:普通模式 运行时打印某个函数的耗时 全局业务代码函数插入 + @Deprecated("已弃用,请在项目根目录的gradle.properties中通过DOKIT_METHOD_STRATEGY=0|1 来控制") + var strategy: Int = STRATEGY_STACK, + //函数功能开关 + @Deprecated("已弃用,请在项目根目录的gradle.properties中通过DoKit_METHOD_SWITCH=true|false 来控制") + var methodSwitch: Boolean = false, + //函数调用栈模式 + var stackMethod: StackMethodExt = StackMethodExt(), + //普通模式 + var normalMethod: NormalMethodExt = NormalMethodExt()) { + + + /** + * 函数功能开关 + */ + fun strategy(strategy: Int) { + this.strategy = strategy + } + + fun methodSwitch(methodSwitch: Boolean) { + this.methodSwitch = methodSwitch + } + + fun stackMethod(closure: Closure?) { + ConfigureUtil.configure(closure, stackMethod) + } + + fun normalMethod(closure: Closure?) { + ConfigureUtil.configure(closure, normalMethod) + } + + class StackMethodExt( + //默认阈值为5ms + var thresholdTime: Int = 5, + //入口函集合 + var enterMethods: MutableSet = mutableSetOf()) { + + + /** + * 默认值为5ms + */ + fun thresholdTime(thresholdTime: Int) { + this.thresholdTime = thresholdTime + } + + + fun normalMethod(enterMethods: MutableSet) { + this.enterMethods = enterMethods + } + + override fun toString(): String { + return "StackMethodExt(thresholdTime=$thresholdTime, enterMethods=$enterMethods)" + } + + + } + + class NormalMethodExt( + //默认阈值为500ms + var thresholdTime: Int = 500, + //普通函数的插装包名集合 + var packageNames: MutableSet = mutableSetOf(), + //插桩黑名单 + var methodBlacklist: MutableSet = mutableSetOf()) { + /** + * 默认值为500ms + */ + + fun thresholdTime(thresholdTime: Int) { + this.thresholdTime = thresholdTime + } + + fun packageNames(packageNames: MutableSet) { + this.packageNames = packageNames + } + + fun methodBlacklist(methodBlacklist: MutableSet) { + this.methodBlacklist = methodBlacklist + } + + override fun toString(): String { + return "NormalMethodExt{" + + "thresholdTime=" + thresholdTime + + ", packageNames=" + packageNames + + ", methodBlacklist=" + methodBlacklist + + '}' + } + } + + override fun toString(): String { + return "SlowMethodExt{" + + "strategy=" + strategy + + ", methodSwitch=" + methodSwitch + + ", stackMethod=" + stackMethod + + ", normalMethod=" + normalMethod + + '}' + } + + companion object { + const val STRATEGY_STACK = 0 + const val STRATEGY_NORMAL = 1 + } +} \ No newline at end of file diff --git a/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/processor/DoKitPluginConfigProcessor.kt b/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/processor/DoKitPluginConfigProcessor.kt new file mode 100644 index 00000000..178c4e41 --- /dev/null +++ b/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/processor/DoKitPluginConfigProcessor.kt @@ -0,0 +1,68 @@ +package com.didichuxing.doraemonkit.plugin.processor + +import com.android.build.gradle.AppExtension +import com.android.build.gradle.api.ApplicationVariant +import com.android.build.gradle.api.BaseVariant +import com.android.build.gradle.api.LibraryVariant +import com.didichuxing.doraemonkit.plugin.DoKitExtUtil +import com.didichuxing.doraemonkit.plugin.extension.DoKitExt +import com.didichuxing.doraemonkit.plugin.isRelease +import com.didichuxing.doraemonkit.plugin.println +import com.didichuxing.doraemonkit.plugin.transform.* +import com.didiglobal.booster.gradle.getAndroid +import com.didiglobal.booster.gradle.isDynamicFeature +import com.didiglobal.booster.gradle.project +import com.didiglobal.booster.gradle.variantData +import com.didiglobal.booster.task.spi.VariantProcessor +import com.didiglobal.booster.transform.ArtifactManager +import com.didiglobal.booster.transform.artifacts +import com.didiglobal.booster.transform.util.ComponentHandler +import com.google.auto.service.AutoService +import org.gradle.api.Project +import javax.xml.parsers.SAXParserFactory + +/** + * ================================================ + * 作 者:jint(金台) + * 版 本:1.0 + * 创建日期:2020/5/15-11:28 + * 描 述: + * 修订历史: + * ================================================ + */ +@AutoService(VariantProcessor::class) +class DoKitPluginConfigProcessor : VariantProcessor { + override fun process(variant: BaseVariant) { + if (!DoKitExtUtil.DOKIT_PLUGIN_SWITCH) { + return + } + + if (variant.isRelease()) { + return + } + //查找application module下的配置 + if (variant is ApplicationVariant) { + //查找AndroidManifest.xml 文件路径 + variant.artifacts.get(ArtifactManager.MERGED_MANIFESTS).forEach { manifest -> + val parser = SAXParserFactory.newInstance().newSAXParser() + val handler = ComponentHandler() + parser.parse(manifest, handler) + DoKitExtUtil.setApplications(handler.applications) + "applications path====>${handler.applications}".println() + } + + + //读取插件配置 + variant.project.getAndroid().let { appExt -> + //查找Application路径 + val doKitExt = variant.project.extensions.getByType(DoKitExt::class.java) + DoKitExtUtil.init(doKitExt, appExt.defaultConfig.applicationId) + } + } else { + "${variant.project.name}-不建议在Library Module下引入dokit插件".println() + } + + } + + +} \ No newline at end of file diff --git a/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/stack_method/MethodStackNode.kt b/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/stack_method/MethodStackNode.kt new file mode 100644 index 00000000..1cb88dcf --- /dev/null +++ b/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/stack_method/MethodStackNode.kt @@ -0,0 +1,18 @@ +package com.didichuxing.doraemonkit.plugin.stack_method + +/** + * ================================================ + * 作 者:jint(金台) + * 版 本:1.0 + * 创建日期:2020/5/20-16:50 + * 描 述: + * 修订历史: + * ================================================ + */ +data class MethodStackNode(var level: Int, + var className: String, + var methodName: String, + var desc: String, + var parentClassName: String, + var parentMethodName: String, + var parentDesc: String) \ No newline at end of file diff --git a/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/stack_method/MethodStackNodeUtil.kt b/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/stack_method/MethodStackNodeUtil.kt new file mode 100644 index 00000000..b048260f --- /dev/null +++ b/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/stack_method/MethodStackNodeUtil.kt @@ -0,0 +1,30 @@ +package com.didichuxing.doraemonkit.plugin.stack_method + +import org.gradle.internal.impldep.org.apache.commons.lang.mutable.Mutable +import java.util.* + +/** + * ================================================ + * 作 者:jint(金台) + * 版 本:1.0 + * 创建日期:2020/5/20-16:58 + * 描 述: + * 修订历史: + * ================================================ + */ +object MethodStackNodeUtil { + + + val METHOD_STACK_KEYS: MutableList> by lazy { + Collections.synchronizedList(mutableListOf>()) + } + + + fun addMethodStackNode(level: Int, methodStackNode: MethodStackNode) { + val key = "${methodStackNode.className}&${methodStackNode.methodName}&${methodStackNode.desc}" + METHOD_STACK_KEYS[level].add(key) + + } + + +} \ No newline at end of file diff --git a/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/transform/DoKitBaseTransform.kt b/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/transform/DoKitBaseTransform.kt new file mode 100644 index 00000000..11cf87f4 --- /dev/null +++ b/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/transform/DoKitBaseTransform.kt @@ -0,0 +1,86 @@ +package com.didichuxing.doraemonkit.plugin.transform + +import com.android.build.api.transform.QualifiedContent +import com.android.build.api.transform.Transform +import com.android.build.api.transform.TransformInvocation +import com.android.build.gradle.BaseExtension +import com.android.build.gradle.internal.pipeline.TransformManager +import com.didichuxing.doraemonkit.plugin.DoKitTransformInvocation +import com.didichuxing.doraemonkit.plugin.loadTransformers +import com.didiglobal.booster.annotations.Priority +import com.didiglobal.booster.gradle.* +import com.didiglobal.booster.transform.AbstractKlassPool +import org.gradle.api.Project + +/** + * Represents the transform base + * DoKitCommTransform 作用于 CommTransformer、BigImgTransformer、UrlConnectionTransformer、GlobalSlowMethodTransformer + * @author johnsonlee + */ +open class DoKitBaseTransform(val project: Project) : Transform() { + + /* + * Preload transformers as List to fix NoSuchElementException caused by ServiceLoader in parallel mode + */ + internal open val transformers = loadTransformers(project.buildscript.classLoader).sortedBy { + it.javaClass.getAnnotation(Priority::class.java)?.value ?: 0 + } + + internal val verifyEnabled = project.getProperty(OPT_TRANSFORM_VERIFY, false) + + private val android: BaseExtension = project.getAndroid() + + private lateinit var androidKlassPool: AbstractKlassPool + + init { + project.afterEvaluate { + androidKlassPool = object : AbstractKlassPool(android.bootClasspath) {} + } + } + + val bootKlassPool: AbstractKlassPool + get() = androidKlassPool + + override fun getName() = this.javaClass.simpleName + + override fun isIncremental() = !verifyEnabled + + override fun isCacheable() = !verifyEnabled + + override fun getInputTypes(): MutableSet = TransformManager.CONTENT_CLASS + + override fun getScopes(): MutableSet = when { + transformers.isEmpty() -> mutableSetOf() + project.plugins.hasPlugin("com.android.library") -> SCOPE_PROJECT + project.plugins.hasPlugin("com.android.application") -> SCOPE_FULL_PROJECT + project.plugins.hasPlugin("com.android.dynamic-feature") -> SCOPE_FULL_WITH_FEATURES + else -> TODO("Not an Android project") + } + + override fun getReferencedScopes(): MutableSet = when { + transformers.isEmpty() -> when { + project.plugins.hasPlugin("com.android.library") -> SCOPE_PROJECT + project.plugins.hasPlugin("com.android.application") -> SCOPE_FULL_PROJECT + project.plugins.hasPlugin("com.android.dynamic-feature") -> SCOPE_FULL_WITH_FEATURES + else -> TODO("Not an Android project") + } + else -> super.getReferencedScopes() + } + + final override fun transform(invocation: TransformInvocation) { + DoKitTransformInvocation(invocation, this).apply { + if (isIncremental) { + doIncrementalTransform() + } else { + outputProvider?.deleteAll() + doFullTransform() + } + } + } + +} + +/** + * The option for transform outputs verifying, default is false + */ +private const val OPT_TRANSFORM_VERIFY = "dokit.transform.verify" \ No newline at end of file diff --git a/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/transform/DoKitCommTransform.kt b/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/transform/DoKitCommTransform.kt new file mode 100644 index 00000000..143a09f2 --- /dev/null +++ b/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/transform/DoKitCommTransform.kt @@ -0,0 +1,12 @@ +package com.didichuxing.doraemonkit.plugin.transform + +import org.gradle.api.Project + +/** + * Represents the transform base + * DoKitCommTransform 作用于 CommTransformer、BigImgTransformer、UrlConnectionTransformer、GlobalSlowMethodTransformer、EnterMethodStackTransformer + * @author johnsonlee + */ +class DoKitCommTransform(androidProject: Project) : DoKitBaseTransform(androidProject) { + +} diff --git a/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/transform/DoKitDependTransform.kt b/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/transform/DoKitDependTransform.kt new file mode 100644 index 00000000..3f8ccf5b --- /dev/null +++ b/Android/java/doraemonkit-plugin/src/main/kotlin/com/didichuxing/doraemonkit/plugin/transform/DoKitDependTransform.kt @@ -0,0 +1,19 @@ +package com.didichuxing.doraemonkit.plugin.transform + +import com.didichuxing.doraemonkit.plugin.asmtransformer.DoKitAsmTransformer +import com.didiglobal.booster.transform.Transformer +import org.gradle.api.Project + +/** + * Represents the transform base + * DoKitCommTransform 作用于 CommTransformer、BigImgTransformer、UrlConnectionTransformer、GlobalSlowMethodTransformer + * @author johnsonlee + */ +open class DoKitDependTransform(androidProject: Project, private val level: Int) : DoKitBaseTransform(androidProject) { + + internal override val transformers = mutableListOf(DoKitAsmTransformer(level)) + + override fun getName(): String { + return "${this.javaClass.simpleName}_$level" + } +} -- GitLab