Android使用Cordova进行混合开发

导语

这篇文章主要介绍了如何在Android平台上使用Cordova 的command-line interface (CLI)接口开发一个JS/HTML程序
Cordova 提供了一组设备相关的API,通过这组API,移动应用能够以JavaScript访问原生的设备功能,如摄像头、麦克风等,还提供了一组统一的JavaScript类库,以及为这些类库所用的设备相关的原生后台代码。
Cordova支持如下移动操作系统:iOS, Android,ubuntu phone os, Blackberry, Windows Phone, Palm WebOS, Bada 和 Symbian。

一、安装Cordova开发环境


Cordova命令行开发工具使用的是NPM开发工具包

NPM 是随同NodeJS一起安装的包管理工具,能解决NodeJS代码部署上的很多问题

  1. 下载安装Node.js ,这样我们可以在终端执行npm命令。
  2. 下载安装git Client ,这是可选的,如果以后需要使用git 连接添加一个Cordova 插件的话需要使用到。
  3. 在终端中使用如下命令安装Cordova
$ sudo npm install -g cordova

完成以上步骤以后,我们就可以在终端使用Cordova命令,如果打印如下信息,则表示已经安装成功:

Chans-MacBook-Pro:YuntxCient kevinchan$ cordova
Synopsis

    cordova command [options]

Global Commands
    create ............................. Create a project
    help ............................... Get help for a command
    telemetry .......................... Turn telemetry collection on or off

Project Commands
    info ............................... Generate project information
    requirements ....................... Checks and print out all the requirements
                                            for platforms specified

    platform ........................... Manage project platforms
    plugin ............................. Manage project plugins

    prepare ............................ Copy files into platform(s) for building
    compile ............................ Build platform(s)
    clean .............................. Cleanup project from build artifacts

    run ................................ Run project
                                            (including prepare && compile)
    serve .............................. Run project with a local webserver
                                            (including prepare)

查看当前系统安装的Cordova版本:

Chans-MacBook-Pro:~ kevinchan$ cordova --v
6.4.0

安装编译工具

编译和运行Cordova程序,需要在对应的平台安装SDK开发工具,但是如果你正在开发基于浏览器运行的程序则可以忽略,如下可以检查当前开发环境是否满足要求

Chans-MacBook-Pro:AvayaClient kevinchan$ cordova requirements

Requirements check results for android:
Java JDK: installed 1.8.0
Android SDK: installed true
Android target: installed android-16,android-18,android-19,android-21,android-    22,android-23,android-24,Google Inc.:Google APIs:16
Gradle: installed 

这里可以查看到官网的支持文档,分别为各个平台所需要满足的编译环境

Cordova和Android版本支持对应关系

Cordova每个版本所支持Android最高的API有可能是不一样的,目前Cordova最新的版本支持Android API为23,其他的版本支持对应关系可以参考如下:

cordova-android Version Supported Android API-Levels
5.X.X 14 - 23
4.1.X 14 - 22
4.0.X 10 - 22
3.7.X 10 - 21

创建Cordova程序

选择一个目录作为工程的根目录,使用Cordova创建命令创建Cordovac程序

Chans-MacBook-Pro:Android kevinchan$ cordova create YuntxCient com.yuntongxun.cordova.plugin YuntxSDK
Using detached cordova-create
Creating a new cordova project.

执行上面的创建命令后,会在本地文件下根目录创建一个Cordova结构目录,默认情况下Cordova会在当前目录生成www文件夹,包含了web应用程序基本的html页面,Cordova目录结构如下:

YuntxClient/
|____config.xml
|____hooks
| |____README.md
|____platforms
|____plugins
|____www
| |____css
| | |____index.css
| |____img
| | |____logo.png
| |____index.html
| |____js
| | |____index.js

可以在Cordova查看当前创建命令的接口描述

以上我们仅仅创建了一个基本的Cordova程序,并且还没有增加一个所支持的Native平台,所以我么看到platforms是空的,所以我们可以根据Cordova提供的命令来为当前创建的Cordova程序增加一个平台支持:

Chans-MacBook-Pro:Android kevinchan$ cd YuntxCient/
Chans-MacBook-Pro:YuntxCient kevinchan$ cordova platform add android --sava
Adding android project...
Creating Cordova project for the Android platform:
    Path: platforms/android
    Package: com.yuntongxun.cordova.plugin
    Name: YuntxSDK
    Activity: MainActivity
    Android target: android-24
