使用Scala編寫Android應用程序

在本文中,咱們將建立一個在 Android 設備上運行的移動應用程序。您將須要安裝 Android SDK;本文使用 V1.5 SDK。應用程序代碼將用 Scala 編程語言編寫。若是您歷來沒用過 Scala,那麼沒有關係,由於本文將解釋 Scala 代碼。可是,即便您不熟悉 Scala,建議您至少熟悉 Java 語言。本文使用 Scala V2.7.5 進行開發。對於 Android 和 Scala 都提供了很好的 Eclipse 插件。本文使用 Eclipse V3.4.2 和 Android Development Tools(ADT) V0.9.1 以及 Scala IDE 插件 V2.7.5。請參閱 參考資料,得到全部這些工具。html

 




回頁首



設置java

編寫 Android 應用程序聽起來像是一個複雜的命題。Android 應用程序在它們本身的虛擬機中運行:Dalvik 虛擬機。可是,Android 應用程序的構建路徑是開放的。下面代表了咱們將使用的基本策略。android


圖 1. Android 上 Scala 的構建路徑
Android 上 Scala 的構建路徑 

編程

其思想是,咱們首先將全部 Scala 代碼編譯成 Java 類文件。這是 Scala 編譯器的工做,因此這方面沒什麼太複雜的事情。接下來,獲取 Java 類文件,使用 Android dex 編譯器將類文件編譯成 Android 設備上的 Dalvik VM 使用的格式。這就是所謂的 dexing,也是 Android 應用程序的常規編譯路徑。一般,要經歷從 .java 文件到 .class 文件再到 .dex 文件的過程。在本文,唯一不一樣的是咱們從 .scala 文件開始。最後,.dex 文件和其餘應用程序資源被壓縮成一個 APK 文件,該文件可安裝到 Android 設備上。數組

那 麼,如何讓這一切發生?咱們將使用 Eclipse 作大部分工做。可是,此外還有一個較複雜的步驟:要讓代碼運行,還須要來自標準 Scala 庫中的代碼。在典型的 Scala 安裝中,這是 /lib/scala-library.jar 中一個單獨的 JAR。可是,這個 JAR 包括一些不受 Android 支持的代碼。有些代碼須要稍做調整,有些代碼則必須移除。scala-library.jar 的定製構建是運行得最好的,至少目前是這樣。請參閱 參考資料,瞭解這裏使用的定製構建。咱們將把這個 JAR 稱做 Android 庫 JAR。安全

有了這個 JAR,剩下的事情就很容易了。只需使用 Eclipse 的 ADT 插件建立一個 Android 項目。而後將一個 Scala 特性(nature)添加到項目中。用前面談到的 Android 庫替代標準的 Scala 庫。最後,將輸出目錄添加到類路徑中。如今,能夠開始了。主 Scala 站點對此有更詳細的描述(請參閱 參考資料)。如今,咱們有了基本的設置,接下來看看咱們將使用 Scala 建立的 Android 應用程序。數據結構

 




回頁首



UnitsConverter閉包

現 在,咱們知道如何利用 Scala 代碼,將它轉換成將在 Android 設備上運行的二進制格式,接下來可使用 Scala 建立一個移動應用程序。咱們將建立的應用程序是一個簡單的單位轉換應用程序。經過這個應用程序能夠方便地在英制單位與公制單位之間來回轉換。這是一個很是 簡單的應用程序,可是咱們將看到,即便是最簡單的應用程序也能夠從使用 Scala 中獲益。咱們首先看看UnitsConverter 的佈局元素。架構

建立佈局app

您 也許對編寫手機上運行的 Scala 感到興奮,可是並不是全部的移動開發編程都應該用 Scala 或 Java 語言完成。Android SDK 提供了一種很好的方式,使用基於 XML 的佈局系統將用戶界面代碼與應用程序邏輯分離。咱們來看看本文中的應用程序的主要佈局文件,如清單 1 所示。


