深入探索 Gradle 自動化構建技術(九、Gradle 插件平臺化框架 ByteX 探祕之旅)

前言

成爲一名優秀的Android開發,需要一份完備的知識體系,在這裏,讓我們一起成長爲自己所想的那樣~。

一、前置知識

1、函數式編程

1)、什麼是函數式編程?

面向對象編程是對數據進行抽象,而函數式編程是對行爲進行抽象。現實世界中,數據和行爲並存,而程序也是如此。

2)爲什麼要學習函數式編程?

  • 用函數(行爲)對數據處理,是學習大數據的基石。
  • 好的效率(併發執行),
  • 完成一個功能使用更少的代碼。
  • 對象轉向面向函數編程的思想有一定難度,需要大量的練習

2、Java 1.8 Lambda 表達式

1)、什麼是 Lambda 表達式?

Lambda 是一個匿名函數,即沒有函數名的函數,它簡化了匿名委託的使用,讓代碼更加簡潔。

2)、兩個簡單的 Lambda 表達式示例

//匿名內部類
Runnable r = new Runnable() {
    @Override
    public void run() {
        System.out.print("hello toroot");
    }
};

//lambda
Runnable r2 = ()->System.out.print("hello toroot");

//匿名內部類
TreeSet<String> ts = new TreeSet<>(new Comparator<String>() {
    @Override
    public int compare(String o1, String o2) {
        return Long.compare(o1.length(),o2.length());
    }
});

//lambda
TreeSet<String> ts2 = new TreeSet<>((o1,o2)-> Long.compare(o1.length(),o2.length()));

3)、Lambda 表達式語法

Lambda 表達式在 Java 語言中引入了一個新的語法元素和操作符。這個操作符爲 “->” ,該操作符被稱爲 Lambda 操作符或剪頭操作符。

它將 Lambda 分爲兩個部分:

  • 左側:指定了 Lambda 表達式需要的所有參數
  • 右側:指定了 Lambda 體,即 Lambda 表達式要執行的功能

4)、Lambda 表達式語法格式

  • 1、無參數,無返回值:
    () -> System.out.println("Hello Lambda!");
  • 2、有一個參數,並且無返回值:
    (x) -> System.out.println(x)
  • 3、若只有一個參數,小括號可以省略不寫:
    x -> System.out.println(x)
  • 4、有兩個以上的參數,有返回值,並且 Lambda 體中有多條語句:
		Comparator<Integer> com = (x, y) -> {
			System.out.println("函數式接口");
			return Integer.compare(x, y);
		};
  • 5、若 Lambda 體中只有一條語句, return 和 大括號 都可以省略不寫:
    Comparator<Integer> com = (x, y) -> Integer.compare(x, y);
  • 6、Lambda 表達式的參數列表的數據類型可以省略不寫,因爲 JVM 編譯器可以通過上下文推斷出數據類型,即“類型推斷”:
    (Integer x, Integer y) -> Integer.compare(x, y);

5)、函數式接口

Lambda 表達式需要“函數式接口”的支持。函數式接口即 接口中只有一個抽象方法的接口,稱爲函數式接口。 可以使用註解 @FunctionalInterface修飾,它可以檢查是否是函數式接口。函數式接口的使用示例如下所示:

@FunctionalInterface
public interface MyFun {
    public double getValue();
}

@FunctionalInterface
public interface MyFun<T> {
    public T getValue(T t);
}

public static void main(String[] args) {
    String newStr = toUpperString((str)->str.toUpperCase(),"toroot");
    System.out.println(newStr);
}

public static String toUpperString(MyFun<String> mf,String str) {
    return mf.getValue(str);
}

6)、Java 內置函數式接口

接口 參數 返回類型 示例
Predicate T boolean 這道題對了嗎?
Consumer T void 輸出一個值
Function<T,R> T R 獲得 Person對象的名字
Supplier None T 工廠方法
UnaryOperator T T 邏輯非 (!)
BinaryOperator (T, T) T 求兩個數的乘積 (*)

7)、方法引用

當要傳遞給 Lambda 體的操作,已經有實現的方法了,可以使用方法引用。方法引用使用 操作符 “ ::” 將方法名和對象或類的名字分隔開來。它主要有如下 三種 使用情況 :

  • 對象 :: 實例方法
  • 類 :: 靜態方法
  • 類 :: 實例方法

