在 Android N 預覽版中使用 Java 8 的新特性
Android團隊最近發布了Android N Preview,帶來了很多提升,包括由Jack編譯器提供的Java 8支持。在這篇文章中,我們將來看看它究竟對Android開發者意味著什么,以及如何嘗試新的語言特性。
免責聲明: 本信息在2016年3月30日是有效的,我不確定在下個release版本中,Google團隊會增加什么新的沒有在此提到的Java 8特性。
概覽
在這篇文章中,去介紹Oracle Java 8的新特性并沒有太大意義 —— 很多信息已經在互聯網上有了。我個人最喜歡的是Simon Ritter的“Java SE 8的55個新特性[2]”。
另一方面,Android 官方的Java 8公告[3] 留下了很多開放的問題給開發者們,感覺上并非所有的原生 Java 8 功能都是可用的。更詳細的 技術公告[4] 確認了這一點。我們可以根據在 Android N 中的可用性,將這些語言特性分類如下:
Android Gingebread (API 9)及以上:
Android N及以上:
- 默認和靜態interface方法
- 可重復的注解
- 流(Streams)
- 反射APIs
所以對Java 8特性和使用的minSdkVersion之間的關聯性,開發者必須去精心選擇。我們也必須注意到語言向后兼容是由Jack編譯器提供的。在概念上,Jack編譯器將javac,ProGuard,以及dex的功能 合并 [5]到了一個轉換步驟中。這意味著[6]其中沒有中間的Java字節碼可用,且像是JaCoCo和Mockito的工具將無法工作,DexGuard也一樣 (ProGuard的企業版本)。讓我們祈禱這只是一個早期的preview版本,且這些問題將在未來被修復。
Lambda表達式以及相關的函數功能APIs —— 這是一個每個Android開發都會喜歡的東西。這類功能將會對增加代碼可讀性極為有用 —— 它替代了提供事件監聽器的匿名內部類。而之前只能通過額外的工具[7] 來實現,或者由Android Studio編輯器去折疊代碼。
默認及靜態interface方法可以幫助我們減少額外的工具類的數量,但顯然不是最需要的特性。還有一些其他的新增功能,我希望去說的更詳細一些,因此不在本文的范圍內。
對我來說最有趣的事 ——?Java 8 流(Streams) —— 在當前的預覽版中不可用。我們可以發現事實上它 剛被merge[8] 到AOSP源碼,所以期望可以在下個N Preview 或者 Beta release中見到它。如果你實在等不及去瀏覽 —— 可以試試使用 Lightweight-Stream-API[9],目前的一個開源向后兼容。
示例項目
官方手冊[10]提供了指示,甚至還有圖展示了如何去配置你的項目使用 Android N Preview 和 Java 8。在這兒沒什么可以再說的,就跟著指示走吧。
下一步是去配置你的app模塊的 build.gradle 文件。你可以在下面看到實例的 build.gradle 文件。從N SDK上的公告來看,似乎可以設置 minSdkVersion 為 Jelly Bean 或者 KitKat。 但… 在將targetSdkVersion 設為Android N Preview后,將無法工作在API低于N的設備上[11]。另外,如果你把 minSdkVersion 設置為23或者更低 —— Java 8代碼將無法編譯。這里是一些在 SO forums[12]的hack,描述了怎么設置minSdk為想要的值并使得app可以工作。我希望你不會在生產代碼中使用這種方法 :)
我決定保持實例代碼干凈,所以沒有添加任何hack手段來做低版本兼容,請讀者自由去嘗試或者使用N的測試設備/模擬器。
android {
compileSdkVersion 'android-N'
buildToolsVersion '24.0.0 rc1'
defaultConfig {
applicationId "org.sergiiz.thermometer"
minSdkVersion 'N' // 在 N Preview 中不能使用低于N的版本
targetSdkVersion 'N'
versionCode 1
versionName "1.0"
jackOptions{
enabled true
}
}
compileOptions {
targetCompatibility 1.8
sourceCompatibility 1.8
}
//...
}
所以來試著實現一些Java 8的優雅代碼到我們陳舊的Thermometer項目。請注意這個設置是跟著新的文檔[13]來的,使用了新的 Gradle DSL 方法 jackOptions 來配置Jack編譯器設置,在更老的版本中,我們使用 useJack true 來達到同樣的結果。
這是一個接口,包含了默認方法:
public interface Thermometer {
void setCelsius(final float celsiusValue);
float getValue();
String getSign();
default String getFormattedValue(){
return String.format(Locale.getDefault(),
"The temperature is %.2f %s", getValue(), getSign());
}
}
實現了這個接口的類:
public class FahrenheitThermometer implements Thermometer {
private float fahrenheitDeg;
public FahrenheitThermometer(float celsius) {
setCelsius(celsius);
}
@Override
public void setCelsius(float celsius) {
fahrenheitDeg = celsius * 9 / 5 + 32f;
}
@Override
public float getValue() {
return fahrenheitDeg;
}
@Override
public String getSign() {
return Constants.DEGREE + "F";
}
}
增加一個點擊事件的lambda函數:
buttonFahrenheit.setOnClickListener(view1 -> {
fahrenheitThermometer.setCelsius(currentCelsius);
String text = fahrenheitThermometer.getFormattedValue();
makeText(MainActivity.this, text, Toast.LENGTH_SHORT).show();
});
例子的完整源碼可見 GitHub repository[14]。
總結
在這篇文章中,我們了解了Java 8的用例,以及目前其在Android N Preview SDK的實現情況。我們也看到了當前Jack編譯器的限制,及其在最后發布前可能被修復的功能。在demo項目中我們檢驗了如何去使用新的Java 8特性,以及它們可以被應用的target SDK版本。