使用 Scala 編寫 Android 應用

轉自 :http://www.madeye.me/2013/05/09/scala-in-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 的情況下代碼量可以大大減少。

Register BroadcastReceiver in Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
BroadcastReceiver connectivityListener = null;

void onResume() {
  super.onResume();
  // ...
  connectivityListener = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
      doSomething();    
    }
  };
  registerReceiver(connectivityListener, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
}

void onPause() {
  unregisterReceiver(connectivityListener);
  // ...
  super.onPause();
}

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

Register BroadcastReceiver in Scala
1
2
3
broadcastReceiver(ConnectivityManager.CONNECTIVITY_ACTION) { (context, intent) =>
  doSomething()
}

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

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

How?

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
project/
  Build.scala
  plugins.sbt
src/
  main/
    res/
       <resource files>
    assets/
       <asset files>
    jni/
       <native codes>
    libs/
       <jar and native libraries>
    scala/
       <main Scala sources>
    java/
       <main Java sources>
    AndroidManifest.xml
       <manifest template>
  test/
    scala/
       <test Scala sources>
    java/
       <test Java sources>

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

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

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

1
2
3
4
5
# build debug apk
sbt android:package-debug

# build signed apk
sbt android:prepare-market

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 因無法從代碼中檢測到相關引用而會錯誤的將其裁減。因此建議你至少加入以下規則:

1
2
3
4
5
-keep class android.support.v4.app.** { *; }
-keep interface android.support.v4.app.** { *; }
-keep class com.actionbarsherlock.** { *; }
-keep class your.project.** { *; }
-keepattributes *Annotation*

Happy Hacking!


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章