在 Android N 预览版中使用 Java 8 的新特性
TamieBriley
8年前
<p>Android团队最近发布了Android N Preview,带来了很多提升,包括由Jack编译器提供的Java 8支持。在这篇文章中,我们将来看看它究竟对Android开发者意味着什么,以及如何尝试新的语言特性。</p> <blockquote> <p><em>免责声明: 本信息在2016年3月30日是有效的,我不确定在下个release版本中,Google团队会增加什么新的没有在此提到的Java 8特性。</em></p> </blockquote> <p><a href="https://simg.open-open.com/show/e240cde4ebcdc1c0613196c6c0e71ab1.png"><img alt="" src="https://simg.open-open.com/show/e240cde4ebcdc1c0613196c6c0e71ab1.png"></a></p> <p> </p> <h2>概览</h2> <p>在这篇文章中,去介绍Oracle Java 8的新特性并没有太大意义 —— 很多信息已经在互联网上有了。我个人最喜欢的是Simon Ritter的“<a href="/misc/goto?guid=4959671977459907895" rel="external">Java SE 8的55个新特性<sup>[2]</sup></a>”。</p> <p>另一方面,Android <a href="/misc/goto?guid=4959671977537844433" rel="external">官方的Java 8公告<sup>[3]</sup></a> 留下了很多开放的问题给开发者们,感觉上并非所有的原生 Java 8 功能都是可用的。更详细的 <a href="/misc/goto?guid=4959671977622701578" rel="external">技术公告<sup>[4]</sup></a> 确认了这一点。我们可以根据在 Android N 中的可用性,将这些语言特性分类如下:</p> <p>Android Gingebread (API 9)及以上:</p> <ul> <li><a href="/misc/goto?guid=4959671977713266591" rel="external">Lambda 表达式</a></li> <li><a href="/misc/goto?guid=4959671533197482795" rel="external">java.util.function</a></li> </ul> <p>Android N及以上:</p> <ul> <li><a href="/misc/goto?guid=4958970912859213006" rel="external">默认和静态interface方法</a></li> <li><a href="/misc/goto?guid=4959671977857125033" rel="external">可重复的注解</a></li> <li><a href="/misc/goto?guid=4958973078512384681" rel="external">流(Streams)</a></li> <li>反射APIs</li> </ul> <p>所以对Java 8特性和使用的minSdkVersion之间的关联性,开发者必须去精心选择。我们也必须注意到语言向后兼容是由Jack编译器提供的。在概念上,Jack编译器将javac,ProGuard,以及dex的功能 <a href="/misc/goto?guid=4959671977971581991" rel="external">合并 <sup>[5]</sup></a>到了一个转换步骤中。<a href="/misc/goto?guid=4959669928266620722" rel="external">这意味着<sup>[6]</sup></a>其中没有中间的Java字节码可用,且像是JaCoCo和Mockito的工具将无法工作,DexGuard也一样 (ProGuard的企业版本)。让我们祈祷这只是一个早期的preview版本,且这些问题将在未来被修复。</p> <p>Lambda表达式以及相关的函数功能APIs —— 这是一个每个Android开发都会喜欢的东西。这类功能将会对增加代码可读性极为有用 —— 它替代了提供事件监听器的匿名内部类。而之前只能通过<a href="/misc/goto?guid=4959671978087841625" rel="external">额外的工具<sup>[7]</sup></a> 来实现,或者由Android Studio编辑器去折叠代码。</p> <p>默认及静态interface方法可以帮助我们减少额外的工具类的数量,但显然不是最需要的特性。还有一些其他的新增功能,我希望去说的更详细一些,因此不在本文的范围内。</p> <p>对我来说最有趣的事 —— Java 8 流(Streams) —— 在当前的预览版中不可用。我们可以发现事实上它 <a href="/misc/goto?guid=4959671978167363396" rel="external">刚被merge<sup>[8]</sup></a> 到AOSP源码,所以期望可以在下个N Preview 或者 Beta release中见到它。如果你实在等不及去浏览 —— 可以试试使用 <a href="/misc/goto?guid=4958962365522687445" rel="external">Lightweight-Stream-API<sup>[9]</sup></a>,目前的一个开源向后兼容。</p> <h3>示例项目</h3> <p><a href="/misc/goto?guid=4958836028214469764" rel="external">官方手册<sup>[10]</sup></a>提供了指示,甚至还有图展示了如何去配置你的项目使用 Android N Preview 和 Java 8。在这儿没什么可以再说的,就跟着指示走吧。</p> <p><a href="https://simg.open-open.com/show/1da533f760e7a0c53c9e87801c1a8611.jpg"><img alt="" src="https://simg.open-open.com/show/1da533f760e7a0c53c9e87801c1a8611.jpg"></a></p> <p>下一步是去配置你的app模块的 build.gradle 文件。你可以在下面看到实例的 build.gradle 文件。从N SDK上的公告来看,似乎可以设置 <em>minSdkVersion</em> 为 Jelly Bean 或者 KitKat。 但… 在将<em>targetSdkVersion</em> 设为Android N Preview后,<a href="/misc/goto?guid=4959671978317638807" rel="external">将无法工作在API低于N的设备上<sup>[11]</sup></a>。另外,如果你把 <em>minSdkVersion</em> 设置为23或者更低 —— Java 8代码将无法编译。这里是一些在 <a href="/misc/goto?guid=4959671978403694982" rel="external">SO forums<sup>[12]</sup></a>的hack,描述了怎么设置minSdk为想要的值并使得app可以工作。我希望你不会在生产代码中使用这种方法 :)</p> <p>我决定保持实例代码干净,所以没有添加任何hack手段来做低版本兼容,请读者自由去尝试或者使用N的测试设备/模拟器。</p> <pre> <code class="language-java">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 } //... }</code></pre> <p><br> 所以来试着实现一些Java 8的优雅代码到我们陈旧的Thermometer项目。请注意这个设置是跟着新的<a href="/misc/goto?guid=4959671978486759930" rel="external">文档<sup>[13]</sup></a>来的,使用了新的 Gradle DSL 方法 <em>jackOptions</em> 来配置Jack编译器设置,在更老的版本中,我们使用 <em>useJack true</em> 来达到同样的结果。</p> <p>这是一个接口,包含了默认方法:</p> <pre> <code class="language-java">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()); } }</code></pre> <p>实现了这个接口的类:</p> <pre> <code class="language-java">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"; } }</code></pre> <p>增加一个点击事件的lambda函数:</p> <pre> <code class="language-java">buttonFahrenheit.setOnClickListener(view1 -> { fahrenheitThermometer.setCelsius(currentCelsius); String text = fahrenheitThermometer.getFormattedValue(); makeText(MainActivity.this, text, Toast.LENGTH_SHORT).show(); });</code></pre> <p>例子的完整源码可见 <a href="/misc/goto?guid=4959671978573169710" rel="external">GitHub repository<sup>[14]</sup></a>。</p> <h2>总结</h2> <p>在这篇文章中,我们了解了Java 8的用例,以及目前其在Android N Preview SDK的实现情况。我们也看到了当前Jack编译器的限制,及其在最后发布前可能被修复的功能。在demo项目中我们检验了如何去使用新的Java 8特性,以及它们可以被应用的target SDK版本。</p>