Android Studio 3.0 新Dex編譯器D8 Desugar R8

Android Studio 3.0  新Dex編譯器D8 Desugar R8

〇.序

將.class自己碼轉化為.dex位元組碼作為Apk打包的關鍵步驟,Google打算在Android 3.0中引入D8作為原先Dex的升級版,以及R8作為原本Proguard 壓縮與優化(minification、shrinking、optimization)部分的替代品。升級Dex編譯器將直接影響構建時間,.dex檔案大小,執行時效能。

一.D8

1.1 D8 的功能是把java位元組碼轉化成dex程式碼,D8作為DX的一個替換方案。

谷歌通過自己的 基準測試專案測出,編譯時間縮短了20%,而且.dex檔案更小,雖然只有幾個百分比。D8編譯的.dex檔案將擁有相同或者是更好的執行時效能。

Dex編譯時間 DX VS D8

dex檔案大小 DX VS D8

Java 8支援相關

Android Studio 3.0 及以上版本支援所有 Java 7 語言功能,以及部分 Java 8 語言功能(具體因平臺版本而異)。
注:在開發 Android 應用時,可以選擇使用 Java 8 語言功能。 您可以將專案的原始碼和目的碼相容性值保留為 Java 7,但仍須使用 JDK 8 進行編譯。
Android Studio 為使用部分 Java 8 語言功能及利用這些功能的第三方庫提供內建支援。 如圖 1 所示,預設工具鏈對 javac 編譯器的輸出執行位元組碼轉換(稱為 desugar),從而實現新語言功能。 Jack 不再受支援,您需要首先停用 Jack 才能使用預設工具鏈內建的 Java 8 支援。
採用 desugar 位元組碼轉換的 Java 8 語言功能支援。
目前Java 8語言支援的處理是在javac之後,與位元組碼處理工具處理之前。在接下來的幾個月,這個步驟將會被移動到pipeline的後一個階段,作為D8的一部分。

其帶來的影響:

  • 減少這塊的編譯時間
  • 可以優化更多程式碼
  • 這麼一來,所有位元組碼處理工具就必須要支援Java8的位元組碼格式了。

1.2 D8的使用

已經在Android Studio 3.0 Beta release中引入

  • Android Studio 3.0
    需要主動在gradle.properties檔案中新增:android.enableD8=true
  • Android Studio 3.1或之後的版本
    在3.1或之後的版本D8將會被作為預設的Dex編譯器。如果遇到問題,你可以通過修改gradle.properties檔案裡的一個屬性恢復到DX android.enableD8=false
  • 除了其他好處外,使用D8還有一個好處,就是支援 脫糖,讓Java 8才提供的特性(如lambdas)可以轉換成Java 7特性。把脫糖步驟整合進D8影響了所有讀或寫.class位元組碼的開發工具,因為它會使用Java 8格式。你可以在gradle檔案中設定一個屬性,恢復到以前的行為,讓脫糖發生在Java編譯之後,.class位元組碼仍遵循Java 7格式:android.enableD8.desugaring = false

D8 dexer
D8 dexer

1.3脫糖(Desugar)

當我們選擇JDK8以上版本時,有時候會使用lambda表示式,在設定android.enableD8.desugaring = false的時候。編譯鏈會對lambda表示式進行一次脫糖處理。請看下面的例子。

1.3.1 純函式脫糖

原始碼很簡單:

一個簡單的Activity,設定ClickListener一種是Java7以下的傳統寫法,一種是Java8的Lambda表示式寫法

public class MainActivity extends Activity {
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView tv = findViewById(R.id.click);
//lambda寫法
tv.setOnClickListener(view -> {
Log.d("MainActivity", "MainActivity");
});
//普通寫法
tv.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View view) {
Log.d("MainActivity", "MainActivity");
}
});
}
}

編譯後的Class檔案如下:

路徑為
app/build/intermediates/transforms/desugar/release(buildType)/0/com.jamin.d8desugar(packageName)/
desugar生成位置

