Android平台指南

这个指南展示如何安装SDK环境以便可以部署Cordova App在Android设备上,以及如何选择使用以Android为中心命令行工具在你的开发工作流中。 不管你是使用以平台为中心的shell工具还是跨平台的Cordova命令行你都需要安装Android SDK。要比较两种开发路径,请参见概述。要获取CLI的详细介绍请参见Cordova CLI 参考.

要求和支持情况

Cordova支持Android需要Android SDK,它可以安装在OS X, Linux和Windows上。查看 Android SDK的 系统要求. Cordova最新的Android包支持的是AndroidAPI-级别 23。 最近几年 cordova-android支持的Android API-级别可以在下面这个表中看到:

cordova-android 版本 支持的 Android API-级别
5.X.X 14 - 23
4.1.X 14 - 22
4.0.X 10 - 22
3.7.X 10 - 21

注意这里列出的版本是给Cordova Android包, cordova-android的, 而不是Cordova CLI的。要想知道你的Cordova项目中安装的Cordova Android包的版本,你可以在项目目录中运行cordova platform ls

作为一般规则,当Android版本在Google分布面板占比跌破5%Cordova就不会支持了。

安装要求

Java开发工具包(JDK)

安装Java Development Kit (JDK) 7或者最新的。

当在Windows上安装的时候需要根据JDK路径设置JAVA_HOME环境变量(参见设置环境变量)

Android SDK

安装Android Stand-alone SDK或者Android Studio。如果你计划开发一个新的Cordova Android插件或者使用Android平台原生的工具去运行和调试,那么就使用Android Studio吧。否则,Android Stand-alone SDK Tools就足够构建和部署Android应用程序了。

详细的安装说明可以点击上面的安装链接查看。

添加SDK包

在安装完Android SDK后,你需要安装任何你希望的目标API级别的SDK包。建议你安装cordova-android(参见上面)支持的最高级别的SDK包。

