另辟蹊径创建移动应用

阅读数:2436 2014 年 9 月 15 日

过去几年,移动应用席卷了整个世界,在工作和生活的方方面面改变着我们使用互联网的方式。创建移动应用的各种技术也随之兴起,各种开发流程也将移动应用视为一等公民,开始考虑适应移动开发的流程。尽管已经让人感觉无处不在,真正的移动应用时代才刚刚开始。我们即将面对新一代的移动设备,如可穿戴设备或组成物联网的各种各样的移动装置。我们将面临全新的用于数据展示和命令接收的用户交互接口。我们也认识到越来越多的公司将真正采取移动优先的战略。所有的这些都将对我们未来几年设计、开发和测试软件的方式产生巨大影响。

这篇 InfoQ文章是“快速变化的移动技术领域”文章系列中的一篇。感兴趣的读者可以通过这里订阅新文章通知。

在苹果和安卓的应用商店中有成千上万各种用途的移动应用。iOS 设备上的应用通常使用 Objective-C 工具库创建而成,而安卓设备上的应用则基于 Java 语言。在这篇文章中我们将向您展示两种不太常用的使用 Java 和 Xtend 构建原生应用的方法,这两种方法能够帮助开发者在两个应用平台上共享代码,从而简化开发工作。

使用 Java和 RoboVM开发原生 iOS应用

将安卓和 iOS 两个平台同时作为目标平台的移动应用开发者经常面临很多挑战。在比较这两个平台的原生应用开发环境时,例如分别由谷歌和苹果提供的开发工具链,很容易就能够发现这两者之间有着本质的区别。谷歌所提供的安卓开发环境是基于 Eclipse 集成开发环境和 Java 程序设计语言的。而由苹果所提供的 iOS 开发环境则是基于 Xcode 集成开发环境和 Objective-C 程序设计语言的。

这些平台间的差异导致代码无法重用。而且很少有开发者能够同时精通两个环境。最终就导致几乎每个跨平台的移动应用都需要为各个平台准备单独的开发团队并使用单独的代码库。

RoboVM 是一个新的开源项目,旨在不影响开发者和应用用户体验的前提下解决上述问题。RoboVM 项目的目标是在 iOS 设备上使用 Java 和其他 JVM 语言,如 Scala,Clojure 和 Kotlin。与其他类似的工具不同,RoboVM 不会对开发者所使用的 Java 平台特性做任何限制,如反射机制或文件 I/O,并且还允许开发人员重用 Java 庞大的第三方库生态系统。RoboVM 的独特之处还在于开发人员能够通过一个 Java 到 Objective-C 的桥接器访问到完整的原生 iOS API。这样,应用程序开发时,就能够用 Java 语言编写真正的原生用户交互界面并且能够获取到完整的硬件访问权限,同时使用的开发工具也是 Java 开发人员所熟悉的 Eclipse 和 Maven 等。

使用 RoboVM,进行跨平台开发将变得相对容易;同一组 Java 开发人员就有能力构建两个版本的移动应用程序并且代码库中的相当一部分代码都能够被共享。

如何开始?

RoboVM 有多种调用方式,如命令行方式或使用 Maven 或 Gradle,最容易上手的方式应该是使用 RoboVM 的 Eclipse 插件。

配置要求

安装 RoboVM 的 Eclipse 插件之前,请确保系统满足如下要求:

  • 一台运行 Mac OS X 10.9 操作系统的 Mac 电脑。
  • Oracle Java SE 7 JDK。
  • 从 Mac 应用商店下载的 Xcode 5.x 集成开发环境。

需要注意的是,必须使用 Oracle Java SE 7 JDK 运行 Eclipse。Eclipse 无法正常运行在苹果的 Java 6 JVM 之上。

安装 RoboVM的 Eclipse插件

系统满足所有的先决条件之后,安装插件是一项很简单的工作。从 Eclipse 的 Help 菜单中打开 Eclipse Marketplace,搜索RoboVM并点击 Install Now 即可。

或者也可以使用如下更新站点

运行一个简单的 iOS应用