8)、什麼時候可以用 :: 方法引用(重點)

在我們使用 Lambda 表達式的時候,”->” 右邊部分是要執行的代碼,即要完成的功能,可以把這部分稱作 Lambda 體。有時候,當我們想要實現一個函數式接口的那個抽象方法,但是已經有類實現了我們想要的功能,這個時候我們就可以用方法引用來直接使用現有類的功能去實現。示例代碼如下所示:

Person p1 = new Person("Av",18,90);
        Person p2 = new Person("King",20,0);
        Person p3 = new Person("Lance",17,100);
        List<Person> list = new ArrayList<>();
        list.add(p1);
        list.add(p2);
        list.add(p3);

        // 這裏我們需要比較 list 裏面的 person,按照年齡排序
        // 那麼我們最常見的做法是
        // sort(List<T> list, Comparator<? super T> c)
        // 1、因爲我們的 sort 方法的第二個參數是一個接口,所以我們需要實現一個匿名內部類
        Collections.sort(list, new Comparator<Person>() {
            @Override
            public int compare(Person person1, Person person2) {
                return person1.getAge().compareTo(person2.getAge());
            }
        });
        // 2、因爲第二個參數是一個 @FunctionalInterface 的函數式接口,所以我們可以用 lambda 寫法
        Collections.sort(list, (person1,person2) -> p1.getScore().compareTo(p2.getAge()));
        // 3、因爲第二個參數我們可以用lambda的方式去實現,
        // 但是剛好又有代碼 Comparator.comparing 已經實現了這個功能
        // 這個時候我們就可以採用方法引用了
        /**
         * 重點:
         * 當我們想要實現一個函數式接口的那個抽象方法,但是已經有類實現了我們想要的功能,
         * 這個時候我們就可以用方法引用來直接使用現有類的功能去實現。
         */
        Collections.sort(list, Comparator.comparing(Person::getAge));

        System.out.println(list);

其它 Java 內置的函數式接口示例如下所示:

public static void main(String[] args) {
    Consumer<String>  c = x->System.out.println(x);
    // 等同於
    Consumer<String> c2 = System.out::print;
}

public static void main(String[] args) {
    BinaryOperator<Double> bo = (n1,n2) ->Math.pow(n1,n2);
    BinaryOperator<Double> bo2 = Math::pow;
}

public static void main(String[] args) {
    BiPredicate<String,String> bp = (str1,str2) ->str1.equals(str2);
    BiPredicate<String,String> bp2 = String::equals;
}

注意:當需要引用方法的第一個參數是調用對象,並且第二個參數是需要引用方法的第二個參數(或無參數)時,使用 ClassName::methodName

9)、構造器引用

格式: ClassName :: new 與函數式接口相結合,自動與函數式接口中方法兼容。
可以把構造器引用賦值給定義的方法,但是構造器參數列表要與接口中抽象方法的參數列表一致。示例如下所示:

public static void main(String[] args) {
    Supplier<Person> x = ()->new Person();
    Supplier<Person> x2 = Person::new;
}

public static void main(String[] args) {
    Function<String,Person> f  = x->new Person(x);
    Function<String,Person> f2 = Person::new;
}

10)、數組引用

格式: type[] :: new,示例如下所示:

public static void main(String[] args) {
   Function<Integer,Person[]> f  = x->new Person[x];
   Function<Integer,Person[]>  f2 = Person[]::new;
}

3、Stream API

1)、Stream 是什麼?

Stream 是數據渠道,用於操作數據源(集合、數組等)所生成的元素序列。記住:“集合講的是數據,流講的是計算!”

2)、特點

  • 1)、Stream 自己不會存儲元素
  • 2)、Stream 不會改變源對象。相反,他們會返回一個持有結果的新 Stream
  • 3)、Stream 操作是延遲執行的。這意味着他們會等到需要結果的時候才執行

3)、一個 Stream 操作實例

