65.9K
CodeProject 正在变化。 阅读更多。
Home

挑战与解决方案 - 现代 Web 应用程序的架构 - 移动应用程序 - 第三部分

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.98/5 (31投票s)

2013 年 10 月 1 日

CPOL

22分钟阅读

viewsIcon

119515

适用于 iOS、Android、Windows Phone 和 BlackBerry 的移动应用开发。

文章系列

引言

在本文中,我们将学习如何为 iOS 和 Windows Phone 等热门设备平台(目前如此,稍后会涵盖 Android 和 BlackBerry)编写原生、混合和响应式移动应用。

虽然本文仅涵盖移动应用的前端开发,但我强烈建议评估 DreamFactory,它可以快速创建后端 REST API!DreamFactory 正在颠覆我们开发和管理后端 REST API 的方式。这项突破性技术可以以惊人的速度降低移动应用的开发时间和成本。

图 - iOS、Android、BlackBerry 和 Windows Phone

iOS 原生应用

图 - iPhone 5S 上的 iOS 7

iOS 是安装在 iPhone、iPod Touch、iPad 和 iPad Mini 上的移动操作系统。使用 Objective-C 语言,通过 Cocoa Touch APIXCode 来为这些设备编写移动应用。Cocoa 用于手势识别、动画等,而 XCode 是 iOS 开发的集成开发环境。

要在实际设备上测试应用或将其发布到 App Store,需要每年支付 99 美元的费用。为了更好地阅读应用代码,我们先来看看 Objective-C,它与 C++ 非常相似。

图 - iOS7 主屏幕

Objective-C

您可以使用 Objective-C 编写 iOS 应用,它是 C 的超集,使用了 SmallTalk 的面向对象语法。它是在大约 33 年前(1980 年)开发的。让我们先来了解一下 Objective-C。

图 - Objective-C 类头文件

要在 Objective-C 中创建一个类,我们需要添加两个文件:**头文件**和**方法实现文件**

// header file Button.h to declare class
@interface Button {
}
@end
// method file Button.m to implement class
@implementation Button

@end

现在我们将添加类成员,即属性和方法。要添加**私有**成员

// way # 1 - Button.h
@interface Button {
	@private
	BOOL visible;
}
@end
// way # 2 - Button.m
@implementation Button
	BOOL visible;
@end

请注意 Button 后面的括号,这是一种声明类扩展的方式。

// way # 3 - Button.m
@interface Button() { // Objective-C 2 Class Extension
	BOOL visible;
}
@end

@implementation Button
	
@end

要添加**受保护**成员(默认情况下,类成员是受保护的)

// way # 1 - Button.h
@interface Button {
	bool rounded;
}
@end
// way # 2 - Button.h
@interface Button {
	@protected
	bool rounded;
}
@end

要添加**公共**成员

// way # 1 - Button.h
@interface Button {
	@public
	NSString* text;
}
@end

要添加**公共属性**

// way # 1 - Button.h
@interface Button {
}
@property NSString* text; // an instance member _text, getter 'text'
// and setter 'setText' will be auto created
@end
// way # 2 - Button.h
@interface Button {
}
@property (readwrite) NSString* text; // note readwrite is default, so no need to specify
@end
// way # 3 - Button.h
@interface Button {
}
@property (readonly) NSString* text; // can only be get
@end
// way # 4 - Button.h
@interface Button {
}
@property (readonly, getter=caption) NSString* text; // override default method name 'text'
@end
// way # 5 - Button.h
@interface Button {
	NSString* buttonText; // instance variable
}
@property NSString* text; // to avoid creation of an instance member '_text' and use 'buttonText'
@end

// Button.m
@implementation Button

@synthesize text = buttonText; // now text will be backed by variable 'buttonText'

@end
// way # 6 - Button.h
@interface Button {

}
@property NSString* text; // to avoid creation of an instance member '_text'
@end

// Button.m
@implementation Button

@synthesize text; // now text will be backed by auto generated variable 'text'

@end
// way # 7 - Button.h
@interface Button {
}
@property (readwrite, nonatomic) NSString* text; // multiple threads can get & set text. by default atomic
@end
// way # 8 - Button.h
@interface Button {
}
@property (readwrite, copy) NSString* text; // 'text' will have its own copy which
// it will retain even if set by some constantly changing variable.
@end

您可以在此处找到有关属性和基本 Objective-C 结构更多的信息。

要添加**静态**成员

// way # 1 - Button.h
@interface Button {
}
NSString *defaultText; // static
@end

要从一个类**继承**

// Button.h
@interface Button {
	
}
@end
// RoundedButton.h
@interface RoundedButton : Button {
	
}
@end

Objective-C 中的方法以 **+**(用于静态或类方法)和 **-**(用于公共或实例方法)开头,后跟括号中的**返回类型**,带冒号的**方法名**,以及一个由冒号分隔的**参数**列表