清單 1. Converter 應用程序的主要佈局

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent" android:layout_height="fill_parent"
    android:gravity="center_horizontal" android:padding="10px"
    >
    <TextView android:id="@+id/prompt_label" android:layout_width="wrap_content"
        android:layout_height="wrap_content" 
        android:text="@string/prompt_metric"/>
    <EditText android:id="@+id/amount" android:layout_below="@id/prompt_label"
        android:layout_width="fill_parent" 
        android:layout_height="wrap_content"/>
    <TextView android:id="@+id/uom_label"  
        android:layout_below="@id/amount"
        android:layout_width="wrap_content" 
        android:layout_height="wrap_content"
        android:text="@string/uom"/>
    <Spinner android:id="@+id/uom_value"
        android:layout_below="@id/uom_label"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
    <Button android:id="@+id/convert_button"
        android:layout_below="@id/uom_value"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/convert_button_label"/>
    <TextView android:id="@+id/result_value"
        android:layout_below="@id/convert_button"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"/>        
</RelativeLayout>

 

以上代碼很是簡潔地建立了該應用程序的主 UI。它的根節點是一個 RelativeLayout 容器元素。Android SDK 中有不少佈局選項。RelativeLayout 指示運行時使用相對定位對不一樣的 UI 小部件進行佈局。要使用相對定位,可添加可見元素 — 在這裏是一個 TextView 元素。這是用於顯示文本的一個簡單的元素。它被賦予一個 ID prompt_label。接下來的元素,即一個 EditText 元素(一個文本輸入框)將用到它。這個元素有一個 layout_below 屬性,它的值等於 prompt_label ID。換句話說,EditText 應該放在名爲 prompt_label 的元素的下方。

佈局代碼剩下的部分很是簡單。有一個帶標籤的文本輸入框、一個帶標籤的微調器(一個組合框或下拉框)、一個按鈕和一個用於輸出的文本框。圖 2 顯示正在運行的應用程序的一個截圖,其中標出了不一樣的元素。


圖 2. Android lLayout — 分解圖
Android lLayout -- 分解圖 

那麼,以上視圖中看到的不一樣文本值來自哪裏呢?注意,清單 1 中的一些元素有一個 text 屬性。例如,prompt_label 元素有一個等於 @string/prompt_metric 的 text 屬性。這代表它將使用 Android 應用程序中一個標準的資源文件:strings.xml 文件,如清單 2 所示。


清單 2. strings.xml 資源

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="prompt_metric">Enter amount (KM, g, L, C)</string>
    <string name="prompt_english">Enter amount (miles, lbs, gallons, 
F)</string>
    <string name="uom">Units of Measure</string>
    <string name="convert_button_label">Convert</string>
    <string name="app_name">Converter</string>
    <string name="english_units">English</string>
    <string name="metric_units">Metric</string>
</resources>

 

如今能夠看到,圖 2 中全部的文原本自何處。微調器有一個下拉框,其中包含可用於度量的單位,那些單位在清單 2 中沒有列出。相反,它們來自另外一個文件 arrays.xml,如清單 3 所示。


清單 3. arrays.xml 資源

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <array name="english_units">
        <item>Fahrenheit</item>
        <item>Pounds</item>
        <item>Ounces</item>
        <item>Fluid Ounces</item>
        <item>Gallons</item>
        <item>Miles</item>
        <item>Inches</item>
    </array>
    <array name="metric_units">
        <item>Celsius</item>
        <item>Kilograms</item>
        <item>Grams</item>
        <item>Millileters</item>
        <item>Liters</item>
        <item>Kilometers</item>
        <item>Centimeters</item>
    </array>    
</resources>

 

如今,咱們能夠看到將用於微調器的那些值。那麼,這些值如何出如今微調器中,應用程序如何在英制單位與公制單位之間切換?要回答這些問題,咱們須要看看應用程序代碼自己。

 




回頁首



Scala 應用程序代碼

Converter 應用程序的代碼很是簡單 — 無論用什麼語言編寫。固然,用 Java 編寫起來很是容易,可是用 Scala 編寫也一樣不復雜。首先咱們看看前面見過的 UI 背後的代碼。

視圖背後的代碼

解釋建立 UI 的 Scala 代碼的最簡單方式是先看看代碼,而後走查一遍。對於任何應用程序,都是在應用程序的 AndroidManifest.xml 文件中定義應用程序的默認活動。任何 UI 背後都有一個Activity 類,默認的 Activity 定義當應用程序初次裝載時執行的 Activity 類。對於像本文這樣簡單的應用程序,有一個 Converter 類,清單 4 中顯示了它的源代碼。