取出所有大於18歲人的姓名,按字典排序,並輸出到控制檯,代碼如下所示:

   private static  List<Person> persons = Arrays.asList(
            new Person("CJK",19,"女"),
            new Person("BODUO",20,"女"),
            new Person("JZ",21,"女"),
            new Person("anglebabby",18,"女"),
            new Person("huangxiaoming",5,"男"),
            new Person("ROY",18,"男")
    );
public static void main(String[] args) throws IOException {
 persons.stream().filter(x-    	>x.getAge()>=18).map(Person::getName).sorted().forEach(System.out::println);
}

4)、Stream 的操作三個步驟

  • 1、創建 Stream:一個數據源(如:集合、數組),獲取一個流
  • 2、中間操作:一箇中間操作鏈,對數據源的數據進行處理
  • 3、終止操作(終端操作):一個終止操作,執行中間操作鏈,併產生結果

1、創建 Steam

創建流主要有四種方式,其示例代碼如下所示:

@Test
public void test1(){
    //1. Collection 提供了兩個方法  stream() 與 parallelStream()
    List<String> list = new ArrayList<>();
    Stream<String> stream = list.stream(); //獲取一個順序流
    Stream<String> parallelStream = list.parallelStream(); //獲取一個並行流

    //2. 通過 Arrays 中的 stream() 獲取一個數組流
    Integer[] nums = new Integer[10];
    Stream<Integer> stream1 = Arrays.stream(nums);

    //3. 通過 Stream 類中靜態方法 of()
    Stream<Integer> stream2 = Stream.of(1,2,3,4,5,6);
    
    //4. 創建無限流
    //迭代
    Stream<Integer> stream3 = Stream.iterate(0, (x) -> x + 2).limit(10);
    stream3.forEach(System.out::println);

    //生成
    Stream<Double> 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<Integer> 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<Integer> list = Arrays.stream(ary).filter(x -> x % 2 == 0).distinct().collect(Collectors.toList());
Set<Integer> 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<Person> emps= list.stream().collect(Collectors.toList());
  • 2)、toSet Set 把流中元素收集 到Set:
Set<Person> emps= list.stream().collect(Collectors.toSet());
  • 3)、toCollection Collection 把流中元素收集到創建的集合:
Collection<Person> 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<Person> max= list.stream().collect(Collectors.maxBy(comparingInt(Person::getSalary)));
  • 10)、minBy Optional 根據比較器選擇最小值:
Optional<Person> 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<Person>> map= list.stream().collect(Collectors.groupingBy(Person::getStatus));
  • 14)、partitioningBy Map<Boolean, List> 根 據true 或 false 進行分區:
Map<Boolean,List<Person>>vd= list.stream().collect(Collectors.partitioningBy(Person::getManage));
4、終止操作練習案例
  • 1)、取出Person對象的所有名字,放到 List 集合中:
List<String> 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<Person>> 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<K> {
    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<T> {
    public T next();
}

但是,實現泛型接口的類,有兩種實現方法

1、未傳入泛型實參

在 new 出類的實例時,需要指定具體類型:

public class ImplGenertor<T> implements Genertor<T> {
    @Override
    public T next() {
        return null;
    }
}
2、傳入泛型實參

在 new 出類的實例時,和普通的類沒區別。

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

泛型方法

泛型方法的 定義在 修飾符與返回值 的中間。示例代碼如下所示:

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

泛型方法,是在調用方法的時候指明泛型的具體類型,泛型方法可以在任何地方和任何場景中使用,包括普通類和泛型類。

泛型類中定義的普通方法和泛型方法的區別

在普通方法中:

// 雖然在方法中使用了泛型,但是這並不是一個泛型方法。
// 這只是類中一個普通的成員方法,只不過他的返回值是在聲明泛型類已經聲明過的泛型。
// 所以在這個方法中才可以繼續使用 T 這個泛型。
public T getKey(){
    return key;
}

在泛型方法中:

/**
 * 這纔是一個真正的泛型方法。
 * 首先在 public 與返回值之間的 <T> 必不可少,這表明這是一個泛型方法,並且聲明瞭一個泛型 T
 * 這個 T 可以出現在這個泛型方法的任意位置,泛型的數量也可以爲任意多個。
 */
public <T,K> K showKeyName(Generic<T> container){
    // ...
}

4)、限定類型變量