public class MainActivity extends Activity {
public MainActivity() {
}
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(2130968576);
TextView tv = (TextView)this.findViewById(2130903040);
//直接使用Lambda表示式脫糖後的靜態引用
tv.setOnClickListener(MainActivity$$Lambda$0.$instance);
tv.setOnClickListener(new OnClickListener() {
public void onClick(View view) {
Log.d("MainActivity", "MainActivity");
}
});
}
}
//Class檔案中生成靜態引用
final class MainActivity$$Lambda$0 implements OnClickListener {
static final OnClickListener $instance = new MainActivity$$Lambda$0();
private MainActivity$$Lambda$0() {
}
public void onClick(View var1) {
MainActivity.lambda$onCreate$0$MainActivity(var1);
}
}

實際上非D8脫糖,為了保證JAVA7及以下的相容性。是將lambda表示式的在javac編譯class的時候就已經將lambda表示式轉化成更高相容度的低版本程式碼。好處是在編譯鏈中,有時候會使用一些java7的工具。他們對於java8的語法糖是無法識別的。

1.3.2 非純函式脫糖

好,我們簡單改寫一下原始檔

public class MainActivity extends Activity {
String abc = "abc";
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView tv = findViewById(R.id.click);
tv.setOnClickListener(view -> {
Log.d("MainActivity", "MainActivity"   abc);
});
tv.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View view) {
Log.d("MainActivity", "MainActivity"   abc);
}
});
}
}
生成的class檔案如下:
public class MainActivity extends Activity {
String abc = "abc";
public MainActivity() {
}
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(2130968576);
TextView tv = (TextView)this.findViewById(2130903040);
//注意this傳遞過去了。類似於內部類的寫法
tv.setOnClickListener(new MainActivity$$Lambda$0(this));
tv.setOnClickListener(new OnClickListener() {
public void onClick(View view) {
Log.d("MainActivity", "MainActivity"   MainActivity.this.abc);
}
});
}
}
// $FF: synthetic class
final class MainActivity$$Lambda$0 implements OnClickListener {
private final MainActivity arg$1;
MainActivity$$Lambda$0(MainActivity var1) {
this.arg$1 = var1;
}
public void onClick(View var1) {
this.arg$1.lambda$onCreate$0$MainActivity(var1);
}
}

此時生成的class檔案就不是純函式了。所以會不會記憶體洩漏?

1.3.3 D8脫糖

在設定android.enableD8.desugaring = true的時候(高版本,比如AGP的版本是com.android.tools.build:gradle:3.3.0-alpha03時,預設是D8脫糖),D8脫糖就不會在transforms目錄下生成desugar目錄。反編譯transforms/dexBuilder/中的jar包。可以看到在jar包中,已經是脫糖後的結果了。大家可以看下圖。也是把lambda表示式生成一個靜態物件。
這裡寫圖片描述
這裡寫圖片描述
當然D8脫糖,要求編譯鏈中所有工具都支援java8,不然不認識class檔案中的部分語法糖。D8脫糖的好處是什麼呢。官方的話說就是可以提高編譯速度。

二.R8

R8作為原本Proguard 壓縮與優化(minification、shrinking、optimization)部分的替代品,依然使用與Proguard一樣的keep規則。
目前R8已經開源: r8/r8,其包含了D8與R8。

目前R8還沒有整合進Android Gradle plugin,不過由於其已經開源,根據文件可以很快的在python環境下執行起來:

  1. 確保本地已經安裝了python 2.7或更高版本(macOS Sierra自帶python 2.7)。
  2. 由於R8專案使用chromium專案提供的depot_tools管理依賴,因此先安裝depot_tools
  3. Clone R8專案:git clone https://r8.googlesource.com/r8 && cd r8
  4. 下載一個Gradle版去編譯,並且聲稱兩個jar檔案: build/libs/d8.jar與build/libs/r8.jar: python tools/gradle.py d8 r8
    根據r8文件進行使用即可

BREAKING NEWS:新版AndroidStudio可以體驗一下。

New code shrinker
R8 is a new tool for code shrinking and obfuscation that replaces ProGuard. You can start using the preview version of R8 by including the following in your project’s gradle.properties file:
android.enableR8 = true

官方文件: New code shrinker

參考資料