清單 4. Converter 活動類

class Converter extends Activity{
    import ConverterHelper._
    private[this] var amountValue:EditText = null
    private[this] var uom:Spinner= null
    private[this] var convertButton:Button = null
    private[this] var resultValue:TextView = null
    
    override def onCreate(savedInstanceState:Bundle){
      super.onCreate(savedInstanceState)
      setContentView(R.layout.main)
      uom = findViewById(R.id.uom_value).asInstanceOf[Spinner]
      this.setUomChoice(ENGLISH)
      amountValue = findViewById(R.id.amount).asInstanceOf[EditText]
      convertButton = findViewById(R.id.convert_button).asInstanceOf[Button]
      resultValue = findViewById(R.id.result_value).asInstanceOf[TextView]
      convertButton.setOnClickListener( () => {
          val unit = uom.getSelectedItem.asInstanceOf[String]
          val amount = parseDouble(amountValue.getText.toString)
          val result = UnitsConverter.convert(Measurement(unit,amount))
          resultValue.setText(result)
      })
    }
    override def onCreateOptionsMenu(menu:Menu) = {
      super.onCreateOptionsMenu(menu)
      menu.add(NONE, 0, 0, R.string.english_units)
      menu.add(NONE, 1, 1, R.string.metric_units)
      true
    }
    override def onMenuItemSelected(featureId:Int, item:MenuItem) = {
      super.onMenuItemSelected(featureId, item)
      setUomChoice(if (item.getItemId == 1) METRIC else ENGLISH)
      true
    }
    private 
    def setUomChoice(unitOfMeasure:UnitsSystem){
      if (uom == null){
        uom = findViewById(R.id.uom_value).asInstanceOf[Spinner]
      }
      val arrayId = unitOfMeasure match {
        case METRIC => R.array.metric_units
        case _ => R.array.english_units
      }
      val units = new ArrayAdapter[String](this, R.layout.spinner_view, 
        getResources.getStringArray(arrayId))
      uom.setAdapter(units)      
    }
}

 

咱們從這個類的頂部開始。它擴展 android.app.Activity。這是一個 Java 類,可是從 Scala 中能夠對 Java 類輕鬆地進行細分。接下來,它有一些實例變量。每一個實例變量對應前面定義的一個 UI 元素。注意,每一個實例變量還被限定爲 private[this]。這演示了 Scala 中特有的一種訪問控制級別,而 Java 語言中不存在這種訪問控制。這些變量不只是私有的,並且只屬於 Converter 類的特定實例。這種級別的訪問控制對於移動應用程序來講有些大材小用,可是若是您是一名 Scala 開發人員,能夠放心地在 Android 應用程序上使用您熟悉的語法。

回到清單 4 中的代碼,注意,咱們覆蓋了 onCreate 方法。這是 Activity 類中定義的方法,一般被定製的 Activity 覆蓋。若是用 Java 語言編寫該代碼,那麼應該添加一個 @Override 標註。在 Scala 中,override 是一個關鍵詞,用於確保正確性。這樣能夠防止誤拼方法名之類的常見錯誤。若是誤拼了方法名,Scala 編譯器將捕捉到方法名並返回一個錯誤。注意,在這個方法上,以及任何其餘方法上,不須要聲明返回類型。Scala 編譯器能夠輕鬆推斷出該信息,因此不須要畫蛇添足。

onCreate 中的大部分代碼相似於 Java 語言編寫的代碼。可是有幾點比較有趣。注意,咱們使用findViewById 方法(在 Activity 子類中定義)得到不一樣 UI 元素的句柄。這個方法不是類型安全的,須要進行類型轉換(cast)。在 Scala 中,要進行類型轉換,可以使用參數化方法asInstanceOf[T],其中 T 是要轉換的類型。這種轉換在功能上與 Java 語言中的轉換同樣。不過 Scala 有更好的語法。接下來,注意對 setUomChoice 的調用(稍後咱們將詳細談到這個方法)。最後,注意上述代碼得到一個在佈局 XML 中建立的按鈕的句柄,並添加一個單擊事件處理程序。