接下来我们将创建一个简单的 iOS 应用。首先创建一个新的项目:File => New => Project......在列表中选择RoboVM iOS Project向导。

Project Name,Main Class和 App name栏中输入 IOSDemo,在App id一栏中输入 org.robovm.IOSDemo。其他栏目保持默认值点击 Finish。

然后,创建一个新的名为 IOSDemo 的类文件,省略包名。将下面的代码拷贝粘贴到新创建的文件中,替换 Eclipse 自动生成的代码。

import org.robovm.apple.coregraphics.*;
import org.robovm.apple.foundation.*;
import org.robovm.apple.uikit.*;

public class IOSDemo extends UIApplicationDelegateAdapter {

    private UIWindow window = null;
    private int clickCount = 0;

    @Override
    public boolean didFinishLaunching(UIApplication application,
            NSDictionary launchOptions) {

        final UIButton button = UIButton.create(UIButtonType.RoundedRect);
        button.setFrame(new CGRect(115.0f, 121.0f, 91.0f, 37.0f));
        button.setTitle("Click me!", UIControlState.Normal);

        button.addOnTouchUpInsideListener(new UIControl.OnTouchUpInsideListener() {
            @Override
            public void onTouchUpInside(UIControl control, UIEvent event) {
                button.setTitle("Click #" + (++clickCount), UIControlState.Normal);

                }
            });

            window = new UIWindow(UIScreen.getMainScreen().getBounds());
            window.setBackgroundColor(UIColor.colorLightGray());
            window.addSubview(button);
            window.makeKeyAndVisible();

            return true;
       }
      
       public static void main(String[] args) {
            NSAutoreleasePool pool = new NSAutoreleasePool();
            UIApplication.main(args, null, IOSDemo.class);
            pool.close();
       }
}

最后,右键点击刚刚创建的项目,选择Run As... =>iOS Simulator App (iPhone),在 iOS 模拟器中启动应用程序。这样,应用程序就运行在一个模拟的 iPhone 上了。

如果需要在真实的设备上运行应用程序,需要使用Run As... =>iOS Device App选项。需要注意的是,执行这一选项之前,所用到的设备需要进行相应的设置。设置的过程超出了本文的讨论范围。详细信息请参照苹果的官方文档

创建用于应用商店发布的 IPA文件

如果应用商店的发布证书(Distribution Certificate)和应用描述文件(Provisioning Profile)已经准备妥当,创建用于提交到应用商店的 IPA 软件包只需要在 Eclipse 中右键点击 RoboVM iOS 项目,选择 RoboVM Tools=>Package for App Store/Ad-Hoc distribution… ,填写对话框中相关信息即可。

完成上述操作后,将会在目标文件夹中生成一个后缀为.IPA 的文件。使用 Application Loader 即可验证生成的 IPA 文件并将其提交到应用商店中。使用 Spotlight 可以很方便地定位到 Application Loader 应用。

从苹果网站上能够找到很多关于如何加入 iOS 开发者计划及如何创建用于应用商店发布的证书和应用描述文件的资源

底层实现机制

字节码编译器

RoboVM 的核心是它的预编译器。预编译器可以通过命令行或者 Maven、Gradle 等构建工具或 IDE 调用。它可以将 Java 字节码翻译成用于特定操作系统和 CPU 型号的机器码。一般来说会翻译成用于 iOS 系统和 ARM 处理器的机器码,不过 RoboVM 也支持将字节码转为运行在 x86 CPU(32 位)上的 Mac OS X 和 Linux 系统的机器码。

这种预编译的方法与 Oracle Hotspot 之类的传统 JVM 的工作机制有很大的区别。这些 JVM 通常会在运行时读取 Java 字节码,然后以某种方式执行包含在字节码的虚拟机指令。为了加快这一进程,JVM 采用了一种被称为即时编译的技术。简单来说,这个过程会在程序第一次调用某个方法时,将这一方法的虚拟机指令翻译成当前系统所用的 CPU 型号对应的机器码。

