深入探索 Gradle 自动化构建技术(六、Gradle 插件平台化框架 ByteX 探秘之旅)

public void test1(){
//1. Collection 提供了两个方法 stream() 与 parallelStream()
List list = new ArrayList<>();
Stream stream = list.stream(); //获取一个顺序流
Stream parallelStream = list.parallelStream(); //获取一个并行流

//2. 通过 Arrays 中的 stream() 获取一个数组流
Integer[] nums = new Integer[10];
Stream stream1 = Arrays.stream(nums);

//3. 通过 Stream 类中静态方法 of()
Stream stream2 = Stream.of(1,2,3,4,5,6);

//4. 创建无限流
//迭代
Stream stream3 = Stream.iterate(0, (x) -> x + 2).limit(10);
stream3.forEach(System.out::println);

//生成
Stream stream4 = Stream.generate(Math::random).limit(2);
stream4.forEach(System.out::println);
}

2、中间操作
  • 1)、筛选与切片
  • filter:接收 Lambda ,从流中排除某些元素
  • limit:截断流,使其元素不超过给定数量
  • skip(n):跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补
  • distinct:筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素
  • 2)、映射
  • map:接收 Lambda ,将元素转换成其他形式或提取信息。接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素,类似于 python、go 的 map 语法
  • flatMap:接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
  • 3)、排序
  • sorted():自然排序
  • sorted(Comparator com):定制排序

这里,我们给出一些常见的使用示例,如下所示:

1、有个数组 Integer[] ary = {1,2,3,4,5,6,7,8,9,10},取出中间的第三到第五个元素。

List collect = Arrays.stream(ary).skip(2).limit(3).collect(Collectors.toList());

2、有个数组 Integer[] ary = {1,2,2,3,4,5,6,6,7,8,8,9,10},取出里面的偶数,并去除重复。

List list = Arrays.stream(ary).filter(x -> x % 2 == 0).distinct().collect(Collectors.toList());
Set integerSet = Arrays.stream(ary).filter(x -> x % 2 == 0).collect(Collectors.toSet());

3、有个二维数组,要求把数组组合成一个一维数组,并排序(1,2,3,4,5……12)

Integer[][] ary = {{3,8,4,7,5}, {9,1,6,2}, {0,10,12,11} };

Arrays.stream(ary).flatMap(item->Arrays.stream(item)).sorted().forEach(System.out::println);

3)、终止操作

终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是 void 。

1、查找与匹配
接口说明
allMatch(Predicate p)检查是否匹配所有元素
anyMatch(Predicate p)检查是否至少匹配一个元素
noneMatch(Predicate p)检查是否没有匹配所有元素
findFirst()返回第一个元素
findAny()返回当前流中的任意元素
count()返回流中元素总数
max(Comparator c)返回流中最大值
min(Comparator c)返回流中最小值
forEach(Consumer c)迭代
2、归约

reduce(T iden, BinaryOperator b) 可以将流中元素反复结合起来,得到一个值。返回 Optional。例如使用 reduce 来求所有人员学生的总分的示例代码如下所示:

Integer all = persons.stream().map(Person::getScore).reduce((integer, integer2) -> integer + integer2).get()

3、收集
  • collect(Collector c) 将流转换为其他形式。它接收一个 Collector 接口的实现,用于给 Stream 中元素做汇总的方法
  • Collector 接口中方法的实现决定了如何对流执行收集操作(如收集到 List、Set、Map)
  • Collectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例

收集相关的 Stream API 与其实例代码如下所示:

  • 1)、toList List 把流中元素收集到 List:

List emps= list.stream().collect(Collectors.toList());

  • 2)、toSet Set 把流中元素收集 到Set:

Set emps= list.stream().collect(Collectors.toSet());

  • 3)、toCollection Collection 把流中元素收集到创建的集合:

Collection emps=list.stream().collect(Collectors.toCollection(ArrayList::new));

  • 4)、counting Long 计算流中元素的个数:

long count = list.stream().collect(Collectors.counting());

  • 5)、summing Int Integer 对流中元素的整数属性求和:

int total=list.stream().collect(Collectors.summingInt(Person::getAge));

  • 6)、averaging Int Double 计算流中元素 Integer 属性的平均值:

double avg= list.stream().collect(Collectors.averagingInt(Person::getAge));

  • 7)、summarizingInt IntSummaryStatistics 收集流中 Integer 属性的统计值。如平均值:

Int SummaryStatisticsiss= list.stream().collect(Collectors.summarizingInt(Person::getAge));

  • 8)、joining String 连接流中每个字符串:

String str= list.stream().map(Person::getName).collect(Collectors.joining());

  • 9)、maxBy Optional 根据比较器选择最大值:

Optional max= list.stream().collect(Collectors.maxBy(comparingInt(Person::getSalary)));

  • 10)、minBy Optional 根据比较器选择最小值:

Optional min = list.stream().collect(Collectors.minBy(comparingInt(Person::getSalary)));

  • 11)、reducing 归约产生的类型,从一个作为累加器的初始值开始,利用 BinaryOperator 与流中元素逐个结合,从而归约成单个值:

int total=list.stream().collect(Collectors.reducing(0, Person::getSalary, Integer::sum));

  • 12)、collectingAndThen 转换函数返回的类型,包裹另一个收集器,对其结果转换函数

int how= list.stream().collect(Collectors.collectingAndThen(Collectors.toList(), List::size));

  • 13)、groupingBy Map<K, List> 根据某属性值对流分组,属性为 K,结果为 V:

Map<Person.Status, List> map= list.stream().collect(Collectors.groupingBy(Person::getStatus));

  • 14)、partitioningBy Map<Boolean, List> 根 据true 或 false 进行分区:

Map<Boolean,List>vd= list.stream().collect(Collectors.partitioningBy(Person::getManage));

4、终止操作练习案例
  • 1)、取出Person对象的所有名字,放到 List 集合中:

List collect2 = persons.stream().map(Person::getName).collect(Collectors.toList());

  • 2、求 Person 对象集合的分数的平均分、总分、最高分,最低分,分数的个数:

IntSummaryStatistics collect = persons.stream().collect(Collectors.summarizingInt(Person::getScore));
System.out.println(collect);

  • 3、根据成绩分组,及格的放一组,不及格的放另外一组:

Map<Boolean, List> collect1 = persons.stream().collect(Collectors.partitioningBy(person -> person.getScore() >= 60));
System.out.println(collect1);

  • 4、统计 aa.txt 里面的单词数:

public static void main(String[] args) throws IOException {
InputStream resourceAsStream = Person.class.getClassLoader().getResourceAsStream(“aa.txt”);
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(resourceAsStream));
bufferedReader.lines().flatMap(x->Stream.of(x.split(" “))).sorted().collect(Collectors.groupingBy(String::toString)).forEach((a,b)-> System.out.println(a+”:"+b.size()));
bufferedReader.close();
}

4、复杂泛型

1)、泛型是什么?

泛型,即 “参数化类型”。就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。

2)、泛型的好处

  • 适用于多种数据类型执行相同的代码
  • 泛型中的类型在使用时指定,不需要强制类型转换

3)、泛型类和泛型接口、泛型方法

泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。而这种参数类型可以用在类、接口和方法中,分别被称为 泛型类、泛型接口、泛型方法

泛型类

引入一个类型变量T(其他大写字母都可以,不过常用的就是T,E,K,V等等),并且用<>括起来,并放在类名的后面。泛型类是允许有多个类型变量的。常见的示例代码如下所示:

public class NormalGeneric {
private K data;

public NormalGeneric() {
}

public NormalGeneric(K data) {
this.data = data;
}

public K getData() {
return data;
}

public void setData(K data) {
this.data = data;
}
}

public class NormalGeneric2<T,K> {
private T data;
private K result;

public NormalGeneric2() {
}

public NormalGeneric2(T data) {
this();
this.data = data;
}

public NormalGeneric2(T data, K result) {
this.data = data;
this.result = result;
}

public T getData() {
return data;
}

public void setData(T data) {
this.data = data;
}

public K getResult() {
return result;
}

public void setResult(K result) {
this.result = result;
}
}

泛型接口

泛型接口与泛型类的定义基本相同。示例代码如下所示:

public interface Genertor {
public T next();
}

但是,实现泛型接口的类,有两种实现方法

1、未传入泛型实参

在 new 出类的实例时,需要指定具体类型:

public class ImplGenertor implements Genertor {
@Override
public T next() {
return null;
}
}

2、传入泛型实参

在 new 出类的实例时,和普通的类没区别。

public class ImplGenertor2 implements Genertor {
@Override
public String next() {
return null;
}
}

泛型方法

泛型方法的 定义在 修饰符与返回值 的中间。示例代码如下所示:

public T genericMethod(T…a){
return a[a.length/2];
}

泛型方法,是在调用方法的时候指明泛型的具体类型,泛型方法可以在任何地方和任何场景中使用,包括普通类和泛型类。

泛型类中定义的普通方法和泛型方法的区别

在普通方法中:

// 虽然在方法中使用了泛型,但是这并不是一个泛型方法。
// 这只是类中一个普通的成员方法,只不过他的返回值是在声明泛型类已经声明过的泛型。
// 所以在这个方法中才可以继续使用 T 这个泛型。
public T getKey(){
return key;
}

在泛型方法中:

/**

  • 这才是一个真正的泛型方法。
  • 首先在 public 与返回值之间的 必不可少,这表明这是一个泛型方法,并且声明了一个泛型 T
  • 这个 T 可以出现在这个泛型方法的任意位置,泛型的数量也可以为任意多个。
    */
    public <T,K> K showKeyName(Generic container){
    // …
    }

4)、限定类型变量

public class ClassBorder {

}

public class GenericRaw<T extends ArrayList&Comparable> {

}

- <T extends Comparable>T 表示应该绑定类型的子类型,Comparable 表示绑定类型,子类型和绑定类型可以是类也可以是接口

  • extends 左右都允许有多个,如 T,V extends Comparable&Serializable
  • 注意限定类型中,只允许有一个类,而且如果有类,这个类必须是限定列表的第一个
  • 限定类型变量既可以用在泛型方法上也可以用在泛型类上

5)、泛型中的约束和局限性

  • 1、不能用基本类型实例化类型参数。
  • 2、运行时类型查询只适用于原始类型。
  • 3、泛型类的静态上下文中类型变量失效:不能在静态域或方法中引用类型变量。因为泛型是要在对象创建的时候才知道是什么类型的,而对象创建的代码执行先后顺序是 static 的部分,然后才是构造函数等等。所以在对象初始化之前 static 的部分已经执行了,如果你在静态部分引用泛型,那么毫无疑问虚拟机根本不知道是什么东西,因为这个时候类还没有初始化。
  • 4、不能创建参数化类型的数组,但是可以定义参数化类型的数组。
  • 5、不能实例化类型变量。
  • 6、不能使用 try-catch 捕获泛型类的实例。

6)、泛型类型的继承规则

泛型类可以继承或者扩展其他泛型类,比如 List 和 ArrayList:

private static class ExtendPair extends Pair{

}

7)、通配符类型

  • ?extends X表示类型的上界,类型参数是 X 的子类
  • ?super X表示类型的下界,类型参数是 X 的超类
?extends X

如果其中提供了 get 和 set 类型参数变量的方法的话,set 方法是不允许被调用的,会出现编译错误,而 get 方法则没问题。

?extends X 表示类型的上界,类型参数是 X 的子类,那么可以肯定的说,get 方法返回的一定是个 X(不管是 X 或者 X 的子类)编译器是可以确定知道的。但是 set 方法只知道传入的是个 X,至于具体是 X 的哪个子类,是不知道的。

