〇.序
將.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文件里的一個屬性恢復到DXandroid.enableD8=false
- 除了其他好處外,使用D8還有一個好處,就是支持 脫糖,讓Java 8才提供的特性(如lambdas)可以轉換成Java 7特性。把脫糖步驟集成進D8影響了所有讀或寫.class字節碼的開發工具,因為它會使用Java 8格式。你可以在gradle文件中設置一個屬性,恢復到以前的行為,讓脫糖發生在Java編譯之后,.class字節碼仍遵循Java 7格式:
android.enableD8.desugaring = true
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);
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(2130968576);
TextView tv = (TextView)this.findViewById(2130903040);
tv.setOnClickListener(MainActivity$$Lambda$0.$instance);
tv.setOnClickListener(new OnClickListener() {
public void onClick(View view) {
Log.d("MainActivity", "MainActivity");
}
});
}
}
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環境下運行起來:
- 確保本地已經安裝了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