Subproject Path: CordovaLib
Android project created with cordova-android@6.0.0
Discovered plugin "cordova-plugin-whitelist" in config.xml. Adding it to the project
Fetching plugin "cordova-plugin-whitelist@1" via npm
Installing "cordova-plugin-whitelist" for android

使用Cordova命令增加一个平台都会在platforms目录下面增加一个以当前平台相关的文件夹,如上使用命令增加了一个Android平台。

注意:当使用CLI构建应用程序的时候,最好不要修改/platforms/目录结构下的任意文件,因为当时用Cordova命令编译程序的时候,该目录会被新的编译文件重新覆盖生成最新的程序运行文件。

在我们添加Andorid支持平台的时候,Cordova会为我们添加一个默认插件,也就是上面信息中所打印的cordova-plugin-whitelist并且自动在config.xml文件中已经帮我们配置

编译并运行

在默认情况下,使用Cordova create脚本命令会创建一个与Web相关的文件夹,里面包含了基于web应用程序运行所需要的html页面和js文件,我们可以在这个web目录下的www\js\index.js文件里面处理Cordova SDK初始化完成事件

使用如下命令编译所有的平台文件,取决于/platforms/目录下增加了多少个平台

$ cordova build 

同样我们也可以针对某一平台进行编译:

$ cordova build android 

至此,我们可以使用真机/模拟器来运行我们编译生成的apk,因为cordova使用的编译插件为gradle,所以如果本机没有安装gradle会先进行下载安装,需要等待一定时间进行下载,也可以从gradle官网下载自行配置环境变量。

二、创建自己的Cordova插件程序


一个cordova 插件可以允许Cordova 内置浏览器加载在任意支持平台终端上运行,并且完成与终端原生功能进行访问,一般使用者在web应用程序无法直接访问平台原生能力api的条件下进行间接访问原生本地api接口,并且Cordova提供了大部分Android原生api的访问插件,比如条形码扫描、NFC通信、定制日历等。

可以在Cordova Plugin Search page开发网站上获取已经实现好的插件来集成到我们的应用中。

一个插件需要包含部分JavaScript接口提供插件框架调用,以及JavaScript接口本地代码实现来完成js与本地原生功能之间进行通信。我们可以把每个平台共用的接口单独定义在一个js文件中,然后根据不同的平台特有的功能提供针对当前平台所支持的JavaScript接口,下面我们通过一个简单的插件实现来完成web页面调用 云通讯Android SDK 来进行SDK初始化。

我们在本地创建一个插件文件夹为cordova-plugin-yuntx,并且增加srcwww两个目录,目录结构为:

cordova-plugin-yuntx
|____plugin.xml 
|____src
| |____android
| | |____YuntxSDK.java
|____www
| |____android
| | |____yuntx.js
| |____yuntx.js

编写本地实现文件

我们在andorid 目录下新建一个以com.yuntongxun.cordova.plugin为包名的java文件,继承于CordovaPlugin(所有自定义插件,都要继承CordovaPlugin),最后重写execute方法。

execute有三个重载方法:

    /**
     * Executes the request.
     *
     * This method is called from the WebView thread. To do a non-trivial amount of work, use:
     *     cordova.getThreadPool().execute(runnable);
     *
     * To run on the UI thread, use:
     *     cordova.getActivity().runOnUiThread(runnable);
     *
     * @param action          The action to execute.
     * @param rawArgs         The exec() arguments in JSON form.
     * @param callbackContext The callback context used when calling back into JavaScript.
     * @return                Whether the action was valid.
     */
    public boolean execute(String action, String rawArgs, CallbackContext callbackContext) throws JSONException {
        JSONArray args = new JSONArray(rawArgs);
        return execute(action, args, callbackContext);
    }

    /**
     * Executes the request.
     *
     * This method is called from the WebView thread. To do a non-trivial amount of work, use:
     *     cordova.getThreadPool().execute(runnable);
     *
     * To run on the UI thread, use:
     *     cordova.getActivity().runOnUiThread(runnable);
     *
     * @param action          The action to execute.
     * @param args            The exec() arguments.
     * @param callbackContext The callback context used when calling back into JavaScript.
     * @return                Whether the action was valid.
     */
    public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
        CordovaArgs cordovaArgs = new CordovaArgs(args);
        return execute(action, cordovaArgs, callbackContext);
    }

    /**
     * Executes the request.
     *
     * This method is called from the WebView thread. To do a non-trivial amount of work, use:
     *     cordova.getThreadPool().execute(runnable);
     *
     * To run on the UI thread, use:
     *     cordova.getActivity().runOnUiThread(runnable);
     *
     * @param action          The action to execute.
     * @param args            The exec() arguments, wrapped with some Cordova helpers.
     * @param callbackContext The callback context used when calling back into JavaScript.
     * @return                Whether the action was valid.
     */
    public boolean execute(String action, CordovaArgs args, CallbackContext callbackContext) throws JSONException {
        return false;
    }