因此,?extends X 主要用于安全地访问数据,可以访问 X 及其子类型,并且不能写入非 null 的数据

?super X

如果其中提供了 get 和 set 类型参数变量的方法的话,set 方法可以被调用,且能传入的参数只能是 X 或者 X 的子类。而 get 方法只会返回一个 Object 类型的值。

? super X 表示类型的下界,类型参数是 X 的超类(包括 X 本身),那么可以肯定的说,get 方法返回的一定是个 X 的超类,那么到底是哪个超类?不知道,但是可以肯定的说,Object 一定是它的超类,所以 get 方法返回 Object。编译器是可以确定知道的。对于 set 方法来说,编译器不知道它需要的确切类型,但是 X 和 X 的子类可以安全的转型为 X。

因此,?super X 主要用于安全地写入数据,可以写入 X 及其子类型

无限定的通配符 ?

表示对类型没有什么限制,可以把 ?看成所有类型的父类,如 ArrayList<?>

8)、虚拟机是如何实现泛型的?

泛型思想早在 C++ 语言的模板(Template)中就开始生根发芽,在 Java 语言处于还没有出现泛型的版本时,只能通过 Object 是所有类型的父类和类型强制转换两个特点的配合来实现类型泛化

由于 Java 语言里面所有的类型都继承于 java.lang.Object,所以 Object 转型成任何对象都是有可能的。但是也因为有无限的可能性,就只有程序员和运行期的虚拟机才知道这个 Object 到底是个什么类型的对象。在编译期间,编译器无法检查这个 Object 的强制转型是否成功,如果仅仅依赖程序员去保障这项操作的正确性,许多 ClassCastException 的风险就会转嫁到程序运行期之中。

此外,泛型技术在 C#/C++ 和 Java 之中的使用方式看似相同,但实现上却有着根本性的分歧,C# 里面的泛型无论在程序源码中、编译后的 IL 中(Intermediate Language,中间语言,这时候泛型是一个占位符),或是运行期的 CLR 中,都是切实存在的,List<int> 与 List<String> 就是两个不同的类型,它们在系统运行期生成,有自己的虚方法表和类型数据,这种实现称为类型膨胀,基于这种方法实现的泛型称为真实泛型

Java 语言中的泛型则不一样,它只在程序源码中存在,在编译后的字节码文件中,就已经替换为原来的原生类型(Raw Type,也称为裸类型)了,并且在相应的地方插入了强制转型代码,因此,对于运行期的 Java 语言来说,ArrayList<int> 与 ArrayList<String> 就是同一个类,所以 泛型技术实际上是 Java 语言的一颗语法糖,Java 语言中的泛型实现方法称为类型擦除,基于这种方法实现的泛型称为伪泛型。 将一段 Java 代码编译成 Class 文件,然后再用字节码反编译工具进行反编译后,将会发现泛型都不见了,程序又变回了 Java 泛型出现之前的写法,泛型类型都变回了原生类型。

由于 Java 泛型的引入,各种场景(虚拟机解析、反射等)下的方法调用都可能对原有的基础产生影响和新的需求,如在泛型类中如何获取传入的参数化类型等。因此,JCP 组织对虚拟机规范做出了相应的修改,引入了诸如 Signature、LocalVariableTypeTable 等新的属性用于解决伴随泛型而来的参数类型的识别问题,Signature 是其中最重要的一项属性,它的作用就是存储一个方法在字节码层面的特征签名,这个属性中保存的参数类型并不是原生类型,而是包括了参数化类型的信息。修改后的虚拟机规范要求所有能识别 49.0 以上版本的 Class 文件的虚拟机都要能正确地识别 Signature 参数。

最后,从 Signature 属性的出现我们还可以得出结论,擦除法所谓的擦除,仅仅是对方法的 Code 属性中的字节码进行擦除,实际上元数据中还是保留了泛型信息,这也是我们能通过反射手段取得参数化类型的根本依据

二、初识 ByteX

ByteX 使用了纯 Java 来编写源码,它是一个基于 Gradle transform api 和 ASM 的字节码插桩平台。

调试:gradle clean :example:assembleRelease -Dorg.gradle.debug=true --no-daemon

1、优势

  • 1)、自动集成到其它宿主和插件一起整合为一个单独的 MainTransformFlow,结合 class 文件多线程并发处理,避免了打包的额外时间呈线性增长
  • 2)、插件、宿主之间完全解耦,便于协同开发
  • 3)、common module 提供通用的代码复用,每个插件只需专注自身的字节码插桩逻辑

2、MainTransformFlow 基本流程

在 MainTransformFlow implements MainProcessHandler 常规处理过程,会遍历两次工程构建中的所有 class。

  • 1)、第一次,遍历 traverse 与 traverseAndroidJar 过程,以形成完整的类图。
  • 2)、第二次,执行 transform:再遍历一次工程中所有的构建产物,并对 class 文件做处理后输出。

3、如何自定义独立的 TransformFlow?

重写 IPlugin 的 provideTransformFlow 即可。

4、类图对象

context.getClassGraph() 获取类图对象,两个 TransformFlow 的类图是隔离的。

5、MainProcessHandler

  • 通过复写 process 方法,注册自己的 FlieProcessor 来处理。
  • FileProcessor 采用了责任链模式,每个 class 文件都会流经一系列的 FileProcessor 来处理。

6、IPlugin.hookTransformName()

使用 反射 Hook 方式 将 Transform 注册到 proguard 之后

三、ByteX 插件平台构建流程探秘

添加 apply plugin: ‘bytex’ 之后,bytex 可以在 Gradle 的构建流程中起作用了。这里的插件 id 为 bytex,我们找到 bytex.properties 文件,查看里面映射的实现类,如下所示:

implementation-class=com.ss.android.ugc.bytex.base.ByteXPlugin