打开Android SDK Manager (例如,在终端上运行`android),并确保下面已经安装:

  1. 目标Android版本的Android Platform SDK
  2. Android SDK build-tools,版本19.1.0或者之上
  3. Android Support Repository (在"Extras"查找)

参见 Android文档安装SDK包获得详细内容。

设置环境变量

为了使功能正常使用Cordova的CLI工具需要设置一些环境变量。CLI会尝试为你设置这些变量,但在某些情况下你需要手动设置。下面这些变量需要更新:

  1. 设置JAVA_HOME环境变量,指定为JDK安装路径
  2. 设置ANDROID_HOME环境变量,指定为Android SDK安装路径
  3. 同时也建议你添加Android SDK的toolsplatform-tools目录到你的PATH

OS X and Linux

在Mac或者Linux上面,你可以使用文本编辑器来创建或者修改文件~/.bash_profile。为了设置一个环境变量,添加一行,使用export像下面这样(用你本地安装路径替代路径):

export ANDROID_HOME=/Development/android-sdk/

为了更新PATH,添加一行类似下面这样(路径替换为你本地的Android SDK安装路径):

export PATH=${PATH}:/Development/android-sdk/platform-tools:/Development/android-sdk/tools

重新启动终端或者运行下面命令来看变化带来的反应:

$ source ~/.bash_profile

Windows

这些步骤可能会因你安装的Windows版本而不同。在更改后,关闭并重新打开命令行提示符窗口,来看看他们的反应

  1. 点击桌面左下角的开始菜单

  2. 在搜索栏中,搜索环境变量并从出现的选项中选择编辑系统的环境变量

  3. 在出现的窗口中,点击环境变量按钮

创建一个新的环境变量:
  1. 点击新建... 并输入变量的名字和值
T设置你的PATH:
  1. 选择PATH变量并点击编辑

  2. 添加条目到PATH相关的位置。 例如(用你本地的Android SDK安装路径替代路径):

    C:\Development\android-sdk\platform-tools
    C:\Development\android-sdk\tools
    

项目配置

设置一个模拟器

如果你想运行你的Cordova应用在Android模拟器上面,首先你要创建一个Android虚拟设备(AVD)。查看Android文档管理AVD和配置模拟器和设置硬件加速的说明

一旦你的AVD配置正确,在Cordova项目里面运行下面命令你应该可以可以看到他们:

$ cordova run --list

配置Gradle(一个构建工具)

cordova-android@4.0.0起,Cordova为Android项目使用 Gradle构建。关于用Ant构建的说明,请参考老版本的文档。

设置Gradle属性

通过设置确定的Cordova暴露的Gradle属性 配置Gradle构建是可能的。下面的属性是可以被设置的:

属性 描述
cdvBuildMultipleApks 如果这个被设置了,那么多个APK文件将会被产生:每一个是由库项目(x86, ARM等)支持的本地平台。如果你的项目使用一个很大的本地库,这会显著提高所产生的APK的大小,这是很重要的。如果不设置,可以在所有设备上使用的单个APK将产生。
cdvVersionCode 重写设置在AndroidManifest.xml里面的versionCode
cdvReleaseSigningPropertiesFile 默认: release-signing.properties
.properties文件的路径,这个文件包含发布版本的签名信息(参见Signing an App)
cdvDebugSigningPropertiesFile 默认: debug-signing.properties
.properties文件的路径,这个文件包含调试版本的签名信息(参见Signing an App)。当你需要吧签名key分享给其他开发者这十分有用
cdvMinSdkVersion 重写设置在AndroidManifest.xmlminSdkVersion的值。当基于SDK版本创建多个APK时是十分有用的
cdvBuildToolsVersion 重写自动检测android.buildToolsVersion的值
cdvCompileSdkVersion 重写自动检测android.compileSdkVersion的值

你可以设置这些属性通过下面四种方式之一:

  1. 像下面这样通过环境变量设置:

      $ export ORG_GRADLE_PROJECT_cdvMinSdkVersion=20
      $ cordova build android
    
  2. 通过使用--gradleArg标志在你的Cordovabuild或者run命令中:

      $ cordova run android -- --gradleArg=-PcdvMinSdkVersion=20
    
  3. 通过在你的Android平台目录(<your-project>/platforms/android)中放置一个名为gradle.properties的文件,并在其中设置属性像这样:

      # In <your-project>/platforms/android/gradle.properties
      cdvMinSdkVersion=20
    
  4. By extending 通过一个build-extras.gradle文件扩展build.gradle ,并设置属性像这样:

      // In <your-project>/platforms/android/build-extras.gradle
      ext.cdvMinSdkVersion = 20
    

后面两种方案都涉及包含了额外的文件在你的平台文件夹中。通常我们不鼓励你编辑这些文件夹中的内容,因为这些变化很容易丢失被重写。代替,这两个文件应该从另一个位置复制到这个文件夹,作为构建命令的一部分,通过使用before_build 钩子.

扩展build.gradle

如果你需要自定义build.gradle,而不是直接编辑他,你要创建一个名为build-extras.gradle的同级文件。这个文件将会包含在主build.gradle里面,当他出现时。这个文件必须覆盖android平台目录(<your-project>/platforms/android),所以建议通过绑定脚本到before_build 钩子来覆盖他。

这里有个例子:

// 例子build-extras.gradle
// 这个文件会包含在`build.gradle`开头
ext.cdvDebugSigningPropertiesFile = '../../android-debug-keys.properties'

// 当设置了,这个方法允许代码在`build.gradle`结尾处运行
ext.postBuildExtras = {
    android.buildTypes.debug.applicationIdSuffix = '.debug'
}

注意插件可以包含在build-extras.gradle文件:

<framework src="some.gradle" custom="true" type="gradleReference" />

设置Version Code(版本编码)

要改变你的应用程序的生成APK的version code,你可以设置应用程序config.xml文件的widget元素的android-versionCode属性。如果android-versionCode没有设置,版本编码将由version属性决定。例子,如果版本是MAJOR.MINOR.PATCH:

versionCode = MAJOR * 10000 + MINOR * 100 + PATCH

如果你的应用程序启动了cdvBuildMultipleApks Gradle属性 (参见 设置Gradle属性),你应用的版本编码还得乘与10以便编码的最后一位可以用暗示apk的构建架构。不管你的版本编码来自android-versionCode还是由version生成,这个乘法都会发生。请注意一些插件添加到你的项目(包括 cordova-plugin-crosswalk-webview)可能会自动设置Gradle属性。

请注意: 当更新android-versionCode属性时,从构建的APK递增版本编码是不明智的。代替的,你应该基于config.xml文件里android-versionCode属性来递增编码。这是因为cdvBuildMultipleApks 属性导致版本编码在构建的apk中被乘与10,所以使用这个值将会导致下一个版本编码是原始的一百倍,等等。

签名一个应用

首先你应该阅读Android应用签名所需

使用标志

签名一个应用,你需要下面参数:

参数 标志 描述
Keystore --keystore 用来存储一组key的二进制文件路径
Keystore Password --storePassword keystore存储密钥
Alias --alias 用来指定私有key用来签名
Password --password 私有key的密码
Keystore的类型 --keystoreType 默认: 自动检测基于文件扩展名
pkcs12或者jks

这些参数可以通过上面的Cordova CLI build 或者 run命令来指定命令行参数。

注意: 你应该使用两个中划线 -- 来表示这些平台特定参数,例如:

cordova run android --release -- --keystore=../my-release-key.keystore --storePassword=password --alias=alias_name --password=password.

使用build.json

或者,你可以在相同的命令中使用--buildConfig参数传递构建配置文件(build.json),并在其中指定他们。这里有个样例配置文件:

{
    "android": {
        "debug": {
            "keystore": "../android.keystore",
            "storePassword": "android",
            "alias": "mykey1",
            "password" : "password",
            "keystoreType": ""
        },
        "release": {
            "keystore": "../android.keystore",
            "storePassword": "",
            "alias": "mykey2",
            "password" : "password",
            "keystoreType": ""
        }
    }
}

对于发布签名,密码可以排除在外,构建系统将会发出提示要求输入密码。

这里同时也支持,命令行参数和build.json参数混合。命令行中的参数优先。这是十分有用的在命令行中输入密码。

使用Gradle

你还可以通过包含一个.properties文件指定签名属性,并指向他到cdvReleaseSigningPropertiesFilecdvDebugSigningPropertiesFile Gradle属性 (see 设置Gradle属性). 这个文件应该像这样:

storeFile=relative/path/to/keystore.p12
storePassword=SECRET1
storeType=pkcs12
keyAlias=DebugSigningKey
keyPassword=SECRET2

storePasswordkeyPassword是选的,如果省略了将会提示

调试

为了获取于AndroidSDK打包在一起调试工具的详细信息, 可以看 Android调试开发者文档。 另外, Android调试web应用程序开发者文档提供了你的应用程序员运行在Webview中这部分调试的介绍。

在Android Studio中打开一个项目

Cordova的Android项目可以被Android IDEAndroid Studio打开。如果你想使用Android Studio内置的Android调试/分析工具或者你要开发Android插件这是十分有用的。请注意当你在Android studio里打开你的项目,建议你不要编辑你的代码在IDE中。这会在 platforms目录中编辑你的代码(而不是 www),并且变化将会被重写。代替,编辑www目录并通过运行cordova build来拷贝过来你的变化。

Plugin开发者希望编辑原生代码在IED中应该是使用--link标志,当他们通过 cordova plugin add添加插件到项目。这会链接文件,以便在platforms文件夹中的插件文件变化会映射到插件源文件夹(反之亦然)。

要在Android Studio中打开Cordova的Android项目:

  1. 启动 Android Studio.

  2. 选择 Import Project (Eclipse ADT, Gradle, etc).

  3. 选择你项目中的Android platform目录(<your-project>/platforms/android)。

  4. 对于Gradle Sync问题你可以简单的回答 Yes.

一旦导入完成,你应该可以在Android Studio中直接构建和运行应用。 参见 Android Studio概述从Android Studio中构建和运行活的详细信息。

平台为中心的工作流

cordova-android包含很多脚本,可以让你以不完全的Cordova CLI使用平台。这种开发路径给开发者很大选择在特定的环境而不是跨平台的cordova CLI。例如,你需要一个shell工具用来不熟自定义的Cordova WebView和本地组件一起。在使用这个开发路径之前,你任然需要配置Android SDK环境,如要求和支持情况上面描述的。

对于下面每一个讨论的脚本, 参考Cordova CLI参考手册获取更多关于参数和使用的信息。对于每一个脚本有一个于CLI命令对应的名字。比如 cordova-android/bin/create对应于cordova create

为了开始, 载cordova-android包从npm 或者 Github

为了创建一个项目使用下面包, 运行 create脚本在bin目录:

$ cordova-android/bin/create ...

创建的项目将会包含一个叫做 cordova 目录,在这里有针对特定项目的Cordova命令脚本(比如run, build等等)。另外,这个项目的结构将不同于普通的Cordova项目。值得注意的 /www被移到/assets/www

为了安装插件在这个项目, 使用Cordova Plugman功能.

升级

参考这个 文章介绍了更新你的cordova-android版本。

生命周期(Lifecycle)指南

Cordova和Android

原生的Android应用通常由一系列活动组成,用于与用户交互。活动可以被认为一个单独的屏幕,组成了一个应用程序。在应用中不同的任务通常拥有自己的活动。每个活动都有自己的生命周期,作为活动的进入和离开用户设备前景维护。

比较起来,Android平台的Cordova应用运行在一个嵌入单独Android活动中的Webview中。活动的生命周期通过文档事件触发暴漏给你的应用程序,事件不保证与Android的生命周期对齐,但可以提供保存和恢复状态的指导方针。这些事件大致与Android回调对应如下:

Cordova事件 粗略的Android等效 含义
deviceready onCreate() 应用程序开始(不是从背景)
pause onPause() 应用程序移动到背景
resume onResume() 应用程序返回到前景

大多数其他Cordova平台有类似的生命周期概念,当类似的动作发生在用户设备上的时候,应该触发同样的事件。然而,Android平台会有一些独有的事件会触发,这归功于原生活动周期。

什么使Android不同?

在Android设备中,操作系统可以选择在后台杀死活动来释放资源,如果当前设备运行程序的内存过低。不幸的是,当支持你的应用程序的活动被杀死,生存在Webview中的应用程序也会被销毁。这种情况任何应用程序维护的状态都会丢失。当用户重新导航到应用程序,活动和Webview将会由操作系统重新创建,但是在你的Cordova应用中状态不会自动恢复。由于这个原因,你的应用程序知道生命周期被触发并维持任何确保用户在离开应用程序用户上下文不丢失的状态,是必须的。

什么时候这会发生?

你的应用程序是很容易被操作系统销毁的,当它离开用户视野的时候,这里有种主要情况会发生。第一种是最显然的情况用户按home键或者切换到另外一个应用程序。

然而,这里有第二种情况(更加微妙),一个插件被引用。如上所述,Cordova应用通常被限定在一个包含Webview的活动中。这里有一些其他的活动的实例会被插件创建并暂时将Cordova活动放入背景。这些其他活动通常是为了执行特定的任务,通过使用安装在用户设备上的原生应用。例如,Apache camera插件启动任何一个原生安装在设备上的camera活动来获取照片。通过这种方式重复利用安装的camera应用,让用户尝试获取照片使用起来更像原生应用。不幸的是,当原生的活动将你的应用放入背景,会给操作系统一次kill掉他的机会。

为了更清晰的理解第二种情况,我们将走过一个使用camera插件的例子。想象一下你有一个应用程序需要获取用户头像资料。当一切都按照计划,应用中的事件流应该是这样子的:

  1. 用户与app交互并需要获得一张图片
  2. camera插件启动一个camera活动
    • Cordova活动被推送入后台(pause事件触发)
  3. 用户获得一张图片
  4. camera活动结束
    • Cordova活动被移动到前台(resume事件被触发)
  5. 用户返回离开的应用程序

然而,如果设备内存过低事件流会被打断。如果活动被操作系统杀死掉,上面的事件流序列会被下面代替:

  1. 用户与app交互并需要获得一张图片
  2. camera插件启动一个camera活动
    • OS销毁Cordova活动(pause事件被触发)
  3. 用户获得一张图片
  4. camera活动结束
    • 操作系统重新创建Cordova活动(deviceready和resume事件被触发)
  5. 用户是困惑的,因为他们突然返回到应用的登录界面

在这个实例里面,操作系统在背景中杀掉了应用程序并且应用程序不会作为生命周期的一部分来维护他的状态。当用户返回应用,Webview被重新创建并且应用从头开始重启(让用户更加困惑)。这个事件序列和用户按home键或者切换到其他应用是一样的。阻止上面体验的关键是订阅事件并合适的维护状态作为活动生命周期的一部分。

关于生命周期

在上面的例子,javascript事件的触发被标注成了斜体。这些事件有机会保存和恢复你的应用程序状态。你应该通过bindEvents 方法来注册应用程序回调来回应生命周期事件来保存状态。保存什么息和怎么保存信息由你决定,但是你要确保保存足够的信息,来精确的恢复到用户离开的地方,s当用户返回到应用时。

这里有一个额外的特性在上面的例子中,他仅仅应用与第二种讨论的情况(也就是一个插件启动了一个额外的活动)。不仅当用户获取完图片应用程序状态丢失,用户获取的图片也一样。通常那个图片会通过注册在插件上的一个回调传递给你的应用程序。然而,当Webview被摧毁,回调会被永远丢失。幸运的是,cordova-android 5.1.0及其以上提供了一个方法来获得插件的结果当你的应用恢复时。

检索插件回调结果(cordova-android 5.1.0+)

当操作系统销毁了由插件推送到背景的Cordova活动,任何添加的回调也丢失了同时。这意味着如果你传递了一个回调给插件,这个插件启动了一个新的活动(例子camera插件),这个回调不会触发当应用程序被重建。然而,从cordova-android 5.1.0开始,resume事件有效载荷将会包含任何附加插件的结果从插件请求启动外部活动使优先级高于活动被销毁。

对于resume事件的有效载荷我们坚持下面格式:

{
    action: "resume",
    pendingResult: {
        pluginServiceName: string,
        pluginStatus: string,
        result: any
    }
}

有效载荷的字段定义如下:

  • pluginServiceName: 插件返回结果的名字(比如"Camera")。这个可以在插件plugin.xml文件中的 <name>标签中找到
  • pluginStatus: 插件调用状态(查看下面)
  • result: 任何插件调用的结果

pluginStatuspendingResult中可能包含的值有:

  • "OK" - 插件调用成功
  • "No Result" - 插件调用结束没有结果
  • "Error" - 插件调用结果一般的错误
  • 其他混杂的错误
    • "Class not found"
    • "Illegal access"
    • "Instantiation error"
    • "Malformed url"
    • "IO error"
    • "Invalid action"
    • "JSON error"

请注意插件决定什么包含在result里面和pluginStatus的含义。当你要使用的时候参考插件的API看看这个字段包含什么,以及如何使用这些值。

例子

下面是一个简单的例子使用resumepause事件管理状态。这里使用Apache camera插件做一个例子,展示如果检索插件调用结果从resume有效载荷。代码处理resumeevent.pendingResult对象部分需要cordova-android 5.1.0+

// 这个状态代表了应用程序的状态并且会在onResume()和onPause()中保存和恢复
var appState = {
    takingPicture: true,
    imageUri: ""
};

var APP_STORAGE_KEY = "exampleAppState";

var app = {
    initialize: function() {
        this.bindEvents();
    },
    bindEvents: function() {
        // 这里我们注册我们关心的生命周期事件回调
        document.addEventListener('deviceready', this.onDeviceReady, false);
        document.addEventListener('pause', this.onPause, false);
        document.addEventListener('resume', this.onResume, false);
    },
    onDeviceReady: function() {
        document.getElementById("take-picture-button").addEventListener("click", function() {
            //由于camera插件方法启动了一个外部活动
            //这里有一次机会我们的应用程序被kill掉在回调被成功或者失败调用之前
            // 在onPause()和onResume()那里我们保存和恢复状态,来处理这个事情
            appState.takingPicture = true;

            navigator.camera.getPicture(cameraSuccessCallback, cameraFailureCallback,
                {
                    sourceType: Camera.PictureSourceType.CAMERA,
                    destinationType: Camera.DestinationType.FILE_URI,
                    targetWidth: 250,
                    targetHeight: 250
                }
            );
        });
    },
    onPause: function() {
        // 这里我们检测我们是否在获取图片,如果在,我们希望保存我们的状态以便onResume()
        // 恢复的时候使用,如果我们获得了图片URI我们也要存储
        if(appState.takingPicture || appState.imageUri) {
            window.localStorage.setItem(APP_STORAGE_KEY, JSON.stringify(appState));
        }
    },
    onResume: function(event) {
        // 这里我们检差存储的状态,如果需要恢复他。由你跟踪任何添加的插件结果的来源
        //  (也就是说你代码的哪一步被调用),还有什么参数提供给插件如果相关
        var storedState = window.localStorage.getItem(APP_STORAGE_KEY);

        if(storedState) {
            appState = JSON.parse(storedState);
        }

        // 检查如果我们需要恢复我们的图片
        if(!appState.takingPicture && appState.imageUri) {
            document.getElementById("get-picture-result").src = appState.imageUri;
        }
        // 现在我们可以检测如果插件结果在事件对象里面
        // 这里需要cordova-android 5.1.0+
        else if(appState.takingPicture && event.pendingResult) {
            // 检测插件调用是否成功并调用相应的回调。对于camera插件,"OK"
            //意味着成功其他意味着错误
            if(event.pendingResult.pluginStatus === "OK") {
                // camera放置同样的结果在resume对象,因为成功回调传递给了getPicture(),
                // 因此我们可以传递同样的回调,返回一些其他东西。查询文档,了解怎么解释你使用
                // 插件的结果字段
                cameraSuccessCallback(event.pendingResult.result);
            } else {
                cameraFailureCallback(event.pendingResult.result);
            }
        }
    }
}

// 这里是回调我们传入getPicture()
function cameraSuccessCallback(imageUri) {
    appState.takingPicture = false;
    appState.imageUri = imageUri;
    document.getElementById("get-picture-result").src = imageUri;
}

function cameraFailureCallback(error) {
    appState.takingPicture = false;
    console.log(error);
}

app.initialize();

对应的html:

<!DOCTYPE html>

<html>
    <head>
        <meta http-equiv="Content-Security-Policy" content="default-src 'self' data: gap: https://ssl.gstatic.com 'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src *">
        <meta name="format-detection" content="telephone=no">
        <meta name="msapplication-tap-highlight" content="no">
        <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width">
        <link rel="stylesheet" type="text/css" href="css/index.css">
        <title>Cordova Android Lifecycle Example</title>
    </head>
    <body>
        <div class="app">
            <div>
                <img id="get-picture-result" />
            </div>
            <Button id="take-picture-button">Take Picture</button>
        </div>
        <script type="text/javascript" src="cordova.js"></script>
        <script type="text/javascript" src="js/index.js"></script>
    </body>
</html>

测试活动生命周期

Android提供了开发者设置测试活动被破坏在低内存的情况下。在你的设备或者模拟上模拟低内存场景通过设置 "Don't keep activities"在开发者选项菜单。你应该在这种情况下做大量测试来确保你的应用保持正确的状态。