標籤:

使用 Scala 編寫 Android 應用

Why?

Android 開發一直有兩個無法迴避的問題:基於 JVM 的設計使得系統的性能受到拖累;Java 冗繁的語法令人絕望。

手機性能的大幅提升、Android 系統的持續改進,以及應用中 NDK 的廣泛使用使得 JVM 帶來的額外開銷變得微不足道。但 Java 作為一門民工語言已經遠遠落後於時代潮流則是無法改變的事實。更令人揪心的是,落在 Oracle 手上的 Java 對於整個開源社區來說都是潛在的威脅,這一點從 Oracle 針對 Google 的一系列 Android 相關的訴訟就可以大概明了。

Scala 作為一門 state-of-the-art 的編程語言,兼具面向對象以及函數式語言的特點。其設計在 JVM 之上,與 Java 類庫完全兼容,甚至可以與 Java 代碼相互轉換。另外 Scala 以類似 BSD 的協議發布,對於開源社區也更為友好。總的來看,Scala 是當前替代 Java 的最好選擇,在不需要放棄已經無比先進的 JVM 和足夠完備的 Java 生態的前提下,開發者們可以獲得更先進的語言特性和更高的開發效率。實際上類似的目標在 Groovy 和 JRuby 中都有所體現,但都做得不好。

至於 Android 開發,Scala 則提供了全新的體驗。以一段常見的 Android 代碼為例,在配合 Scaloid 的情況下代碼量可以大大減少。

BroadcastReceiver connectivityListener = null;nnvoid onResume() {n super.onResume();n // ...n connectivityListener = new BroadcastReceiver() {n @Overriden public void onReceive(Context context, Intent intent) {n doSomething();tn }n };n registerReceiver(connectivityListener, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));n}nnvoid onPause() {n unregisterReceiver(connectivityListener);n // ...n super.onPause();n}n

上面是 Java 的實現,可以看出兩個問題:Java 的回調用的是匿名類,顯得頗為繁瑣;註冊與反註冊這樣一對調用需要成對出現,當代碼量變大時,一不小心就會漏掉。

broadcastReceiver(ConnectivityManager.CONNECTIVITY_ACTION) { (context, intent) =>n doSomething()n}n

同樣的功能,用 Scala 實現的代碼可以非常簡潔:函數式的寫法替代了匿名類;隱式的方法被用來統一管理生命周期。

類似的例子還有很多,在這裡就不逐一列出了,有興趣的可以關注 Scala Android Blog。

How?

現階段使用 Scala 開發的 Android 應用還不多,技術上也不夠統一。在被坑了幾次後,總算有了一套靠譜的方案。

若是想要在現有的 Android 項目的基礎上進行重構,建議先將項目 Maven 化,具體方法可以見本博客的上一篇文章。在此基礎上通過將 Maven 替換為 SBT,並引入 sbt-android-plugin 這個插件,可以快速重構為標準的 Scala 項目結構。一個典型的 Scala Android 項目如下:

project/n Build.scalan plugins.sbtnsrc/n main/n res/n <resource files>n assets/n <asset files>n jni/n <native codes>n libs/n <jar and native libraries>n scala/n <main Scala sources>n java/n <main Java sources>n AndroidManifest.xmln <manifest template>n test/n scala/n <test Scala sources>n java/n <test Java sources>n

需要注意的是項目中的 AndroidManifest.xml 是不含 android:version 和 android:versionCode 這兩個屬性的。這兩個屬性會根據 Build.scala 中的設定自動生成。

在重構代碼之前,我們可以把已有的 Java 和 JNI 代碼放置到相應目錄中,將所有的依賴加入 Build.scala 文件或放在 libs 文件夾下。之後則可以挨個的將原來的 Java 代碼重構為 Scala。

Scala 是支持和 Java 混合編譯的,因此你可以隨時執行以下命令編譯並測試:

# build debug apknsbt android:package-debugnn# build signed apknsbt android:prepare-marketn

Android 的介面是專為 Java 設計的,而為了寫出更加地道的 Scala 代碼,建議再引入 Scaloid 來簡化 API 的調用。當熟練使用 Scala 編寫代碼後,代碼量可以減少至少一半。

一個完整的例子可以見我的 shadowsocks-android 項目。而更多的細節請參考 sbt-android-plugin 的 Wiki 頁面。

Tips

學習 Scala

Scala 雖然許多地方長的和 Java 很像,但是想要寫出「函數式」的風格需要重新學習很多內容。對於比較資深的 Java 程序員,建議直接去看《Programming in Scala》這本書,和《Scala API Doc》。之前還翻過一本《Scala for the Impatient》,標題很誘人但內容太淺顯,這裡不做推薦。

sbt-android-plugin

由於缺乏文檔,sbt-android-plugin 里有不少的坑,這裡大概列一下:

  • 簽名用的 keystore 要位於 ~/.keystore
  • 若是項目依賴於 APK Library,如 ActionBarSherlock。請務必在 Build.scala 中將 compileOrder 設定為 CompileOrder.JavaThenScala
  • Scala 對於 Java 7 的支持不好,所以如果系統中裝的是 JDK 7 以上版本,務必在 javacOptions 中加入 Seq("-source", "1.6", "-target", "1.6")

Proguard

Android 上是沒有 Scala 標準庫的,但若是將所有 Scala 的庫都打包進 APK,體積上會非常驚人(>20MB)。因此 sbt-android-plugin 默認會對沒有用到的類和方法進行精簡。由於其規則過於激進,偶爾會發生代碼被裁減的問題。比如一個自定義的 View,且只在布局文件中被使用,這時 Proguard 因無法從代碼中檢測到相關引用而會錯誤的將其裁減。因此建議你至少加入以下規則:

-keep class android.support.v4.app.** { *; }n-keep interface android.support.v4.app.** { *; }n-keep class com.actionbarsherlock.** { *; }n-keep class your.project.** { *; }n-keepattributes *Annotation*n

Happy Hacking!

推薦閱讀:

Android 源碼分析開篇

TAG:Scala | Android |