可以看到,bytex 的实现类为 ByteXPlugin,其源码如下所示:

public class ByteXPlugin implements Plugin {
@Override
public void apply(@NotNull Project project) {
// 1
AppExtension android = project.getExtensions().getByType(AppExtension.class);
// 2
ByteXExtension extension = project.getExtensions().create(“ByteX”, ByteXExtension.class);
// 3
android.registerTransform(new ByteXTransform(new Context(project, android, extension)));
}
}

首先,注释1处,获取 Android 为 App 提供的扩展属性 AppExtension 实例。然后,在注释2处,获取 ByteX 自身创建的扩展属性 ByteXExtension 实例。最后,在注释3处,注册 ByteXTransform 实例。ByteXTransform 继承了抽象类 CommonTransform,其实现了关键的 transform 方法,其实现源码如下所示:

@Override
public final void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
super.transform(transformInvocation);
// 1、如果不是增量模式,则清楚输出目录的文件。
if (!transformInvocation.isI​
ncremental()) {
transformInvocation.getOutputProvider().deleteAll();
}
// 2、获取 transformContext 实例。
TransformContext transformContext = getTransformContext(transformInvocation);
// 3、初始化 HtmlReporter(生成 ByteX 构建产生日志的 HTML 文件)
init(transformContext);
// 4、过滤掉没有打开插件开关的 plugin。
List plugins = getPlugins().stream().filter(p -> p.enable(transformContext)).collect(Collectors.toList());
Timer timer = new Timer();
// 5、创建一个 transformEngine 实例。
TransformEngine transformEngine = new TransformEngine(transformContext);
try {
if (!plugins.isEmpty()) {
// 6、使用 PriorityQueue 对每一个 TransformFlow 进行优先级排序(在这里添加的是与之对应的实现类 MainTransformFlow)。
Queue flowSet = new PriorityQueue<>((o1, o2) -> o2.getPriority() - o1.getPriority());
MainTransformFlow commonFlow = new MainTransformFlow(transformEngine);
flowSet.add(commonFlow);
for (int i = 0; i < plugins.size(); i++) {
// 7、给每一个 Plugin 注册 MainTransformFlow,其实质是将每一个 Plugin 的 MainProcessHandler 添加到 MainTransformFlow 中的 handlers 列表中。
IPlugin plugin = plugins.get(i);
TransformFlow flow = plugin.registerTransformFlow(commonFlow, transformContext);
if (!flowSet.contains(flow)) {
flowSet.add(flow);
}
}
while (!flowSet.isEmpty()) {
TransformFlow flow = flowSet.poll();
if (flow != null) {
if (flowSet.size() == 0) {
flow.asTail();
}
// 8、按指定优先级执行每一个 TransformFlow 的 run 方法,默认只有一个 MainTransformFlow 实例。
flow.run();
// 9、获取流中的 graph 类图对象并清除。
Graph graph = flow.getClassGraph();
if (graph != null) {
//clear the class diagram.we won’t use it anymore
graph.clear();
}
}
}
} else {
transformEngine.skip();
}
// 10
afterTransform(transformInvocation);
} catch (Throwable throwable) {
LevelLog.sDefaultLogger.e(throwable.getClass().getName(), throwable);
throw throwable;
} finally {
for (IPlugin plugin : plugins) {
try {
plugin.afterExecute();
} catch (Throwable throwable) {
LevelLog.sDefaultLogger.e(“do afterExecute”, throwable);
}
}
transformContext.release();
release();
timer.record(“Total cost time = [%s ms]”);
if (BooleanProperty.ENABLE_HTML_LOG.value()) {
HtmlReporter.getInstance().createHtmlReporter(getName());
HtmlReporter.getInstance().reset();
}
}
}

在注释7处,调用了 plugin.registerTransformFlow 方法,其源码如下所示:

@Nonnull
@Override
public final TransformFlow registerTransformFlow(@Nonnull MainTransformFlow mainFlow, @Nonnull TransformContext transformContext) {
if (transformFlow == null) {
transformFlow = provideTransformFlow(mainFlow, transformContext);
if (transformFlow == null) {
throw new RuntimeException(“TransformFlow can not be null.”);
}
}
return transformFlow;
}

这里继续调用了 provideTransformFlow 方法,其源码如下所示:

/**

  • create a new transformFlow or just return mainFlow and append a handler.
  • It will be called by {@link IPlugin#registerTransformFlow(MainTransformFlow, TransformContext)} when
  • handle start.
  • @param mainFlow main TransformFlow
  • @param transformContext handle context
  • @return return a new TransformFlow object if you want make a new flow for current plugin
    */
    protected TransformFlow provideTransformFlow(@Nonnull MainTransformFlow mainFlow, @Nonnull TransformContext transformContext) {
    return mainFlow.appendHandler(this);
    }

可以看到,通过调用 mainFlow.appendHandler(this) 方法将每一个 Plugin 的 MainProcessHandler 添加到 MainTransformFlow 中的 handlers 列表之中。

在注释8处,按指定优先级执行了每一个 TransformFlow 的 run 方法,默认只有一个 MainTransformFlow 实例。我们看到了 MianTransformFlow 的 run 方法:

@Override
public void run() throws IOException, InterruptedException {
try {
// 1
beginRun();
// 2
runTransform();
} finally {
// 3
endRun();
}
}

首先,在注释1出,调用了 beginRun 方法,其实现如下:

// AbsTransformFlow
protected void beginRun() {
transformEngine.beginRun();
}

// TransformEngine
public void beginRun(){
context.markRunningState(false);
}

// TransformContext
private final AtomicBoolean running = new AtomicBoolean(false);

void markRunningState(boolean running) {
this.running.set(running);
}

最后,在 TransformContext 实例中使用了一个 AtomicBoolean 实例标记 MainTransformFlow 是否正在运行中。

