transform 多线程+增量编译 提升1.5倍

This commit is contained in:
yummylau
2019-08-26 15:46:34 +08:00
parent 526313c79d
commit 0d2da9081a
63 changed files with 125 additions and 225 deletions

View File

@@ -73,5 +73,5 @@ todo list
1. 自定义scope逻辑统一配置(done)
2. 模块内互相引用 done
3. 仓库管理
5. transform 优化
5. transform 优化 gradle增量前7秒多 多线程+增量编译 快了1.5倍 3s
6. kotlin 模块测试 done

View File

@@ -1,4 +1,3 @@
apply plugin: 'com.android.application'
android {
compileSdkVersion 29
buildToolsVersion "29.0.1"

View File

@@ -9,7 +9,8 @@ import com.plugin.component.extension.option.PublicationOption
import com.plugin.component.extension.option.DebugOption
import com.plugin.component.listener.OnModuleExtensionListener
import com.plugin.component.transform.ComponentTransform
import com.plugin.component.transform.InjectCodeTransform
import com.plugin.component.transform.ScanCodeTransform
import com.plugin.component.utils.JarUtil
import com.plugin.component.extension.PublicationManager
import com.plugin.component.extension.ComponentExtension
@@ -237,7 +238,8 @@ class ComponentPlugin implements Plugin<Project> {
}
}
}
childProject.extensions.findByType(BaseExtension.class).registerTransform(new ComponentTransform())
childProject.extensions.findByType(BaseExtension.class).registerTransform(new ScanCodeTransform(childProject))
childProject.extensions.findByType(BaseExtension.class).registerTransform(new InjectCodeTransform(childProject))
} else {
childProject.apply plugin: Constants.PLUGIN_LIBRARY
Logger.buildOutput("project.apply plugin: com.android.library")

View File

@@ -1,200 +0,0 @@
package com.plugin.component.transform
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.Transform
import com.android.build.api.transform.TransformException
import com.android.build.api.transform.TransformInput
import com.android.build.api.transform.TransformInvocation
import com.android.build.gradle.internal.pipeline.TransformManager
import com.android.utils.FileUtils
import com.plugin.component.Logger
import com.plugin.component.asm.ComponentInjectClassVisitor
import com.plugin.component.asm.ComponentScanClassVisitor
import com.plugin.component.utils.FileUtil
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.ClassWriter
import java.util.jar.JarEntry
import java.util.jar.JarFile
import java.util.jar.JarOutputStream
import java.util.zip.ZipEntry
import com.plugin.component.asm.ScanRuntime
/**
* 插入方法统计transform
* created by yummylau 2019/08/12
*/
class ComponentTransform extends Transform {
@Override
String getName() {
return "ComponentTransform"
}
@Override
Set<QualifiedContent.ContentType> getInputTypes() {
return TransformManager.CONTENT_CLASS
}
@Override
Set<? super QualifiedContent.Scope> getScopes() {
return TransformManager.SCOPE_FULL_PROJECT
}
@Override
boolean isIncremental() {
return false
}
@Override
public void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
long startTime = System.currentTimeMillis();
transformInvocation.inputs.each { TransformInput input ->
input.directoryInputs.each { DirectoryInput directoryInput ->
scanClass(directoryInput.file)
}
input.jarInputs.each { JarInput jarInput ->
scanClass(jarInput.file)
}
}
//log
ScanRuntime.logScanInfo()
ScanRuntime.buildComponentSdkInfo()
transformInvocation.inputs.each { TransformInput input ->
input.directoryInputs.each { DirectoryInput directoryInput ->
def dest = transformInvocation.outputProvider.getContentLocation(directoryInput.name,
directoryInput.contentTypes, directoryInput.scopes, Format.DIRECTORY)
FileUtils.copyDirectory(directoryInput.file, dest)
injectClass(dest)
}
input.jarInputs.each { JarInput jarInput ->
def dest = transformInvocation.outputProvider.getContentLocation(jarInput.name, jarInput.contentTypes, jarInput.scopes, Format.JAR)
FileUtils.copyFile(jarInput.file, dest)
injectClass(dest)
}
}
ScanRuntime.loadInjectInfo()
ScanRuntime.clearScanInfo()
Logger.buildOutput("transform cost : " + System.currentTimeMillis() - startTime + "ms")
}
private boolean filterClass(String fileName) {
return !FileUtil.isValidClassFile(fileName)
}
private boolean filterPackage(String fileName) {
return false
}
private void scanClass(File source) {
if (source.isDirectory()) {
source.eachFileRecurse { File file ->
String filename = file.getName()
if (filterClass(filename)) return
ClassReader cr = new ClassReader(file.readBytes())
ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS)
ClassVisitor cv = new ComponentScanClassVisitor(cw)
cr.accept(cv, ClassReader.EXPAND_FRAMES)
}
} else {
JarFile jarFile = new JarFile(source)
Enumeration<JarEntry> entries = jarFile.entries()
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement()
String filename = entry.getName()
if (filterPackage(filename)) break
if (filterClass(filename)) continue
InputStream stream = jarFile.getInputStream(entry)
if (stream != null) {
ClassReader cr = new ClassReader(stream.bytes)
ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS)
ClassVisitor cv = new ComponentScanClassVisitor(cw)
cr.accept(cv, ClassReader.EXPAND_FRAMES)
stream.close()
}
}
jarFile.close()
}
}
private void injectClass(File source) {
if (source.isDirectory()) {
source.eachFileRecurse { File file ->
String filename = file.getName()
if (filterClass(filename)) return
ClassReader cr = new ClassReader(file.bytes)
ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS)
ClassVisitor cv = new ComponentInjectClassVisitor(cw)
cr.accept(cv, ClassReader.EXPAND_FRAMES)
FileOutputStream outputStream = new FileOutputStream(file)
outputStream.write(cw.toByteArray())
outputStream.close()
}
} else {
Map<String, byte[]> tempModifiedClassByteMap = new HashMap()
JarFile jarFile = new JarFile(source)
Enumeration<JarEntry> entries = jarFile.entries()
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement()
String filename = entry.getName()
if (filterPackage(filename)) break
if (filterClass(filename)) continue
InputStream stream = jarFile.getInputStream(entry)
if (stream != null) {
ClassReader cr = new ClassReader(stream.bytes)
ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS)
ClassVisitor cv = new ComponentInjectClassVisitor(cw)
cr.accept(cv, ClassReader.EXPAND_FRAMES)
tempModifiedClassByteMap.put(filename, cw.toByteArray())
stream.close()
}
}
if (tempModifiedClassByteMap.size() != 0) {
File tempJar = new File(source.absolutePath.replace('.jar', 'temp.jar'))
if (tempJar.exists()) {
tempJar.delete()
}
entries = jarFile.entries()
JarOutputStream jarOutputStream = new JarOutputStream(new FileOutputStream(tempJar))
while (entries.hasMoreElements()) {
JarEntry jarEntry = entries.nextElement()
String filename = jarEntry.getName()
ZipEntry zipEntry = new ZipEntry(filename)
jarOutputStream.putNextEntry(zipEntry)
if (tempModifiedClassByteMap.containsKey(filename)) {
jarOutputStream.write(tempModifiedClassByteMap.get(filename))
} else {
InputStream inputStream = jarFile.getInputStream(jarEntry)
jarOutputStream.write(inputStream.bytes)
inputStream.close()
}
jarOutputStream.closeEntry()
}
jarOutputStream.close()
FileOutputStream outputStream = new FileOutputStream(source)
outputStream.write(tempJar.bytes)
outputStream.close()
tempJar.delete()
}
jarFile.close()
}
}
}