+ (int) add : (int) a : (int) b; // static or class method
- (int) square : (int) a; // public or instance method

图 - Objective-C 方法语法

我们不能将 @public@private@protected 访问修饰符应用于方法。此外,方法也不能是**私有**的、**受保护**的,但我们可以使用类扩展来大致模拟这一点。要向类添加**公共**、**私有**和**静态**方法

// Button.h
@interface Button {
}
@end

+ (void) setDefaultText : (NSString*) val; // static method
- (int) textLength; // public method

// Button.m
@interface Button() { // Objective-C 2.0 Class Extension
- (bool) hasDefaultText; // private method
}

@implementation Button
+ (void) setDefaultText : (NSString*) val {

}
- (int) textLength {

}
- (bool) hasDefaultText {

}
@end

要添加**构造函数**,请添加一个返回 id 对象的 init 方法。id 对象类似于 C 中的 void*

// 1 constructor in Button.m
@implement Button
- (id) init
{
    self = [super init]; // note self == 'this' while super == 'base' in C# or C++ 

    if (self != nil) // if 'super' init returns 'nil', simply skip initialization
    {
        // your code here
    }

    return self;
}
@end
// 2 constructors in Button.m
@implement Button
- (id) init
{
  // default constructor
}

- (id) init : (NSString*) text
{
  // constructor with one parameter
}

- (id) init : (NSString*) text : (bool) rounded
{
  // constructor with one parameter
}
@end

Objective-C 不支持**方法重载**。但是,由于 Objective-C 中的方法名(或选择器)也包括参数的冒号,因此它看起来可能像方法重载,但行为不像真正的 C++ 重载。上面的构造函数不是重载,而是三个不同的选择器,即(initinit:init::)。

图 - 方法名在 Objective-C 中包含冒号

请注意,以下操作会产生错误

// 2 methods (or constructors) with same name and number of colens
@implement Button

- (id) init : (NSString*) text // 1 colen init i.e. init:
{
  
}

- (id) init : (bool) rounded // error alreay have selector init: above with 1 colen
{
  
}

@end

要创建类的**实例**

// 3 constructors in Button.m
@implement Button
- (id) init
{
  
}

- (id) init : (NSString*) text
{
  
}

- (id) init : (NSString*) text : (bool) rounded
{
  
}

+ (void) Tester 
{
    Button* b1 = [[Button alloc] init]; 

    Button* b2 = [Button new]; // short form of above

    Button* b3 = [[Button alloc] init: text:@"Hello world"];

    Button* b4 = [[Button alloc] init: text:@"Hello world" rounded:true];

    [b1 release];
    [b2 release];
    [b3 release];
    [b4 release];
}

@end

调用对象的 release 会调用 dealloc。要添加 dealloc(或**析构函数**)来释放资源

// constructor & destructor Button.m
@implement Button
- (id) init
{
    _someResource = [foo retain];

    return self;
}
// destructor
- (id) dealloc
{
    [_someResource release];

    [super dealloc];
}
@end

要在一个**类**上实现或采用**接口**(即协议)

// interface ITouchable.h
@protocol ITouchable 

  - (void) tap : (int) x: (int) y; // by default this method is required

  @optional 
  - (void) doubleTap : (int) x: (int) y; // a class can choose not to implement this

  @required 
  - (void) multiTap : (int) x1: (int) y1 : (int) x2: (int) y2; // explicitly required
}

// Button.h
@interface Button : NSObject<ITouchable> {
}
@end

// Button.m - Note that @optional method is not implemented
@implement Button
- (void) tap : (int) x: (int) y
{
  // default constructor
}

- (void) multiTap : (int) x1: (int) y1 : (int) x2: (int) y2
{
  // constructor with one parameter
}
@end

您可以使用**类别 (Category)** 向现有的**类**添加方法

// MyNsString.h - add stripHtml() method in iOS NSString class
@interface NSString(MyNsString) {
}
- (NSString*) stripHtml;
@end

// MyNsString.m
@implement NSString(MyNsString)
- (NSString*) stripHtml
{
 
}
@end

RayWenderlich 提供的方便的 Objective-C 备忘单 PDF 版本可以在此处获取。

图 - Objective-C 备忘单

Objective-C 内存管理

Objective-C 中有三种内存管理方式

  1. 手动保留释放 (MRR)
  2. 垃圾回收 (GC)
  3. 自动引用计数 (ARC)

要理解现有的代码库,您应该掌握这三种方式。

手动保留-释放 (MRR) - 1980 年至今

图 - MRR 是最古老的内存管理技术

MRR 基于一条简单的规则:“如果您**拥有**一个对象,您就必须**释放**它”。如果您调用一个**方法**,您就可以**拥有一个对象**,该方法

  1. 以 **alloc**、**copy**、**new**、**init** 或 **retain** 开头,例如 allocnewObject,或者
  2. 包含 **Copy** 或 **Create**,例如 mutableCopy