public class ClassBorder<T extends Comparable> {
...
}

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<T> extends Pair<T>{
...
}

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<Project> {
    @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.isIncremental()) {
        transformInvocation.getOutputProvider().deleteAll();
    }
    // 2、獲取 transformContext 實例。
    TransformContext transformContext = getTransformContext(transformInvocation);
    // 3、初始化 HtmlReporter(生成 ByteX 構建產生日誌的 HTML 文件)
    init(transformContext);
    // 4、過濾掉沒有打開插件開關的 plugin。
    List<IPlugin> 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<TransformFlow> 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<FileProcessor> processors = handlers.stream()
            .flatMap((Function<MainProcessHandler, Stream<FileProcessor>>) 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<FileTransformTask> 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<FileTraverseTask> tasks = source.map(cache -> new FileTraverseTask(cache, processors)).collect(Collectors.toList());
    // 1
    invokeAll(tasks);
}

註釋1處,繼續回調所有的 FileTraverseTask 實例的 compute 方法,源碼如下所示:

@Override
protected void compute() {
    List<TraverseTask> 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) {
            if (!handler.transform(relativePath, cn)) {
                fileData.delete();
                return;
            }
        }
        cn.accept(cw);
        if (!GlobalWhiteListManager.INSTANCE.shouldIgnore(fileData.getRelativePath())) {
            raw = cw.toByteArray();
            if (needVerify) {
                ClassNode verifyNode = new ClassNode();
                new ClassReader(raw).accept(verifyNode, crFlags);
                AsmVerifier.verify(verifyNode);
            }
            // 5、如果不是白名單裏的文件,則將 ClassWriter 中的數據放入 fileData 之中。
            fileData.setBytes(raw);
        }
    } catch (ByteXException e) {
        throw e;
    } catch (Exception e) {
        LevelLog.sDefaultLogger.e(String.format("Failed to handle class %s", fileData.getRelativePath()), e);
        if (!GlobalWhiteListManager.INSTANCE.shouldIgnore(fileData.getRelativePath())) {
            throw e;
        }
    }
}

在 ClassFileProcessor 的 process 方法的註釋2處,又會繼續調用 ProcessorChain 的 proceed 方法,這裏會回調 BackupFileProcessor 實例的 process 方法,源碼如下所示:

@Override
public Output process(Chain chain) throws IOException {
    Input input = chain.input();
    // 僅僅是返回處理過的輸出文件
    return new Output(input.getFileData());
}

按照這樣的模式,PerformTraverseTask 實例的所有 task 都被遍歷執行了。

最後,便會調用 MainTransformFlow 實例的 afterTransform 方法,源碼如下:

@Override
protected AbsTransformFlow afterTransform(TransformEngine transformEngine) {
    handlers.forEach(plugin -> plugin.afterTransform(transformEngine));
    return this;
}

這裏遍歷執行了所有 plugin 的 afterTransform 方法。

然後,我們再回到 CommonTransform 的 transform 方法,在執行完 MainTransformFlow 的 run 方法後,便會調用註釋9處的代碼來獲取流中的 graph 類圖對象並清除。最後,執行註釋10處的 afterTransform 方法用來做 transform 之後的收尾工作。

四、總結

在本文中,我們一起對 ByteX 插件平臺的構建流程進行了探祕。從 ByteX 的源碼實現中,我們可以看出作者對 函數式編程、Java 1.8 Lambda 表達式、Java 1.8 Stream API、複雜泛型 等技術的靈活運用,所以,幾乎所有看似很 🐂 的輪子,其實質都是依賴於對基礎技術的深度掌握。那麼,如何才能達到深度掌握基礎技術的程度呢?— 唯有不斷地練習與有規律的複習

公衆號

我的公衆號 JsonChao 開通啦,歡迎關注~

參考鏈接:


Contanct Me

● 微信:

歡迎關注我的微信:bcce5360

● 微信羣:

由於微信羣已超過 200 人,麻煩大家想進微信羣的朋友們,加我微信拉你進羣。

● QQ羣:

2千人QQ羣,Awesome-Android學習交流羣,QQ羣號:959936182, 歡迎大家加入~

About me

很感謝您閱讀這篇文章,希望您能將它分享給您的朋友或技術羣,這對我意義重大。