然后,在注释2处执行了 runTransform 方法,这里就是真正执行 transform 的地方,其源码如下所示:

private void runTransform() throws IOException, InterruptedException {
if (handlers.isEmpty()) return;
Timer timer = new Timer();
timer.startRecord(“PRE_PROCESS”);
timer.startRecord(“INIT”);
// 1、初始化 handlers 列表中的每一个 handler。
for (MainProcessHandler handler : handlers) {
handler.init(transformEngine);
}
timer.stopRecord(“INIT”, “Process init cost time = [%s ms]”);
// 如果不是 跳过 traverse 仅仅只执行 Transform 方法时,才执行 traverse 过程。
if (!isOnePassEnough()) {
if (!handlers.isEmpty() && context.isIncremental()) {
timer.startRecord(“TRAVERSE_INCREMENTAL”);
// 2、如果是 增量模式,则执行 traverseArtifactOnly(仅仅增量遍历产物)调用每一个 plugin 的对应的 MainProcessHandler 的 traverseIncremental 方法。这里最终会调用 ClassFileAnalyzer.handle 方法进行遍历分发操作。
traverseArtifactOnly(getProcessors(Process.TRAVERSE_INCREMENTAL, new ClassFileAnalyzer(context, Process.TRAVERSE_INCREMENTAL, null, handlers)));
timer.stopRecord(“TRAVERSE_INCREMENTAL”, “Process project all .class files cost time = [%s ms]”);
}
handlers.forEach(plugin -> plugin.beforeTraverse(transformEngine));
timer.startRecord(“LOADCACHE”);
// 3、创建一个 CachedGraphBuilder 对象:能够缓存 类图 的 类图构建者对象。
GraphBuilder graphBuilder = new CachedGraphBuilder(context.getGraphCache(), context.isIncremental(), context.shouldSaveCache());
if (context.isIncremental() && !graphBuilder.isCacheValid()) {
// 4、如果是增量更新 && graphBuilder 的缓存失效则直接请求非增量运行。
context.requestNotIncremental();
}
timer.stopRecord(“LOADCACHE”, “Process loading cache cost time = [%s ms]”);
// 5、内部会调用 running.set(true) 来标记正在运行的状态。
running();
if (!handlers.isEmpty()) {
timer.startRecord(“PROJECT_CLASS”);
// 6、执行 traverseArtifactOnly(遍历产物)调用每一个 plugin 的对应的 MainProcessHandler 的 traverse 方法,这里最终会调用 ClassFileAnalyzer.handle 方法进行遍历分发操作。
traverseArtifactOnly(getProcessors(Process.TRAVERSE, new ClassFileAnalyzer(context, Process.TRAVERSE, graphBuilder, handlers)));
timer.stopRecord(“PROJECT_CLASS”, “Process project all .class files cost time = [%s ms]”);
}
if (!handlers.isEmpty()) {
timer.startRecord(“ANDROID”);
// 7、仅仅遍历 Android.jar
traverseAndroidJarOnly(getProcessors(Process.TRAVERSE_ANDROID, new ClassFileAnalyzer(context, Process.TRAVERSE_ANDROID, graphBuilder, handlers)));
timer.stopRecord(“ANDROID”, “Process android jar cost time = [%s ms]”);
}
timer.startRecord(“SAVECACHE”);
// 8、构建 mClassGraph 类图实例。
mClassGraph = graphBuilder.build();
timer.stopRecord(“SAVECACHE”, “Process saving cache cost time = [%s ms]”);
}
timer.stopRecord(“PRE_PROCESS”, “Collect info cost time = [%s ms]”);
if (!handlers.isEmpty()) {
timer.startRecord(“PROCESS”);
// 9、遍历执行每一个 plugin 的 transform 方法。
transform(getProcessors(Process.TRANSFORM, new ClassFileTransformer(handlers, needPreVerify(), needVerify())));
timer.stopRecord(“PROCESS”, “Transform cost time = [%s ms]”);
}
}

首先,在注释1处,遍历调用了每一个 MainProcessHandler 的 init 方法,它是用于 transform 开始前的初始化实现方法。

MainProcessHandler 接口的 init 方法是一个 default 方法,里面直接调用了每一个 pluign 实现的 init 方法(如果 plugin 没有实现,则仅仅调用 CommonPlugin 实现的 init 方法:这里通常是用于把不需要处理的文件添加到 mWhiteList 列表),这里可以做一些 plugin 的准备工作。

1、仅仅遍历产物

traverseArtifactOnly(getProcessors(Process.TRAVERSE, new ClassFileAnalyzer(context, Process.TRAVERSE, graphBuilder, handlers)));

getProcessors 方法的源码如下所示:

private FileProcessor[] getProcessors(Process process, FileHandler fileHandler) {
List processors = handlers.stream()
.flatMap((Function<MainProcessHandler, Stream>) handler -> handler.process(process).stream())
.collect(Collectors.toList());
switch (process) {
case TRAVERSE_INCREMENTAL:
processors.add(0, new FilterFileProcessor(fileData -> fileData.getStatus() != Status.NOTCHANGED));
processors.add(new IncrementalFileProcessor(handlers, ClassFileProcessor.newInstance(fileHandler)));
break;
case TRAVERSE:
case TRAVERSE_ANDROID:
case TRANSFORM:
processors.add(ClassFileProcessor.newInstance(fileHandler));
processors.add(0, new FilterFileProcessor(fileData -> fileData.getStatus() != Status.NOTCHANGED && fileData.getStatus() != Status.REMOVED))
break;
default:
throw new RuntimeException(“Unknow Process:” + process);
}
return processors.toArray(new FileProcessor[0]);
}

这里的 processor 的添加由 增量 进行界定,具体的处理标准如下:

  • TRAVERSE_INCREMENTAL
  • FilterFileProcessor按照不同的过程过滤掉不需要的 FileData
  • IncrementalFileProcessor用于进行 增量文件的处理
  • TRAVERSE/TRAVERSE_ANDROID/TRANSFORM:
  • FilterFileProcessor按照不同的过程过滤掉不需要的 FileData
  • ClassFileProcessor用于处理 .class 文件

