[TOC]
ASM自定義函數(shù)耗時插件(一)
ASM自定義函數(shù)耗時插件(二)
簡介
使用ASM技術,在android transform過程中完成對Java或者kotlin方法的函數(shù)耗時代碼插樁内贮,用于解決性能問題做函數(shù)耗時計算查看代碼優(yōu)化成果的輔助工具
技術前置了解能力
寫這個小插件主要需要了解以下幾個技術點:
- Java字節(jié)碼
- Android打包流程(這里主要知道Android的transform調(diào)用時機以及部分源碼即可)
- ASM使用
- ASM 系列詳細教程
-
深入理解Transform
以上需要是在寫之前需要了解的知識點饮怯,不用太糾結細節(jié),了解清楚每個流程即可陪竿,下面直接上寫法
Coding
-
創(chuàng)建一個JavaLib迄汛,一定要命名為buildSrc
刪除所有文件寂恬,保留一個build.gradle,完成相關配置
apply plugin: 'groovy'
apply plugin: 'java'
apply plugin: 'kotlin'
apply plugin: 'kotlin-android-extensions'
ext {
kt_v = "1.3.50"
}
buildscript {
ext.kt_v = "1.3.50"
repositories {
maven {
url 'https://maven.aliyun.com/repository/jcenter'
}
maven {
url 'https://maven.aliyun.com/nexus/content/repositories/google'
}
google()
jcenter()
mavenCentral() //必須
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${this.ext.kt_v}"
}
}
sourceSets {
main {
groovy {
srcDir '../buildSrc/src/main/groovy'
}
java {
srcDir '../buildSrc/src/main/java'
}
kotlin {
srcDir '../buildSrc/src/main/kotlin'
}
resources {
srcDir '../buildSrc/src/main/resources'
}
}
}
repositories {
maven {
url 'https://maven.aliyun.com/repository/jcenter'
}
maven {
url 'https://maven.aliyun.com/nexus/content/repositories/google'
}
google()
jcenter()
mavenCentral() //必須
}
dependencies {
compile gradleApi() //必須
compile localGroovy() //必須
implementation 'com.android.tools.build:gradle:4.0.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kt_v"
// ASM 相關
implementation 'org.ow2.asm:asm:7.2'
implementation 'org.ow2.asm:asm-util:7.2'
implementation 'org.ow2.asm:asm-commons:7.2'
}
sourceCompatibility = "8"
targetCompatibility = "8"
-
創(chuàng)建插件資源目錄,用于標識插件使用
implementation-class內(nèi)容寫您的插件名字即可
implementation-class=com.done.plugin.PagePlugin
對應插件類代碼
package com.done.plugin
import com.android.build.gradle.BaseExtension
import org.gradle.api.Plugin
import org.gradle.api.Project
public class PagePlugin implements Plugin<Project> {
private Project mProject
@Override
void apply(Project project) {
mProject = project
project.getExtensions().add(InsectExtension.CONFIG_NAME, InsectExtension)
def android = project.extensions.findByType(BaseExtension)
android.registerTransform(new CostTransform(project))
LogUtilsGv.log("register PagePlugin")
}
}
在插件被調(diào)用的時候扛吞,gradle會調(diào)用apply方法呻惕,在這里注冊插樁的transform-CostTransform,可以看下transform的配置
/**
* 執(zhí)行transform時候task的名字
* @return
*/
@Override
String getName() {
return this.class.getName()
}
/**
* 只需要class文件輸入
* @return
*/
@Override
Set<QualifiedContent.ContentType> getInputTypes() {
return TransformManager.CONTENT_CLASS
}
/**
* 如果是app使用插件滥比,則傳遞所有的class進來亚脆,如果是lib使用的話,僅傳遞對應lib project的class過來即可
* @return
*/
@Override
Set<? super QualifiedContent.Scope> getScopes() {
if (mType == PROJECT_TYPE.LIB) {
return TransformManager.PROJECT_ONLY
} else {
return TransformManager.SCOPE_FULL_PROJECT
}
}
/**
* TODO 暫時不支持增量編譯
* @return
*/
@Override
boolean isIncremental() {
return false
}
上面需要重寫的方法主要是表明僅需要class的流輸入進來盲泛,接下來就是核心的transform方法
@Override
void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
mInsectExtension = mProject.getExtensions().getByName(InsectExtension.CONFIG_NAME)
if (mInsectExtension == null || mInsectExtension.annotationNames == null || mInsectExtension.annotationNames.length < 1 || !mInsectExtension.isDebug) {
LogUtilsGv.log("do not execute insert byte code")
return
}
LogUtilsGv.log("startTransform anno:${mInsectExtension.annotationNames}")
long startTime = System.currentTimeMillis()
Collection<TransformInput> inputs = transformInvocation.inputs
TransformOutputProvider outputProvider = transformInvocation.outputProvider
if (outputProvider != null) {
outputProvider.deleteAll()
}
//處理目錄中的文件
inputs.each {
TransformInput input ->
input.directoryInputs.each {
DirectoryInput directoryInput ->
File dest = outputProvider.getContentLocation(directoryInput.getFile().getAbsolutePath(),
directoryInput.getContentTypes(), directoryInput.getScopes(), Format.DIRECTORY)
handleDirectoryFile(directoryInput.getFile(), dest, mInsectExtension)
}
}
//處理Jar中的文件
inputs.each {
TransformInput input ->
input.jarInputs.each {
JarInput jarInput ->
File dest = outputProvider.getContentLocation(jarInput.getFile().getAbsolutePath(),
jarInput.getContentTypes(), jarInput.getScopes(), Format.JAR)
handleJarFile(jarInput.getFile(), dest)
}
}
LogUtilsGv.log("endTransform cost:" + (System.currentTimeMillis() - startTime))
}
為了方便外部使用濒持,需要對自定義的插件增加一些配置,如標識的注解類和需要調(diào)用的方法寺滚,插件內(nèi)部寫死傳遞兩個參數(shù)柑营,分別是method名稱和方法執(zhí)行的起始時間
@Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
boolean hasMethod = true
if (Utils.isEmptyString(mInsectExtension.methodOwner) || Utils.isEmptyString(mInsectExtension.methodName)) {
hasMethod = false
} else {
mCostMethod = mInsectExtension.methodName
mCostMethodClass = mInsectExtension.methodOwner.replaceAll("(\\.)", "/")
}
if (hasMethod) {
for (String annotation : mInsectExtension.annotationNames) {
String replaceAnno = "L" + annotation.replaceAll("(\\.)", "/") + ";"
canPrint = canPrint || replaceAnno == desc
LogUtilsGv.log("外部配置的注解:$replaceAnno, $mClassName.$mMethodName 插樁:$canPrint, 插入$mCostMethodClass#$mCostMethod")
}
} else {
LogUtilsGv.log("does not have cost method, do not insert cost code")
}
return super.visitAnnotation(desc, visible)
}
@Override
protected void onMethodExit(int opcode) {
if (canPrint) {
String newName = (mClassName + "#" + mMethodName).replaceAll("/", ".")
mv.visitLdcInsn(newName)
mv.visitVarInsn(LLOAD, startTime)
mv.visitMethodInsn(INVOKESTATIC, mCostMethodClass, mCostMethod, "(Ljava/lang/String;J)V", false)
}
}
@Override
protected void onMethodEnter() {
if (canPrint) {
invokeStatic(Type.getType("Landroid/os/SystemClock;"), new Method("uptimeMillis", "()J"))
startTime = newLocal(Type.LONG_TYPE)
storeLocal(startTime)
}
}
- 在對應的Lib或者App下進行使用
apply plugin: 'com.done.plugin'
insectConfig {
annotationNames = ['com.done.asm.Cost',
'com.done.asm.KtCost']
methodOwner = 'com.done.asm.Utils'
methodName = 'printCost'
isDebug = true
}
集成插件后的能力,可以在修飾對應注解的地方插樁函數(shù)耗時的代碼
annotationNames 注解類作為數(shù)組方法中
isDebug 負責是否進行插樁村视,用于遠程打包的時候配置使用
methodOwner 是調(diào)用的靜態(tài)方法類名
methodName 是調(diào)用的靜態(tài)方法名稱
代碼量其實很少