希望我們能成爲朋友,在 Github掘金上一起分享知識。

前言

成爲一名優秀的Android開發,需要一份完備的知識體系,在這裏,讓我們一起成長爲自己所想的那樣~。

一、前置知識

1、函數式編程

1)、什麼是函數式編程?

面向對象編程是對數據進行抽象,而函數式編程是對行爲進行抽象。現實世界中,數據和行爲並存,而程序也是如此。

2)爲什麼要學習函數式編程?

  • 用函數(行爲)對數據處理,是學習大數據的基石。
  • 好的效率(併發執行),
  • 完成一個功能使用更少的代碼。
  • 對象轉向面向函數編程的思想有一定難度,需要大量的練習

2、Java 1.8 Lambda 表達式

1)、什麼是 Lambda 表達式?

Lambda 是一個匿名函數,即沒有函數名的函數,它簡化了匿名委託的使用,讓代碼更加簡潔。

2)、兩個簡單的 Lambda 表達式示例

//匿名內部類
Runnable r = new Runnable() {
    @Override
    public void run() {
        System.out.print("hello toroot");
    }
};

//lambda
Runnable r2 = ()->System.out.print("hello toroot");

//匿名內部類
TreeSet<String> ts = new TreeSet<>(new Comparator<String>() {
    @Override
    public int compare(String o1, String o2) {
        return Long.compare(o1.length(),o2.length());
    }
});

//lambda
TreeSet<String> ts2 = new TreeSet<>((o1,o2)-> Long.compare(o1.length(),o2.length()));

3)、Lambda 表達式語法

Lambda 表達式在 Java 語言中引入了一個新的語法元素和操作符。這個操作符爲 “->” ,該操作符被稱爲 Lambda 操作符或剪頭操作符。

它將 Lambda 分爲兩個部分:

  • 左側:指定了 Lambda 表達式需要的所有參數
  • 右側:指定了 Lambda 體,即 Lambda 表達式要執行的功能

4)、Lambda 表達式語法格式

  • 1、無參數,無返回值:
    () -> System.out.println("Hello Lambda!");
  • 2、有一個參數,並且無返回值:
    (x) -> System.out.println(x)
  • 3、若只有一個參數,小括號可以省略不寫:
    x -> System.out.println(x)
  • 4、有兩個以上的參數,有返回值,並且 Lambda 體中有多條語句:
		Comparator<Integer> com = (x, y) -> {
			System.out.println("函數式接口");
			return Integer.compare(x, y);
		};
  • 5、若 Lambda 體中只有一條語句, return 和 大括號 都可以省略不寫:
    Comparator<Integer> com = (x, y) -> Integer.compare(x, y);
  • 6、Lambda 表達式的參數列表的數據類型可以省略不寫,因爲 JVM 編譯器可以通過上下文推斷出數據類型,即“類型推斷”:
    (Integer x, Integer y) -> Integer.compare(x, y);

5)、函數式接口

Lambda 表達式需要“函數式接口”的支持。函數式接口即 接口中只有一個抽象方法的接口,稱爲函數式接口。 可以使用註解 @FunctionalInterface修飾,它可以檢查是否是函數式接口。函數式接口的使用示例如下所示:

@FunctionalInterface
public interface MyFun {
    public double getValue();
}

@FunctionalInterface
public interface MyFun<T> {
    public T getValue(T t);
}

public static void main(String[] args) {
    String newStr = toUpperString((str)->str.toUpperCase(),"toroot");
    System.out.println(newStr);
}

public static String toUpperString(MyFun<String> mf,String str) {
    return mf.getValue(str);
}

6)、Java 內置函數式接口

接口 參數 返回類型 示例
Predicate T boolean 這道題對了嗎?
Consumer T void 輸出一個值
Function<T,R> T R 獲得 Person對象的名字
Supplier None T 工廠方法
UnaryOperator T T 邏輯非 (!)
BinaryOperator (T, T) T 求兩個數的乘積 (*)

7)、方法引用

當要傳遞給 Lambda 體的操作,已經有實現的方法了,可以使用方法引用。方法引用使用 操作符 “ ::” 將方法名和對象或類的名字分隔開來。它主要有如下 三種 使用情況 :

  • 對象 :: 實例方法
  • 類 :: 靜態方法
  • 類 :: 實例方法

