From 726ed351e1876130b7bbf238d0382b4c285089f7 Mon Sep 17 00:00:00 2001 From: "linzheng\\n8383" Date: Mon, 21 Oct 2019 14:38:59 +0800 Subject: [PATCH 1/5] =?UTF-8?q?=E5=B0=9D=E8=AF=95=E5=90=88=E5=B9=B6Transfo?= =?UTF-8?q?rm?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 7 +- build.gradle | 12 +- component-plugin/build.gradle | 2 +- .../plugin/component/ComponentPlugin.groovy | 10 +- .../transform/CodeInjectProcessor.java | 146 ++++++++++ .../transform/ScanCodeTransform2.java | 272 ++++++++++++++++++ .../component/transform/ScanCodeWeaver2.java | 146 ++++++++++ settings.gradle | 4 +- 8 files changed, 582 insertions(+), 17 deletions(-) create mode 100644 component-plugin/src/main/groovy/com/plugin/component/transform/CodeInjectProcessor.java create mode 100644 component-plugin/src/main/groovy/com/plugin/component/transform/ScanCodeTransform2.java create mode 100644 component-plugin/src/main/groovy/com/plugin/component/transform/ScanCodeWeaver2.java diff --git a/.gitignore b/.gitignore index d45a624..95cf385 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,7 @@ *.iml .gradle /local.properties -/.idea/caches -/.idea/libraries -/.idea/modules.xml -/.idea/workspace.xml -/.idea/navEditor.xml -/.idea/assetWizardSettings.xml +/.idea .DS_Store /build /captures diff --git a/build.gradle b/build.gradle index 72dc5b2..3e143c6 100644 --- a/build.gradle +++ b/build.gradle @@ -2,12 +2,15 @@ apply from: "./gradleScript/config.gradle" buildscript { ext.kotlin_version = '1.3.50' - ext.component_version = '1.0.3-beta' + ext.component_version = '1.0.4-beta' repositories { google() jcenter() - maven { url 'https://dl.bintray.com/yummylau/maven' } +// maven { url 'https://dl.bintray.com/yummylau/maven' } + maven { + url uri('./repo') + } } dependencies { classpath "com.effective.plugins:component:$component_version" @@ -25,7 +28,10 @@ allprojects { google() jcenter() maven { url "http://dl.bintray.com/kotlin/kotlin-eap" } - maven { url 'https://dl.bintray.com/yummylau/maven' } +// maven { url 'https://dl.bintray.com/yummylau/maven' } + maven { + url uri('../repo') + } } gradle.projectsEvaluated { diff --git a/component-plugin/build.gradle b/component-plugin/build.gradle index 108ca43..8a8a228 100644 --- a/component-plugin/build.gradle +++ b/component-plugin/build.gradle @@ -4,7 +4,7 @@ apply plugin: 'maven' group = 'com.effective.plugins' archivesBaseName = 'component' -version = '1.0.3-beta' +version = '1.0.4-beta' //设置本地发布路径 uploadArchives { diff --git a/component-plugin/src/main/groovy/com/plugin/component/ComponentPlugin.groovy b/component-plugin/src/main/groovy/com/plugin/component/ComponentPlugin.groovy index d137dfa..c459a00 100644 --- a/component-plugin/src/main/groovy/com/plugin/component/ComponentPlugin.groovy +++ b/component-plugin/src/main/groovy/com/plugin/component/ComponentPlugin.groovy @@ -11,7 +11,7 @@ import com.plugin.component.extension.option.publication.PublicationDependencies import com.plugin.component.extension.option.publication.PublicationOption import com.plugin.component.transform.InjectCodeTransform import com.plugin.component.transform.MethodCostTransform -import com.plugin.component.transform.ScanCodeTransform +import com.plugin.component.transform.ScanCodeTransform2 import com.plugin.component.utils.JarUtil import com.plugin.component.utils.ProjectUtil import com.plugin.component.utils.PublicationUtil @@ -228,10 +228,10 @@ class ComponentPlugin implements Plugin { if (it instanceof AppPlugin) { if (projectInfo.isDebugModule() || projectInfo.isMainModule()) { Logger.buildOutput("plugin is AppPlugin and isDebugModule or isMainModule") - Logger.buildOutput("registerTransform", "ScanCodeTransform") - Logger.buildOutput("registerTransform", "InjectCodeTransform") - childProject.extensions.findByType(BaseExtension.class).registerTransform(new ScanCodeTransform(childProject)) - childProject.extensions.findByType(BaseExtension.class).registerTransform(new InjectCodeTransform(childProject)) + Logger.buildOutput("registerTransform", "ScanCodeTransform2") +// Logger.buildOutput("registerTransform", "InjectCodeTransform") + childProject.extensions.findByType(BaseExtension.class).registerTransform(new ScanCodeTransform2(childProject)) +// childProject.extensions.findByType(BaseExtension.class).registerTransform(new InjectCodeTransform(childProject)) if (Runtimes.enbaleMethodCost()) { Logger.buildOutput("registerTransform", "MethodCostTransform") childProject.extensions.findByType(BaseExtension.class).registerTransform(new MethodCostTransform(project)) diff --git a/component-plugin/src/main/groovy/com/plugin/component/transform/CodeInjectProcessor.java b/component-plugin/src/main/groovy/com/plugin/component/transform/CodeInjectProcessor.java new file mode 100644 index 0000000..3ed36db --- /dev/null +++ b/component-plugin/src/main/groovy/com/plugin/component/transform/CodeInjectProcessor.java @@ -0,0 +1,146 @@ +package com.plugin.component.transform; + +import org.apache.commons.io.FileUtils; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.attribute.FileTime; +import java.util.Enumeration; +import java.util.zip.CRC32; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipOutputStream; + +/** + * author : linzheng + * e-mail : linzheng@corp.netease.com + * time : 2019/10/21 + * desc : + * version: 1.0 + */ +public class CodeInjectProcessor { + + + private static final FileTime ZERO = FileTime.fromMillis(0); + private static final String FILE_SEP = File.separator; + + + public void injectCode(String filePath) { + try { + if (filePath != null && filePath.endsWith(".jar")) { + File file = new File(filePath); + weaveJar(file); + } else if (filePath != null && filePath.endsWith(".class")) { + File file = new File(filePath); + weaveSingleClassToFile(file); + } + + } catch (Exception e) { + e.printStackTrace(); + } + + } + + + public boolean isWeavableClass(String fullQualifiedClassName) { + return fullQualifiedClassName.endsWith(".class") && !fullQualifiedClassName.contains("R$") && !fullQualifiedClassName.contains("R.class") && !fullQualifiedClassName.contains("BuildConfig.class"); + } + + + public final void weaveSingleClassToFile(File inputFile) throws IOException { + File outputFile = new File(inputFile.getParent(), inputFile.getName() + ".temp"); + FileUtils.touch(outputFile); + InputStream inputStream = new FileInputStream(inputFile); + byte[] bytes = doGenerateCode(inputStream); + if (bytes != null) { + FileOutputStream fos = new FileOutputStream(outputFile); + fos.write(bytes); + fos.close(); + inputStream.close(); + if (inputFile.exists()) { + inputFile.delete(); + } + outputFile.renameTo(inputFile); + } + } + + + public final void weaveJar(File inputJar) throws IOException { + + File outputJar = new File(inputJar.getParent(), inputJar.getName() + ".temp"); + if (outputJar.exists()) { + outputJar.delete(); + } + + ZipFile inputZip = new ZipFile(inputJar); + ZipOutputStream outputZip = new ZipOutputStream(new BufferedOutputStream(java.nio.file.Files.newOutputStream(outputJar.toPath()))); + Enumeration inEntries = inputZip.entries(); + + + String outputPath = outputJar.getAbsolutePath(); + while (inEntries.hasMoreElements()) { + ZipEntry entry = inEntries.nextElement(); + InputStream originalFile = new BufferedInputStream(inputZip.getInputStream(entry)); + ZipEntry outEntry = new ZipEntry(entry.getName()); + byte[] newEntryContent; + // seperator of entry name is always '/', even in windows + String className = outEntry.getName().replace("/", "."); + + + if (!isWeavableClass(className)) { + newEntryContent = org.apache.commons.io.IOUtils.toByteArray(originalFile); + } else { + newEntryContent = doGenerateCode(originalFile); + } + CRC32 crc32 = new CRC32(); + crc32.update(newEntryContent); + outEntry.setCrc(crc32.getValue()); + outEntry.setMethod(ZipEntry.STORED); + outEntry.setSize(newEntryContent.length); + outEntry.setCompressedSize(newEntryContent.length); + outEntry.setLastAccessTime(ZERO); + outEntry.setLastModifiedTime(ZERO); + outEntry.setCreationTime(ZERO); + outputZip.putNextEntry(outEntry); + outputZip.write(newEntryContent); + outputZip.closeEntry(); + } + outputZip.flush(); + outputZip.close(); + + inputZip.close(); + + if (inputJar.exists()) { + boolean result = inputJar.delete(); + System.out.println("delete jar result = " + result); + } + boolean rename = outputJar.renameTo(inputJar); + System.out.println("rename jar result = " + rename); + + + } + + + private byte[] doGenerateCode(InputStream inputStream) { + try { + ClassReader classReader = new ClassReader(inputStream); + ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_MAXS); + ClassVisitor classVisitor = new InjectCodeAdapter(classWriter); + classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES); + return classWriter.toByteArray(); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + +} diff --git a/component-plugin/src/main/groovy/com/plugin/component/transform/ScanCodeTransform2.java b/component-plugin/src/main/groovy/com/plugin/component/transform/ScanCodeTransform2.java new file mode 100644 index 0000000..f791777 --- /dev/null +++ b/component-plugin/src/main/groovy/com/plugin/component/transform/ScanCodeTransform2.java @@ -0,0 +1,272 @@ +package com.plugin.component.transform; + +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.Status; +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.TransformOutputProvider; +import com.android.build.gradle.internal.pipeline.TransformManager; +import com.android.ide.common.internal.WaitableExecutor; +import com.google.common.io.Files; +import com.plugin.component.transform.info.ScanRuntime; +import com.quinn.hunter.transform.RunVariant; +import com.quinn.hunter.transform.asm.ClassLoaderHelper; + +import org.apache.commons.io.FileUtils; +import org.gradle.api.Project; +import org.gradle.api.logging.Logger; + +import java.io.File; +import java.io.IOException; +import java.net.URLClassLoader; +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * author : linzheng + * e-mail : linzheng@corp.netease.com + * time : 2019/10/18 + * desc : + * version: 1.0 + */ +public class ScanCodeTransform2 extends Transform { + + private final Logger logger; + + private static final Set SCOPES = new HashSet<>(); + + static { + SCOPES.add(QualifiedContent.Scope.PROJECT); + SCOPES.add(QualifiedContent.Scope.SUB_PROJECTS); + SCOPES.add(QualifiedContent.Scope.EXTERNAL_LIBRARIES); + } + + private Project project; + protected ScanCodeWeaver2 bytecodeWeaver; + private WaitableExecutor waitableExecutor; + private boolean emptyRun = false; + + public ScanCodeTransform2(Project project) { + this.project = project; + this.logger = project.getLogger(); + this.waitableExecutor = WaitableExecutor.useGlobalSharedThreadPool(); + this.bytecodeWeaver = new ScanCodeWeaver2(); + } + + @Override + public String getName() { + return this.getClass().getSimpleName(); + } + + @Override + public Set getInputTypes() { + return TransformManager.CONTENT_CLASS; + } + + @Override + public Set getScopes() { + return SCOPES; + } + + @Override + public boolean isIncremental() { + return false; + } + + + @SuppressWarnings("deprecation") + @Override + public void transform(Context context, + Collection inputs, + Collection referencedInputs, + TransformOutputProvider outputProvider, + boolean isIncremental) throws IOException, TransformException, InterruptedException { + RunVariant runVariant = getRunVariant(); + if ("debug".equals(context.getVariantName())) { + emptyRun = runVariant == RunVariant.RELEASE || runVariant == RunVariant.NEVER; + } else if ("release".equals(context.getVariantName())) { + emptyRun = runVariant == RunVariant.DEBUG || runVariant == RunVariant.NEVER; + } + logger.warn(getName() + " isIncremental = " + isIncremental + ", runVariant = " + runVariant + ", emptyRun = " + emptyRun + ", inDuplcatedClassSafeMode = " + inDuplcatedClassSafeMode()); + long startTime = System.currentTimeMillis(); + if (!isIncremental) { + outputProvider.deleteAll(); + } + URLClassLoader urlClassLoader = ClassLoaderHelper.getClassLoader(inputs, referencedInputs, project); + this.bytecodeWeaver.setClassLoader(urlClassLoader); + boolean flagForCleanDexBuilderFolder = false; + for (TransformInput input : inputs) { + for (JarInput jarInput : input.getJarInputs()) { + Status status = jarInput.getStatus(); + File dest = outputProvider.getContentLocation(jarInput.getFile().getAbsolutePath(), jarInput.getContentTypes(), jarInput.getScopes(), Format.JAR); + if (isIncremental && !emptyRun) { + switch (status) { + case NOTCHANGED: + break; + case ADDED: + case CHANGED: + transformJar(jarInput.getFile(), dest, status); + break; + case REMOVED: + if (dest.exists()) { + FileUtils.forceDelete(dest); + } + break; + } + } else { + //Forgive me!, Some project will store 3rd-party aar for serveral copies in dexbuilder folder,,unknown issue. + if (inDuplcatedClassSafeMode() & !isIncremental && !flagForCleanDexBuilderFolder) { + cleanDexBuilderFolder(dest); + flagForCleanDexBuilderFolder = true; + } + transformJar(jarInput.getFile(), dest, status); + } + } + + for (DirectoryInput directoryInput : input.getDirectoryInputs()) { + File dest = outputProvider.getContentLocation(directoryInput.getName(), directoryInput.getContentTypes(), directoryInput.getScopes(), Format.DIRECTORY); + FileUtils.forceMkdir(dest); + if (isIncremental && !emptyRun) { + String srcDirPath = directoryInput.getFile().getAbsolutePath(); + String destDirPath = dest.getAbsolutePath(); + Map fileStatusMap = directoryInput.getChangedFiles(); + for (Map.Entry changedFile : fileStatusMap.entrySet()) { + Status status = changedFile.getValue(); + File inputFile = changedFile.getKey(); + String destFilePath = inputFile.getAbsolutePath().replace(srcDirPath, destDirPath); + File destFile = new File(destFilePath); + switch (status) { + case NOTCHANGED: + break; + case REMOVED: + if (destFile.exists()) { + //noinspection ResultOfMethodCallIgnored + destFile.delete(); + } + break; + case ADDED: + case CHANGED: + try { + FileUtils.touch(destFile); + } catch (IOException e) { + //maybe mkdirs fail for some strange reason, try again. + Files.createParentDirs(destFile); + } + transformSingleFile(inputFile, destFile, srcDirPath); + break; + } + } + } else { + transformDir(directoryInput.getFile(), dest); + } + + } + + } + waitableExecutor.waitForTasksWithQuickFail(true); + + + ScanRuntime.logScanInfo(); + ScanRuntime.buildComponentSdkInfo(); + + long costTime = System.currentTimeMillis() - startTime; + logger.warn((getName() + "scan code costed " + costTime + "ms")); + + startTime = System.currentTimeMillis(); + + String injectFile = bytecodeWeaver.getInjectClassFile(); + if (injectFile != null) { + logger.warn(" inject file find : file = " + injectFile); + CodeInjectProcessor codeInjectProcessor = new CodeInjectProcessor(); + codeInjectProcessor.injectCode(injectFile); + logger.warn("inject Code end"); + } + + long injectCostTime = System.currentTimeMillis() - startTime; + logger.warn((getName() + " inject code costed " + injectCostTime + "ms")); + } + + private void transformSingleFile(final File inputFile, final File outputFile, final String srcBaseDir) { + waitableExecutor.execute(() -> { + bytecodeWeaver.weaveSingleClassToFile(inputFile, outputFile, srcBaseDir); + return null; + }); + } + + private void transformDir(final File inputDir, final File outputDir) throws IOException { + if (emptyRun) { + FileUtils.copyDirectory(inputDir, outputDir); + return; + } + final String inputDirPath = inputDir.getAbsolutePath(); + final String outputDirPath = outputDir.getAbsolutePath(); + if (inputDir.isDirectory()) { + for (final File file : com.android.utils.FileUtils.getAllFiles(inputDir)) { + waitableExecutor.execute(() -> { + String filePath = file.getAbsolutePath(); + File outputFile = new File(filePath.replace(inputDirPath, outputDirPath)); + bytecodeWeaver.weaveSingleClassToFile(file, outputFile, inputDirPath); + return null; + }); + } + } + } + + private void transformJar(final File srcJar, final File destJar, Status status) { + waitableExecutor.execute(() -> { + if (emptyRun) { + FileUtils.copyFile(srcJar, destJar); + return null; + } + bytecodeWeaver.weaveJar(srcJar, destJar); + return null; + }); + } + + private void cleanDexBuilderFolder(File dest) { + waitableExecutor.execute(() -> { + try { + String dexBuilderDir = replaceLastPart(dest.getAbsolutePath(), getName(), "dexBuilder"); + //intermediates/transforms/dexBuilder/debug + File file = new File(dexBuilderDir).getParentFile(); + project.getLogger().warn("clean dexBuilder folder = " + file.getAbsolutePath()); + if (file.exists() && file.isDirectory()) { + com.android.utils.FileUtils.deleteDirectoryContents(file); + } + } catch (Exception e) { + e.printStackTrace(); + } + return null; + }); + } + + private String replaceLastPart(String originString, String replacement, String toreplace) { + int start = originString.lastIndexOf(replacement); + StringBuilder builder = new StringBuilder(); + builder.append(originString.substring(0, start)); + builder.append(toreplace); + builder.append(originString.substring(start + replacement.length())); + return builder.toString(); + } + + @Override + public boolean isCacheable() { + return true; + } + + protected RunVariant getRunVariant() { + return RunVariant.ALWAYS; + } + + protected boolean inDuplcatedClassSafeMode() { + return false; + } + +} diff --git a/component-plugin/src/main/groovy/com/plugin/component/transform/ScanCodeWeaver2.java b/component-plugin/src/main/groovy/com/plugin/component/transform/ScanCodeWeaver2.java new file mode 100644 index 0000000..e5614e3 --- /dev/null +++ b/component-plugin/src/main/groovy/com/plugin/component/transform/ScanCodeWeaver2.java @@ -0,0 +1,146 @@ +package com.plugin.component.transform; + +import com.quinn.hunter.transform.asm.ExtendClassWriter; +import com.quinn.hunter.transform.asm.IWeaver; + +import org.apache.commons.io.FileUtils; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.attribute.FileTime; +import java.util.Enumeration; +import java.util.zip.CRC32; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipOutputStream; + +/** + * author : linzheng + * e-mail : linzheng@corp.netease.com + * time : 2019/10/18 + * desc : + * version: 1.0 + */ +public class ScanCodeWeaver2 implements IWeaver { + + private static final FileTime ZERO = FileTime.fromMillis(0); + private static final String FILE_SEP = File.separator; + + private String injectClassFile; + + protected ClassLoader classLoader; + + public ScanCodeWeaver2() { + } + + + public String getInjectClassFile() { + return injectClassFile; + } + + public final void weaveJar(File inputJar, File outputJar) throws IOException { + ZipFile inputZip = new ZipFile(inputJar); + ZipOutputStream outputZip = new ZipOutputStream(new BufferedOutputStream(java.nio.file.Files.newOutputStream(outputJar.toPath()))); + Enumeration inEntries = inputZip.entries(); + String outputPath = outputJar.getAbsolutePath(); + while (inEntries.hasMoreElements()) { + ZipEntry entry = inEntries.nextElement(); + InputStream originalFile = new BufferedInputStream(inputZip.getInputStream(entry)); + ZipEntry outEntry = new ZipEntry(entry.getName()); + byte[] newEntryContent; + // seperator of entry name is always '/', even in windows + String className = outEntry.getName().replace("/", "."); + + beforeWeaveClass(outputPath, className); + + if (!isWeavableClass(className)) { + newEntryContent = org.apache.commons.io.IOUtils.toByteArray(originalFile); + } else { + newEntryContent = weaveSingleClassToByteArray(originalFile); + } + CRC32 crc32 = new CRC32(); + crc32.update(newEntryContent); + outEntry.setCrc(crc32.getValue()); + outEntry.setMethod(ZipEntry.STORED); + outEntry.setSize(newEntryContent.length); + outEntry.setCompressedSize(newEntryContent.length); + outEntry.setLastAccessTime(ZERO); + outEntry.setLastModifiedTime(ZERO); + outEntry.setCreationTime(ZERO); + outputZip.putNextEntry(outEntry); + outputZip.write(newEntryContent); + outputZip.closeEntry(); + } + outputZip.flush(); + outputZip.close(); + } + + public final void weaveSingleClassToFile(File inputFile, File outputFile, String inputBaseDir) throws IOException { + if (!inputBaseDir.endsWith(FILE_SEP)) inputBaseDir = inputBaseDir + FILE_SEP; + String className = inputFile.getAbsolutePath().replace(inputBaseDir, "").replace(FILE_SEP, "."); + + beforeWeaveClass(outputFile.getAbsolutePath(), className); + + if (isWeavableClass(className)) { + FileUtils.touch(outputFile); + InputStream inputStream = new FileInputStream(inputFile); + byte[] bytes = weaveSingleClassToByteArray(inputStream); + FileOutputStream fos = new FileOutputStream(outputFile); + fos.write(bytes); + fos.close(); + inputStream.close(); + } else { + if (inputFile.isFile()) { + FileUtils.touch(outputFile); + FileUtils.copyFile(inputFile, outputFile); + } + } + } + + public final void setClassLoader(ClassLoader classLoader) { + this.classLoader = classLoader; + } + + @Override + public byte[] weaveSingleClassToByteArray(InputStream inputStream) throws IOException { + ClassReader classReader = new ClassReader(inputStream); + ClassWriter classWriter = new ExtendClassWriter(classLoader, ClassWriter.COMPUTE_MAXS); + ClassVisitor classWriterWrapper = wrapClassWriter(classWriter); + classReader.accept(classWriterWrapper, ClassReader.EXPAND_FRAMES); + return classWriter.toByteArray(); + } + + + private static final String sComponentManagerPath = "com.plugin.component.ComponentManager"; + + + public void beforeWeaveClass(String outputFile, String className) { + if (injectClassFile == null && className != null && className.contains(sComponentManagerPath)) { + System.out.println("find class ComponentManager : file is : " + outputFile); + injectClassFile = outputFile; + } + } + + public void setExtension(Object extension) { + + } + + protected ClassVisitor wrapClassWriter(ClassWriter classWriter) { + return new ScanCodeAdapter(classWriter); + } + + @Override + public boolean isWeavableClass(String fullQualifiedClassName) { + return fullQualifiedClassName.endsWith(".class") && !fullQualifiedClassName.contains("R$") && !fullQualifiedClassName.contains("R.class") && !fullQualifiedClassName.contains("BuildConfig.class"); + } + + +} diff --git a/settings.gradle b/settings.gradle index fb976d3..589da9a 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,8 +1,8 @@ //发布调试 core -//include ':component-core' +include ':component-core' // //发布插件 -//include ':component-plugin' +include ':component-plugin' //调试插件 //includeBuild './component-plugin' From f16da08fafa540c10965fbf88eb553d7e4620d28 Mon Sep 17 00:00:00 2001 From: "linzheng\\n8383" Date: Tue, 22 Oct 2019 16:04:01 +0800 Subject: [PATCH 2/5] =?UTF-8?q?=E5=B0=9D=E8=AF=95=E5=90=88=E5=B9=B6Transfo?= =?UTF-8?q?rm?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugin/component/ComponentPlugin.groovy | 7 +++---- ...ransform2.java => ComponentTransform.java} | 10 +++++----- ...nCodeWeaver2.java => ComponentWeaver.java} | 19 +++++++++++++++---- .../transform/ScanCodeAdapter.groovy | 5 +++++ 4 files changed, 28 insertions(+), 13 deletions(-) rename component-plugin/src/main/groovy/com/plugin/component/transform/{ScanCodeTransform2.java => ComponentTransform.java} (97%) rename component-plugin/src/main/groovy/com/plugin/component/transform/{ScanCodeWeaver2.java => ComponentWeaver.java} (85%) diff --git a/component-plugin/src/main/groovy/com/plugin/component/ComponentPlugin.groovy b/component-plugin/src/main/groovy/com/plugin/component/ComponentPlugin.groovy index c459a00..a4b4d5e 100644 --- a/component-plugin/src/main/groovy/com/plugin/component/ComponentPlugin.groovy +++ b/component-plugin/src/main/groovy/com/plugin/component/ComponentPlugin.groovy @@ -9,9 +9,8 @@ import com.plugin.component.extension.module.ProjectInfo import com.plugin.component.extension.option.debug.DebugDependenciesOption import com.plugin.component.extension.option.publication.PublicationDependenciesOption import com.plugin.component.extension.option.publication.PublicationOption -import com.plugin.component.transform.InjectCodeTransform import com.plugin.component.transform.MethodCostTransform -import com.plugin.component.transform.ScanCodeTransform2 +import com.plugin.component.transform.ComponentTransform import com.plugin.component.utils.JarUtil import com.plugin.component.utils.ProjectUtil import com.plugin.component.utils.PublicationUtil @@ -228,9 +227,9 @@ class ComponentPlugin implements Plugin { if (it instanceof AppPlugin) { if (projectInfo.isDebugModule() || projectInfo.isMainModule()) { Logger.buildOutput("plugin is AppPlugin and isDebugModule or isMainModule") - Logger.buildOutput("registerTransform", "ScanCodeTransform2") + Logger.buildOutput("registerTransform", "ComponentTransform") // Logger.buildOutput("registerTransform", "InjectCodeTransform") - childProject.extensions.findByType(BaseExtension.class).registerTransform(new ScanCodeTransform2(childProject)) + childProject.extensions.findByType(BaseExtension.class).registerTransform(new ComponentTransform(childProject)) // childProject.extensions.findByType(BaseExtension.class).registerTransform(new InjectCodeTransform(childProject)) if (Runtimes.enbaleMethodCost()) { Logger.buildOutput("registerTransform", "MethodCostTransform") diff --git a/component-plugin/src/main/groovy/com/plugin/component/transform/ScanCodeTransform2.java b/component-plugin/src/main/groovy/com/plugin/component/transform/ComponentTransform.java similarity index 97% rename from component-plugin/src/main/groovy/com/plugin/component/transform/ScanCodeTransform2.java rename to component-plugin/src/main/groovy/com/plugin/component/transform/ComponentTransform.java index f791777..2127fe9 100644 --- a/component-plugin/src/main/groovy/com/plugin/component/transform/ScanCodeTransform2.java +++ b/component-plugin/src/main/groovy/com/plugin/component/transform/ComponentTransform.java @@ -36,7 +36,7 @@ import java.util.Set; * desc : * version: 1.0 */ -public class ScanCodeTransform2 extends Transform { +public class ComponentTransform extends Transform { private final Logger logger; @@ -49,15 +49,15 @@ public class ScanCodeTransform2 extends Transform { } private Project project; - protected ScanCodeWeaver2 bytecodeWeaver; + protected ComponentWeaver bytecodeWeaver; private WaitableExecutor waitableExecutor; private boolean emptyRun = false; - public ScanCodeTransform2(Project project) { + public ComponentTransform(Project project) { this.project = project; this.logger = project.getLogger(); this.waitableExecutor = WaitableExecutor.useGlobalSharedThreadPool(); - this.bytecodeWeaver = new ScanCodeWeaver2(); + this.bytecodeWeaver = new ComponentWeaver(); } @Override @@ -77,7 +77,7 @@ public class ScanCodeTransform2 extends Transform { @Override public boolean isIncremental() { - return false; + return true; } diff --git a/component-plugin/src/main/groovy/com/plugin/component/transform/ScanCodeWeaver2.java b/component-plugin/src/main/groovy/com/plugin/component/transform/ComponentWeaver.java similarity index 85% rename from component-plugin/src/main/groovy/com/plugin/component/transform/ScanCodeWeaver2.java rename to component-plugin/src/main/groovy/com/plugin/component/transform/ComponentWeaver.java index e5614e3..b90f0f2 100644 --- a/component-plugin/src/main/groovy/com/plugin/component/transform/ScanCodeWeaver2.java +++ b/component-plugin/src/main/groovy/com/plugin/component/transform/ComponentWeaver.java @@ -29,7 +29,7 @@ import java.util.zip.ZipOutputStream; * desc : * version: 1.0 */ -public class ScanCodeWeaver2 implements IWeaver { +public class ComponentWeaver implements IWeaver { private static final FileTime ZERO = FileTime.fromMillis(0); private static final String FILE_SEP = File.separator; @@ -38,7 +38,7 @@ public class ScanCodeWeaver2 implements IWeaver { protected ClassLoader classLoader; - public ScanCodeWeaver2() { + public ComponentWeaver() { } @@ -47,6 +47,7 @@ public class ScanCodeWeaver2 implements IWeaver { } public final void weaveJar(File inputJar, File outputJar) throws IOException { + String filePath = inputJar.getAbsolutePath(); ZipFile inputZip = new ZipFile(inputJar); ZipOutputStream outputZip = new ZipOutputStream(new BufferedOutputStream(java.nio.file.Files.newOutputStream(outputJar.toPath()))); Enumeration inEntries = inputZip.entries(); @@ -64,7 +65,7 @@ public class ScanCodeWeaver2 implements IWeaver { if (!isWeavableClass(className)) { newEntryContent = org.apache.commons.io.IOUtils.toByteArray(originalFile); } else { - newEntryContent = weaveSingleClassToByteArray(originalFile); + newEntryContent = weaveSingleClassToByteArray(filePath, originalFile); } CRC32 crc32 = new CRC32(); crc32.update(newEntryContent); @@ -87,12 +88,13 @@ public class ScanCodeWeaver2 implements IWeaver { if (!inputBaseDir.endsWith(FILE_SEP)) inputBaseDir = inputBaseDir + FILE_SEP; String className = inputFile.getAbsolutePath().replace(inputBaseDir, "").replace(FILE_SEP, "."); + String filePath = inputFile.getAbsolutePath(); beforeWeaveClass(outputFile.getAbsolutePath(), className); if (isWeavableClass(className)) { FileUtils.touch(outputFile); InputStream inputStream = new FileInputStream(inputFile); - byte[] bytes = weaveSingleClassToByteArray(inputStream); + byte[] bytes = weaveSingleClassToByteArray(filePath, inputStream); FileOutputStream fos = new FileOutputStream(outputFile); fos.write(bytes); fos.close(); @@ -118,6 +120,15 @@ public class ScanCodeWeaver2 implements IWeaver { return classWriter.toByteArray(); } + public byte[] weaveSingleClassToByteArray(String filePath, InputStream inputStream) throws IOException { + ClassReader classReader = new ClassReader(inputStream); + ClassWriter classWriter = new ExtendClassWriter(classLoader, ClassWriter.COMPUTE_MAXS); + ScanCodeAdapter classWriterWrapper = new ScanCodeAdapter(classWriter); + classWriterWrapper.setFilePath(filePath); + classReader.accept(classWriterWrapper, ClassReader.EXPAND_FRAMES); + return classWriter.toByteArray(); + } + private static final String sComponentManagerPath = "com.plugin.component.ComponentManager"; diff --git a/component-plugin/src/main/groovy/com/plugin/component/transform/ScanCodeAdapter.groovy b/component-plugin/src/main/groovy/com/plugin/component/transform/ScanCodeAdapter.groovy index baafa9f..c5560f8 100644 --- a/component-plugin/src/main/groovy/com/plugin/component/transform/ScanCodeAdapter.groovy +++ b/component-plugin/src/main/groovy/com/plugin/component/transform/ScanCodeAdapter.groovy @@ -13,11 +13,16 @@ import org.objectweb.asm.Type class ScanCodeAdapter extends ClassVisitor { private String className + private String filePath ScanCodeAdapter(ClassVisitor classVisitor) { super(Opcodes.ASM7, classVisitor) } + void setFilePath(String filePath) { + this.filePath = filePath + } + @Override void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { className = name From 07338e18f88d4ee6571cdbca1d49749951bffbee Mon Sep 17 00:00:00 2001 From: "linzheng\\n8383" Date: Wed, 23 Oct 2019 16:48:11 +0800 Subject: [PATCH 3/5] =?UTF-8?q?=E5=90=88=E5=B9=B6Transform=20=EF=BC=8C=20?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=A2=9E=E9=87=8F=E7=BC=96=E8=AF=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugin/component/ComponentPlugin.groovy | 9 +- .../com/plugin/component/Constants.groovy | 1 + .../transform/CodeInjectProcessor.java | 42 +- .../transform/ComponentTransform.java | 65 +- .../component/transform/ComponentWeaver.java | 22 +- .../transform/ScanCodeAdapter.groovy | 6 +- .../transform/info/ScanRuntime.groovy | 75 ++ .../transform/info/ScanSummaryInfo.java | 35 + .../component/utils/CacheDiskUtils.java | 694 ++++++++++++++++++ 9 files changed, 901 insertions(+), 48 deletions(-) create mode 100644 component-plugin/src/main/groovy/com/plugin/component/transform/info/ScanSummaryInfo.java create mode 100644 component-plugin/src/main/groovy/com/plugin/component/utils/CacheDiskUtils.java diff --git a/component-plugin/src/main/groovy/com/plugin/component/ComponentPlugin.groovy b/component-plugin/src/main/groovy/com/plugin/component/ComponentPlugin.groovy index 6fe8273..2c4841b 100644 --- a/component-plugin/src/main/groovy/com/plugin/component/ComponentPlugin.groovy +++ b/component-plugin/src/main/groovy/com/plugin/component/ComponentPlugin.groovy @@ -9,6 +9,7 @@ import com.plugin.component.extension.module.ProjectInfo import com.plugin.component.extension.option.debug.DebugDependenciesOption import com.plugin.component.extension.option.publication.PublicationDependenciesOption import com.plugin.component.extension.option.publication.PublicationOption +import com.plugin.component.transform.ComponentTransform import com.plugin.component.transform.InjectCodeTransform import com.plugin.component.transform.ScanCodeTransform @@ -236,10 +237,10 @@ class ComponentPlugin implements Plugin { if (it instanceof AppPlugin) { if (projectInfo.isDebugModule() || projectInfo.isMainModule()) { Logger.buildOutput("plugin is AppPlugin") - Logger.buildOutput("registerTransform", "ScanCodeTransform") - Logger.buildOutput("registerTransform", "InjectCodeTransform") - childProject.extensions.findByType(BaseExtension.class).registerTransform(new ScanCodeTransform(childProject)) - childProject.extensions.findByType(BaseExtension.class).registerTransform(new InjectCodeTransform(childProject)) + Logger.buildOutput("registerTransform", "ComponentTransform") +// Logger.buildOutput("registerTransform", "InjectCodeTransform") +// childProject.extensions.findByType(BaseExtension.class).registerTransform(new InjectCodeTransform(childProject)) + childProject.extensions.findByType(BaseExtension.class).registerTransform(new ComponentTransform(childProject)) } } Logger.buildOutput("=====> project[" + childProject.name + "]注入插件 <=====") diff --git a/component-plugin/src/main/groovy/com/plugin/component/Constants.groovy b/component-plugin/src/main/groovy/com/plugin/component/Constants.groovy index 426b1e9..6de5c79 100644 --- a/component-plugin/src/main/groovy/com/plugin/component/Constants.groovy +++ b/component-plugin/src/main/groovy/com/plugin/component/Constants.groovy @@ -24,6 +24,7 @@ class Constants { public static String PUBLISHING = 'publishing' public static String LOCAL_PROPERTIES = "local.properties" public static String CLEAN = 'clean' + public static String PLUGIN_CACHE = "scan_cache" //file public static String DEFAULT_MAIN_MODULE_NAME = "app" diff --git a/component-plugin/src/main/groovy/com/plugin/component/transform/CodeInjectProcessor.java b/component-plugin/src/main/groovy/com/plugin/component/transform/CodeInjectProcessor.java index 3ed36db..71ee1dc 100644 --- a/component-plugin/src/main/groovy/com/plugin/component/transform/CodeInjectProcessor.java +++ b/component-plugin/src/main/groovy/com/plugin/component/transform/CodeInjectProcessor.java @@ -33,14 +33,12 @@ public class CodeInjectProcessor { private static final String FILE_SEP = File.separator; - public void injectCode(String filePath) { + public void injectCode(String inputPath, String outputPath) { try { - if (filePath != null && filePath.endsWith(".jar")) { - File file = new File(filePath); - weaveJar(file); - } else if (filePath != null && filePath.endsWith(".class")) { - File file = new File(filePath); - weaveSingleClassToFile(file); + if (inputPath != null && inputPath.endsWith(".jar")) { + weaveJar(inputPath, outputPath); + } else if (inputPath != null && inputPath.endsWith(".class")) { + weaveSingleClassToFile(inputPath, outputPath); } } catch (Exception e) { @@ -55,8 +53,13 @@ public class CodeInjectProcessor { } - public final void weaveSingleClassToFile(File inputFile) throws IOException { - File outputFile = new File(inputFile.getParent(), inputFile.getName() + ".temp"); + public final void weaveSingleClassToFile(String inputPath, String outputPath) throws IOException { + File inputFile = new File(inputPath); + File outputFile = new File(outputPath); + if (outputFile.exists()) { + outputFile.delete(); + } + FileUtils.touch(outputFile); InputStream inputStream = new FileInputStream(inputFile); byte[] bytes = doGenerateCode(inputStream); @@ -65,27 +68,22 @@ public class CodeInjectProcessor { fos.write(bytes); fos.close(); inputStream.close(); - if (inputFile.exists()) { - inputFile.delete(); - } - outputFile.renameTo(inputFile); } } - public final void weaveJar(File inputJar) throws IOException { + public final void weaveJar(String inputPath, String outputPath) throws IOException { - File outputJar = new File(inputJar.getParent(), inputJar.getName() + ".temp"); + File outputJar = new File(outputPath); if (outputJar.exists()) { outputJar.delete(); } - ZipFile inputZip = new ZipFile(inputJar); + ZipFile inputZip = new ZipFile(new File(inputPath)); ZipOutputStream outputZip = new ZipOutputStream(new BufferedOutputStream(java.nio.file.Files.newOutputStream(outputJar.toPath()))); Enumeration inEntries = inputZip.entries(); - String outputPath = outputJar.getAbsolutePath(); while (inEntries.hasMoreElements()) { ZipEntry entry = inEntries.nextElement(); InputStream originalFile = new BufferedInputStream(inputZip.getInputStream(entry)); @@ -115,17 +113,7 @@ public class CodeInjectProcessor { } outputZip.flush(); outputZip.close(); - inputZip.close(); - - if (inputJar.exists()) { - boolean result = inputJar.delete(); - System.out.println("delete jar result = " + result); - } - boolean rename = outputJar.renameTo(inputJar); - System.out.println("rename jar result = " + rename); - - } diff --git a/component-plugin/src/main/groovy/com/plugin/component/transform/ComponentTransform.java b/component-plugin/src/main/groovy/com/plugin/component/transform/ComponentTransform.java index 2127fe9..dfef2ee 100644 --- a/component-plugin/src/main/groovy/com/plugin/component/transform/ComponentTransform.java +++ b/component-plugin/src/main/groovy/com/plugin/component/transform/ComponentTransform.java @@ -13,13 +13,18 @@ import com.android.build.api.transform.TransformOutputProvider; import com.android.build.gradle.internal.pipeline.TransformManager; import com.android.ide.common.internal.WaitableExecutor; import com.google.common.io.Files; +import com.google.gson.Gson; +import com.plugin.component.Constants; import com.plugin.component.transform.info.ScanRuntime; +import com.plugin.component.transform.info.ScanSummaryInfo; +import com.plugin.component.utils.CacheDiskUtils; import com.quinn.hunter.transform.RunVariant; import com.quinn.hunter.transform.asm.ClassLoaderHelper; import org.apache.commons.io.FileUtils; import org.gradle.api.Project; import org.gradle.api.logging.Logger; +import org.jetbrains.annotations.NotNull; import java.io.File; import java.io.IOException; @@ -105,6 +110,9 @@ public class ComponentTransform extends Transform { for (TransformInput input : inputs) { for (JarInput jarInput : input.getJarInputs()) { Status status = jarInput.getStatus(); + + String filePath = jarInput.getFile().getAbsolutePath(); + File dest = outputProvider.getContentLocation(jarInput.getFile().getAbsolutePath(), jarInput.getContentTypes(), jarInput.getScopes(), Format.JAR); if (isIncremental && !emptyRun) { switch (status) { @@ -118,6 +126,7 @@ public class ComponentTransform extends Transform { if (dest.exists()) { FileUtils.forceDelete(dest); } + ScanRuntime.removedFile(filePath); break; } } else { @@ -150,6 +159,7 @@ public class ComponentTransform extends Transform { //noinspection ResultOfMethodCallIgnored destFile.delete(); } + ScanRuntime.removedFile(inputFile.getAbsolutePath()); break; case ADDED: case CHANGED: @@ -172,25 +182,66 @@ public class ComponentTransform extends Transform { } waitableExecutor.waitForTasksWithQuickFail(true); + ScanSummaryInfo cacheSummary = readPluginCache(); // 读取缓存 - ScanRuntime.logScanInfo(); + ScanSummaryInfo scanSummaryInfo = ScanRuntime.updateSummaryInfo(cacheSummary); // 整理本次扫码结果,返回最新的模块结构 ScanRuntime.buildComponentSdkInfo(); + ScanRuntime.logScanInfo(); + + updatePluginCache(cacheSummary); // 保存缓存 long costTime = System.currentTimeMillis() - startTime; logger.warn((getName() + "scan code costed " + costTime + "ms")); - startTime = System.currentTimeMillis(); + injectCode(scanSummaryInfo); + } - String injectFile = bytecodeWeaver.getInjectClassFile(); - if (injectFile != null) { - logger.warn(" inject file find : file = " + injectFile); - CodeInjectProcessor codeInjectProcessor = new CodeInjectProcessor(); - codeInjectProcessor.injectCode(injectFile); + private void injectCode(@NotNull ScanSummaryInfo scanSummaryInfo) { + long startTime = System.currentTimeMillis(); + + String injectInputPath = scanSummaryInfo.inputFilePath; // 输入文件 + String injectOutputPath = scanSummaryInfo.outputFilePath; // 输出文件 + if (injectInputPath != null) { + logger.warn(" inject file find : file = " + injectInputPath); + try { + CodeInjectProcessor codeInjectProcessor = new CodeInjectProcessor(); + codeInjectProcessor.injectCode(injectInputPath, injectOutputPath); + } catch (Exception e) { + e.printStackTrace(); + logger.warn("inject Code error"); + } logger.warn("inject Code end"); } long injectCostTime = System.currentTimeMillis() - startTime; logger.warn((getName() + " inject code costed " + injectCostTime + "ms")); + ScanRuntime.clearScanInfo(); + ScanRuntime.clearSummaryInfo(); + } + + private void updatePluginCache(ScanSummaryInfo cacheSummary) { + try { + String json = new Gson().toJson(cacheSummary); + CacheDiskUtils.getInstance(project.getBuildDir()).put(Constants.PLUGIN_CACHE, json); + logger.warn((getName() + "save cache success")); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @NotNull + private ScanSummaryInfo readPluginCache() { + ScanSummaryInfo cacheSummary = null; + try { + String json = CacheDiskUtils.getInstance(project.getBuildDir()).getString(Constants.PLUGIN_CACHE); + cacheSummary = new Gson().fromJson(json, ScanSummaryInfo.class); + } catch (Exception e) { + e.printStackTrace(); + } + if (cacheSummary == null) { + cacheSummary = new ScanSummaryInfo(); + } + return cacheSummary; } private void transformSingleFile(final File inputFile, final File outputFile, final String srcBaseDir) { diff --git a/component-plugin/src/main/groovy/com/plugin/component/transform/ComponentWeaver.java b/component-plugin/src/main/groovy/com/plugin/component/transform/ComponentWeaver.java index b90f0f2..dc194c7 100644 --- a/component-plugin/src/main/groovy/com/plugin/component/transform/ComponentWeaver.java +++ b/component-plugin/src/main/groovy/com/plugin/component/transform/ComponentWeaver.java @@ -1,5 +1,6 @@ package com.plugin.component.transform; +import com.plugin.component.transform.info.ScanRuntime; import com.quinn.hunter.transform.asm.ExtendClassWriter; import com.quinn.hunter.transform.asm.IWeaver; @@ -47,11 +48,11 @@ public class ComponentWeaver implements IWeaver { } public final void weaveJar(File inputJar, File outputJar) throws IOException { - String filePath = inputJar.getAbsolutePath(); + String inputPath = inputJar.getAbsolutePath(); + String outputPath = outputJar.getAbsolutePath(); ZipFile inputZip = new ZipFile(inputJar); ZipOutputStream outputZip = new ZipOutputStream(new BufferedOutputStream(java.nio.file.Files.newOutputStream(outputJar.toPath()))); Enumeration inEntries = inputZip.entries(); - String outputPath = outputJar.getAbsolutePath(); while (inEntries.hasMoreElements()) { ZipEntry entry = inEntries.nextElement(); InputStream originalFile = new BufferedInputStream(inputZip.getInputStream(entry)); @@ -60,12 +61,12 @@ public class ComponentWeaver implements IWeaver { // seperator of entry name is always '/', even in windows String className = outEntry.getName().replace("/", "."); - beforeWeaveClass(outputPath, className); + beforeWeaveClass(inputPath, outputPath, className); if (!isWeavableClass(className)) { newEntryContent = org.apache.commons.io.IOUtils.toByteArray(originalFile); } else { - newEntryContent = weaveSingleClassToByteArray(filePath, originalFile); + newEntryContent = weaveSingleClassToByteArray(inputPath, originalFile); } CRC32 crc32 = new CRC32(); crc32.update(newEntryContent); @@ -88,13 +89,15 @@ public class ComponentWeaver implements IWeaver { if (!inputBaseDir.endsWith(FILE_SEP)) inputBaseDir = inputBaseDir + FILE_SEP; String className = inputFile.getAbsolutePath().replace(inputBaseDir, "").replace(FILE_SEP, "."); - String filePath = inputFile.getAbsolutePath(); - beforeWeaveClass(outputFile.getAbsolutePath(), className); + String inputPath = inputFile.getAbsolutePath(); + String outputPath = outputFile.getAbsolutePath(); + + beforeWeaveClass(inputPath, outputPath, className); if (isWeavableClass(className)) { FileUtils.touch(outputFile); InputStream inputStream = new FileInputStream(inputFile); - byte[] bytes = weaveSingleClassToByteArray(filePath, inputStream); + byte[] bytes = weaveSingleClassToByteArray(inputPath, inputStream); FileOutputStream fos = new FileOutputStream(outputFile); fos.write(bytes); fos.close(); @@ -133,10 +136,13 @@ public class ComponentWeaver implements IWeaver { private static final String sComponentManagerPath = "com.plugin.component.ComponentManager"; - public void beforeWeaveClass(String outputFile, String className) { + public void beforeWeaveClass(String inputPath, String outputFile, String className) { if (injectClassFile == null && className != null && className.contains(sComponentManagerPath)) { System.out.println("find class ComponentManager : file is : " + outputFile); injectClassFile = outputFile; + ScanRuntime.getsSummaryInfo().inputFilePath = inputPath; + ScanRuntime.getsSummaryInfo().outputFilePath = outputFile; + } } diff --git a/component-plugin/src/main/groovy/com/plugin/component/transform/ScanCodeAdapter.groovy b/component-plugin/src/main/groovy/com/plugin/component/transform/ScanCodeAdapter.groovy index b2cdd2e..2fc9486 100644 --- a/component-plugin/src/main/groovy/com/plugin/component/transform/ScanCodeAdapter.groovy +++ b/component-plugin/src/main/groovy/com/plugin/component/transform/ScanCodeAdapter.groovy @@ -73,11 +73,13 @@ class ScanCodeAdapter extends ClassVisitor { @Override void visitEnd() { if (scanComponentInfo != null) { - ScanRuntime.addComponentInfo(scanComponentInfo) +// ScanRuntime.addComponentInfo(scanComponentInfo) + ScanRuntime.addComponentInfo(filePath, scanComponentInfo) scanComponentInfo = null } if (scanSdkInfo != null) { - ScanRuntime.addSdkInfo(scanSdkInfo) +// ScanRuntime.addSdkInfo(scanSdkInfo) + ScanRuntime.addSdkInfo(filePath, scanSdkInfo) scanSdkInfo = null } super.visitEnd() diff --git a/component-plugin/src/main/groovy/com/plugin/component/transform/info/ScanRuntime.groovy b/component-plugin/src/main/groovy/com/plugin/component/transform/info/ScanRuntime.groovy index dc75623..ab0b458 100644 --- a/component-plugin/src/main/groovy/com/plugin/component/transform/info/ScanRuntime.groovy +++ b/component-plugin/src/main/groovy/com/plugin/component/transform/info/ScanRuntime.groovy @@ -10,6 +10,9 @@ class ScanRuntime { static List sSdkInfo = new ArrayList<>() static List componentSdkInfoList = new ArrayList<>() + static ScanSummaryInfo sSummaryInfo = new ScanSummaryInfo() + + static void addComponentInfo(@NonNull ScanComponentInfo scanComponentInfo) { sComponentInfo.add(scanComponentInfo) } @@ -19,10 +22,12 @@ class ScanRuntime { } static void logScanInfo() { + Logger.buildOutput("Dodge sdk info size = " + sSdkInfo.size()) for (ScanSdkInfo sdkInfo : sSdkInfo) { Logger.buildOutput(sdkInfo.toString()) } + Logger.buildOutput("Dodge Component Info size = " + sComponentInfo.size()) for (ScanComponentInfo scanComponentInfo : sComponentInfo) { Logger.buildOutput(scanComponentInfo.toString()) } @@ -68,4 +73,74 @@ class ScanRuntime { static List getComponentSdkInfoList() { return componentSdkInfoList } + + + static void addSdkInfo(String filePath, @NonNull ScanSdkInfo scanSdkInfo) { + Set set = sSummaryInfo.updateSdkMap.get(filePath) + if (set == null) { + set = new HashSet<>() + sSummaryInfo.updateSdkMap.put(filePath, set) + } + set.add(scanSdkInfo) + } + + static void addComponentInfo(String filePath, @NonNull ScanComponentInfo componentInfo) { + Set set = sSummaryInfo.updateComponentMap.get(filePath) + if (set == null) { + set = new HashSet<>() + sSummaryInfo.updateComponentMap.put(filePath, set) + } + set.add(componentInfo) + } + + + static void removedFile(String path) { + sSummaryInfo.removedFileSet.add(path) + } + + static ScanSummaryInfo updateSummaryInfo(ScanSummaryInfo cacheSummary) { + if (sSummaryInfo.inputFilePath != null) { + cacheSummary.inputFilePath = sSummaryInfo.inputFilePath + } + + if (sSummaryInfo.outputFilePath != null) { + cacheSummary.outputFilePath = sSummaryInfo.outputFilePath + } + + sSummaryInfo.removedFileSet.each { + cacheSummary.updateSdkMap.remove(it) + cacheSummary.updateComponentMap.remove(it) + } + + sSummaryInfo.updateSdkMap.each { + cacheSummary.updateSdkMap.put(it.key, it.value) + } + + sSummaryInfo.updateComponentMap.each { + cacheSummary.updateComponentMap.put(it.key, it.value) + } + + cacheSummary.updateSdkMap.each { + if (it != null && !it.value.isEmpty()) { + sSdkInfo.addAll(it.value) + } + } + + cacheSummary.updateComponentMap.each { + if (it != null && !it.value.isEmpty()) { + sComponentInfo.addAll(it.value) + } + } + + return cacheSummary + + } + + static void clearSummaryInfo() { + // TODO clear data + sSummaryInfo = new ScanSummaryInfo() + + } + + } diff --git a/component-plugin/src/main/groovy/com/plugin/component/transform/info/ScanSummaryInfo.java b/component-plugin/src/main/groovy/com/plugin/component/transform/info/ScanSummaryInfo.java new file mode 100644 index 0000000..81e06bb --- /dev/null +++ b/component-plugin/src/main/groovy/com/plugin/component/transform/info/ScanSummaryInfo.java @@ -0,0 +1,35 @@ +package com.plugin.component.transform.info; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * author : linzheng + * e-mail : linzheng@corp.netease.com + * time : 2019/10/22 + * desc : + * version: 1.0 + */ +public class ScanSummaryInfo { + + + public String inputFilePath; + + public String outputFilePath; + + Map> updateSdkMap = new HashMap<>(); + + Map> updateComponentMap = new HashMap<>(); + + + Set removedFileSet = new HashSet<>(); + + + + + + + +} \ No newline at end of file diff --git a/component-plugin/src/main/groovy/com/plugin/component/utils/CacheDiskUtils.java b/component-plugin/src/main/groovy/com/plugin/component/utils/CacheDiskUtils.java new file mode 100644 index 0000000..6b9f90b --- /dev/null +++ b/component-plugin/src/main/groovy/com/plugin/component/utils/CacheDiskUtils.java @@ -0,0 +1,694 @@ +package com.plugin.component.utils; + + +import com.android.annotations.NonNull; +import com.android.ddmlib.Log; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.FilenameFilter; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.RandomAccessFile; +import java.io.Serializable; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.util.Collections; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +/** + *
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2017/05/24
+ *     desc  : utils about disk cache
+ * 
+ */ +public final class CacheDiskUtils { + + private static final long DEFAULT_MAX_SIZE = Long.MAX_VALUE; + private static final int DEFAULT_MAX_COUNT = Integer.MAX_VALUE; + private static final String CACHE_PREFIX = "cdu_"; + private static final String TYPE_BYTE = "by_"; + private static final String TYPE_STRING = "st_"; + private static final String TYPE_JSON_OBJECT = "jo_"; + private static final String TYPE_JSON_ARRAY = "ja_"; + private static final String TYPE_BITMAP = "bi_"; + private static final String TYPE_DRAWABLE = "dr_"; + private static final String TYPE_PARCELABLE = "pa_"; + private static final String TYPE_SERIALIZABLE = "se_"; + + private static final Map CACHE_MAP = new HashMap<>(); + + private final String mCacheKey; + private final File mCacheDir; + private final long mMaxSize; + private final int mMaxCount; + private DiskCacheManager mDiskCacheManager; + + + /** + * Return the single {@link CacheDiskUtils} instance. + *

cache size: unlimited

+ *

cache count: unlimited

+ * + * @param cacheDir The directory of cache. + * @return the single {@link CacheDiskUtils} instance + */ + public static CacheDiskUtils getInstance(@NonNull final File cacheDir) { + return getInstance(cacheDir, DEFAULT_MAX_SIZE, DEFAULT_MAX_COUNT); + } + + /** + * Return the single {@link CacheDiskUtils} instance. + * + * @param cacheDir The directory of cache. + * @param maxSize The max size of cache, in bytes. + * @param maxCount The max count of cache. + * @return the single {@link CacheDiskUtils} instance + */ + public static CacheDiskUtils getInstance(@NonNull final File cacheDir, + final long maxSize, + final int maxCount) { + final String cacheKey = cacheDir.getAbsoluteFile() + "_" + maxSize + "_" + maxCount; + CacheDiskUtils cache = CACHE_MAP.get(cacheKey); + if (cache == null) { + synchronized (CacheDiskUtils.class) { + cache = CACHE_MAP.get(cacheKey); + if (cache == null) { + cache = new CacheDiskUtils(cacheKey, cacheDir, maxSize, maxCount); + CACHE_MAP.put(cacheKey, cache); + } + } + } + return cache; + } + + private CacheDiskUtils(final String cacheKey, + final File cacheDir, + final long maxSize, + final int maxCount) { + mCacheKey = cacheKey; + mCacheDir = cacheDir; + mMaxSize = maxSize; + mMaxCount = maxCount; + } + + private DiskCacheManager getDiskCacheManager() { + if (mCacheDir.exists()) { + if (mDiskCacheManager == null) { + mDiskCacheManager = new DiskCacheManager(mCacheDir, mMaxSize, mMaxCount); + } + } else { + if (mCacheDir.mkdirs()) { + mDiskCacheManager = new DiskCacheManager(mCacheDir, mMaxSize, mMaxCount); + } else { + Log.e("CacheDiskUtils", "can't make dirs in " + mCacheDir.getAbsolutePath()); + } + } + return mDiskCacheManager; + } + + @Override + public String toString() { + return mCacheKey + "@" + Integer.toHexString(hashCode()); + } + + /////////////////////////////////////////////////////////////////////////// + // about bytes + /////////////////////////////////////////////////////////////////////////// + + /** + * Put bytes in cache. + * + * @param key The key of cache. + * @param value The value of cache. + */ + public void put(@NonNull final String key, final byte[] value) { + put(key, value, -1); + } + + /** + * Put bytes in cache. + * + * @param key The key of cache. + * @param value The value of cache. + * @param saveTime The save time of cache, in seconds. + */ + public void put(@NonNull final String key, final byte[] value, final int saveTime) { + realPutBytes(TYPE_BYTE + key, value, saveTime); + } + + private void realPutBytes(final String key, byte[] value, int saveTime) { + if (value == null) return; + DiskCacheManager diskCacheManager = getDiskCacheManager(); + if (diskCacheManager == null) return; + if (saveTime >= 0) value = DiskCacheHelper.newByteArrayWithTime(saveTime, value); + File file = diskCacheManager.getFileBeforePut(key); + writeFileFromBytes(file, value); + diskCacheManager.updateModify(file); + diskCacheManager.put(file); + } + + + /** + * Return the bytes in cache. + * + * @param key The key of cache. + * @return the bytes if cache exists or null otherwise + */ + public byte[] getBytes(@NonNull final String key) { + return getBytes(key, null); + } + + /** + * Return the bytes in cache. + * + * @param key The key of cache. + * @param defaultValue The default value if the cache doesn't exist. + * @return the bytes if cache exists or defaultValue otherwise + */ + public byte[] getBytes(@NonNull final String key, final byte[] defaultValue) { + return realGetBytes(TYPE_BYTE + key, defaultValue); + } + + private byte[] realGetBytes(@NonNull final String key) { + return realGetBytes(key, null); + } + + private byte[] realGetBytes(@NonNull final String key, final byte[] defaultValue) { + DiskCacheManager diskCacheManager = getDiskCacheManager(); + if (diskCacheManager == null) return defaultValue; + final File file = diskCacheManager.getFileIfExists(key); + if (file == null) return defaultValue; + byte[] data = readFile2Bytes(file); + if (DiskCacheHelper.isDue(data)) { + diskCacheManager.removeByKey(key); + return defaultValue; + } + diskCacheManager.updateModify(file); + return DiskCacheHelper.getDataWithoutDueTime(data); + } + + /////////////////////////////////////////////////////////////////////////// + // about String + /////////////////////////////////////////////////////////////////////////// + + /** + * Put string value in cache. + * + * @param key The key of cache. + * @param value The value of cache. + */ + public void put(@NonNull final String key, final String value) { + put(key, value, -1); + } + + /** + * Put string value in cache. + * + * @param key The key of cache. + * @param value The value of cache. + * @param saveTime The save time of cache, in seconds. + */ + public void put(@NonNull final String key, final String value, final int saveTime) { + realPutBytes(TYPE_STRING + key, string2Bytes(value), saveTime); + } + + /** + * Return the string value in cache. + * + * @param key The key of cache. + * @return the string value if cache exists or null otherwise + */ + public String getString(@NonNull final String key) { + return getString(key, null); + } + + /** + * Return the string value in cache. + * + * @param key The key of cache. + * @param defaultValue The default value if the cache doesn't exist. + * @return the string value if cache exists or defaultValue otherwise + */ + public String getString(@NonNull final String key, final String defaultValue) { + byte[] bytes = realGetBytes(TYPE_STRING + key); + if (bytes == null) return defaultValue; + return bytes2String(bytes); + } + + + /** + * Put serializable in cache. + * + * @param key The key of cache. + * @param value The value of cache. + */ + public void put(@NonNull final String key, final Serializable value) { + put(key, value, -1); + } + + /** + * Put serializable in cache. + * + * @param key The key of cache. + * @param value The value of cache. + * @param saveTime The save time of cache, in seconds. + */ + public void put(@NonNull final String key, final Serializable value, final int saveTime) { + realPutBytes(TYPE_SERIALIZABLE + key, serializable2Bytes(value), saveTime); + } + + /** + * Return the serializable in cache. + * + * @param key The key of cache. + * @return the bitmap if cache exists or null otherwise + */ + public Object getSerializable(@NonNull final String key) { + return getSerializable(key, null); + } + + /** + * Return the serializable in cache. + * + * @param key The key of cache. + * @param defaultValue The default value if the cache doesn't exist. + * @return the bitmap if cache exists or defaultValue otherwise + */ + public Object getSerializable(@NonNull final String key, final Object defaultValue) { + byte[] bytes = realGetBytes(TYPE_SERIALIZABLE + key); + if (bytes == null) return defaultValue; + return bytes2Object(bytes); + } + + /** + * Return the size of cache, in bytes. + * + * @return the size of cache, in bytes + */ + public long getCacheSize() { + DiskCacheManager diskCacheManager = getDiskCacheManager(); + if (diskCacheManager == null) return 0; + return diskCacheManager.getCacheSize(); + } + + /** + * Return the count of cache. + * + * @return the count of cache + */ + public int getCacheCount() { + DiskCacheManager diskCacheManager = getDiskCacheManager(); + if (diskCacheManager == null) return 0; + return diskCacheManager.getCacheCount(); + } + + /** + * Remove the cache by key. + * + * @param key The key of cache. + * @return {@code true}: success
{@code false}: fail + */ + public boolean remove(@NonNull final String key) { + DiskCacheManager diskCacheManager = getDiskCacheManager(); + if (diskCacheManager == null) return true; + return diskCacheManager.removeByKey(TYPE_BYTE + key) + && diskCacheManager.removeByKey(TYPE_STRING + key) + && diskCacheManager.removeByKey(TYPE_JSON_OBJECT + key) + && diskCacheManager.removeByKey(TYPE_JSON_ARRAY + key) + && diskCacheManager.removeByKey(TYPE_BITMAP + key) + && diskCacheManager.removeByKey(TYPE_DRAWABLE + key) + && diskCacheManager.removeByKey(TYPE_PARCELABLE + key) + && diskCacheManager.removeByKey(TYPE_SERIALIZABLE + key); + } + + /** + * Clear all of the cache. + * + * @return {@code true}: success
{@code false}: fail + */ + public boolean clear() { + DiskCacheManager diskCacheManager = getDiskCacheManager(); + if (diskCacheManager == null) return true; + return diskCacheManager.clear(); + } + + private static final class DiskCacheManager { + private final AtomicLong cacheSize; + private final AtomicInteger cacheCount; + private final long sizeLimit; + private final int countLimit; + private final Map lastUsageDates + = Collections.synchronizedMap(new HashMap()); + private final File cacheDir; + private final Thread mThread; + + private DiskCacheManager(final File cacheDir, final long sizeLimit, final int countLimit) { + this.cacheDir = cacheDir; + this.sizeLimit = sizeLimit; + this.countLimit = countLimit; + cacheSize = new AtomicLong(); + cacheCount = new AtomicInteger(); + mThread = new Thread(new Runnable() { + @Override + public void run() { + int size = 0; + int count = 0; + final File[] cachedFiles = cacheDir.listFiles(new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return name.startsWith(CACHE_PREFIX); + } + }); + if (cachedFiles != null) { + for (File cachedFile : cachedFiles) { + size += cachedFile.length(); + count += 1; + lastUsageDates.put(cachedFile, cachedFile.lastModified()); + } + cacheSize.getAndAdd(size); + cacheCount.getAndAdd(count); + } + } + }); + mThread.start(); + } + + private long getCacheSize() { + wait2InitOk(); + return cacheSize.get(); + } + + private int getCacheCount() { + wait2InitOk(); + return cacheCount.get(); + } + + private File getFileBeforePut(final String key) { + wait2InitOk(); + File file = new File(cacheDir, getCacheNameByKey(key)); + if (file.exists()) { + cacheCount.addAndGet(-1); + cacheSize.addAndGet(-file.length()); + } + return file; + } + + private void wait2InitOk() { + try { + mThread.join(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + private File getFileIfExists(final String key) { + File file = new File(cacheDir, getCacheNameByKey(key)); + if (!file.exists()) return null; + return file; + } + + private String getCacheNameByKey(final String key) { + return CACHE_PREFIX + key.substring(0, 3) + key.substring(3).hashCode(); + } + + private void put(final File file) { + cacheCount.addAndGet(1); + cacheSize.addAndGet(file.length()); + while (cacheCount.get() > countLimit || cacheSize.get() > sizeLimit) { + cacheSize.addAndGet(-removeOldest()); + cacheCount.addAndGet(-1); + } + } + + private void updateModify(final File file) { + Long millis = System.currentTimeMillis(); + file.setLastModified(millis); + lastUsageDates.put(file, millis); + } + + private boolean removeByKey(final String key) { + File file = getFileIfExists(key); + if (file == null) return true; + if (!file.delete()) return false; + cacheSize.addAndGet(-file.length()); + cacheCount.addAndGet(-1); + lastUsageDates.remove(file); + return true; + } + + private boolean clear() { + File[] files = cacheDir.listFiles(new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return name.startsWith(CACHE_PREFIX); + } + }); + if (files == null || files.length <= 0) return true; + boolean flag = true; + for (File file : files) { + if (!file.delete()) { + flag = false; + continue; + } + cacheSize.addAndGet(-file.length()); + cacheCount.addAndGet(-1); + lastUsageDates.remove(file); + } + if (flag) { + lastUsageDates.clear(); + cacheSize.set(0); + cacheCount.set(0); + } + return flag; + } + + /** + * Remove the oldest files. + * + * @return the size of oldest files, in bytes + */ + private long removeOldest() { + if (lastUsageDates.isEmpty()) return 0; + Long oldestUsage = Long.MAX_VALUE; + File oldestFile = null; + Set> entries = lastUsageDates.entrySet(); + synchronized (lastUsageDates) { + for (Map.Entry entry : entries) { + Long lastValueUsage = entry.getValue(); + if (lastValueUsage < oldestUsage) { + oldestUsage = lastValueUsage; + oldestFile = entry.getKey(); + } + } + } + if (oldestFile == null) return 0; + long fileSize = oldestFile.length(); + if (oldestFile.delete()) { + lastUsageDates.remove(oldestFile); + return fileSize; + } + return 0; + } + } + + private static final class DiskCacheHelper { + + static final int TIME_INFO_LEN = 14; + + private static byte[] newByteArrayWithTime(final int second, final byte[] data) { + byte[] time = createDueTime(second).getBytes(); + byte[] content = new byte[time.length + data.length]; + System.arraycopy(time, 0, content, 0, time.length); + System.arraycopy(data, 0, content, time.length, data.length); + return content; + } + + /** + * Return the string of due time. + * + * @param seconds The seconds. + * @return the string of due time + */ + private static String createDueTime(final int seconds) { + return String.format( + Locale.getDefault(), "_$%010d$_", + System.currentTimeMillis() / 1000 + seconds + ); + } + + private static boolean isDue(final byte[] data) { + long millis = getDueTime(data); + return millis != -1 && System.currentTimeMillis() > millis; + } + + private static long getDueTime(final byte[] data) { + if (hasTimeInfo(data)) { + String millis = new String(copyOfRange(data, 2, 12)); + try { + return Long.parseLong(millis) * 1000; + } catch (NumberFormatException e) { + return -1; + } + } + return -1; + } + + private static byte[] getDataWithoutDueTime(final byte[] data) { + if (hasTimeInfo(data)) { + return copyOfRange(data, TIME_INFO_LEN, data.length); + } + return data; + } + + private static byte[] copyOfRange(final byte[] original, final int from, final int to) { + int newLength = to - from; + if (newLength < 0) throw new IllegalArgumentException(from + " > " + to); + byte[] copy = new byte[newLength]; + System.arraycopy(original, from, copy, 0, Math.min(original.length - from, newLength)); + return copy; + } + + private static boolean hasTimeInfo(final byte[] data) { + return data != null + && data.length >= TIME_INFO_LEN + && data[0] == '_' + && data[1] == '$' + && data[12] == '$' + && data[13] == '_'; + } + } + + /////////////////////////////////////////////////////////////////////////// + // other utils methods + /////////////////////////////////////////////////////////////////////////// + + private static byte[] string2Bytes(final String string) { + if (string == null) return null; + return string.getBytes(); + } + + private static String bytes2String(final byte[] bytes) { + if (bytes == null) return null; + return new String(bytes); + } + + private static byte[] serializable2Bytes(final Serializable serializable) { + if (serializable == null) return null; + ByteArrayOutputStream baos; + ObjectOutputStream oos = null; + try { + oos = new ObjectOutputStream(baos = new ByteArrayOutputStream()); + oos.writeObject(serializable); + return baos.toByteArray(); + } catch (Exception e) { + e.printStackTrace(); + return null; + } finally { + try { + if (oos != null) { + oos.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + private static Object bytes2Object(final byte[] bytes) { + if (bytes == null) return null; + ObjectInputStream ois = null; + try { + ois = new ObjectInputStream(new ByteArrayInputStream(bytes)); + return ois.readObject(); + } catch (Exception e) { + e.printStackTrace(); + return null; + } finally { + try { + if (ois != null) { + ois.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + + private static void writeFileFromBytes(final File file, final byte[] bytes) { + FileOutputStream outputStream = null; + FileChannel fc = null; + try { + outputStream = new FileOutputStream(file, false); + fc = outputStream.getChannel(); + fc.write(ByteBuffer.wrap(bytes)); + fc.force(true); + } catch (IOException e) { + e.printStackTrace(); + } finally { + try { + if (fc != null) { + fc.close(); + } + if (outputStream != null) { + outputStream.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + private static byte[] readFile2Bytes(final File file) { + RandomAccessFile accessFile = null; + FileChannel fc = null; + FileInputStream inputStream = null; + try { + accessFile = new RandomAccessFile(file, "r"); + fc = accessFile.getChannel(); + int size = (int) fc.size(); + byte[] data = new byte[size]; + fc.read(ByteBuffer.wrap(data)); + return data; + } catch (IOException e) { + e.printStackTrace(); + return null; + } finally { + try { + if (fc != null) { + fc.close(); + } + if (accessFile != null) { + accessFile.close(); + } + if (inputStream != null) { + inputStream.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + private static boolean isSpace(final String s) { + if (s == null) return true; + for (int i = 0, len = s.length(); i < len; ++i) { + if (!Character.isWhitespace(s.charAt(i))) { + return false; + } + } + return true; + } +} From 3994a08c09bda153d92d932f4b3512b548fbb7ed Mon Sep 17 00:00:00 2001 From: "linzheng\\n8383" Date: Thu, 24 Oct 2019 15:41:23 +0800 Subject: [PATCH 4/5] =?UTF-8?q?=E5=BC=80=E5=90=AF=E8=B0=83=E8=AF=95?= =?UTF-8?q?=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 4 ++-- settings.gradle | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index dcd0928..1ebef7b 100644 --- a/build.gradle +++ b/build.gradle @@ -14,10 +14,10 @@ buildscript { } dependencies { classpath 'com.android.tools.build:gradle:3.1.0' - classpath "com.effective.plugins:component:$component_version" +// classpath "com.effective.plugins:component:$component_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" //调试需要 -// classpath "com.effective.plugins:component-plugin" + classpath "com.effective.plugins:component-plugin" //发布需要 // classpath 'com.novoda:bintray-release:0.9.1' diff --git a/settings.gradle b/settings.gradle index 76f8e12..5957a4a 100644 --- a/settings.gradle +++ b/settings.gradle @@ -2,10 +2,10 @@ include ':component-core' // //发布插件 -include ':component-plugin' +//include ':component-plugin' //调试插件 -//includeBuild './component-plugin' +includeBuild './component-plugin' // include ':app' include ':debugModule' From b4f8fd3b46fedfd9058e31822f2e14ddb07c34a4 Mon Sep 17 00:00:00 2001 From: "linzheng\\n8383" Date: Thu, 24 Oct 2019 16:03:35 +0800 Subject: [PATCH 5/5] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=9C=AC=E5=9C=B0?= =?UTF-8?q?=E4=BB=93=E5=BA=93=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/build.gradle b/build.gradle index 1ebef7b..3a33b83 100644 --- a/build.gradle +++ b/build.gradle @@ -7,10 +7,7 @@ buildscript { repositories { google() jcenter() -// maven { url 'https://dl.bintray.com/yummylau/maven' } - maven { - url uri('./repo') - } + maven { url 'https://dl.bintray.com/yummylau/maven' } } dependencies { classpath 'com.android.tools.build:gradle:3.1.0' @@ -29,10 +26,7 @@ allprojects { google() jcenter() maven { url "http://dl.bintray.com/kotlin/kotlin-eap" } -// maven { url 'https://dl.bintray.com/yummylau/maven' } - maven { - url uri('../repo') - } + maven { url 'https://dl.bintray.com/yummylau/maven' } } gradle.projectsEvaluated {