若是用 Java 語言編寫,那麼必須傳入 Android 接口 OnClickListener 的一個實現。這個接口只定義一個方法:onClick。實際上,您關心的只是那個方法,可是在 Java 語言中沒法直接傳入方法。而在 Scala 中則不一樣,在 Scala 中能夠傳入方法字面量(literal)或閉包。在這裏,咱們用語法 () => { ... } 表示閉包,其中方法的主體就是花括號中的內容。開始/結束括號表示一個不帶參數的函數。可是,我將這個閉包傳遞到 Button 的一個實例上的 setOnClickListener 方法,Button 是 Android SDK 中定義的一個 Java 類。如何將 Scala 閉包傳遞到 Java API?咱們來看看。

 




回頁首



Android 上的函數式編程

爲了理解如何讓 Android API 使用函數字面量,看看 Converter 類定義的第一行。這是一條重要的語句。這是 Scala 的另外一個很好的特性。您能夠在代碼的任何地方導入包、類等,它們的做用域限於導入它們的文件。在這裏,咱們導入 ConverterHelper 中的全部東西。清單 5 顯示ConverterHelper 代碼。


清單 5. ConverterHelper

object ConverterHelper{
  import android.view.View.OnClickListener
  implicit def funcToClicker(f:View => Unit):OnClickListener = 
    new OnClickListener(){ def onClick(v:View)=f.apply(v)}
  implicit def funcToClicker0(f:() => Unit):OnClickListener = 
    new OnClickListener() { def onClick(v:View)=f.apply}
}

 

這是一個 Scala 單例(singleton),由於它使用對象聲明,而不是類聲明。單例模式被直接內置在 Scala 中,能夠替代 Java 語言中的靜態方法或變量。在這裏,這個單例存放一對函數:funcToClicker 和 funcToClicker0。這兩個函數以一個函數做爲輸入參數,並返回OnClickListener 的一個實例,OnClickListener 是 Android SDK 中定義的一個接口。例如,funcToClicker 被定義爲以一個函數 f 爲參數。這個函數 f 的類型爲帶一個 View 類型(Android 中的另外一個類)的輸入參數的函數,並返回 Unit,它是 void 在 Scala 中的對等物。而後,它返回 OnClickListener 的一個實現,在這個實現中,該接口的 onClick 方法被實現爲將輸入函數 f 應用到 View 參數。另外一個函數 funcToClick0 也作一樣的事情,只是以一個不帶輸入參數的函數爲參數。

這兩個函數(funcToClicker 和 funcToClicker0)都被定義爲隱式函數(implicit)。這是 Scala 的一個方便的特性。它可讓編譯器隱式地將一種類型轉換成另外一種類型。在這裏,當編譯器解析Converter 類的 onCreate 方法時,它遇到一個 setOnClickListener 調用。這個方法須要一個OnClickListener 實例。可是,編譯器卻發現一個函數。在報錯並出現編譯失敗以前,編譯器將檢查是否存在隱式函數,容許將函數轉換爲 OnClickListener。因爲確實還有這樣的函數,因此它執行轉換,編譯成功。如今,咱們理解了如何使用 Android 中的閉包,接下來更仔細地看看應用程序邏輯 — 特別是,如何執行單位轉換計算。

單位轉換和計算

咱們回到清單 4。傳入 onClickListener 的函數收到用戶輸入的度量單位和值。而後,它建立一個Measurement 實例,並將該實例傳遞到一個 UnitsConverter 對象。清單 6 顯示相應的代碼。


清單 6. Measurement 和 UnitsConverter

case class Measurement(uom:String, amount:Double)

object UnitsConverter{
      // constants
    val lbToKg = 0.45359237D
      val ozToG = 28.3495231
      val fOzToMl = 29.5735296
      val galToL = 3.78541178
      val milesToKm = 1.609344
      val inchToCm = 2.54  
   
      def convert (measure:Measurement)= measure.uom match {
          case "Fahrenheit" => (5.0/9.0)*(measure.amount - 32.0) + " C"
            case "Pounds" => lbToKg*measure.amount + " kg"
            case "Ounces" => ozToG*measure.amount + " g"
            case "Fluid Ounces" => fOzToMl*measure.amount + " mL"
            case "Gallons" => galToL*measure.amount + " L"
            case "Miles" => milesToKm*measure.amount + " km"
            case "Inches" => inchToCm*measure.amount + " cm"
            case "Celsius" => (9.0/5.0*measure.amount + 32.0) + " F"
            case "Kilograms" => measure.amount/lbToKg + " lbs"
            case "Grams" => measure.amount/ozToG + " oz"
            case "Millileters" => measure.amount/fOzToMl + " fl. oz."
            case "Liters" => measure.amount/galToL + " gallons"
            case "Kilometers" => measure.amount/milesToKm + " miles"
            case "Centimeters" => measure.amount/inchToCm + " inches"
            case _ => ""
      }
}

 