由于苹果内置于 iOS 中的技术限制,在 iOS 应用中使用任何形式的即时编译技术都是不可能的。唯一的替代方案就是使用解释器或像 RoboVM 中所用的预编译技术,而解释器这种方式速度很慢并且十分耗电。预编译的过程发生在使用开发者的机器进行编译的时候,在 iOS 设备上运行时,生成的机器码就能够全速运行,因此在速度上可以与由 Objective-C 编译生成的代码媲美,甚至可能会更快一些。

由于 RoboVM 预编译器消费 Java 字节码,而不是 Java 源代码,因此至少在理论上可以用于任何能够编译成字节码的 JVM 语言。目前已知的 RoboVM 预编译器能够正常工作的 JVM 语言有 Scala,Clojure 和 Kotlin。这种方法的另一个好处是,可在无需任何原始源代码的情况下,在标准 JAR 文件中的第三方库上使用 RoboVM,这样就可以在应用中使用专有的和闭源的库。

增量编译

即使是非常简单的 RoboVM 应用,例如 IOSDemo 应用,在第一次启动时,都需要耗费较长一段时间。RoboVM 编译器编译应用的过程是从应用的 main 类开始。然后编译 main 类所用到的所有的类,之后再编译前面的类所用到的所有类,如此循环,直到应用所需的所有类均完成编译为止。在这一过程中,标准的运行时类,如 java.lang.Object 和 java.lang.String,也包含在编译的范围内。这是一个一次性的过程。RoboVM 会缓存已经编译过的类,只有一个类或与它有直接依赖关系的类已经发生了改变时,才会重新编译这个类。

增量编译和缓存目标文件的好处在于能够减少编译所耗费的时间。在生成的可执行文件中仅包含能够从 Main 类触及到的类可以降低可执行文件的大小。不过,在某些情况下(如通过反射机制加载类时),RoboVM 编译器无法决定是否应该对某个类进行编译。不过,可以给编译器下达指令,显式地将某个特定的类或者所有符合某个条件的类包含在编译范围内。

基于安卓的运行时类库

任何的 JVM 虚拟机都需要运行时类库。这个类库为所有的 Java 程序提供标准的包和类,如 java.lang.Object 和 java.lang.String。RoboVM 的运行时类库来自于安卓开源项目和已经被移植到 RoboVM 的非安卓专用的包中。这就意味着如果 Java 或 JVM 代码中只用到了安卓标准包中的类,那么这些代码直接就能够在 RoboVM 上正常运行。

RoboVM现状

RoboVM 目前仍在开发过程中,不过已经可以基本使用。1.0 版本预计将在 2014 年年底之前发布。

在苹果应用商店中已经有至少 50 个基于 RoboVM 的应用。已知应用程序的最新列表请参见这里

目前为止,大概有 50% 左右的 iOS API 可用在基于 RoboVM 的 iOS 应用中。在 RoboVM 的 Wiki 上可以查看到关于这些绑定的当前状态列表。截至现在,RoboVM 上已经能够运行由Scala,Clojure 和Kotlin 所编写的代码。

关于 RoboVM 的文档,目前仍在完善过程中。在 2014 年晚些时候,1.0 版本发布时,将会有较为完备的文档说明。

RoboVM 的应用目前仍无法进行 Debug。这一问题也将在今年的晚些时候解决。

限制

RoboVM 只能够将已经完成预编译的类加载到应用中。这就意味着在 RoboVM 应用中,无法在运行时使用自定义的类加载器动态创建字节码并将其加载到应用中。也就是说,RoboVM 无法支持运行时创建或修改类的技术。

更多相关信息

使用 Xtend创建安卓应用

Xtend简介

Xtend 1是一种可以编译成可读 Java 源码的静态类型程序设计语言。这种语言本身是同类设计中的最佳典范,特别是在可读性和强大的可扩展性方面,不过它也让 Java 的互操作性问题显而易见。这种语言鼓吹函数式编程风格和多分派、扩展方法、拉姆达表达式及编译期宏等特性。与其他的 Java 替代品不同,Xtend 本身并不包含庞大的标准库,而只是在标准的 JDK 上添加了一些扩展方法。Xtend 还可以保证避免 Java 互操作性问题的出现并且能够提供强大的 IDE 支持。