您通过 A)和 B)或**所有权方法**而拥有的对象是您的“责任”,您应该通过调用 releaseautorelease 来放弃对该对象的拥有权。在任何其他情况下,您都不得**释放**对象。

图 - 您**永远不应**使用 retainCount

当您通过调用**所有权方法**来**拥有**一个对象时,该对象的 retainCount 会增加 1,当您调用 releaseautorelease 时,它会减少 1。当 retainCount 达到 0 时,对象的 dealloc 方法会被调用,并且对象会被销毁。这看起来很简单,但您应该永远不要使用 retainCount,因为它会**误导人**。

// 1) 's' will be autoreleased and using it in calling method will result in error
- (NSString*) newStr: (NSString*) input {
    NSString* s = [NSString stringWithString:@"Hello world"];
    
    return s;
}

// 2) 's' is retained to avoid error in calling method
// calling method must call release to avoid memory leaks
- (NSString*) newStr: (NSString*) input {
    NSString* s = [NSString stringWithString:@"Hello world"];
    
    [s retain]; 
    
    return s;
}

// 3) 's' is constructed using alloc & initWithString so 
// retained is not required but calling method must call release
- (NSString*) newStr: (NSString*) input {
    NSString* s = [[NSString alloc] initWithString:@"Hello world"];
    
    return s;
}

// 4) 's' is constructed using alloc & initWithString so 
// retained is not required and calling method should not call release
// as object in put in autorelease pool and will be released automatically
- (NSString*) newStr: (NSString*) input {
    NSString* s = [[NSString alloc] initWithString:@"Hello world"];
    
    [s autorelease]; 
    
    return s;
}

// 5) 's' will be autoreleased but 's2' is a copy and its retainCount = 1
// calling method must call release to free up memory used by 's2'
- (NSString*) newStr: (NSString*) input {
    NSString* s = [NSString stringWithString:@"Hello world"];
   
    NSString* s2 = [s copy];
    
    return s2;
}

releaseautorelease 方法的区别在于,release 会立即调用;将 retainCount 减 1,如果变为零则调用 dealloc。另一方面,autorelease 只是将对象“放入”一个由 Application Kit 创建的特殊池中,称为**自动释放池**。**自动释放池**基本上是一个 NSAutoreleasePool 对象,它在每个事件循环周期的开始在主线程上创建,并在结束时进行释放(drain),从而释放所有自动释放的对象。

图 - 调用 autorelease 方法会将对象放入一个特殊池中
int main(void) {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    
    NSString *s = [[[NSString alloc] init] autorelease];
    
    [pool drain]; // release 's' as well as pool
}

如果您使用的是 GC,那么 drain 会为 GC 发送消息(objc_collect_if_needed);而在 MRR 和 ARC 中,drain 相当于对 NSAutoreleasePool 调用**release**。

您可以嵌套池,并且 autorelease 对象将被添加到最近的池中。

 int main(void) {
    NSAutoreleasePool *pool1 = [[NSAutoreleasePool alloc] init];
    
    NSString *s1 = [[[NSString alloc] init] autorelease]; // 's' is added to 'pool1'
    
    NSAutoreleasePool *pool2 = [[NSAutoreleasePool alloc] init];
    
    NSString *s2 = [[[NSString alloc] init] autorelease]; // 's' is added to 'pool2'
    
    [pool2 drain]; // release 's2' as well as 'pool2'
    
    [pool1 drain]; // release 's1' as well as 'pool1'
}

但是,推荐的方式是使用 @autoreleasepool

@autoreleasepool {
 
    NSString *s1 = [[[NSString alloc] init] autorelease]; // 's' is added to 'pool1'
 
    @autoreleasepool {
 
        NSString *s2 = [[[NSString alloc] init] autorelease]; // 's' is added to 'pool2'
 
    }
} // pool drained at the end of block

垃圾回收 (GC) - 2006 - 2011 年

图 - GC 甚至可以收集循环对象引用

GC 是一项运行时技术,在 iOS 上不可用,因此我们简要讨论一下。使用 GC 时,一个低优先级线程会收集未使用的对象。在 GC 模式下,调用**拥有**对象或**释放**对象的操作无效。对于新的 iOS 应用,推荐使用 ARC,但您也可以使用传统的 MRR。

自动引用计数 (ARC) - 2011 年至今

图 - ARC 在编译时插入保留-释放调用

ARC 从 iOS 5 开始可用,它是一项编译时技术,在代码编译时插入必要的**保留-释放**调用,并在对象被释放时将弱引用设置为 **nil**。这与 GC 不同,GC 作为单独的后台进程运行,并在运行时收集未使用的对象。

但是,ARC 无法自动收集循环引用,而 GC 可以。在使用 ARC 时,您不能调用 retainreleaseretainCountautoreleasedealloc。您可以在此处找到 ARC 规则的完整列表。