Measurement 是一個 case 類。這是 Scala 中的一個方便的特性。用 「case」 修飾一個類會致使這個類生成這樣一個構造函數:這個構造函數須要類的屬性,以及 equals、 hashCode 和 toString 的實現。它對於像 Measurement 這樣的數據結構類很是適合。它還爲定義的屬性(在這裏就是 uom 和amount)生成 getter 方法。也能夠將那些屬性定義爲 vars(可變變量),而後也會生成 setter 方法。僅僅一行 Scala 代碼能夠作這麼多事情!

接下來,UnitsConverter 也是一個單例模式,由於它是使用 object 關鍵詞定義的。它只有一個 convert 方法。注意,convert 被定義爲至關於一條單一語句 — 一條 match 語句。它是一個單一表達式,因此不須要額外的花括號。它使用 Scala 的模式匹配。這是函數式編程語言中常見的一個強大特性。它相似於 Java 語言和不少其餘語言中的 switch 語句。可是,咱們能夠匹配字符串(實際上,還能夠有比這高級得多的匹配)。若是字符串匹配,則執行適當的計算,並返回格式化的字符串,以供顯示。最後,注 意與 _ 匹配的最後一個 case。Scala 中的不少地方使用下劃線做爲通配符。在這裏,它表示匹配任何東西,這相似於 Java 語言中的 default 語句。

如今,咱們理解了應用程序中的計算,最後來看看剩下的 UI 設置和菜單。

UI 初始化和菜單

回到清單 4。咱們說過要看看 setUomChoice。這個方法被定義爲帶有一個 UnitsSystem 類型的參數。咱們來看看如何定義這個類型。


清單 7. UnitsSystem

sealed case class UnitsSystem()
case object ENGLISH extends UnitsSystem
case object METRIC extends UnitsSystem

 

咱們看到,UnitsSystem 是一個密封的 case 類,沒有屬性。看上去它不是頗有用。接下來,咱們看看兩個 case 對象。還記得嗎,object 表示 Scala 中的一個單例。在這裏,有兩個 case 對象,每一個 case 對象都擴展 UnitsSystem。這是 Scala 中的一個常見的特點,它能夠提供更簡單、更類型安全的枚舉方式。

如今 setUomChoice 的實現更加合理。在得到微調器的一個句柄後,咱們匹配傳入的 UnitsSystem 的類型。這標識了咱們在前面見到的 arrays.xml 中的一個數組。這是使用 Android SDK 生成的 R類表示資源,例如 arrays.xml 文件。一旦知道使用哪一個數組,咱們就經過建立一個傳入微調器的適配器(在這裏是一個 ArrayAdapter),使用那個數組做爲微調器的數據源。

最後,看看清單 4 中的 onCreateOptionsMenu 和 onMenuItemSelected 方法。這些方法是在Activity 中定義的,咱們將在 Converter 活動中覆蓋這些方法。第一個方法建立一個菜單。第二個方法處理用戶從菜單中選擇 English 或 metric 的事件。它再次調用 setUomChoice。這使用戶能夠在從英制單位轉換爲公制單位與從公制單位轉換爲英制單位之間進行切換。

 




回頁首



結束語

Android 平臺的架構使它能夠用於在 Java 虛擬機上運行的任何編程語言。咱們看到了如何設置 Android 項目,使它使用 Scala 代碼。這個過程也能夠延伸到其餘 JVM 編程語言,例如 Groovy、JRuby 或 Fan。當能夠任意使用 Scala 編程語言時,編寫 Android 應用程序將變得更輕鬆。您仍可使用 Eclipse 進行開發。仍然能夠在 Eclipse 中用模擬器和設備進行調試。您能夠繼續使用全部的工具,同時又獲得一種生產率更高的編程語言。

相關文章
相關標籤/搜索