〇.序
将.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檔案将擁有相同或者是更好的運作時性能。

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 支援。
目前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
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)/
public class MainActivity extends Activity {
public MainActivity() {
}
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView();
TextView tv = (TextView)this.findViewById();
//直接使用Lambda表達式脫糖後的靜态引用
tv.setOnClickListener(MainActivity$$Lambda$$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$();
private MainActivity$$Lambda$() {
}
public void onClick(View var1) {
MainActivity.lambda$onCreate$$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();
TextView tv = (TextView)this.findViewById();
//注意this傳遞過去了。類似于内部類的寫法
tv.setOnClickListener(new MainActivity$$Lambda$(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$;
MainActivity$$Lambda$(MainActivity var1) {
this.arg$ = var1;
}
public void onClick(View var1) {
this.arg$lambda$onCreate$$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環境下運作起來:
- 確定本地已經安裝了python 2.7或更高版本(macOS Sierra自帶python 2.7)。
- 由于R8項目使用chromium項目提供的depot_tools管理依賴,是以先安裝depot_tools
- Clone R8項目:git clone https://r8.googlesource.com/r8 && cd r8
-
下載下傳一個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
參考資料
- Android Studio switching to D8 dexer
- Next-generation Dex Compiler Now in Preview
- 官方文檔 : 使用 Java 8 語言功能