一个使用 APT+字节码插桩优化代码设计的 Android Demo(1)

  1. 创建注解处理器,并且注册该处理器

@AutoService(Processor.class)

public class RegisterProcessor extends AbstractProcessor{

private Map<String, TypeElement> mMap = new HashMap<>();

private ProcessingEnvironment mProcessingEnvironment;

@Override

public synchronized void init(ProcessingEnvironment processingEnvironment) {

super.init(processingEnvironment);

mProcessingEnvironment = processingEnvironment;

}

@Override

public SourceVersion getSupportedSourceVersion() {

return SourceVersion.latestSupported();

}

@Override

public Set getSupportedAnnotationTypes() {

return Collections.singleton(Register.class.getCanonicalName());

}

@Override

public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {

for (Element element : roundEnvironment.getElementsAnnotatedWith(Register.class)) {

processElement(element);

}

if (roundEnvironment.processingOver()) {

generateCode();

}

return true;

}

private void processElement(Element element) {

TypeElement typeElement = (TypeElement) element;

String qualifiedName = typeElement.getQualifiedName().toString();

if (mMap.get(qualifiedName) == null) {

mMap.put(qualifiedName, typeElement);

}

}

private void generateCode() {

if (mMap.isEmpty()) return;

Set set = new HashSet<>();

set.addAll(mMap.values());

GenerateClassHelper helper = new GenerateClassHelper(mProcessingEnvironment, set);

helper.generateCode();

}

}

  • 使用 @AutoService 的方式来自动注册此处理器,会在 build/classes/java/main 下生成 META-INF/services/javax.annotation.processing.Processor 文件:

打开此文件内容如下:

com.wbh.decoupling.compile.RegisterProcessor

如果不使用注解方式注册,就需要自己手动实现 META-INF/services/javax.annotation.processing.Processor 内容。

  • process() 方法中收集所有被 Register 注解的 Element,然后通过 GenerateClassHelper 类来生成代码。因为 process() 方法会执行多次,所以使用 roundEnvironment.processingOver() 判断只有在最后的时候再去生成代码。
  1. 我们来实现 GenerateClassHelper 的代码:

public class GenerateClassHelper {

private static final String PACKAGE_NAME = “com.wbh.decoupling.generate”;

private static final String CLASS_NAME_PREFIX = “CardManager_”;

private static final String METHOD_NAME = “init”;

private static final ClassName CARD_MANAGER = ClassName.get(“com.wbh.decoupling.businesslayer.card”, “CardManager”);

private Filer mFiler;

private Elements mElementUtils;

private Set mElementSet;

public GenerateClassHelper(ProcessingEnvironment processingEnvironment, Set set) {

mFiler = processingEnvironment.getFiler();

mElementUtils = processingEnvironment.getElementUtils();

mElementSet = set;

}

public void generateCode() {

try {

JavaFile javaFile = JavaFile.builder(PACKAGE_NAME, getGenTypeSpec()).build();

javaFile.writeTo(mFiler);

} catch (IOException e) {

e.printStackTrace();

}

}

private TypeSpec getGenTypeSpec() {

return TypeSpec.classBuilder(getClassName())

.addModifiers(Modifier.PUBLIC)

.addMethod(getGenInitMethodSpec())

.build();

}

private String getClassName() {

for (TypeElement element : mElementSet) {

// 姑且用获取到的第一个类的md5来做生成的类名的后缀,实际是不合理,可能会有问题

return CLASS_NAME_PREFIX + EncryptHelper.md5String(mElementUtils.getPackageOf(element).getQualifiedName().toString());

}

return “”;

}

private MethodSpec getGenInitMethodSpec() {

String format = “$T.registerCard(new $T())”;

CodeBlock.Builder builder = CodeBlock.builder();

for (TypeElement typeElement : mElementSet) {

ClassName className = ClassName.get(typeElement);

builder.addStatement(format, CARD_MANAGER, className);

}

CodeBlock codeBlock = builder.build();

return MethodSpec.methodBuilder(METHOD_NAME)

.addModifiers(Modifier.PUBLIC)

.addModifiers(Modifier.STATIC)

.addCode(codeBlock)

.build();

}

}