2、仅仅遍历 Android Jar 包

traverseAndroidJarOnly(getProcessors(Process.TRAVERSE_ANDROID, new ClassFileAnalyzer(context, Process.TRAVERSE_ANDROID, graphBuilder, handlers)));

3、构建 mClassGraph 类图对象

mClassGraph = graphBuilder.build();

4、执行 Transform

transform(getProcessors(Process.TRANSFORM, new ClassFileTransformer(handlers, needPreVerify(), needVerify())));

transform 的源码如下所示:

// AbsTransformFlow 类中
protected AbsTransformFlow transform(FileProcessor… processors) throws IOException, InterruptedException {
beforeTransform(transformEngine);
transformEngine.transform(isLast, processors);
afterTransform(transformEngine);
return this;
}

1)、beforeTransform(transformEngine)

// MainTransformFlow
@Override
protected AbsTransformFlow beforeTransform(TransformEngine transformEngine) {
// 1
handlers.forEach(plugin -> plugin.beforeTransform(transformEngine));
return this;
}

注释1处,遍历执行 每一个 plugin 的 beforeTransform 方法做一些自身 transform 前的准备工作。

2)、transformEngine.transform(isLast, processors)

// TranformEngine
public void transform(boolean isLast, FileProcessor… processors) {
Schedulers.FORKJOINPOOL().invoke(new PerformTransformTask(context.allFiles(), getProcessorList(processors), isLast, context));
}

Shedulers.FORKJOINPOOL() 方法的源码如下所示:

public class Schedulers {
private static final int cpuCount = Runtime.getRuntime().availableProcessors();
private final static ExecutorService IO = new ThreadPoolExecutor(0, cpuCount * 3,
30L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>());

// 1
private static final ExecutorService COMPUTATION = Executors.newWorkStealingPool(cpuCount);

public static Worker IO() {
return new Worker(IO);
}

public static Worker COMPUTATION() {
return new Worker(COMPUTATION);
}

public static ForkJoinPool FORKJOINPOOL() {
return (ForkJoinPool) COMPUTATION;
}
}

可以看到,最终是执行 Executors.newWorkStealingPool(cpuCount) 方法生成了一个 ForkJoinPool 实例。

ForkJoinPool 与 ThreadPoolExecutor 是属于平级关系,ForkJoinPool 线程池是为了实现“分治法”这一思想而创建的,通过把大任务拆分成小任务,然后再把小任务的结果汇总起来就是最终的结果,和 MapReduce 的思想很类似。除了“分治法”之外,ForkJoinPool 还使用了工作窃取算法,即所有线程均尝试找到并执行已提交的任务,或是通过其他任务创建的子任务。有了它我们就可以尽量避免一个线程执行完自己的任务后“无所事事”的情况。

然后这里会回调 PerformTransformTask 实例的 compute 方法,源码如下所示:

@Override
protected void compute() {
if (outputFile) {
// 1、如果是最后一个 TransformFlow,则递归调用所有的 FileTransformTask。
List tasks = source.map(cache -> new FileTransformTask(context, cache, processors)).collect(Collectors.toList());
// 2、对于Fork/Join模式,假如Pool里面线程数量是固定的,那么调用子任务的fork方法相当于A先分工给B,然后A当监工不干活,B去完成A交代的任务。所以上面的模式相当于浪费了一个线程。那么如果使用invokeAll相当于A分工给B后,A和B都去完成工作。这样可以更好的利用线程池,缩短执行的时间。
invokeAll(tasks);
} else {
// 3、、递归调用 FileTransformTask
PerformTraverseTask traverseTask = new PerformTraverseTask(source, processors);
invokeAll(traverseTask);
}
}

在注释1处,如果是最后一个 TransformFlow,则调用所有的 FileTransformTask。注释2处,对于 Fork/Join 模式,假如 Pool 里面线程数量是固定的,那么调用子任务的 fork 方法相当于 A 先分工给 B,然后 A 当监工不干活,B 去完成 A 交代的任务。所以上面的模式相当于浪费了一个线程。那么如果使用 invokeAll 相当于 A 分工给 B 后,A 和 B 都去完成工作。这样可以更好的利用线程池,缩短执行的时间。注释3处,执行了 ForkJoinTask 的 invokeAll 方法,这里便会回调 compute 方法,源码如下所示:

@Override
protected void compute() {
List tasks = source.map(cache -> new FileTraverseTask(cache, processors)).collect(Collectors.toList());
// 1
invokeAll(tasks);
}

注释1处,继续回调所有的 FileTraverseTask 实例的 compute 方法,源码如下所示:

@Override
protected void compute() {
List tasks = fileCache.stream().map(file -> new TraverseTask(fileCache, file, processors))
.toList().blockingGet();
// 1
invokeAll(tasks);
}

注释1处,继续回调所有的 TraverseTask 实例的 compute 方法,源码如下所示:

@Override
protected void compute() {
try {
Input input = new Input(fileCache.getContent(), file);
ProcessorChain chain = new ProcessorChain(processors, input, 0);
// 1、调用 ProcessorChain 的 proceed 方法。
chain.proceed(input);
} catch (IOException e) {
throw new RuntimeException(e);
}
}

注释1处,调用了 ProcessorChain 的 proceed 方法。源码如下所示:

@Override
public Output proceed(Input input) throws IOException {
if (index >= processors.size()) throw new AssertionError();
// 1
FileProcessor next = processors.get(index);

return next.process(new ProcessorChain(processors, input, index + 1));
}

注释1处,会从 processors 处理器列表中获取第一个处理器—FilterFileProcessor,并调用它的 process 方法,源码如下所示:

@Override
public Output process(Chain chain) throws IOException {
Input input = chain.input();
if (predicate.test(input.getFileData())) {
// 1
return chain.proceed(input);
} else {
return new Output(input.getFileData());
}
}

注释1处,如果有 FileData 的话,则继续调用 chain 的 proceed 方法,内部会继续调用 ClassFileProcessor 的 process 方法,源码如下:

@Override
public Output process(Chain chain) throws IOException {
Input input = chain.input();
FileData fileData = input.getFileData();
if (fileData.getRelativePath().endsWith(“.class”)) {
// 1、如果 fileData 是 .class 文件,则调用 ClassFileTransformer 的 handle 方法进行处理。
handler.handle(fileData);
}
// 2、
return chain.proceed(input);
}

注释1处,如果 fileData 是 .class 文件,则调用 ClassFileTransformer 的 handle 方法进行处理。其源码如下所示:

@Override
public void handle(FileData fileData) {
try {
byte[] raw = fileData.getBytes();
String relativePath = fileData.getRelativePath();
int cwFlags = 0; //compute nothing
int crFlags = 0;
for (MainProcessHandler handler : handlers) {
// 1、设置 ClassWrite 的 flag 的默认值为 ClassWriter.COMPUTE_MAXS。
cwFlags |= handler.flagForClassWriter();
if ((handler.flagForClassReader(Process.TRANSFORM) & ClassReader.EXPAND_FRAMES) == ClassReader.EXPAND_FRAMES) {
crFlags |= ClassReader.EXPAND_FRAMES;
}
}
ClassReader cr = new ClassReader(raw);
ClassWriter cw = new ClassWriter(cwFlags);
ClassVisitorChain chain = getClassVisitorChain(relativePath);
if (needPreVerify) {
// 2、如果需要预校验,则将责任链表头尾部设置为 AsmVerifyClassVisitor 实例。
chain.connect(new AsmVerifyClassVisitor());
}
if (handlers != null && !handlers.isEmpty()) {
for (MainProcessHandler handler : handlers) {
// 3、遍历执行所有 plugin 的 transform。其内部会使用 chain.connect(new ReferCheckClassVisitor(context)) 的方式
if (!handler.transform(relativePath, chain)) {
fileData.delete();
return;
}
}
}
// 4、兼容 ClassNode 处理的模式
ClassNode cn = new SafeClassNode();
chain.append(cn);
chain.accept(cr, crFlags);
for (MainProcessHandler handler : handlers) {

最后

小编这些年深知大多数初中级Android工程师,想要提升自己,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助

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

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人

都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

资料⬅专栏获取
r cr = new ClassReader(raw);
ClassWriter cw = new ClassWriter(cwFlags);
ClassVisitorChain chain = getClassVisitorChain(relativePath);
if (needPreVerify) {
// 2、如果需要预校验,则将责任链表头尾部设置为 AsmVerifyClassVisitor 实例。
chain.connect(new AsmVerifyClassVisitor());
}
if (handlers != null && !handlers.isEmpty()) {
for (MainProcessHandler handler : handlers) {
// 3、遍历执行所有 plugin 的 transform。其内部会使用 chain.connect(new ReferCheckClassVisitor(context)) 的方式
if (!handler.transform(relativePath, chain)) {
fileData.delete();
return;
}
}
}
// 4、兼容 ClassNode 处理的模式
ClassNode cn = new SafeClassNode();
chain.append(cn);
chain.accept(cr, crFlags);
for (MainProcessHandler handler : handlers) {

最后

小编这些年深知大多数初中级Android工程师,想要提升自己,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助

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

[外链图片转存中…(img-FHcsMy7Q-1719112655985)]一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人

都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

资料⬅专栏获取

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/735737.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

使用Spring Boot构建全栈应用程序:从理论到实践

文章目录 引言第一章 项目初始化1.1 使用Spring Initializr生成项目1.2 创建基本项目结构 第二章 后端服务开发2.1 定义实体类2.2 创建Repository接口2.3 实现Service类2.4 创建Controller类 第三章 前端集成3.1 使用Thymeleaf模板引擎3.2 创建前端控制器 第四章 安全配置4.1 S…

AcWing算法基础课笔记——求组合数4

求组合数Ⅳ 用来解决求 C a b C_a^b Cab​的问题&#xff08;没有模运算&#xff09; 解决办法&#xff1a;分解质因数&#xff0c;实现高精度乘法。 C a b a ! b ! ( a − b ) ! C_a^b \frac{a!}{b!(a - b)!} Cab​b!(a−b)!a!​ 其中 a ! a! a!可以用 p p p的倍数来表示…

Linux中web集群-nginx负载均衡及案例

概述 代理&#xff1a;外卖&#xff0c;中介&#xff0c;中间商&#xff0c;用户无法直接做事情&#xff0c;通过中介进行处理 用户–》代理–》节点&#xff0c;后面只有一个节点&#xff0c;一般使用的是nginx代理功能即可&#xff0c;如果是集群就需要使用nginx负载均衡 …

世界名著精选,免费!精选了数百本世界名著,打包带走!

世界名著精选是一款汇集了全球经典文学作品的电子书阅读软件。是一个旨在让经典文学作品更易于接触和阅读的平台。它不仅收录了世界各地的经典名著&#xff0c;还通过现代技术手段&#xff0c;让阅读变得更加便捷和个性化。 软件链接&#xff1a;免费&#xff01;几百种资源&a…

【数据结构与算法】图论 详解

何为完全图、稀疏图、稠密图。 完全图&#xff1a;完全图是一种简单的无向图&#xff0c;其中每对不同的顶点之间都恰好有一条边。对于有n个顶点的完全图&#xff0c;它包含n(n-1)/2条边。在有向图中&#xff0c;如果任意两个顶点之间都存在方向相反的两条边&#xff0c;包含n(…

OA流程节点超时功能

在OA系统中&#xff0c;节点超时功能是一个关键的技术特性&#xff0c;它能够确保流程的顺畅进行&#xff0c;避免任务因为个别环节的延误而影响整体进度。本文将探讨OA系统中节点超时功能的技术实现和优化策略。 参考泛微OA ecology E9 前台设置&#xff1a; 系统管理员在管…

2000年 - 2022年 Fama-French三因子模型数据+代码

Fama-French三因子模型是由著名经济学家尤金法玛&#xff08;Eugene Fama&#xff09;和肯尼斯法兰奇&#xff08;Kenneth French&#xff09;提出的&#xff0c;旨在改进资本资产定价模型&#xff08;CAPM&#xff09;&#xff0c;更全面地解释资产收益率的变化。该模型认为&a…

pytest测试框架pytest-rerunfailures插件重试失败用例

Pytest提供了丰富的插件来扩展其功能&#xff0c;介绍下插件pytest-rerunfailures &#xff0c;用于在测试用例失败时自动重新运行这些测试用例。 pytest-rerunfailures官方显示的python和pytest版本限制&#xff1a; Python 3.8pytest 7.2 或更新版本 此插件可以通过以下可…

CentOS 7 内核 3.10 升级 6.5.2 (RPM 直装 + 源码编译)

方案一 直接基于 RPM 在线升级&#xff08;简单&#xff0c;速度快&#xff09; rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org yum install https://www.elrepo.org/elrepo-release-7.el7.elrepo.noarch.rpm -y # &#xff08;选项一&#xff09;升级最新版内…

【GD32】从零开始学兆易创新32位微处理器——RTC实时时钟+日历例程

1 简介 RTC实时时钟顾名思义作用和墙上挂的时钟差不多&#xff0c;都是用于记录时间和日历&#xff0c;同时也有闹钟的功能。从硬件实现上来说&#xff0c;其实它就是一个特殊的计时器&#xff0c;它内部有一个32位的寄存器用于计时。RTC在低功耗应用中可以说相当重要&#xf…

Linux操作系统段式存储管理、 段页式存储管理

1、段式存储管理 1.1分段 进程的地址空间&#xff1a;按照程序自身的逻辑关系划分为若干个段&#xff0c;每个段都有一个段名&#xff08;在低级语言中&#xff0c;程序员使用段名来编程&#xff09;&#xff0c;每段从0开始编址。内存分配规则&#xff1a;以段为单位进行分配…

Redis-事务-watch-unwatch

文章目录 1、监视key2、提交事务 1、监视key 打开两个窗口&#xff0c;第一个窗口先监视key&#xff0c;然后开始事务&#xff0c;然后再打开第二个窗口&#xff0c;修改balance为0 2、提交事务 此时事务被打断

分布式定时任务系列10:XXL-job源码分析之路由策略

传送门 分布式定时任务系列1&#xff1a;XXL-job安装 分布式定时任务系列2&#xff1a;XXL-job使用 分布式定时任务系列3&#xff1a;任务执行引擎设计 分布式定时任务系列4&#xff1a;任务执行引擎设计续 分布式定时任务系列5&#xff1a;XXL-job中blockingQueue的应用 …

【深度学习驱动流体力学】计算流体力学算例剖析与实现

目录 一.求解器分类汇总压缩性流动求解器(Compressible Flow Solvers):不可压缩流动求解器(Incompressible Flow Solvers):多相流动求解器(Multiphase Flow Solvers):热传递求解器(Heat Transfer Solvers):其他特殊求解器:其他常见求解器:求解器分类:二.求解器案…

黑马苍穹外卖6 清理redis缓存+Spring Cache+购物车的增删改查

缓存菜品 后端服务都去查询数据库&#xff0c;对数据库访问压力增大。 解决方式&#xff1a;使用redis来缓存菜品&#xff0c;用内存比磁盘性能更高。 key :dish_分类id String key “dish_” categoryId; RestController("userDishController") RequestMapping…

Android Studio 安卓手机上实现火柴人动画(Java源代码—Python)

android:layout_marginLeft“88dp” android:layout_marginTop“244dp” android:text“Python” android:textSize“25sp” app:layout_constraintStart_toStartOf“parent” app:layout_constraintTop_toTopOf“parent” /> </androidx.constraintlayout.widget.…

【尚庭公寓SpringBoot + Vue 项目实战】看房预约管理(十三)

【尚庭公寓SpringBoot Vue 项目实战】看房预约管理&#xff08;十三&#xff09; 文章目录 【尚庭公寓SpringBoot Vue 项目实战】看房预约管理&#xff08;十三&#xff09;1、业务说明2、代码开发2.1、根据条件分页查询预约信息2.2、根据ID更新预约状态 1、业务说明 看房预约…

Vue77-编程式路由

一、需求 不写<router-link>实行路由的跳转。 因为<router-link>的本质是<a>&#xff0c;但是&#xff0c;有时&#xff0c;导航不一定是a标签&#xff01;或者&#xff0c;有时需要等一段时间&#xff0c;页面才跳转。 二、代码实现 三、小结

macbook配置adb环境和用adb操作安卓手机

&#xff08;参考&#xff1a;ADB工具包的安装与使用_adb工具箱-CSDN博客&#xff09; 第一步&#xff1a;从Android开发者网站下载Android SDK&#xff08;软件开发工具包&#xff09;。下载地址为&#xff1a; 第二步&#xff1a;解压下载的SDK压缩文件到某个目录中。 进入解…

ubuntu 18.04 server源码编译安装freeswitch 1.10.11——筑梦之路

前言 这里主要编译支持语音通话、视频通话、短信、webrtc功能的PBX。 安装编译工具包和依赖包 sudo apt-get updatesudo apt-get install -y autoconf git libtool g zlib1g-dev libjpeg-dev libcurl4-openssl-dev libspeex-dev libldns-dev libedit-dev libssl-dev pkg-con…