为什么安卓上的 Java如此难用

Java 代码往往十分冗长,特别是在安卓操作系统上。由于 Android API 的级别很低而且经常出现没有经过充分定义的类型(到处都是 int 类型)。另外一个烦恼就是无处不在的 XML 文件的使用和绑定。由于 Android 上尚未支持 Java 8,我们还不得不仔细阅读无处不在的匿名类。而且不幸的是,Java 无法修剪代码以增强可读性,我们只能将代码与多余的符号、类型信息和样板习语(boilerplate idioms)混杂在一起。

安卓对 JVM语言的最低要求

Java 语言在安卓上的替代品必须要能够保证不增加任何运行时的系统开销,这就将所有的动态语言排除在外。另外,也不希望出现任何不必要的间接类型转换。例如,代码中应该只使用 Java 和安卓类型,不应因为互操作性问题而需要来回转换。这不仅是处于性能方面的担心,在调试时也比较令人烦恼。最后,安卓系统限制每个应用只能够使用 65536 个方法。因此,寻找 Java 的替代品时,一定不能在应用中添加大的标准 SDK,因为这样会大大减少开发人员所能使用的方法数量。举例来说,使用 Groovy 的 SDK 会增加 8000 多个方法。

Xtend——安卓开发的完美解决方案?

Xtend 能够转化成地道的 Java 源代码,并且基本上只依赖于 JDK 和安卓系统的类。在运行时,也没有间接寻址、转换或者其他任何额外的开销。也就是说,Xtend 代码能够和 Java 的源代码有着基本一致的运行速度。另外,Xtend 还包含一个经过精简的为安卓系统提供的运行时库,只有 275kb 大小并且几乎包含了你所需要的一切。Xtend Eclipse 插件与 ADT(安卓开发工具)的整合也相当完美,对于新的安卓构建系统3,甚至还提供了相应的 Gradle 插件2。接下来就让我们详细了解一下如何使用 Xtend 改善典型的安卓代码。

Hello安卓!

与往常一样,我们先看一个简单的 Hello World 示例程序:

class HelloWorldActivity extends Activity { 

   override protected void onCreate(Bundle savedInstanceState) { 
      super.onCreate(savedInstanceState) 
   
      val button = new Button(this)
      button.text = "Say Hello!"  
      button.onClickListener = [ 
         Toast.makeText(context, "Hello Android from Xtend!", Toast.LENGTH_LONG).show 
      ] 
      val layout = new LinearLayout(this) 
      layout.gravity = Gravity.CENTER 
      layout.addView(button) 
      contentView = layout 
  } 
} 

对于 Java 开发者来说,这个例子使用了类 Java 的编程风格,因此第一眼看上去会非常熟悉。另外,你可能会注意到,示例中所用的 API 100% 来自于安卓 SDK 和 JDK。

主要的区别在于:

  • 没有分号(分号是可选的)
  • 使用 setter 和 getter 访问对象属性
  • 属性的默认可见性(如,类默认是共有的)
  • 使用拉姆达表达式替代匿名类

在语言的特性方面,有很多地方可以深入探讨,不过在此之前,先让我们看一下如何将 Xtend 编译器与相应的 Android 构建过程整合在一起。

使用 Gradle进行构建

对于目前最常用的三个构建系统:Maven,Gradle 和 Ant,Xtend 都有相应的插件支持。谷歌最近为安卓项目引入了新的基于 Gradle 的构建系统。接下来我们看一下使用 Gradle 构建我们的“Hello World”项目需要做哪些工作。

本文假设你已经在系统中安装了最新版本的 Gradle 和安卓 SDK 并且正确的设置了 ANDROID_HOME 环境变量。同时,你已经将 Gradle 的 /bin 目录添加到了 PATH 环境变量中。

接下来需要将构建脚本“build.gradle”添加到你的 Eclipse Android 项目的根目录下,build.gradle 文件样例如下:

buildscript {
   repositories {
      mavenCentral()
   }
   dependencies {
      classpath 'com.android.tools.build:gradle:0.8.+'
      classpath 'org.xtend:xtend-gradle-plugin:0.1.+'  
   }
}

apply plugin: 'android' 
apply plugin: 'xtend-android' 

repositories { 
  mavenCentral() 
} 

dependencies { 
  compile ('org.eclipse.xtend:org.eclipse.xtend.lib:2.6.+') 
} 

android { 
   compileSdkVersion 19 
   buildToolsVersion "19.1.0"
   sourceSets { 
      main { 
         manifest { 
            srcFile 'AndroidManifest.xml' 
         } 
         java { 
            srcDir 'src' 
         } 
         res { 
            srcDir 'res' 
         } 
         assets { 
            srcDir 'assets' 
         } 
         resources { 
            srcDir 'src' 
         } 
         aidl { 
            srcDir 'src' 
         } 
      } 
   } 
} 

其主要工作就是导入并调用 Maven 和 Xtend 的构建插件。此外,我们将运行时库添加到项目中并告知 Android 插件我们正在使用 Eclipse 风格的项目布局。上述工作完成后,在命令行窗口中进入项目的根目录并运行“gradle build”,Gradle 将为你完成剩余的所有工作。

深入 Xtend

除了语法糖之外,Xtend 还附带了许多非常有用的语言特性,例如操作符重载,模板表达式和 switch 表达式。而且还可以通过结合不同的功能创建新的特性。例如,假如你需要动态的 UI,不能用静态的 XML 文件构建,而需要声明式的编写。Xtend 为开发者提供了构建器语法(builder syntax)的支持。“Hello World”实例的 UI 实现代码如下:

import static extension com.example.helloworld.UiBuilder.* 

class HelloWorldActivity extends Activity { 

   override protected void onCreate(Bundle savedInstanceState) { 
      super.onCreate(savedInstanceState) 

      contentView = linearLayout [ 
         gravity = Gravity.CENTER 
         addButton("Say Hello!") [ 
            onClickListener = [ 
               Toast.makeText(context, 
                              "Hello Android from Xtend!", 
                              Toast.LENGTH_LONG).show 
            ] 
        ] 
    ] 

  } 
}

linearLayout(Context ctx, (LinearLayout)=>void initializer)button(ViewGroup group, String name, (Button)=>void initializer) 两个方法作为扩展被引入到 Activity 中。这两个方法将拉姆达函数作为其参数之一。传入拉姆达函数中的参数被称为implicit it, 与 this 类似,implicit it不需要显式地解引用。如上所示,拉姆达函数,扩展方法和implicit it结合使用能够产生非常漂亮的构建器语法。通过 Xtend 也可以构建许多其他漂亮的 API,从而以一种易读的声明式的方式编写代码。

来自于 XML地狱的问候!

安卓开发者的相当一大部分日常工作就是配置和开发各种 XML 文件,用作国际化字符串的资源或用于各类视图的声明。安卓平台推荐使用 XML 文件,因为平台已经为开发者提供了针对大型设备和 SDK 碎片化的解决方案。然而应用程序最终不可能只由静态视图和数据组成。开发者需要将所有的素材组合并为其赋予生命。在安卓平台,通过 R 类来完成这些工作。这个自动生成的类包含了许多对应在 XML 文件中声明的各种元素的整型常量。假设一个视图 XML 文件中声明了如下两个元素,点击 Button 可以更新 TextView 中的消息:

<TextView android:id="@+id/message_view"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:hint="@string/empty" >
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="sayHello"
android:text="@string/hello_world" >
</Button> 

典型的安卓式开发方法是通过 R 类中生成的常量获取到 TextView 的控制权然后实现 onClick 的回调方法“sayHello”:

class HelloWorldActivity extends Activity { 

   TextView messageView 

   override protected void onCreate(Bundle savedInstanceState) { 
      super.onCreate(savedInstanceState) 
      // set the view using the int constant 
      contentView = R.layout.main 
      // get a handle on the TextView 
      messageView = findViewById(R.id.message_view) as TextView 
   } 