GenerateClassHelper 使用 JavaPoet 库来生成代码,JavaFile、TypeSpec、MethodSpec、CodeBlock 等,都是 JavaPoet 的,代码里按照我们想要生成的 CardManager_??? 类来编写。

3.2.3 业务中使用自定义注解和解析器

在 BusinessLayer、BusinessCn、BusinessExp 中的各个 build.gradle 中都依赖 annotation 和 compile 两个 module

dependencies {

implementation project(path: ‘:annotation’)

annotationProcessor project(path: ‘:compile’)

}

然后对需要注册的 Card 类加上 @Register 的注解,比如:

@Register

public class ACard implements ICard {

}

最后我们运行下代码,就能自动生成 CardManager 相关的代码,它在 build/generated/source/apt 目录下。

我们来看下 BusinessLayer 的目录:

打开 CardManager_becf3fc7606c9b461025f1def7ff27ac 文件:

package com.wbh.decoupling.generate;

import com.wbh.decoupling.businesslayer.card.ACard;

import com.wbh.decoupling.businesslayer.card.BCard;

import com.wbh.decoupling.businesslayer.card.CardRegister;

public class CardManager_becf3fc7606c9b461025f1def7ff27ac {

public static void init() {

CardManager.registerCard(new ACard());

CardManager.registerCard(new BCard());

}

}

其他 module 中也会生成类似的此文件,这里就不一一展示了。

既然 CardManager 已经是通过 APT 自动生成了,那么我们手写的各个 module 中的 CardManager 类就可以删掉了。

3.3 调用注解生成的各个类文件

那么问题来了,CardManager_??? 这几个文件自动生成后,该怎么调用呢?我们不知道现在以及未来可能有多少个 module,也不知道这些 CardManager_??? 的具体名字。

一个简单粗暴的方法,就是遍历 dex 文件的方式。

这种方式主要是通过遍历 dex 文件中所有以com.wbh.decoupling.generate开头的类,这个也是之前说到要将所有生成的类都放在这个包下的原因,然后反射的方式来调用这些类的 init() 方法。

在 BusinessLayer 里创建 CrossCompileUtils 类来完成这些操作,然后在 Application 中调用 CrossCompileUtils.init() 方法执行此过程,看下代码的实现:

public class CrossCompileUtils {

private static final String GENERATE_CODE_PACKAGE_NAME = “com.wbh.decoupling.generate”;

private static final String METHOD_NAME = “init”;

public static void init(Context context) {

try {

List targetClassList = getTargetClassList(context, GENERATE_CODE_PACKAGE_NAME);

for (String className : targetClassList) {

Class<?> cls = Class.forName(className);

Method method = cls.getMethod(METHOD_NAME);

method.invoke(null);

}

} catch (Exception e) {

e.printStackTrace();

}

}

// 获取以 target 为开头的所有类

private static List getTargetClassList(Context context, String target) {

List classList = new ArrayList<>();

try {

ApplicationInfo info = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0);

String path = info.sourceDir;

DexFile dexFile = new DexFile(path);

Enumeration entries = dexFile.entries();

while (entries.hasMoreElements()) {

String name = entries.nextElement();

if (name.startsWith(target)) {

classList.add(name);

}

}

} catch (Exception e) {

e.printStackTrace();

}

return classList;

}

}

这种方式有个很明显的缺陷,就是整个过程是在运行时进行的,当代码量越多,此过程越耗时,对冷启动会有很明显的影响。

四. 字节码插桩改造优化


这种方式主要通过在编译期插入相应字节码的方式,将耗时放在编译期以优化运行时效率。

4.1 明确插桩的代码

我们先整理接下来的主要任务,在编译期生成类AsmCrossCompileUtils,这个类的有个 init() 方法,这个方法会调用所有 CardManager_XXX.init():

package com.wbh.decoupling.generate;

public class AsmCrossCompileUtils {

public static void init() {

CardManager_becf3fc7606c9b461025f1def7ff27ac.init();

CardManager_dc2db21188334cfca97494d99700395.init();

}

}

假定以上的类已经生成了,那么 CrossCompileUtils 可以改为:

public class CrossCompileUtils {

public static void init(Context context) {

initByAsm();

}

private static void initByAsm() {

try {

Class cls = Class.forName(“com.wbh.decoupling.generate.AsmCrossCompileUtils”);

Method method = cls.getMethod(METHOD_NAME);

method.invoke(null);

} catch (Exception e) {

e.printStackTrace();

}

}

}

这种方式只需要通过一次反射,相比遍历 dex 的方式,性能就好很多了。

这种方式有两个关键点:一个是如何获取注解生成的各个类,一个是如何生成 AsmCrossCompileUtils 类。

4.2 自定义 gradle plugin

我们创建一个 module(哪一种类型都可以) ,姑且命名为 WPlugin 作为插件名字,然后将 src 目录下的所有文件都删除,只保留 main 目录,main 目录下的文件也全部删除,然后在 main 目录下创建 groovy 目录(这是因为 gradle 的语法是 groovy),我们编写的插件代码就在这目录下。

接下来我们需要配置下 build.gradle,将原本的 build.gradle 内容都删除,配置如下:

apply plugin: ‘groovy’ // 因为plugin是由groovy语法编写,所以需要应用此插件

dependencies {

implementation fileTree(dir: ‘libs’, include: [‘*.jar’])

implementation gradleApi() // 自定义插件中需要用到 gradle 的各种 api

}

sourceCompatibility = “1.7”

targetCompatibility = “1.7”

group = ‘com.wbh.decoupling.plugin’ // 自定义插件的组别

version = ‘1.0.0’ // 自定义插件的版本号

然后我们在 groovy 目录下创建一个 groovy 文件,命名为 WPlugin:

import org.gradle.api.Plugin

import org.gradle.api.Project

class WPlugin implements Plugin {

@Override

void apply(Project project) {

println(‘this is my WPlugin’)

}

}

插件代码执行的入口就是这里的 apply() 方法,不过我们还得需要注册这个插件。在 main 目录下创建 resources 目录,在该目录下创建 META-INF.gradle-plugins 目录,然后在该目录下创建 xxx.properties 文件,这里的 xxx 表示当我们在其他项目应用此插件时的名字 apply plugin: ‘xxx’,我们还是将之命名为 WPlugin,在这个文件里编写内容如下:

implementation-class=WPlugin

implementation-class 用来配置插件入口类的,这里也就是配置我们自定义的 WPlugin 类。

至此整个自定义插件的结构已经完成了,在看下这个目录结构如下:

4.3 发布 plugin

在使用自定义的插件前,得先发布这个插件。

这里我们将插件发布到本地目录下,在根目录下创建 maven 目录,用来存放我们发布的插件,然后在 build.gradle 配置如下:

apply plugin: ‘maven’

uploadArchives {

repositories {

mavenDeployer {

repository(url: uri(‘…/maven’)) // 指定发布到的目录

}

}

}

然后点击以下 uploadArchives ,运行发布插件。

接下来我们就能在 maven 目录下看到我们发布的插件了。

4.4 使用自定义的 plugin

在 app 这个 module 中使用自定义的插件 WPlugin,在 build.gradle 中配置如下:

apply plugin: WPlugin

buildscript {

repositories {

maven {

url uri(‘…/maven’)

}

}

dependencies {

classpath ‘com.wbh.decoupling.plugin:WPlugin:1.0.0’

}

}

然后 Sync Gradle,我们就能在 Build 窗口中看到 WPlugin 打印的内容。

屏幕快照 2019-09-15 下午10.38.13.png 至此,我们说完了如何自定义、发布以及使用 gradle 插件。

接下来我们该完善下这个 WPlugin 的内容。

4.5 自定义 Transform

编写 Transform,需要使用到 gradle 的 API,所以我们需要在 build.gradle 添加如下的依赖:

implementation’com.android.tools.build:gradle-api:3.4.2’

注意:由于 gradle 插件使用了上述依赖,所以在使用该插件的项目的build.grdle里需要配置如下:

buildscript {

repositories {

google()

jcenter()

}

dependencies {

classpath ‘com.android.tools.build:gradle:3.4.2’

}

}

接着创建一个类继承 Transform :