从源代码可以看出,其中两个方法都掉用了第三个方法CordovaArgs只是对JSONArray的一个封装,方便操作json数据,所以可以根据自己的使用习惯来选择重写。

  • String action:一个类里面可以提供多个功能,action就是指名了要调用哪个功能。
  • CordovaArgs args:web以json的数据格式传递给Android native,CordovaArgs 是对JSONArray 的一个封装。
  • CallbackContext callbackContext:这个是回调给web,有success和error两种回调方法

具体实现的注册请求如下:

    @Override
    public boolean execute(String action, CordovaArgs args, CallbackContext callbackContext) throws JSONException {

        if(action.equals("initSDK")) {
            // 注册SDK事件
            String appKey = args.getString(0);
            String appToken = args.getString(1);
            String userName = args.getString(2);
            String password = args.getString(3);
            initYuntxSDK(appKey , appToken , userName , callbackContext);
            return true;
        }

        return super.execute(action, args, callbackContext);
    }


    /**
     * 注册sdk请求
     * @param appKey 应用id
     * @param appToken 应用token
     * @param userName 注册id
     */
    void initYuntxSDK(final String appKey, final String appToken, final String userName , final CallbackContext callbackContext) {
        ECDevice.initial(this.cordova.getActivity(), new ECDevice.InitListener() {
            @Override
            public void onInitialized() {

                ECDevice.setOnDeviceConnectListener(new ECDevice.OnECDeviceConnectListener() {
                    @Override
                    public void onConnectState(ECDevice.ECConnectState ecConnectState, ECError ecError) {
                        //  注册结果
                        if(ecError.errorCode == SdkErrorCode.REQUEST_SUCCESS ) {
                            callbackContext.success("注册成功");
                        } else if(ecError.errorCode == SdkErrorCode.CONNECTING ) {
                            callbackContext.success("注册中...");
                        } else  {
                            callbackContext.success("注册失败");
                        }
                    }
                });

                ECInitParams params = ECInitParams.createParams();
                params.setAppKey(appKey);
                params.setToken(appToken);
                params.setAuthType(ECInitParams.LoginAuthType.NORMAL_AUTH);
                params.setMode(ECInitParams.LoginMode.FORCE_LOGIN);
                params.setUserid(userName);
                ECDevice.login(params);
            }

            @Override
            public void onError(Exception e) {
                // 初始化失败
            }
        });
    }

如果我们的Web页面中配置使用了当前注册插件,调用注册方法initSDK , 这时候插件就会调用如上方法获取到注册参数信息,最终调用云平台的Android SDK注册方法进行注册,并且将注册结果返回给Web页面进行显示。

这里需要注意的是execute方法需要返回true,表示接口调用成功

编写接口调用文件

www目录下新建yuntx.js文件,同样我们也可以为Android平台定义该平台特有的一些接口文件放在android目录下作为跨平台私有访问接口

var exec = require('cordova/exec');
var platform = require('cordova/platform');

module.exports = {

    /**
     * 根据提供的帐号信息注册sdk
     */
    initSDK: function(appKey , token, userid , completeCallback , errorCallback) {
        var _appKey = (typeof appKey === "string" ? appKey : "");
        var _token = (typeof token === "string" ? token : "");
        var _userid = (typeof userid === "string" ? userid : "");
        exec(completeCallback, errorCallback, "YuntxSDK", "initSDK", [_appKey, _token, _userid]);
    },

    /**
     * 根据提供的帐号发起呼叫
     *
     * @param {Function} resultCallback 
     * @param {String} calledParty         
     * @param {String} isVideoCall    
     */
    makeCall: function(resultCallback, number ,isVideoCall) {
        var _isVideoCall = (typeof isVideoCall === "boolean" ? isVideoCall : false);
        var _number = (typeof number === "string" ? number : "number");
        exec(resultCallback, null, "YuntxSDK", "makeCall", [_number, isVideoCall]);
    },

};

填写plugin.xml配置文件

这里配置了插件的基本信息以及本地实现代码路径。