   /**  
    * Callback automagically called by Android  
    */ 
   def void sayHello(View v) { 
     messageView.text = "Hello Android from Xtend!" 
   } 
} 

上面一段安卓的典型代码中包含了不安全的类型转换,命名规范和各种样板文件。用 Xtend 我们能够做得更好。

你好,Xtendroid

Xtendroid4是一个专门为安卓开发提供类库以及所谓的积极注解(active annotation)的小型项目。积极注解可以理解为编译时的宏,它能够参与到从 Xtend 到 Java 转化的编译过程中。你可以随意修改被注解的类,生成附加类型或使用这个钩子读写纯文本文件。

这样只要有一个注释,我们就知道要绑定哪个视图并且注释还可以帮助我们生成样板文件。除此之外,它还能够提供类型安全的元素访问方法和回调方法。下面一段代码就是用 Xtendroid 的 @AndroidActivity 注释编写的 Activity 类。

@AndroidActivity(R.layout.main) class HelloWorldActivity { 

   /**  
    * Type safe callback  
    */ 
   override void sayHello(View v) { 
      messageView.text = "Hello Android from Xtend!" 
   } 
} 

现在,这个 Activity 中只包含了我们想要加入的行为。其他的设置都是自动实现的,例如设置管道绑定、内容视图或扩展 Activity 的样板文件。而且现在一切都是类型安全的,IDE 能够了解其中的来龙去脉并为开发者提供适当的自动完成建议。

此外,Xtendroid 还能够为开发者处理 JSON 对象,资源文件或 SQLite 数据库提供便利。而且,积极注解以库的形式存在,因此通过自行开发或定制化已有库的方式,开发者可以很容易地构建更适于自己的库。

从下方1下载 Eclipse 并使用更新站点5安装 ADT 就可以开始自己亲自尝试上面所讲的内容。Xtendroid 项目包含许多类似本文中所展示的示例。最后祝大家能从中找到乐趣。

  1. Eclipse Xtend
  2. Xtend Gradle 插件
  3. Android Gradle 插件
  4. Xtendroid
  5. ADT 更新站点

关于作者

Niklas Therning是开源项目 RoboVM 的创建者和的联合创始人——RoboVM 项目的主要贡献者。他把如何合理地将 Java 引入 iOS 平台作为其使命。开始 RoboVM 项目前,Niklas 参与创建了 SpamDrain 反垃圾邮件服务,并且作为其承包商,主要从事 Java EE 和 web 应用程序开发的工作。Niklas 持有位瑞典哥德堡查尔姆斯理工大学的计算机科学理学硕士学位。可以通过 Twitter 账号 @robovm 关注他。

Sven Efftinge是一个充满激情的软件开发人员,他喜欢风筝冲浪运动、音乐和美食。他是 Xtext 项目的领导人。Xtext 是一个程序设计语言、领域特定语言和 JVM 静态类型程序设计语言 Xtend 的开发框架。在位于 Kiel 的 itemis 公司,Sven 领导一个研究部门。

过去几年,移动应用席卷了整个世界,在工作和生活的方方面面改变着我们使用互联网的方式。创建移动应用的各种技术也随之兴起,各种开发流程也将移动应用视为一等公民,开始考虑适应移动开发的流程。尽管已经让人感觉无处不在,真正的移动应用时代才刚刚开始。我们即将面对新一代的移动设备,如可穿戴设备或组成物联网的各种各样的移动装置。我们将面临全新的用于数据展示和命令接收的用户交互接口。我们也认识到越来越多的公司将真正采取移动优先的战略。所有的这些都将对我们未来几年设计、开发和测试软件的方式产生巨大影响。

这篇 InfoQ文章是“快速变化的移动技术领域”文章系列中的一篇。感兴趣的读者可以通过这里订阅新文章通知。

查看英文原文: Unusual Ways to Create a Mobile App

收藏

评论

微博

发表评论

注册/登录 InfoQ 发表评论