class WTransform extends Transform {

@Override

String getName() {

return null

}

@Override

Set<QualifiedContent.ContentType> getInputTypes() {

return null

}

@Override

Set<? super QualifiedContent.Scope> getScopes() {

return null

}

@Override

boolean isIncremental() {

return false

}

@Override

void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {

super.transform(transformInvocation)

}

}

  • getName(): 返回自定义的 Transform 名字

  • geInputTypes(): 指定 Transform 要处理的输入类型,主要有 QualifiedContent.DefaultContentType.CLASSESQualifiedContent.DefaultContentType.RESOURCES 两种类型,对应为 .class 文件 和 java 资源文件

  • getScopes():指定输入文件的所属的范围。

public interface QualifiedContent {

enum Scope implements ScopeType {

/** Only the project (module) content */

PROJECT(0x01),

/** Only the sub-projects (other modules) */

SUB_PROJECTS(0x04),

/** Only the external libraries */

EXTERNAL_LIBRARIES(0x10),

/** Code that is being tested by the current variant, including dependencies */

TESTED_CODE(0x20),

/** Local or remote dependencies that are provided-only */

PROVIDED_ONLY(0x40),

}

}

  • isIncremental():当前是支持增量编译

  • transform():执行转化的方法,通过参数 transformInvocation 可以获取到该节点的输入和输出:

Collection inputs = transformInvocation.getInputs()

TransformOutputProvider outputProvider = transformInvocation.getOutputProvider()

TransformInput 分为两类,一类是 Jarinput,TransformInput#getJarInputs(),一类是 DirectoryInput,TransformInput#getDirectoryInputs()。而 TransformOutputProvider 指向了文件/目录输出路径。

4.6 注册 Transform

要使我们自定义的 Transform 生效参与到编译过程中,还需要注册 WTransform,注册过程很简单,只要在 Plugin 中注册即可。

class WPlugin implements Plugin {

@Override

void apply(Project project) {

println(‘this is my WPlugin’)

def andr = project.extensions.getByName(‘android’)

andr.registerTransform(new WTransform())

}

}

4.7 完善 Transform

import com.android.build.api.transform.DirectoryInput

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.api.transform.TransformOutputProvider

import com.android.build.api.transform.Format

import com.android.utils.FileUtils