8)、什麼時候可以用 :: 方法引用(重點)

在我們使用 Lambda 表達式的時候,”->” 右邊部分是要執行的代碼,即要完成的功能,可以把這部分稱作 Lambda 體。有時候,當我們想要實現一個函數式接口的那個抽象方法,但是已經有類實現了我們想要的功能,這個時候我們就可以用方法引用來直接使用現有類的功能去實現。示例代碼如下所示:

Person p1 = new Person("Av",18,90);
        Person p2 = new Person("King",20,0);
        Person p3 = new Person("Lance",17,100);
        List<Person> list = new ArrayList<>();
        list.add(p1);
        list.add(p2);
        list.add(p3);

        // 這裏我們需要比較 list 裏面的 person,按照年齡排序
        // 那麼我們最常見的做法是
        // sort(List<T> list, Comparator<? super T> c)
        // 1、因爲我們的 sort 方法的第二個參數是一個接口,所以我們需要實現一個匿名內部類
        Collections.sort(list, new Comparator<Person>() {
            @Override
            public int compare(Person person1, Person person2) {
                return person1.getAge().compareTo(person2.getAge());
            }
        });
        // 2、因爲第二個參數是一個 @FunctionalInterface 的函數式接口,所以我們可以用 lambda 寫法
        Collections.sort(list, (person1,person2) -> p1.getScore().compareTo(p2.getAge()));
        // 3、因爲第二個參數我們可以用lambda的方式去實現,
        // 但是剛好又有代碼 Comparator.comparing 已經實現了這個功能
        // 這個時候我們就可以採用方法引用了
        /**
         * 重點:
         * 當我們想要實現一個函數式接口的那個抽象方法,但是已經有類實現了我們想要的功能,
         * 這個時候我們就可以用方法引用來直接使用現有類的功能去實現。
         */
        Collections.sort(list, Comparator.comparing(Person::getAge));

        System.out.println(list);

其它 Java 內置的函數式接口示例如下所示:

public static void main(String[] args) {
    Consumer<String>  c = x->System.out.println(x);
    // 等同於
    Consumer<String> c2 = System.out::print;
}

public static void main(String[] args) {
    BinaryOperator<Double> bo = (n1,n2) ->Math.pow(n1,n2);
    BinaryOperator<Double> bo2 = Math::pow;
}

public static void main(String[] args) {
    BiPredicate<String,String> bp = (str1,str2) ->str1.equals(str2);
    BiPredicate<String,String> bp2 = String::equals;
}

注意:當需要引用方法的第一個參數是調用對象,並且第二個參數是需要引用方法的第二個參數(或無參數)時,使用 ClassName::methodName

9)、構造器引用

格式: ClassName :: new 與函數式接口相結合,自動與函數式接口中方法兼容。
可以把構造器引用賦值給定義的方法,但是構造器參數列表要與接口中抽象方法的參數列表一致。示例如下所示:

public static void main(String[] args) {
    Supplier<Person> x = ()->new Person();
    Supplier<Person> x2 = Person::new;
}

public static void main(String[] args) {
    Function<String,Person> f  = x->new Person(x);
    Function<String,Person> f2 = Person::new;
}

10)、數組引用

格式: type[] :: new,示例如下所示:

public static void main(String[] args) {
   Function<Integer,Person[]> f  = x->new Person[x];
   Function<Integer,Person[]>  f2 = Person[]::new;
}

3、Stream API

1)、Stream 是什麼?

Stream 是數據渠道,用於操作數據源(集合、數組等)所生成的元素序列。記住:“集合講的是數據,流講的是計算!”

2)、特點

  • 1)、Stream 自己不會存儲元素
  • 2)、Stream 不會改變源對象。相反,他們會返回一個持有結果的新 Stream
  • 3)、Stream 操作是延遲執行的。這意味着他們會等到需要結果的時候才執行

3)、一個 Stream 操作實例

取出所有大於18歲人的姓名,按字典排序,並輸出到控制檯,代碼如下所示:

   private static  List<Person> persons = Arrays.asList(
            new Person("CJK",19,"女"),
            new Person("BODUO",20,"女"),
            new Person("JZ",21,"女"),
            new Person("anglebabby",18,"女"),
            new Person("huangxiaoming",5,"男"),
            new Person("ROY",18,"男")
    );
