天天看點

深入探索 Gradle 自動化建構技術(九、Gradle 插件平台化架構 ByteX 探秘之旅)前言一、前置知識二、初識 ByteX三、ByteX 插件平台建構流程探秘四、總結公衆号參考連結:Contanct Me前言一、前置知識二、初識 ByteX三、ByteX 插件平台建構流程探秘四、總結公衆号參考連結:Contanct Me

前言

成為一名優秀的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},取出中間的第三到第五個元素。
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} };

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、僅僅周遊産物

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 包

3、建構 mClassGraph 類圖對象

4、執行 Transform

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

開通啦,歡迎關注~

深入探索 Gradle 自動化建構技術(九、Gradle 插件平台化架構 ByteX 探秘之旅)前言一、前置知識二、初識 ByteX三、ByteX 插件平台建構流程探秘四、總結公衆号參考連結:Contanct Me前言一、前置知識二、初識 ByteX三、ByteX 插件平台建構流程探秘四、總結公衆号參考連結:Contanct Me

參考連結:

  • 1、ByteX
  • 2、高并發之 Fork/Join 架構使用及注意事項
  • 3、《深入了解 JVM》
  • 4、《Java 程式設計思想》

Contanct Me

● 微信:

歡迎關注我的微信:

bcce5360

● 微信群:

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

● QQ群:

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

About me

  • Email: [email protected]

  • Blog: https://jsonchao.github.io/

  • 掘金: https://juejin.im/user/5a3ba9375188252bca050ade

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

希望我們能成為朋友,在 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},取出中間的第三到第五個元素。
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} };

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、僅僅周遊産物

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 包

3、建構 mClassGraph 類圖對象

4、執行 Transform

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

開通啦,歡迎關注~

深入探索 Gradle 自動化建構技術(九、Gradle 插件平台化架構 ByteX 探秘之旅)前言一、前置知識二、初識 ByteX三、ByteX 插件平台建構流程探秘四、總結公衆号參考連結:Contanct Me前言一、前置知識二、初識 ByteX三、ByteX 插件平台建構流程探秘四、總結公衆号參考連結:Contanct Me

參考連結:

  • 1、ByteX
  • 2、高并發之 Fork/Join 架構使用及注意事項
  • 3、《深入了解 JVM》
  • 4、《Java 程式設計思想》

Contanct Me

● 微信:

歡迎關注我的微信:

bcce5360

● 微信群:

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

● QQ群:

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

About me

  • Email: [email protected]

  • Blog: https://jsonchao.github.io/

  • 掘金: https://juejin.im/user/5a3ba9375188252bca050ade

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

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