<?xml version="1.0" encoding="UTF-8"?>
<!-- 插件的Id,安装后注册的Id为此id加js-moudle的name属性,即cordova_plugins.js里的id属性 -->
<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0"
           id="cordova-plugin-yuntx"
      version="1.0.0">

    <!-- 插件名称 -->
    <name>YuntxSDK</name>
    <!-- 插件描述 -->
    <description>Cordova YuntxSDK Plugin</description>
    <license>Apache 2.0</license>
    <keywords>cordova,yuntx_android</keywords>
    <issue>https://issues.apache.org/jira/browse/CB/component/12320642</issue>
    <!-- js文件的地址,安装后路径为:plugins/插件Id/src属性值 -->
    <js-module src="www/yuntx.js" name="yuntx">
        <merges target="android.yuntx" />
    </js-module>


    <!-- 这里对不同平台的插件文件进行配置 -->
    <!-- android -->
    <platform name="android">
        <!-- config-file中包含的这段会原封不动的插入到config.xml文件中 -->
        <config-file target="res/xml/config.xml" parent="/*">
            <feature name="YuntxSDK">
                <param name="android-package" value="com.yuntongxun.cordova.plugin.YuntxSDK"/>
            </feature>
        </config-file>
        <!-- 本地代码,有多个文件就写多个source-file,src对应本项目,target对应安装后的目录 -->
        <source-file src="src/android/YuntxSDK.java" target-dir="src/com/yuntongxun/cordova/plugin" />

        <!-- 这里可用配置针对Android的一些特殊apis -->
        <js-module src="www/android/yuntx.js" name="yuntx_android">
            <merges target="android.yuntx" />
        </js-module>

    </platform>
</plugin>

至此,我们已经完成了插件基本功能的配置和接口的编写,现在我们需要调用Cordova命令将我们编写的插件增加到我们篇头创建的Cordova工程中。

增加自定义插件到插件工程中

之前我们提到过,我们不能直接在/platforms/目录下直接修改插件内容,因为调用安装插件的命令后,Cordova跟将我们指定的插件路径将插件内容覆盖到/platforms/目录下对应的插件目录中,所以我们在这个目录里面所做的修改就会全部被覆盖了。

添加插件包的命令如下:

cordova plugins add <插件包路径>

我们调用增加插件命令将我们编写的插件集成到Cordova项目中

Chans-MacBook-Pro:~ kevinchan$ cd Workspace/Android/YuntxCient/
Chans-MacBook-Pro:YuntxCient kevinchan$ cordova plugins add /Users/kevinchan/Workspace/Android/cordova-plugin-yuntx
Installing "cordova-plugin-yuntx" for android
ANDROID_HOME=/Users/kevinchan/Library/Android/sdk
JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_111.jdk/Contents/Home
Subproject Path: CordovaLib
Starting a new Gradle Daemon for this build (subsequent builds will be faster).
Incremental java compilation is an incubating feature.
:clean
:CordovaLib:clean

BUILD SUCCESSFUL

Total time: 8.03 secs

我们在工程目录查看生成的android项目目录结构,如:/platforms/android/assets/
里面增加了我们自己写的插件cordova-plugin-yuntx

assets
|____www
| |____cordova-js-src
| | |____android
| | | |____nativeapiprovider.js
| | | |____promptbasednativeapi.js
| | |____exec.js
| | |____platform.js
| | |____plugin
| | | |____android
| | | | |____app.js
| |____cordova.js
| |____cordova_plugins.js
| |____css
| | |____index.css
| |____img
| | |____logo.png
| |____index.html
| |____js
| | |____index.js
| |____plugins // 这里就是我们刚刚自己定义的插件
| | |____cordova-plugin-yuntx
| | | |____www
| | | | |____android
| | | | | |____yuntx.js
| | | | |____yuntx.js

并且在工程根目录/res/xml/config.xml中,Cordova为我们自动加入了yuntx插件的配置信息

<?xml version='1.0' encoding='utf-8'?>
<widget id="com.yuntongxun.cordova.plugin" version="1.0.0" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
    <name>YuntxSDK</name>
    <description>
        A sample Apache Cordova application that responds to the deviceready event.
    </description>
    <author email="dev@cordova.apache.org" href="http://cordova.io">
        Apache Cordova Team
    </author>
    <content src="index.html" />
    <access origin="*" />
    <allow-intent href="http://*/*" />
    <allow-intent href="https://*/*" />
    <allow-intent href="tel:*" />
    <allow-intent href="sms:*" />
    <allow-intent href="mailto:*" />
    <allow-intent href="geo:*" />
    <allow-intent href="market:*" />
    <preference name="loglevel" value="DEBUG" />
    // 云平台SDK插件
    <feature name="YuntxSDK">
        <param name="android-package" value="com.yuntongxun.cordova.plugin.YuntxSDK" />
    </feature>