public static void main(String[] args) throws IOException {
 persons.stream().filter(x-    	>x.getAge()>=18).map(Person::getName).sorted().forEach(System.out::println);
}

4)、Stream 的操作三個步驟

  • 1、創建 Stream:一個數據源(如:集合、數組),獲取一個流
  • 2、中間操作:一箇中間操作鏈,對數據源的數據進行處理
  • 3、終止操作(終端操作):一個終止操作,執行中間操作鏈,併產生結果

1、創建 Steam

創建流主要有四種方式,其示例代碼如下所示:

@Test
public void test1(){
    //1. Collection 提供了兩個方法  stream() 與 parallelStream()
    List<String> list = new ArrayList<>();
    Stream<String> stream = list.stream(); //獲取一個順序流
    Stream<String> parallelStream = list.parallelStream(); //獲取一個並行流

    //2. 通過 Arrays 中的 stream() 獲取一個數組流
    Integer[] nums = new Integer[10];
    Stream<Integer> stream1 = Arrays.stream(nums);

    //3. 通過 Stream 類中靜態方法 of()
    Stream<Integer> stream2 = Stream.of(1,2,3,4,5,6);
    
    //4. 創建無限流
    //迭代
    Stream<Integer> stream3 = Stream.iterate(0, (x) -> x + 2).limit(10);
    stream3.forEach(System.out::println);

    //生成
    Stream<Double> 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<Integer> 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<Integer> list = Arrays.stream(ary).filter(x -> x % 2 == 0).distinct().collect(Collectors.toList());
Set<Integer> 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<Person> emps= list.stream().collect(Collectors.toList());
  • 2)、toSet Set 把流中元素收集 到Set:
Set<Person> emps= list.stream().collect(Collectors.toSet());
  • 3)、toCollection Collection 把流中元素收集到創建的集合:
Collection<Person> 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<Person> max= list.stream().collect(Collectors.maxBy(comparingInt(Person::getSalary)));
  • 10)、minBy Optional 根據比較器選擇最小值:
Optional<Person> 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<Person>> map= list.stream().collect(Collectors.groupingBy(Person::getStatus));
  • 14)、partitioningBy Map<Boolean, List> 根 據true 或 false 進行分區:
Map<Boolean,List<Person>>vd= list.stream().collect(Collectors.partitioningBy(Person::getManage));
4、終止操作練習案例
  • 1)、取出Person對象的所有名字,放到 List 集合中:
List<String> 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<Person>> 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<K> {
    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<T> {
    public T next();
}

但是,實現泛型接口的類,有兩種實現方法

1、未傳入泛型實參

在 new 出類的實例時,需要指定具體類型:

public class ImplGenertor<T> implements Genertor<T> {
    @Override
    public T next() {
        return null;
    }
}
2、傳入泛型實參

在 new 出類的實例時,和普通的類沒區別。

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

泛型方法

泛型方法的 定義在 修飾符與返回值 的中間。示例代碼如下所示:

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

泛型方法,是在調用方法的時候指明泛型的具體類型,泛型方法可以在任何地方和任何場景中使用,包括普通類和泛型類。

泛型類中定義的普通方法和泛型方法的區別

在普通方法中:

// 雖然在方法中使用了泛型,但是這並不是一個泛型方法。
// 這只是類中一個普通的成員方法,只不過他的返回值是在聲明泛型類已經聲明過的泛型。
// 所以在這個方法中才可以繼續使用 T 這個泛型。
public T getKey(){
    return key;
}

在泛型方法中:

/**
 * 這纔是一個真正的泛型方法。
 * 首先在 public 與返回值之間的 <T> 必不可少,這表明這是一個泛型方法,並且聲明瞭一個泛型 T
 * 這個 T 可以出現在這個泛型方法的任意位置,泛型的數量也可以爲任意多個。
 */
public <T,K> K showKeyName(Generic<T> container){
    // ...
}

4)、限定類型變量

