使用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 中用模拟器和设备进行调试。您能够继续使用全部的工具,同时又获得一种生产率更高的编程语言。

相关文章
相关标签/搜索