图 - ARC 无法自动处理循环引用

在本系列的后续部分中,我们将介绍如何使用 XCode 和 Cocoa 框架开发 iOS 应用。

Windows Phone 原生应用

图 - 诺基亚 Lumia 520 上的 Windows Phone 8

Windows Phone (WP) 是微软的移动操作系统。WP 应用使用 Windows Phone SDK 和 Visual Studio IDE 开发。使用 XML 标记语言 XAML 来开发用户界面,同时您可以使用 C#、VB.NET 或您喜欢的任何其他语言来连接用户界面事件。

以下是各种手机上可用的操作系统版本

Windows Phone 版本 操作系统版本
Windows Phone 8 Windows Phone OS 8.0
Windows Phone 7.5 Windows Phone OS 7.1
Windows Phone 7 Windows Phone OS 7.0

发布 Windows Phone 应用到 Windows Store 需要支付年费。

年费

图 - 年费

每个移动平台提供商都会收取某种形式的**年费**

  • 用于解锁一定数量的移动设备来测试您的应用。
  • 用于向其市场发布付费或免费应用。

这是一个有趣的总结

平台 每年费用 测试设备限制 免费应用限制 付费应用限制 文件大小限制
Window Phone $19(之前为 $99)
DreamSpark Student = 免费
3 100 无限 XAP = 1GB[1]
Apple iOS 开发者 = $99
企业 = $299
大学 = 免费
 
100[2] 无限 无限 IPA = 60MB[3]
EXT = 2GB
Google Play 开发者 = $25(终身) 无限 无限 无限 APK = 50MB
EXT = 2x2GB
 
Black Berry $0 无限 无限 无限 COD = 128KB[4]
EXT = 14MB

[1] 在大多数平台上,超过 20-50MB 的文件需要 Wi-Fi 或连接笔记本电脑才能下载,无法通过无线数据连接下载。

[2] 即使您可以从您的帐户中删除设备,Apple 仍会将其计入您的 100 台设备限制。

[3] Apple 对可执行文件设置了 60MB 的限制(第 232 页)。语言包、图像和其他资源可以单独打包在 SD 卡上。

[4] 128KB = 64KB 代码 + 64KB 数据。在包括 Black Berry 在内的所有移动平台上,您可以在需要时从网站下载资源,并将其本地缓存到 SD 卡上。

图 - SD 卡(顶部)、mini SD、micro SD 卡

Windows Phone 项目

图 - Windows Phone 项目类型

您可以开始许多类型的 Windows Phone 项目。让我们从**Windows Phone App**项目开始,讨论一些常见的 Windows Phone 开发概念。

一个页面

通常,您可以通过添加必要的 XAML 文件(或页面)来开始 Windows Phone 应用的开发。

图 - 您通常可以添加到 Windows Phone 项目的内容

一个(几乎)空的页面看起来是这样的。除了顶部的命名空间,还有一个名为 'LayoutRoot' 的 Grid,下面有两个行。第一行的“自动”高度,意味着它会根据其内容“调整”高度。第二行的*-高度设置为“*”,意味着它将占用“所有”可用空间。

<phone:PhoneApplicationPage
    x:Class="PanoramaApp1.Page1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="Portrait" Orientation="Portrait"
    mc:Ignorable="d"
    shell:SystemTray.IsVisible="True">
 
    <!--LayoutRoot is the root grid where all page content is placed-->
    <Grid x:Name="LayoutRoot" Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
 
        <!--TitlePanel contains the name of the application and page title-->
        <StackPanel Grid.Row="0" Margin="12,17,0,28">
            <TextBlock Text="MY APPLICATION" Style="{StaticResource PhoneTextNormalStyle}"/>
            <TextBlock Text="page name" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
        </StackPanel>
 
        <!--ContentPanel - place additional content here-->
        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
 
        </Grid>
    </Grid>
 
</phone:PhoneApplicationPage>

网格面板

在此页面中,您在 ContentPanel 网格内添加控件。通常,您会先放置 StackGridCanvas 面板来布局控件。在面板内部,您放置实际的手机控件,例如 Button、TextBlock、Textbox 等。

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"></RowDefinition>
        <RowDefinition Height="Auto"></RowDefinition>
        <RowDefinition Height="Auto"></RowDefinition>
    </Grid.RowDefinitions>
    <TextBox Text="Enter User Name" Grid.Row="0" />
    <TextBox Text="Enter Password" Grid.Row="1" />
    <Button Content="Sign In" Grid.Row="2"/>
</Grid>

图 - 带有两个 TextBox 和一个 Button 控件的网格

Windows Phone 工具包

另外,Windows Phone Toolkit 提供了一些非常有趣的控件和过渡效果。

图 - Windows Phone 工具包

堆栈面板