class WTransform extends Transform {

@Override

String getName() {

return ‘WTransform’

}

@Override

Set<QualifiedContent.ContentType> getInputTypes() {

return Collections.singleton(QualifiedContent.DefaultContentType.CLASSES)

}

@Override

Set<? super QualifiedContent.Scope> getScopes() {

Set<? super QualifiedContent.Scope> set = new HashSet<>()

set.add(QualifiedContent.Scope.PROJECT)

set.add(QualifiedContent.Scope.SUB_PROJECTS)

return set

}

@Override

boolean isIncremental() {

return false

}

@Override

void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {

super.transform(transformInvocation)

Collection inputs = transformInvocation.getInputs()

TransformOutputProvider outputProvider = transformInvocation.getOutputProvider()

inputs.each {

it.getJarInputs().each { jarInput ->

transformJar(jarInput, outputProvider)

}

it.getDirectoryInputs().each { dirInput ->

transformDir(dirInput, outputProvider)

}

}

}

private static void transformJar(JarInput jarInput, TransformOutputProvider outputProvider) {

File dstFile = outputProvider.getContentLocation(

jarInput.getName(),

jarInput.getContentTypes(),

jarInput.getScopes(),

Format.JAR)

FileUtils.copyFile(jarInput.getFile(), dstFile)

println('jarInputFile ==> ’ + jarInput.file.absolutePath)

println('dstFile ==> ’ + dstFile.absolutePath)

}

private static void transformDir(DirectoryInput dirInput, TransformOutputProvider outputProvider) {

File dstDir = outputProvider.getContentLocation(

dirInput.getName(),

dirInput.getContentTypes(),

dirInput.getScopes(),

Format.DIRECTORY)

FileUtils.copyDirectory(dirInput.getFile(), dstDir)

println('directory input ==> ’ + dirInput.file.absolutePath)

println('dstDir ==> ’ + dstDir.absolutePath)

}

}

transform() 方法做的事很简单,就是获取所有的输入文件/目录,然后拷贝到输出路径。

我们重新 uploadArchives 这个插件,然后 clear project,在重新构建项目,就能在 build 窗口看到我们打印的输出:

根据打印的输入,我们能在相应的目录下找到 WTransform 生成的文件:

屏幕快照 2019-09-15 下午11.26.40.png

由于每次编译所有文件/目录都会重新复制一次,所以加上支持增量编译:

class WTransform extends Transform {

@Override

boolean isIncremental() {

return true

}

@Override

void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {

super.transform(transformInvocation)

boolean isIncremental = transformInvocation.isIncremental()

println('isIncremental ==> ’ + isIncremental)

Collection inputs = transformInvocation.getInputs()

TransformOutputProvider outputProvider = transformInvocation.getOutputProvider()

if (!isIncremental) {

outputProvider.deleteAll()

}

inputs.each {

it.getJarInputs().each { jarInput ->

transformJar(jarInput, outputProvider, isIncremental)

}

it.getDirectoryInputs().each { dirInput ->

transformDir(dirInput, outputProvider, isIncremental)

}

}

}

private static void transformJar(JarInput jarInput, TransformOutputProvider outputProvider, boolean isIncremental) {

File dstFile = outputProvider.getContentLocation(

jarInput.getName(),

jarInput.getContentTypes(),

jarInput.getScopes(),

Format.JAR)

println('jar input ==> ’ + jarInput.file.absolutePath)

println('dstFile ==> ’ + dstFile.getAbsolutePath())

if (!isIncremental) {

FileUtils.copyFile(jarInput.file, dstFile)

return

}

Status status = jarInput.status

switch (status) {

case Status.NOTCHANGED:

break

case Status.ADDED:

case Status.CHANGED:

FileUtils.deleteIfExists(dstFile)

FileUtils.copyFile(jarInput.file, dstFile)

break

case Status.REMOVED:

FileUtils.deleteIfExists(dstFile)

break

}

}

private static void transformDir(DirectoryInput dirInput, TransformOutputProvider outputProvider, boolean isIncremental) {

File dstDir = outputProvider.getContentLocation(

dirInput.getName(),

dirInput.getContentTypes(),

dirInput.getScopes(),

Format.DIRECTORY)

println('directory input ==> ’ + dirInput.file.absolutePath)

println('dstDir ==> ’ + dstDir.absolutePath)

if (!isIncremental) {

FileUtils.copyDirectory(dirInput.getFile(), dstDir)

return

}

String srcDirPath = dirInput.getFile().getAbsolutePath()

String dstDirPath = dstDir.getAbsolutePath()

Map<File, Status> fileStatusMap = dirInput.getChangedFiles()

fileStatusMap.entrySet().each { Map.Entry<File, Status> changedFileMapEntry ->

Status status = changedFileMapEntry.getValue()

File inputFile = changedFileMapEntry.getKey()

println('change file: ’ + inputFile.getAbsolutePath() + ", status: " + status)

String dstFilePath = inputFile.getAbsolutePath().replace(srcDirPath, dstDirPath)

File dstFile = new File(dstFilePath)

switch (status) {

case Status.NOTCHANGED:

break

case Status.REMOVED:

FileUtils.deleteIfExists(dstFile)

break

case Status.ADDED:

case Status.CHANGED:

FileUtils.deleteIfExists(dstFile)

FileUtils.copyFile(inputFile, dstFile)

break

}

}

}

}

增量编译的文件都有个状态 Status,根据文件的状态做相应不同对应操作即可。

  • Status.NOTCHANGED:该文件没有变动,所以不需要重新复制一份

  • Status.REMOVED:该文件被删除,所以对应输出文件也要删除

  • Status.ADDED:该文件为新加的,所以需要复制一份到输出路径

  • Status.CHANGED:该文件被修改,所以需要重新复制一份到输出路径

修改完后,我们测试下效果,在执行一次完整编译后,创建一个Test.java 类,再执行一次编译,能看到以下打印结果:

Task :app:transformClassesWithWTransformForExpDebug

isIncremental ==> true

jar input ==> /Users/wubohua/work/project/Android/Application_decoupling2/annotation/build/libs/annotation.jar

dstFile ==> /Users/wubohua/work/project/Android/Application_decoupling2/app/build/intermediates/transforms/WTransform/exp/debug/2.jar

jar input ==> /Users/wubohua/work/project/Android/Application_decoupling2/businessexp/build/intermediates/runtime_library_classes/debug/classes.jar

dstFile ==> /Users/wubohua/work/project/Android/Application_decoupling2/app/build/intermediates/transforms/WTransform/exp/debug/0.jar

jar input ==> /Users/wubohua/work/project/Android/Application_decoupling2/businesslayer/build/intermediates/runtime_library_classes/debug/classes.jar

dstFile ==> /Users/wubohua/work/project/Android/Application_decoupling2/app/build/intermediates/transforms/WTransform/exp/debug/1.jar

directory input ==> /Users/wubohua/work/project/Android/Application_decoupling2/app/build/intermediates/javac/expDebug/compileExpDebugJavaWithJavac/classes

dstDir ==> /Users/wubohua/work/project/Android/Application_decoupling2/app/build/intermediates/transforms/WTransform/exp/debug/3

change file: /Users/wubohua/work/project/Android/Application_decoupling2/app/build/intermediates/javac/expDebug/compileExpDebugJavaWithJavac/classes/com/wbh/decoupling/Test.class, status: ADDED

修改Test.java的代码,比如添加个方法后,再执行编译,能看到如下打印:

Task :app:transformClassesWithWTransformForExpDebug

isIncremental ==> true

jar input ==> /Users/wubohua/work/project/Android/Application_decoupling2/annotation/build/libs/annotation.jar

dstFile ==> /Users/wubohua/work/project/Android/Application_decoupling2/app/build/intermediates/transforms/WTransform/exp/debug/2.jar

jar input ==> /Users/wubohua/work/project/Android/Application_decoupling2/businessexp/build/intermediates/runtime_library_classes/debug/classes.jar

dstFile ==> /Users/wubohua/work/project/Android/Application_decoupling2/app/build/intermediates/transforms/WTransform/exp/debug/0.jar

jar input ==> /Users/wubohua/work/project/Android/Application_decoupling2/businesslayer/build/intermediates/runtime_library_classes/debug/classes.jar

dstFile ==> /Users/wubohua/work/project/Android/Application_decoupling2/app/build/intermediates/transforms/WTransform/exp/debug/1.jar

directory input ==> /Users/wubohua/work/project/Android/Application_decoupling2/app/build/intermediates/javac/expDebug/compileExpDebugJavaWithJavac/classes

dstDir ==> /Users/wubohua/work/project/Android/Application_decoupling2/app/build/intermediates/transforms/WTransform/exp/debug/3

change file: /Users/wubohua/work/project/Android/Application_decoupling2/app/build/intermediates/javac/expDebug/compileExpDebugJavaWithJavac/classes/com/wbh/decoupling/Test.class, status: CHANGED

将Test.java 文件删除后,再执行编译,打印结果如下:

Task :app:transformClassesWithWTransformForExpDebug

isIncremental ==> true

jar input ==> /Users/wubohua/work/project/Android/Application_decoupling2/annotation/build/libs/annotation.jar

dstFile ==> /Users/wubohua/work/project/Android/Application_decoupling2/app/build/intermediates/transforms/WTransform/exp/debug/2.jar

jar input ==> /Users/wubohua/work/project/Android/Application_decoupling2/businessexp/build/intermediates/runtime_library_classes/debug/classes.jar

dstFile ==> /Users/wubohua/work/project/Android/Application_decoupling2/app/build/intermediates/transforms/WTransform/exp/debug/0.jar

jar input ==> /Users/wubohua/work/project/Android/Application_decoupling2/businesslayer/build/intermediates/runtime_library_classes/debug/classes.jar

dstFile ==> /Users/wubohua/work/project/Android/Application_decoupling2/app/build/intermediates/transforms/WTransform/exp/debug/1.jar

directory input ==> /Users/wubohua/work/project/Android/Application_decoupling2/app/build/intermediates/javac/expDebug/compileExpDebugJavaWithJavac/classes

dstDir ==> /Users/wubohua/work/project/Android/Application_decoupling2/app/build/intermediates/transforms/WTransform/exp/debug/3

change file: /Users/wubohua/work/project/Android/Application_decoupling2/app/build/intermediates/javac/expDebug/compileExpDebugJavaWithJavac/classes/com/wbh/decoupling/Test.class, status: REMOVED

至此,讲完了 Transform 的基本用法,而且 WTransform 现在实现的逻辑只是拷贝输入的代码到输出。

4.8 获取注解生成的类

在遍历 jar 文件的时候,同时遍历获取其中以 com.wbh.decoupling.generate 为开头的所有类并收集起来:

private static final String TARGET = ‘com/wbh/decoupling/generate/’

private static List sTargetList = new ArrayList<>()

private static void transformJar(JarInput jarInput, TransformOutputProvider outputProvider, boolean isIncremental) {

File dstFile = outputProvider.getContentLocation(

jarInput.getName(),

jarInput.getContentTypes(),

jarInput.getScopes(),

Format.JAR)

JarFile jarFile = new JarFile(jarInput.file)

println(jarFile.name)

jarFile.entries().each {

if (it.name.contains(TARGET)) {

sTargetList.add(it.name)

}

}

// … …

}

在收集到注解生成的类文件后,然后通过生成固定的class文件,在这个类文件里去调用这几个注解生成的类文件:

// com.wbh.decoupling.generate

public class AsmCrossCompileUtils {

public static void init() {

CardManager_becf3fc7606c9b461025f1def7ff27ac.init();

CardManager_dc2db21188334cfca97494d99700395.init();

}

}

接下来的主要任务就是生成这个类文件。

4.9 ASM 的引入

ASM 是一个 Java 字节码操控框架,我们通过这个框架来生成字节码文件,使用这个框架需要对字节码规范有一定的了解,这里不做详解。

为了方便,这里需要用到一个studio插件ASM Bytecode Outline,这个插件可以用来查看一个类文件编译后的字节码文件,以及生成这个字节码文件的 ASM 代码,安装过程略。

我们先自己编写AsmCrossCompileUtils.java这个类,然后右键选择Show Bytecode outline 屏幕快照 2019-10-04 上午12.27.36.jpg

然后会对这个文件自动编译生成字节码文件:

我们将生成的 ASM 代码复制到 WPlugin 里,同时还需要引入依赖 implementation 'org.ow2.asm:asm:6.0'

import java.util.*;

import org.objectweb.asm.*;

public class AsmCrossCompileUtilsDump implements Opcodes {

public static byte[] dump() throws Exception {

ClassWriter cw = new ClassWriter(0);

FieldVisitor fv;

MethodVisitor mv;

AnnotationVisitor av0;

cw.visit(V1_7, ACC_PUBLIC + ACC_SUPER, “com/wbh/decoupling/generate/AsmCrossCompileUtils”, null, “java/lang/Object”, null);

cw.visitSource(“AsmCrossCompileUtils.java”, null);

{

mv = cw.visitMethod(ACC_PUBLIC, “”, “()V”, null, null);

mv.visitCode();

Label l0 = new Label();

mv.visitLabel(l0);

mv.visitLineNumber(3, l0);

mv.visitVarInsn(ALOAD, 0);

mv.visitMethodInsn(INVOKESPECIAL, “java/lang/Object”, “”, “()V”, false);

mv.visitInsn(RETURN);

Label l1 = new Label();

mv.visitLabel(l1);

mv.visitLocalVariable(“this”, “Lcom/wbh/decoupling/generate/AsmCrossCompileUtils;”, null, l0, l1, 0);

mv.visitMaxs(1, 1);

mv.visitEnd();

}

{

mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, “init”, “()V”, null, null);

mv.visitCode();

Label l0 = new Label();

mv.visitLabel(l0);

mv.visitLineNumber(6, l0);

mv.visitMethodInsn(INVOKESTATIC, “com/wbh/decoupling/generate/CardManager_becf3fc7606c9b461025f1def7ff27ac”, “init”, “()V”, false);

Label l1 = new Label();

mv.visitLabel(l1);

mv.visitLineNumber(7, l1);

mv.visitMethodInsn(INVOKESTATIC, “com/wbh/decoupling/generate/CardManager_dc2db21188334cfca97494d99700395”, “init”, “()V”, false);

Label l2 = new Label();

mv.visitLabel(l2);

mv.visitLineNumber(8, l2);

mv.visitInsn(RETURN);

mv.visitMaxs(0, 0);

mv.visitEnd();

}

cw.visitEnd();

return cw.toByteArray();

}

}

分析下这份 ASM 代码,dump() 方法里的第一块代码块是在生成目标类 AsmCrossCompileUtils 的构造器字节码,第二块代码块是在生成 init() 方法,我们整理下这份代码:

class AsmCrossCompileUtilsDump implements Opcodes {

private static final String CLASS_PATH = ‘com/wbh/decoupling/generate/’

private static final String CLASS_SIMPLE_NAME = ‘AsmCrossCompileUtils’

private static final String CLASS_FULL_NAME = CLASS_PATH + CLASS_SIMPLE_NAME

private static final String JAVA_FILE_NAME = CLASS_SIMPLE_NAME + ‘.java’

private static final String CLASS_FILE_NAME = CLASS_SIMPLE_NAME + ‘.class’

static void injectClass(TransformOutputProvider outputProvider, List list) {

File dstFile = outputProvider.getContentLocation(

CLASS_SIMPLE_NAME,

Collections.singleton(QualifiedContent.DefaultContentType.CLASSES),

Collections.singleton(QualifiedContent.Scope.PROJECT),

Format.DIRECTORY)

byte[] bytes = dump(list)

File file = new File(dstFile.absolutePath + File.separator + CLASS_PATH)

file.mkdirs()

FileOutputStream fos = new FileOutputStream(new File(file, CLASS_FILE_NAME))

fos.write(bytes)

}

// ASM 框架生成类 AsmCrossCompileUtils 的字节码

private static byte[] dump(List list) throws Exception {

ClassWriter cw = new ClassWriter(0)

cw.visit(V1_7, ACC_PUBLIC + ACC_SUPER, CLASS_FULL_NAME, null, “java/lang/Object”, null)

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

总结

开发是面向对象。我们找工作应该更多是面向面试。哪怕进大厂真的只是去宁螺丝,但你要进去得先学会面试的时候造飞机不是么?

作者13年java转Android开发,在小厂待过,也去过华为,OPPO等,去年四月份进了阿里一直到现在。等大厂待过也面试过很多人。深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。

这里附上上述的技术体系图相关的几十套腾讯、头条、阿里、美团等公司的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。

相信它会给大家带来很多收获:

960页全网最全Android开发笔记

资料太多,全部展示会影响篇幅,暂时就先列举这些部分截图

当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

te[] bytes = dump(list)

File file = new File(dstFile.absolutePath + File.separator + CLASS_PATH)

file.mkdirs()

FileOutputStream fos = new FileOutputStream(new File(file, CLASS_FILE_NAME))

fos.write(bytes)

}

// ASM 框架生成类 AsmCrossCompileUtils 的字节码

private static byte[] dump(List list) throws Exception {

ClassWriter cw = new ClassWriter(0)

cw.visit(V1_7, ACC_PUBLIC + ACC_SUPER, CLASS_FULL_NAME, null, “java/lang/Object”, null)

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-PfESW6s8-1713442963232)]

[外链图片转存中…(img-kID1wmw7-1713442963233)]

[外链图片转存中…(img-Rny58rPv-1713442963234)]

[外链图片转存中…(img-jr6gzyqv-1713442963235)]

[外链图片转存中…(img-uGOqQM26-1713442963236)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

总结

开发是面向对象。我们找工作应该更多是面向面试。哪怕进大厂真的只是去宁螺丝,但你要进去得先学会面试的时候造飞机不是么?

作者13年java转Android开发,在小厂待过,也去过华为,OPPO等,去年四月份进了阿里一直到现在。等大厂待过也面试过很多人。深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。

这里附上上述的技术体系图相关的几十套腾讯、头条、阿里、美团等公司的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。

相信它会给大家带来很多收获:

[外链图片转存中…(img-XaWsKrpO-1713442963237)]

[外链图片转存中…(img-zGIuT9B8-1713442963241)]

资料太多,全部展示会影响篇幅,暂时就先列举这些部分截图

当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值