View File

@@ -1,6 +1,8 @@
package com.plugin.component.asm
package com.plugin.component.transform
import com.plugin.component.Logger
import com.plugin.component.transform.info.ComponentSdkInfo
import com.plugin.component.transform.info.ScanRuntime
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.MethodVisitor
import org.objectweb.asm.Opcodes
@@ -15,14 +17,14 @@ import org.objectweb.asm.commons.AdviceAdapter
* <p>
* visit visitSource? visitOuterClass? ( visitAnnotation | visitAttribute )* ( visitInnerClass | visitField | visitMethod )* visitEnd
*/
class ComponentInjectClassVisitor extends ClassVisitor {
class InjectCodeAdapter extends ClassVisitor {
private static final String sCostCachePath = "com/plugin/component/CostCache"
private static final String sComponentManagerPath = "com/plugin/component/ComponentManager"
private String className
ComponentInjectClassVisitor(ClassVisitor classVisitor) {
InjectCodeAdapter(ClassVisitor classVisitor) {
super(Opcodes.ASM7, classVisitor)
}

View File

@@ -0,0 +1,38 @@
package com.plugin.component.transform
import com.android.build.api.transform.Context
import com.android.build.api.transform.TransformException
import com.android.build.api.transform.TransformInput
import com.android.build.api.transform.TransformOutputProvider
import com.plugin.component.Logger
import com.plugin.component.transform.info.ScanRuntime
import com.quinn.hunter.transform.HunterTransform
import com.quinn.hunter.transform.RunVariant
import org.gradle.api.Project
class InjectCodeTransform extends HunterTransform {
InjectCodeTransform(Project project) {
super(project)
this.bytecodeWeaver = new InjectCodeWeaver()
}
@Override
void transform(Context context, Collection<TransformInput> inputs, Collection<TransformInput> referencedInputs, TransformOutputProvider outputProvider, boolean isIncremental) throws IOException, TransformException, InterruptedException {
long startTime = System.currentTimeMillis()
super.transform(context, inputs, referencedInputs, outputProvider, isIncremental)
ScanRuntime.logInjectInfo()
ScanRuntime.clearScanInfo()
Logger.buildOutput("inject code cost : " + (System.currentTimeMillis() - startTime) + "ms")
}
@Override
protected RunVariant getRunVariant() {
return super.getRunVariant()
}
@Override
protected boolean inDuplcatedClassSafeMode() {
return super.inDuplcatedClassSafeMode()
}
}

View File

@@ -0,0 +1,13 @@
package com.plugin.component.transform
import com.quinn.hunter.transform.asm.BaseWeaver
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.ClassWriter
class InjectCodeWeaver extends BaseWeaver{
@Override
protected ClassVisitor wrapClassWriter(ClassWriter classWriter) {
return new ScanCodeAdapter(classWriter)
}
}

View File

@@ -1,20 +1,19 @@
package com.plugin.component.asm
package com.plugin.component.transform
import com.plugin.component.anno.AutoInjectComponent
import com.plugin.component.anno.AutoInjectImpl
import com.plugin.component.anno.MethodCost
import org.objectweb.asm.AnnotationVisitor
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.MethodVisitor
import org.objectweb.asm.Opcodes
import org.objectweb.asm.Type
import com.plugin.component.transform.info.ScanComponentInfo
import com.plugin.component.transform.info.ScanRuntime
import com.plugin.component.transform.info.ScanSdkInfo
import org.objectweb.asm.*
import org.objectweb.asm.commons.AdviceAdapter
class ComponentScanClassVisitor extends ClassVisitor {
class ScanCodeAdapter extends ClassVisitor {
private String className
ComponentScanClassVisitor(ClassVisitor classVisitor) {
ScanCodeAdapter(ClassVisitor classVisitor) {
super(Opcodes.ASM7, classVisitor)
}

View File

@@ -0,0 +1,38 @@
package com.plugin.component.transform
import com.android.build.api.transform.Context
import com.android.build.api.transform.TransformException
import com.android.build.api.transform.TransformInput
import com.android.build.api.transform.TransformOutputProvider
import com.plugin.component.Logger
import com.plugin.component.transform.info.ScanRuntime
import com.quinn.hunter.transform.HunterTransform
import com.quinn.hunter.transform.RunVariant
import org.gradle.api.Project
class ScanCodeTransform extends HunterTransform {
ScanCodeTransform(Project project) {
super(project)
this.bytecodeWeaver = new ScanCodeWeaver()
}
@Override
void transform(Context context, Collection<TransformInput> inputs, Collection<TransformInput> referencedInputs, TransformOutputProvider outputProvider, boolean isIncremental) throws IOException, TransformException, InterruptedException {
long startTime = System.currentTimeMillis()
super.transform(context, inputs, referencedInputs, outputProvider, isIncremental)
ScanRuntime.logScanInfo()
ScanRuntime.buildComponentSdkInfo()
Logger.buildOutput("scan code cost : " + (System.currentTimeMillis() - startTime) + "ms")
}
@Override
protected RunVariant getRunVariant() {
return super.getRunVariant()
}
@Override
protected boolean inDuplcatedClassSafeMode() {
return super.inDuplcatedClassSafeMode()
}
}

View File

@@ -0,0 +1,13 @@
package com.plugin.component.transform
import com.quinn.hunter.transform.asm.BaseWeaver
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.ClassWriter
class ScanCodeWeaver extends BaseWeaver {
@Override
protected ClassVisitor wrapClassWriter(ClassWriter classWriter) {
return new InjectCodeAdapter(classWriter)
}
}

View File

@@ -1,4 +1,4 @@
package com.plugin.component.asm
package com.plugin.component.transform.info
class ComponentSdkInfo {

View File

@@ -1,4 +1,4 @@
package com.plugin.component.asm
package com.plugin.component.transform.info
class ScanComponentInfo {

View File

@@ -1,8 +1,7 @@
package com.plugin.component.asm
package com.plugin.component.transform.info
import com.android.annotations.NonNull
import com.android.annotations.Nullable
import com.android.builder.sdk.SdkInfo
import com.plugin.component.Logger
class ScanRuntime {
@@ -50,7 +49,7 @@ class ScanRuntime {
}
}
static void loadInjectInfo() {
static void logInjectInfo() {
for (ComponentSdkInfo componentSdkInfo : componentSdkInfoList) {
Logger.buildOutput(componentSdkInfo.toString())
}

View File

@@ -1,4 +1,4 @@
package com.plugin.component.asm
package com.plugin.component.transform.info
class ScanSdkInfo {

View File

@@ -1,4 +1,3 @@
apply plugin: 'com.android.library'
android {
compileSdkVersion 29
buildToolsVersion "29.0.1"

View File

@@ -1,4 +1,3 @@
apply plugin: 'com.android.library'
android {
compileSdkVersion 29

View File

@@ -1,4 +1,3 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'