就像使用 Grid 面板以表格形式布局控件一样,您可以使用 Stack 面板将控件一个接一个地放置。

<StackPanel>
    <TextBox Text="Enter User Name" />
    <TextBox Text="Enter Password" />
    <Button Content="Sign In" />
</StackPanel>

画布面板

或者使用 Canvas 面板进行绝对定位。请注意,Canvas.Top 用于指定绝对位置。

<Canvas>
    <TextBox Text="Enter User Name" />
    <TextBox Text="Enter Password" Canvas.Top="72" />
    <Button Content="Sign In" Canvas.Top="144" />
</Canvas>

图 - 使用 Canvas 面板布局控件

控件因为我们没有指定“绝对”宽度(例如,Width="456")而缩小到默认的“自动”宽度。

横屏左还是右?

手机可以有三种方向:纵向、横向(左或右)。

事件 - 点击还是轻触?

您可以添加 Button **Tap** 事件来处理登录。对于手机上的每一次*Tap*,还会触发一个**Click**事件,延迟(+/-)300 毫秒。因此,在**Click**事件中处理事件会使滚动、取消和其他用户交互感觉非常迟钝。

private void Button1_Tap(object sender, EventArgs e)
{
     if (MyDbContext.SignIn(this.UserNameTextBox.Text, this.PasswordTextBox.Text))
     {
       // OK: go to home page
     }
     else
     {
       // ERROR: show error popup
     }
}

事件冒泡

请注意,如果 Button Tap 事件不可用,运行时将搜索父控件,直到找到事件处理程序。

图 - Windows Phone 事件冒泡

Windows Phone 8 支持具有 WVGA、WXGA 和 720p 分辨率的手机。这与仅支持 WVGA 分辨率的 Windows Phone OS 7.1 不同。

导航

移动应用程序通常包含大量页面,并且根据用户与应用程序的交互显示不同的页面。在 Windows Phone 中,您可以使用 NavigationService 在页面之间进行导航和传递参数。

// go to another page
void passParam_Click(object sender, RoutedEventArgs e)
{
   NavigationService.Navigate(new Uri("/SecondPage.xaml?msg=" + textBox1.Text, UriKind.Relative));
}

// get the parameter value
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
    base.OnNavigatedTo(e);

    string msg = "";
    
    if (NavigationContext.QueryString.TryGetValue("msg", out msg))

    textBlock1.Text = msg;
}

// return back to previous page
private void button1_Click(object sender, RoutedEventArgs e)
{
    NavigationService.GoBack();
}

全景控件

Windows Phone Panorama Control 是一种提供长画布和多个面板的方法,用于承载网格、文本框、标签和其他控件。Panorama 控件鼓励用户在深入了解细节之前,先浏览可能不相关的内容。

图 - Panorama 控件提供页面中不相关内容的摘要

枢纽控件

另一方面,Pivot Control 提供了一种在页面中过滤相关内容的方法。查看使用枢纽控件过滤页面中**所有**、**未读**和**已标记**消息的电子邮件应用程序。

图 - Pivot 控件用于过滤页面中的相关项

语音识别

在小型设备上打字非常痛苦。在 Windows Phone 中,使用 SpeechRecognizerUI 类可以非常轻松地在您的应用程序中启用语音识别。

private async void ButtonSR_Click(object sender, RoutedEventArgs e)
{
  // Create an instance of SpeechRecognizerUI.
  this.recoWithUI = new SpeechRecognizerUI();

  // Start recognition (load the dictation grammar by default).
  SpeechRecognitionUIResult recoResult = await recoWithUI.RecognizeWithUIAsync();

  // Do something with the recognition result.
  MessageBox.Show(string.Format("You said {0}.", recoResult.RecognitionResult.Text));
}

图 - Windows Phone 语音识别体验

Windows Phone 一次只能运行一个应用程序,以节省电池并提供流畅运行应用程序所需的资源。如果用户切换应用程序,应用程序状态将被“tomb-stoned”(暂停)并且其进程将被终止,除非它是一个后台位置跟踪应用。当用户切换回来时,应用程序应该从存储中恢复其状态

移动应用开发 - 可能的路径

图 - 哪条路是正确的?

有三种开发移动应用的方法:混合、原生和 HTML 5。

混合

对于一般的商业应用程序,**混合应用**通常能提供所需的功能和性能。混合移动应用使用 HTML5、CSS3、JavaScript、PhoneGap 构建,并在 iOS、Android、Windows Phone 和 Black Berry 上运行。

图 - 使用 Knockout 和 ASP.NET MVC 的混合移动应用

原生

对于需要更高性能、图形和更小文件大小的游戏开发,**原生应用**是正确的选择,但您也可以使用 PhoneGap 进行游戏开发

HTML 5

最后,至少任何新的网站(或 Web 应用)都应该使用**HTML 5** 和响应式布局技术(如 BootstrapFoundation)来构建。响应式 Web 设计为日益扩大的设备世界提供了一个最低的入口点。