public class ClassBorder<T extends Comparable> {
...
}

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<T> extends Pair<T>{
...
}

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<Project> {
    @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.isIncremental()) {
        transformInvocation.getOutputProvider().deleteAll();
    }
    // 2、獲取 transformContext 實例。
    TransformContext transformContext = getTransformContext(transformInvocation);
    // 3、初始化 HtmlReporter(生成 ByteX 構建產生日誌的 HTML 文件)
    init(transformContext);
    // 4、過濾掉沒有打開插件開關的 plugin。
    List<IPlugin> 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<TransformFlow> 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<FileProcessor> processors = handlers.stream()
            .flatMap((Function<MainProcessHandler, Stream<FileProcessor>>) 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<FileTransformTask> 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<FileTraverseTask> tasks = source.map(cache -> new FileTraverseTask(cache, processors)).collect(Collectors.toList());
    // 1
    invokeAll(tasks);
}

註釋1處,繼續回調所有的 FileTraverseTask 實例的 compute 方法,源碼如下所示:

@Override
protected void compute() {
    List<TraverseTask> 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) {
            if (!handler.transform(relativePath, cn)) {
                fileData.delete();
                return;
            }
        }
        cn.accept(cw);
        if (!GlobalWhiteListManager.INSTANCE.shouldIgnore(fileData.getRelativePath())) {
            raw = cw.toByteArray();
            if (needVerify) {
                ClassNode verifyNode = new ClassNode();
                new ClassReader(raw).accept(verifyNode, crFlags);
                AsmVerifier.verify(verifyNode);
            }
            // 5、如果不是白名單裏的文件,則將 ClassWriter 中的數據放入 fileData 之中。
            fileData.setBytes(raw);
        }
    } catch (ByteXException e) {
        throw e;
    } catch (Exception e) {
        LevelLog.sDefaultLogger.e(String.format("Failed to handle class %s", fileData.getRelativePath()), e);
        if (!GlobalWhiteListManager.INSTANCE.shouldIgnore(fileData.getRelativePath())) {
            throw e;
        }
    }
}

在 ClassFileProcessor 的 process 方法的註釋2處,又會繼續調用 ProcessorChain 的 proceed 方法,這裏會回調 BackupFileProcessor 實例的 process 方法,源碼如下所示:

@Override
public Output process(Chain chain) throws IOException {
    Input input = chain.input();
    // 僅僅是返回處理過的輸出文件
    return new Output(input.getFileData());
}

按照這樣的模式,PerformTraverseTask 實例的所有 task 都被遍歷執行了。

最後,便會調用 MainTransformFlow 實例的 afterTransform 方法,源碼如下:

@Override
protected AbsTransformFlow afterTransform(TransformEngine transformEngine) {
    handlers.forEach(plugin -> plugin.afterTransform(transformEngine));
    return this;
}

這裏遍歷執行了所有 plugin 的 afterTransform 方法。

然後,我們再回到 CommonTransform 的 transform 方法,在執行完 MainTransformFlow 的 run 方法後,便會調用註釋9處的代碼來獲取流中的 graph 類圖對象並清除。最後,執行註釋10處的 afterTransform 方法用來做 transform 之後的收尾工作。

四、總結

在本文中,我們一起對 ByteX 插件平臺的構建流程進行了探祕。從 ByteX 的源碼實現中,我們可以看出作者對 函數式編程、Java 1.8 Lambda 表達式、Java 1.8 Stream API、複雜泛型 等技術的靈活運用,所以,幾乎所有看似很 🐂 的輪子,其實質都是依賴於對基礎技術的深度掌握。那麼,如何才能達到深度掌握基礎技術的程度呢?— 唯有不斷地練習與有規律的複習

公衆號

我的公衆號 JsonChao 開通啦,歡迎關注~

參考鏈接:


Contanct Me

● 微信:

歡迎關注我的微信:bcce5360

● 微信羣:

由於微信羣已超過 200 人,麻煩大家想進微信羣的朋友們,加我微信拉你進羣。

● QQ羣:

2千人QQ羣,Awesome-Android學習交流羣,QQ羣號:959936182, 歡迎大家加入~

About me

很感謝您閱讀這篇文章,希望您能將它分享給您的朋友或技術羣,這對我意義重大。

希望我們能成爲朋友,在 Github掘金上一起分享知識。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章