</widget>

以及在www根目录会生成插件配置文件

cordova.define('cordova/plugin_list', function(require, exports, module) {
module.exports = [
    {
        "id": "cordova-plugin-yuntx.yuntx",
        "file": "plugins/cordova-plugin-yuntx/www/yuntx.js",
        "pluginId": "cordova-plugin-yuntx",
        "merges": [
            "android.yuntx"
        ]
    },
    {
        "id": "cordova-plugin-yuntx.yuntx_android",
        "file": "plugins/cordova-plugin-yuntx/www/android/yuntx.js",
        "pluginId": "cordova-plugin-yuntx",
        "merges": [
            "android.yuntx"
        ]
    }
];
module.exports.metadata = 
// TOP OF METADATA
{
    "cordova-plugin-yuntx": "1.0.0"
};
// BOTTOM OF METADATA
});

插件使用方法

我们打开www/js/index.js文件,在index.jsonDeviceReady方法调用

    onDeviceReady: function() {
        this.receivedEvent('deviceready');

        //注册成功回调
        function onSuccess(results) {
            alert("注册成功");
        }

        // 失败回调
        function onError(errCode) {
            alert('注册失败 ' + errCode);
        }

        // 发起注册请求
        android.yuntx.initSDK(
            '201002000000000000000000000000000002',
            'adneojdowenneneofwifojweofjewo',
            'yuntongxun.com',
            onSuccess,
            onError
        );


         /*android.yuntx.makeCall(
            function onMakeCallResult() {

            },
            'm.yuntongxun.com' ,
            false
         );*/
    },

Cordova生命周期

原生的Android App一般由多个Activity组成任务栈,而且从一个APP切换到另一个APP之后,当前与之交互的app就会退到后台,根据每个Activity从前后退到后台,或者从后台切换到前台的过程中,都会回调Activity的生命周期方法,我们可以根据这些生命周期方法来处理不同场景下的逻辑。

而相比于Cordova程序,因为所有的Cordova页面都只使用到了Android系统的独立一个Activity,并将自定义的WebView嵌入到当前的Activity中完成与用户的交互动作,所以不能保证Cordova的生命周期和Android的Activity的生命周期保持一致,但是可以根据Activity的生命周期状态来对数据做保存和恢复处理,Activity的生命周期方法和Cordova生命周期方法对应关系可以参考如下:

Cordova Event Rough Android Equivalent Meaning
deviceready onCreate() 程序第一次运行(却别于从后台切到前台)
pause onPause() 应用程序从前台切换到后台显示
resume onResume() 应用程序从后台切换到前台显示

Android操作系统当手机可用内存很低的情况下会选择性的结束一些后台运行的程序来释放内存资源,所以当我们的Cordova程序退到后台的时候有可能会被系统结束,导致WebView结束运行,所以这个时候程序的运行状态中所保存的数据就会丢失,当用户从最近打开程序列表中选择打开被结束的Cordova程序的时候,此时Activtiy 和WebView都会被冲洗创建,但是我们之前运行的时候所保留的运行数据已经丢失,所以可能你上一次是处于聊天界面,而现在打开程序的时候你却有可能处于登录界面,这会让用户感到迷惑,解决方法就是根据当前的Activity周期调用相对应的Cordovas事件通知机制来对用户的访问数据进行保存,等到Activtiy 和WebView重新创建的时候可以恢复到上一次离开的状态。

比如用户想上传一张图片到服务器中,这个时候会有如下的操作步骤:

  1. 点击界面的图片选择按钮跳转到Android原生相机拍照界面(这个时候Cordova程序停止运行并且退到后台)
  2. 用户完成拍照并保存图片
  3. Android系统保存完拍照图片后关闭相机应用(这个时候系统会将Cordova程序推倒前台显示)
  4. 用户回到上一次离开的界面

然而,以上的使用流程如果在一些内存很低的手机上的时候有可能会执行出错(不完整),就比如系统把退到后台的程序给结束了,这个时候就变成:

  1. 点击界面的图片选择按钮跳转到Android原生相机拍照界面(这个时候Cordova程序停止运行并且退到后台)
  2. 用户完成拍照并保存图片
  3. Android系统保存完拍照图片后关闭相机应用(这个时候系统会将Cordova程序推倒前台显示)
  4. 程序重新运行回到登录界面