您是否拥有原生平台技能?

影响路径选择的另一个因素是,原生应用开发需要**每种平台的语言技能**(即 C#、Objective-C 和 Java),而混合应用可以使用熟悉的 CSS3、HTML5 和 JavaScript 进行开发。因此,混合应用的学习曲线较低,开发起来相对快捷,成本也较低。

平台独立性还是特定平台(具有高性能)?

除了学习曲线,**原生应用是平台特定的**,所以您必须为每个平台开发一个应用,而**混合应用可以在许多平台**上运行,例如 iOS、Android、Windows Phone 和 Black Berry。然而,有时客户会寻找具有高性能的特定平台用户界面。在这种情况下,您可能希望选择原生应用而不是混合应用。如果性能不重要,那么像 KendoUI MobileSencha TouchjQuery Mobile 这样的移动库的 theaming 功能可以为各种平台和同一平台的不同版本提供必要的外观和感觉。

移动应用用户体验何时重要?

对于面向大众的通用移动应用程序,平台特定的用户体验很重要。显然,对于这些应用程序,最终用户不期望在 Android 设备上看到 iOS 用户界面,或者在 Windows Phone 上看到类似 Android 的用户体验。但是,对于针对单个企业或部门的业务应用程序,客户可能会选择在所有平台上统一用户体验,以降低开发和培训成本。如果平台特定的用户体验很重要,那么更好的方法是选择像 KendoUI Mobile 这样的混合移动框架。

图 1 - 使用 Kendo UI Mobile 在各种平台上构建的混合应用

混合框架支持哪些平台?

还有一个您可能想选择原生而不是混合的场景是“平台支持”。例如,考虑 Sencha Touch 不支持 Windows 7 Phone。要编写 Windows 7 特定的移动应用,您可能需要选择原生应用。请参阅下图,了解 Sencha Touch 的平台支持情况。

图 1 - Sencha Touch 有限的平台支持

PhoneGap 对混合应用的功能支持

就像混合框架的平台支持有限一样,PhoneGap 在一定程度上也是如此。然而,PhoneGap 3.0 的功能支持对于大多数不需要高性能的场景来说已经足够了。例如,使用 PhoneGap 无法访问 iPhone 和 BlackBerry 的**指南针**,而 BlackBerry、WebOS、Symbian 和 Bada 则不支持**媒体**。

图 - PhoneGap 3.0 对各种平台的功能支持

在提交 PhoneGap 应用之前

在将 PhoneGap 应用程序提交到 Apple iTunes、Google Play 或 Windows Phone Marketplace 等市场时,您必须谨慎包含 PhoneGap 提供的所有功能,无论您的应用程序是否使用了其中任何一个。如果您不包含它们,应用程序可能会被拒绝,或者在最终用户设备上无法正常运行。

在您的应用程序中添加以下 PhoneGap 功能:数据服务、移动和方向传感器、麦克风、音乐和视频库、所有者身份、相机、联系人、相机和指南针。未提及这些功能将导致应用程序被拒绝或在最终用户设备上无法正常运行。

希望通过这些要点,您可以轻松决定是选择混合、原生还是 HTML 5 移动应用。由于大多数人出于低入门门槛和发布到多个平台的优势而偏爱混合应用,因此我们将首先介绍混合移动应用技术。

混合移动应用

混合应用之所以是“混合”的,是因为它使用 HTML 5 和 CSS3 来实现移动 UI,并使用 JavaScript 来与设备 SDK 通信。

混合应用 = 单页应用

混合移动应用基本上是一个单页应用(或 SPA)。SPA 是一种 Web 应用程序,它驻留在单个 HTML 页面中。当用户在应用程序中导航时,“视图”会根据需要注入到 DOM 中并从中移除。单页应用架构特别适合移动应用。

  • 由于没有持续的页面刷新,因此提供了更流畅/更接近原生体验。
  • UI 完全在客户端创建,不依赖服务器来创建 UI,这使其成为适用于离线工作的应用程序的理想架构。

PhoneGap 如何工作?

PhoneGap 为移动应用开发者提供了一个 JavaScript API **phonegap-3.0.0.js**。这个 JavaScript API 调用 PhoneGap 特定平台的引擎/桥接器,后者又调用原生平台 SDK 来在设备上执行操作。例如,访问联系人列表、拨打电话等。对于 Windows Phone,PhoneGap 引擎/桥接器是一个 Silverlight 程序集 WP7GapClassLib.dll

PhoneGap 引擎使用每种平台的原生语言(C#、Objective-C 和 Java)构建,这些语言向 JavaScript 开发者公开接口。PhoneGap JavaScript API 和引擎之间的大部分通信是通过设置无 Chrome 浏览器 URL 来实现的。

gap://SomePlugin.someMethod?arg1Name=someArg1&arg2Name=someArg2

图 - PhoneGap 应用程序架构

PhoneGap 还提供了一个构建系统,可以将 HTML 5、JavaScript 和 CSS3 打包到一个无 Chrome 浏览器中,即没有用户界面的浏览器。

图 - Phonegap 构建系统将 js、CSS 和 HTML 与无 Chrome 浏览器打包

加速度计

图 - Dimension Engineering 的一款售价 35 美元的 3D 加速度计

加速度计可以捕获设备在 x、y 和 z 方向上的运动。使用 PhoneGap,您可以访问 iPhone、Android、Windows Phone 和 Black Berry 上安装的加速度计。因此,例如,在 App/Supporting Files/Cordova.plist 中添加以下配置以获取使用 iOS 加速度计的权限。

<key>Plugins</key>
<dict>
    <key>Accelerometer</key>
    <string>CDVAccelerometer</string>
</dict>

可以使用 Properties/WPAppManifest.xml 配置来获取 Windows Phone 的类似权限。

<Capabilities>
    <Capability Name="ID_CAP_SENSORS" />
</Capabilities>

图 - 加速度计检测设备方向的变化。

图 - iPhone 游戏“Labyrinth”使用加速度计。

混合移动应用开发的移动框架

虽然您可以编写 HTML 5、CSS 3 和 JavaScript 并将其与 PhoneGap 打包以生成支持平台的原生镜像,但人们通常会选择某种移动框架来进行混合移动应用开发。这可以节省大量代码行和大量时间。以下是一些流行的框架,它们在功能、平台支持和社区采用方面各不相同

我们将在下面的章节中介绍 KendoUI MobileSencha TouchjQuery Mobile,这将让您了解选择混合移动应用框架时哪些因素很重要。我们将在本系列的后续部分中介绍其他框架。

 

1. Appcelerator Titanium Mobile

16. WebApp.net

31. The-M-Project

2. Apache Flex

17. XUI

32. NimbleKit

3. GWT mobile webkit + gwt mobile ui

18. Zepto.js

33. Mono for Android

4. CNET iPhone UI

19. ChocolateChip-UI

34. MonoTouch

5. iPhone Universal

20. Application Craft

35. qooxdoo

6. Jo HTML5 Mobile App Framework

21. DHTMLX Touch

36. ShiVa 3D

7. iUI

22. Corona

37. RareWire

8. JQ Touch

23. eMobc

38. V-Play

9. jQuery Mobile

24. Dojo Mobile

39. NSB/AppStudio

10. mobione

25. Marmalade

40. AppConKit

11. Phone Gap

26. Kendo UI

41. Trigger.io

12. Quick Connect

27. Handheld Designer

42. wink

13. Rhodes

28. Mobify.js

43. ViziApps

14. Sencha Touch

29. iWebKit

 

15. TapLynx

30. Moai

 

jQuery Mobile - 混合移动应用框架

jQuery Mobile 是一个易于学习且拥有庞大社区支持的移动框架,并提供移动小部件。它不像 Sencha Touch(595 美元)和 KendoUI mobile(699 美元)那样有陡峭的学习曲线。尽管如此,在加载包含 50-60 个项目的列表视图时存在性能问题,因为 jQuery Mobile 应用会变得迟钝(甚至导致移动浏览器崩溃)。另一方面,Sencha Touch 可以加载 200 多个项目而没有性能问题。

jQuery Mobile 1.3.2 包括 JavaScript(147KB)、CSS(92KB)、图像(约 6.6MB zip)以及核心 jQuery 1.10.2 库(91KB)。在内存和 CPU 资源有限的设备上解析 JavaScript 时,大小是一个问题。可以通过使用 Google Closure Compilerminifyyui compressor最小化大小,这些工具会剥离无法访问的 JavaScript 代码。我们将在另一篇文章中讨论优化。现在,让我们专注于移动应用框架。

图 - Closure 产生功能与原始代码相同的、小得多的 JavaScript 代码版本

要设计 jQuery mobile 页面,可以在 jQuery Mobile 上找到一个方便的 codiqa 设计器。一旦页面 HTML、CSS 和 JavaScript 设计完成,就可以以 zip 格式下载。请记住,jQuery UI(一个用于桌面 Web 应用的 jQuery 小部件库)由于 CSS 冲突不能与 jQuery Mobile 一起使用。因此,您只能使用 jQuery Mobile 小部件或社区构建的小部件。

图 - Codiqa 用于构建移动 UI

要绘制上面的登录 UI,您只需要使用熟悉的 HTML 结合 jQuery CSS 和数据属性。data-attribute 是 HTML 5 的一项功能,它允许您在元素上定义任意数量的以 data- 开头的属性来“存储任何信息”,而它们不会影响页面布局。请注意,<div> 上的 **data-role** 属性使其成为 **label** 和 **textbox** 的容器。

KendoUI Mobile - 混合移动应用框架

KendoUI mobile 是一个基于 MVVM 的移动应用框架,提供图表和一些非常有用的移动小部件,价格为 699 美元。KendoUI 支持像 Knockout 这样的模型绑定,这为您节省了大量代码。

图 - KendoUI mobile 提供用于构建混合移动应用的组件和框架

要实现移动平台的流式布局,请将 KendoUI 与布局库(如 BootstrapFoundation)结合使用,因为它本身不是一个布局库。与完全是 JavaScript 的 Sencha Touch 相比,KendoUI 的学习曲线较低,但它提供了具有 MVC 架构的巨大灵活性和性能。

KendoUI 的 Hierarchical ListViewActionSheetListView 控件提供了列出项目的绝佳方式,而 Tablet SplitView 控件则是显示平板电脑上主/详细信息场景的绝佳方式。

图 - 带有 iPhone 主题的 KendoUI mobile ActionSheet 控件

图 - 带有 Windows Phone 主题的 KendoUI mobile ListView 控件

图 - 带有 Android 主题的 KendoUI mobile SplitView 控件(适用于平板电脑)

KendoUI Mobile 主题生成器可用于创建特定平台的主题。

图 - Kendo UI Mobile 主题生成器

为了获得性能和灵活性,Kendo UI mobile iOS 主题不使用任何图像。UI 元素仅通过 CSS 效果构建,因此应用的外观与实际的 iOS 应用程序略有不同。

KendoUI 通过其 dataviz 组件绘制交互式图表,提供数据可视化功能。要绘制图表,KendoUI 会自动检测浏览器功能并使用 SVG(或 VML 作为回退)。IE6、IE7 和 IE8 仅支持 VML;IE9 支持部分 SVG;其他主流浏览器支持 SVG。

图 - KendoUI mobile dataviz 图表

Sencha Touch - 混合移动应用框架

Sencha Touch 在复杂性和陡峭的学习曲线的代价下提供了极致的性能。Sencha Touch 是 MVC 并且完全是 JavaScript,这对于 Web 开发者来说可能会有点困惑,对于 Java/C# 开发者来说则稍微好一些。但这种学习成本带来了巨大的性能提升,而这在 jQuery Mobile 应用中是值得怀疑的。

由于 Sencha Touch 最初是针对 iOS 开发的,后来才增加了对 Android、Black Berry 和 Windows Phone 的支持,所以您可能会期望在所有平台上的性能不尽相同。

Sencha Touch 拥有丰富的移动小部件,如 Navigation View 和 Carousel,以及强大的布局库。

图 - Sencha Touch Carousel View 控件

Sencha Touch 拥有像 PhoneGap 一样的原生打包和部署系统。因此,从某种意义上说,您可以使用 Sencha Touch 而无需 PhoneGap 来构建端到端的跨平台解决方案。

HTML 5 响应式网站

我们在第一部分中已经讨论过这种应用开发。然而,让我们在这里再讨论几个对移动网站开发很重要的概念。

移动浏览器

移动浏览器与桌面浏览器不同,因为它们支持两个视口:**布局**和**视觉**。布局视口是用于所有 CSS 计算的视口,而视觉视口是设备屏幕上当前显示的 HTML 文档的部分。布局视口的宽度因浏览器而异。**Safari** iPhone 使用 980px,**Opera** 使用 850px,Android **WebKit** 使用 800px,**IE** 使用 974px。在 BlackBerry 上,布局视口等于视觉视口,缩放比例为 100%。这没有改变。

window.innerWidth // and innerHeight for visual viewport dimensions
document.clientWidth //and clientHeight for layout viewport dimensions

图 - 移动浏览器支持两个视口 - 布局和视觉

哈希片段

图 - 哈希片段用于索引基于 AJAX 的网站

大多数移动网站都是基于 AJAX 的,以便在需要时加载内容。在典型的网站中,URL 是书签和分享您喜欢的内容的方式,也是搜索引擎索引您网站的方式。然而,像 Twitter 和 Facebook 这样的 AJAX 网站会下载一个初始 JavaScript,该 JavaScript 会发出 AJAX 调用以获取更多内容。爬虫(如 Google)的问题在于它们不解析 JavaScript 或发出 AJAX 调用,因此永远无法看到用户看到的相同页面。这导致网站索引不佳。

为了创建一个向爬虫返回 HTML 快照而不是 JavaScript 的 URL,会使用哈希片段(即 **#!**)。AJAX 网站或单页应用上的 URL http://twitter.com/#!/mashablewww.example.com/ajax.html#!key=value 会向爬虫返回静态 HTML,从而提高网站索引。

下一步

在本文的下一更新中,我们将进一步探索各种移动平台的功能。我们还将探讨 iOS、Android、WP 和 BlackBerry 的开发,并编写代码来使用设备功能。

© . All rights reserved.