提交 e738fe27 编写于 作者: J jackjintai

android:更新打包规则

上级 7dce9e91
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("<init>") && 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<InsnNode>? {
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
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<String> = 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<String>) {
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
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<Project> {
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<AppExtension>().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<String>())
val methodStackRange = 1 until methodStackLevel
if (methodStackLevel > 1) {
for (index in methodStackRange) {
MethodStackNodeUtil.METHOD_STACK_KEYS.add(index, mutableSetOf<String>())
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<LibraryExtension>().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
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<File>()
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 <T> 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<Future<*>>) {
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
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<Unit>("init")
}
}
}
\ No newline at end of file
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<T> {
fun load(vararg args: Any): List<T>
}
internal class ServiceLoaderFactory<T>(private val classLoader: ClassLoader, private val service: Class<T>) {
fun newServiceLoader(vararg types: Class<*>) = object : ServiceLoader<T> {
@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 <reified T> 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<Transformer>(classLoader, ClassLoader::class.java).load(classLoader)
/**
* Load [VariantProcessor]s with the specified [classLoader]
*/
@Throws(ServiceConfigurationError::class)
internal fun loadVariantProcessors(project: Project) = newServiceLoader<VariantProcessor>(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<String>()
}
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
}
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<ClassTransformer, Long>()
internal val transformers: Collection<ClassTransformer>
/**
* 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 <R> 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
}
}
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)) {
}
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 == "<init>") && 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 == "<init>" && 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 == "<init>" && 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 == "<init>" && 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
}
}
}
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 == "<init>" && 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 == "<init>" && 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 == "<init>" && 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 == "<init>" && 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", "<init>", "()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", "<init>", "(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", "<init>", "(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
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 != "<init>"
}.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
}
}
}
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 != "<init>" &&
!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
}
}
}
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<String> = MethodStackNodeUtil.METHOD_STACK_KEYS[level - 1]
klass.methods.filter { methodNode ->
methodNode.name != "<init>" &&
!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 != "<init>"
}.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
}
}
}
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
}
}
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
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<CommExt>) {
action.execute(comm)
}
/**
* 让slowMethod 支持 DSL 语法
*
* @param action
*/
fun slowMethod(action: Action<SlowMethodExt>) {
action.execute(slowMethod)
}
override fun toString(): String {
return "DoKitExt(comm=$comm, slowMethod=$slowMethod)"
}
}
\ No newline at end of file
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<StackMethodExt?>?) {
ConfigureUtil.configure(closure, stackMethod)
}
fun normalMethod(closure: Closure<NormalMethodExt?>?) {
ConfigureUtil.configure(closure, normalMethod)
}
class StackMethodExt(
//默认阈值为5ms
var thresholdTime: Int = 5,
//入口函集合
var enterMethods: MutableSet<String> = mutableSetOf()) {
/**
* 默认值为5ms
*/
fun thresholdTime(thresholdTime: Int) {
this.thresholdTime = thresholdTime
}
fun normalMethod(enterMethods: MutableSet<String>) {
this.enterMethods = enterMethods
}
override fun toString(): String {
return "StackMethodExt(thresholdTime=$thresholdTime, enterMethods=$enterMethods)"
}
}
class NormalMethodExt(
//默认阈值为500ms
var thresholdTime: Int = 500,
//普通函数的插装包名集合
var packageNames: MutableSet<String> = mutableSetOf(),
//插桩黑名单
var methodBlacklist: MutableSet<String> = mutableSetOf()) {
/**
* 默认值为500ms
*/
fun thresholdTime(thresholdTime: Int) {
this.thresholdTime = thresholdTime
}
fun packageNames(packageNames: MutableSet<String>) {
this.packageNames = packageNames
}
fun methodBlacklist(methodBlacklist: MutableSet<String>) {
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
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<AppExtension>().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
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
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<MutableSet<String>> by lazy {
Collections.synchronizedList(mutableListOf<MutableSet<String>>())
}
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
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<QualifiedContent.ContentType> = TransformManager.CONTENT_CLASS
override fun getScopes(): MutableSet<in QualifiedContent.Scope> = 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<in QualifiedContent.Scope> = 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
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) {
}
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<Transformer>(DoKitAsmTransformer(level))
override fun getName(): String {
return "${this.javaClass.simpleName}_$level"
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册