所以针对上面的这种场景,Cordova增加了对插件的事件回调方法:
当程序退到后台的时候/或者从后台切换到前台的时候,Cordova可以根据回调事件来判断是否需要保存数据/恢复用户数据

比如Cordova调用页面重新加载的事件通知格式如下:

{
    action: "resume",
    pendingResult: {
        pluginServiceName: string,
        pluginStatus: string,
        result: any
    }
}
  • pluginServiceName : 插件的名字(比如我们刚才定义的YuntxSDK),也就是我们在plugin.xml文件中的<name>标签中配置的值
  • pluginStatus:插件的状态
  • result:

其中插件的状态pluginStatus值有如下几种:

  • "OK" 插件是否调用成功

  • "No Result" 插件调用结束并且无返回值

  • "Error" 插件调用发生错误

  • 其他可能的错误

    • "Class not found"
    • "Illegal access"
    • "Instantiation error"
    • "Malformed url"
    • "IO error"
    • "Invalid action"
    • "JSON error"

所以我们按照上面的格式在Activity的生命周期方法中根据当前的Activity状态来传相对应的字符串,告诉Cordova我们当前的状态,这样Cordova就可以对插件的一些数据做保存,等到程序在后台被结束的时候再次返回后可以恢复到离开状态。

使用方法

下面是Cordova官方提供的示例代码,告诉我们如何使用resumepause事件来管理状态,以及如何根据resume的返回值来回复当前的activity状态。

// This state represents the state of our application and will be saved and
// restored by onResume() and onPause()
var appState = {
    takingPicture: true,
    imageUri: ""
};

var APP_STORAGE_KEY = "exampleAppState";

var app = {
    initialize: function() {
        this.bindEvents();
    },
    bindEvents: function() {
        // Here we register our callbacks for the lifecycle events we care about
        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() {
            // Because the camera plugin method launches an external Activity,
            // there is a chance that our application will be killed before the
            // success or failure callbacks are called. See onPause() and
            // onResume() where we save and restore our state to handle this case
            appState.takingPicture = true;

            navigator.camera.getPicture(cameraSuccessCallback, cameraFailureCallback,
                {
                    sourceType: Camera.PictureSourceType.CAMERA,
                    destinationType: Camera.DestinationType.FILE_URI,
                    targetWidth: 250,
                    targetHeight: 250
                }
            );
        });
    },
    onPause: function() {
        // Here, we check to see if we are in the middle of taking a picture. If
        // so, we want to save our state so that we can properly retrieve the
        // plugin result in onResume(). We also save if we have already fetched
        // an image URI
        if(appState.takingPicture || appState.imageUri) {
            window.localStorage.setItem(APP_STORAGE_KEY, JSON.stringify(appState));
        }
    },
    onResume: function(event) {
        // Here we check for stored state and restore it if necessary. In your
        // application, it's up to you to keep track of where any pending plugin
        // results are coming from (i.e. what part of your code made the call)
        // and what arguments you provided to the plugin if relevant
        var storedState = window.localStorage.getItem(APP_STORAGE_KEY);

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

        // Check to see if we need to restore an image we took
        if(!appState.takingPicture && appState.imageUri) {
            document.getElementById("get-picture-result").src = appState.imageUri;
        }
        // Now we can check if there is a plugin result in the event object.
        // This requires cordova-android 5.1.0+
        else if(appState.takingPicture && event.pendingResult) {
            // Figure out whether or not the plugin call was successful and call
            // the relevant callback. For the camera plugin, "OK" means a
            // successful result and all other statuses mean error
            if(event.pendingResult.pluginStatus === "OK") {
                // The camera plugin places the same result in the resume object
                // as it passes to the success callback passed to getPicture(),
                // thus we can pass it to the same callback. Other plugins may
                // return something else. Consult the documentation for
                // whatever plugin you are using to learn how to interpret the
                // result field
                cameraSuccessCallback(event.pendingResult.result);
            } else {
                cameraFailureCallback(event.pendingResult.result);
            }
        }
    }
}

// Here are the callbacks we pass to 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系统设置/开发者选项/中提高了一些设置api来模拟低内存状态,在开发者选项中,我们将不保留活动设置为启用状态来模拟低内存的场景,这样我们运行上面的程序打开系统相机界面的时候,等我们拍完照片返回的时候,系统已经将我们的插件程序结束并且重新初始化了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容