導(dǎo)語
這篇文章主要介紹了如何在Android平臺(tái)上使用Cordova 的command-line interface (CLI)接口開發(fā)一個(gè)JS/HTML程序
Cordova 提供了一組設(shè)備相關(guān)的API,通過這組API,移動(dòng)應(yīng)用能夠以JavaScript訪問原生的設(shè)備功能,如攝像頭、麥克風(fēng)等,還提供了一組統(tǒng)一的JavaScript類庫,以及為這些類庫所用的設(shè)備相關(guān)的原生后臺(tái)代碼。
Cordova支持如下移動(dòng)操作系統(tǒng):iOS, Android,ubuntu phone os, Blackberry, Windows Phone, Palm WebOS, Bada 和 Symbian。
一、安裝Cordova開發(fā)環(huán)境
Cordova命令行開發(fā)工具使用的是NPM開發(fā)工具包
NPM 是隨同NodeJS一起安裝的包管理工具,能解決NodeJS代碼部署上的很多問題
- 下載安裝Node.js ,這樣我們可以在終端執(zhí)行npm命令。
- 下載安裝git Client ,這是可選的,如果以后需要使用git 連接添加一個(gè)Cordova 插件的話需要使用到。
- 在終端中使用如下命令安裝Cordova
$ sudo npm install -g cordova
完成以上步驟以后,我們就可以在終端使用Cordova命令,如果打印如下信息,則表示已經(jīng)安裝成功:
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)
查看當(dāng)前系統(tǒng)安裝的Cordova版本:
Chans-MacBook-Pro:~ kevinchan$ cordova --v
6.4.0
安裝編譯工具
編譯和運(yùn)行Cordova程序,需要在對應(yīng)的平臺(tái)安裝SDK開發(fā)工具,但是如果你正在開發(fā)基于瀏覽器運(yùn)行的程序則可以忽略,如下可以檢查當(dāng)前開發(fā)環(huán)境是否滿足要求
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
這里可以查看到官網(wǎng)的支持文檔,分別為各個(gè)平臺(tái)所需要滿足的編譯環(huán)境
Cordova和Android版本支持對應(yīng)關(guān)系
Cordova每個(gè)版本所支持Android最高的API有可能是不一樣的,目前Cordova最新的版本支持Android API為23,其他的版本支持對應(yīng)關(guān)系可以參考如下:
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 |
創(chuàng)建Cordova程序
選擇一個(gè)目錄作為工程的根目錄,使用Cordova創(chuàng)建命令創(chuàng)建Cordovac程序
Chans-MacBook-Pro:Android kevinchan$ cordova create YuntxCient com.yuntongxun.cordova.plugin YuntxSDK
Using detached cordova-create
Creating a new cordova project.
執(zhí)行上面的創(chuàng)建命令后,會(huì)在本地文件下根目錄創(chuàng)建一個(gè)Cordova結(jié)構(gòu)目錄,默認(rèn)情況下Cordova會(huì)在當(dāng)前目錄生成www文件夾,包含了web應(yīng)用程序基本的html頁面,Cordova目錄結(jié)構(gòu)如下:
YuntxClient/
|____config.xml
|____hooks
| |____README.md
|____platforms
|____plugins
|____www
| |____css
| | |____index.css
| |____img
| | |____logo.png
| |____index.html
| |____js
| | |____index.js
可以在Cordova查看當(dāng)前創(chuàng)建命令的接口描述
以上我們僅僅創(chuàng)建了一個(gè)基本的Cordova程序,并且還沒有增加一個(gè)所支持的Native平臺(tái),所以我么看到platforms是空的,所以我們可以根據(jù)Cordova提供的命令來為當(dāng)前創(chuàng)建的Cordova程序增加一個(gè)平臺(tái)支持:
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命令增加一個(gè)平臺(tái)都會(huì)在platforms目錄下面增加一個(gè)以當(dāng)前平臺(tái)相關(guān)的文件夾,如上使用命令增加了一個(gè)Android平臺(tái)。
注意:當(dāng)使用CLI構(gòu)建應(yīng)用程序的時(shí)候,最好不要修改
/platforms/
目錄結(jié)構(gòu)下的任意文件,因?yàn)楫?dāng)時(shí)用Cordova命令編譯程序的時(shí)候,該目錄會(huì)被新的編譯文件重新覆蓋生成最新的程序運(yùn)行文件。
在我們添加Andorid支持平臺(tái)的時(shí)候,Cordova會(huì)為我們添加一個(gè)默認(rèn)插件,也就是上面信息中所打印的cordova-plugin-whitelist
并且自動(dòng)在config.xml
文件中已經(jīng)幫我們配置
編譯并運(yùn)行
在默認(rèn)情況下,使用Cordova create
腳本命令會(huì)創(chuàng)建一個(gè)與Web相關(guān)的文件夾,里面包含了基于web應(yīng)用程序運(yùn)行所需要的html頁面和js文件,我們可以在這個(gè)web目錄下的www\js\index.js
文件里面處理Cordova SDK初始化完成事件
使用如下命令編譯所有的平臺(tái)文件,取決于/platforms/
目錄下增加了多少個(gè)平臺(tái)
$ cordova build
同樣我們也可以針對某一平臺(tái)進(jìn)行編譯:
$ cordova build android
至此,我們可以使用真機(jī)/模擬器來運(yùn)行我們編譯生成的apk,因?yàn)?code>cordova使用的編譯插件為gradle
,所以如果本機(jī)沒有安裝gradle
會(huì)先進(jìn)行下載安裝,需要等待一定時(shí)間進(jìn)行下載,也可以從gradle
官網(wǎng)下載自行配置環(huán)境變量。
二、創(chuàng)建自己的Cordova插件程序
一個(gè)cordova 插件可以允許Cordova 內(nèi)置瀏覽器加載在任意支持平臺(tái)終端上運(yùn)行,并且完成與終端原生功能進(jìn)行訪問,一般使用者在web應(yīng)用程序無法直接訪問平臺(tái)原生能力api的條件下進(jìn)行間接訪問原生本地api接口,并且Cordova提供了大部分Android原生api的訪問插件,比如條形碼掃描、NFC通信、定制日歷等。
可以在Cordova Plugin Search page開發(fā)網(wǎng)站上獲取已經(jīng)實(shí)現(xiàn)好的插件來集成到我們的應(yīng)用中。
一個(gè)插件需要包含部分JavaScript接口提供插件框架調(diào)用,以及JavaScript接口本地代碼實(shí)現(xiàn)來完成js與本地原生功能之間進(jìn)行通信。我們可以把每個(gè)平臺(tái)共用的接口單獨(dú)定義在一個(gè)js文件中,然后根據(jù)不同的平臺(tái)特有的功能提供針對當(dāng)前平臺(tái)所支持的JavaScript接口,下面我們通過一個(gè)簡單的插件實(shí)現(xiàn)來完成web頁面調(diào)用 云通訊Android SDK 來進(jìn)行SDK初始化。
我們在本地創(chuàng)建一個(gè)插件文件夾為cordova-plugin-yuntx
,并且增加src
和www
兩個(gè)目錄,目錄結(jié)構(gòu)為:
cordova-plugin-yuntx
|____plugin.xml
|____src
| |____android
| | |____YuntxSDK.java
|____www
| |____android
| | |____yuntx.js
| |____yuntx.js
編寫本地實(shí)現(xiàn)文件
我們在andorid
目錄下新建一個(gè)以com.yuntongxun.cordova.plugin
為包名的java文件,繼承于CordovaPlugin
(所有自定義插件,都要繼承CordovaPlugin
),最后重寫execute方法。
execute有三個(gè)重載方法:
/**
* 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;
}
從源代碼可以看出,其中兩個(gè)方法都掉用了第三個(gè)方法CordovaArgs只是對JSONArray的一個(gè)封裝,方便操作json數(shù)據(jù),所以可以根據(jù)自己的使用習(xí)慣來選擇重寫。
- String action:一個(gè)類里面可以提供多個(gè)功能,action就是指名了要調(diào)用哪個(gè)功能。
- CordovaArgs args:web以json的數(shù)據(jù)格式傳遞給Android native,CordovaArgs 是對JSONArray 的一個(gè)封裝。
- CallbackContext callbackContext:這個(gè)是回調(diào)給web,有success和error兩種回調(diào)方法
具體實(shí)現(xiàn)的注冊請求如下:
@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 應(yīng)用id
* @param appToken 應(yīng)用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) {
// 注冊結(jié)果
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頁面中配置使用了當(dāng)前注冊插件,調(diào)用注冊方法initSDK
, 這時(shí)候插件就會(huì)調(diào)用如上方法獲取到注冊參數(shù)信息,最終調(diào)用云平臺(tái)的Android SDK注冊方法進(jìn)行注冊,并且將注冊結(jié)果返回給Web頁面進(jìn)行顯示。
這里需要注意的是
execute
方法需要返回true,表示接口調(diào)用成功
編寫接口調(diào)用文件
在www
目錄下新建yuntx.js
文件,同樣我們也可以為Android平臺(tái)定義該平臺(tái)特有的一些接口文件放在android
目錄下作為跨平臺(tái)私有訪問接口
var exec = require('cordova/exec');
var platform = require('cordova/platform');
module.exports = {
/**
* 根據(jù)提供的帳號(hào)信息注冊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]);
},
/**
* 根據(jù)提供的帳號(hào)發(fā)起呼叫
*
* @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配置文件
這里配置了插件的基本信息以及本地實(shí)現(xiàn)代碼路徑。
<?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>
<!-- 這里對不同平臺(tái)的插件文件進(jìn)行配置 -->
<!-- android -->
<platform name="android">
<!-- config-file中包含的這段會(huì)原封不動(dòng)的插入到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>
<!-- 本地代碼,有多個(gè)文件就寫多個(gè)source-file,src對應(yīng)本項(xiàng)目,target對應(yīng)安裝后的目錄 -->
<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>
至此,我們已經(jīng)完成了插件基本功能的配置和接口的編寫,現(xiàn)在我們需要調(diào)用Cordova
命令將我們編寫的插件增加到我們篇頭創(chuàng)建的Cordova
工程中。
增加自定義插件到插件工程中
之前我們提到過,我們不能直接在/platforms/
目錄下直接修改插件內(nèi)容,因?yàn)檎{(diào)用安裝插件的命令后,Cordova
跟將我們指定的插件路徑將插件內(nèi)容覆蓋到/platforms/
目錄下對應(yīng)的插件目錄中,所以我們在這個(gè)目錄里面所做的修改就會(huì)全部被覆蓋了。
添加插件包的命令如下:
cordova plugins add <插件包路徑>
我們調(diào)用增加插件命令將我們編寫的插件集成到Cordova
項(xiàng)目中
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項(xiàng)目目錄結(jié)構(gòu),如:/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為我們自動(dòng)加入了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" >
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" />
// 云平臺(tái)SDK插件
<feature name="YuntxSDK">
<param name="android-package" value="com.yuntongxun.cordova.plugin.YuntxSDK" />
</feature>
</widget>
以及在www
根目錄會(huì)生成插件配置文件
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.js
的onDeviceReady
方法調(diào)用
onDeviceReady: function() {
this.receivedEvent('deviceready');
//注冊成功回調(diào)
function onSuccess(results) {
alert("注冊成功");
}
// 失敗回調(diào)
function onError(errCode) {
alert('注冊失敗 ' + errCode);
}
// 發(fā)起注冊請求
android.yuntx.initSDK(
'201002000000000000000000000000000002',
'adneojdowenneneofwifojweofjewo',
'yuntongxun.com',
onSuccess,
onError
);
/*android.yuntx.makeCall(
function onMakeCallResult() {
},
'm.yuntongxun.com' ,
false
);*/
},
Cordova生命周期
原生的Android App一般由多個(gè)Activity組成任務(wù)棧,而且從一個(gè)APP切換到另一個(gè)APP之后,當(dāng)前與之交互的app就會(huì)退到后臺(tái),根據(jù)每個(gè)Activity從前后退到后臺(tái),或者從后臺(tái)切換到前臺(tái)的過程中,都會(huì)回調(diào)Activity的生命周期方法,我們可以根據(jù)這些生命周期方法來處理不同場景下的邏輯。
而相比于Cordova程序,因?yàn)樗械腃ordova頁面都只使用到了Android系統(tǒng)的獨(dú)立一個(gè)Activity,并將自定義的WebView嵌入到當(dāng)前的Activity中完成與用戶的交互動(dòng)作,所以不能保證Cordova的生命周期和Android的Activity的生命周期保持一致,但是可以根據(jù)Activity的生命周期狀態(tài)來對數(shù)據(jù)做保存和恢復(fù)處理,Activity的生命周期方法和Cordova生命周期方法對應(yīng)關(guān)系可以參考如下:
Cordova Event | Rough Android Equivalent | Meaning |
---|---|---|
deviceready |
onCreate() |
程序第一次運(yùn)行(卻別于從后臺(tái)切到前臺(tái)) |
pause |
onPause() |
應(yīng)用程序從前臺(tái)切換到后臺(tái)顯示 |
resume |
onResume() |
應(yīng)用程序從后臺(tái)切換到前臺(tái)顯示 |
Android操作系統(tǒng)當(dāng)手機(jī)可用內(nèi)存很低的情況下會(huì)選擇性的結(jié)束一些后臺(tái)運(yùn)行的程序來釋放內(nèi)存資源,所以當(dāng)我們的Cordova程序退到后臺(tái)的時(shí)候有可能會(huì)被系統(tǒng)結(jié)束,導(dǎo)致WebView結(jié)束運(yùn)行,所以這個(gè)時(shí)候程序的運(yùn)行狀態(tài)中所保存的數(shù)據(jù)就會(huì)丟失,當(dāng)用戶從最近打開程序列表中選擇打開被結(jié)束的Cordova程序的時(shí)候,此時(shí)Activtiy 和WebView都會(huì)被沖洗創(chuàng)建,但是我們之前運(yùn)行的時(shí)候所保留的運(yùn)行數(shù)據(jù)已經(jīng)丟失,所以可能你上一次是處于聊天界面,而現(xiàn)在打開程序的時(shí)候你卻有可能處于登錄界面,這會(huì)讓用戶感到迷惑,解決方法就是根據(jù)當(dāng)前的Activity周期調(diào)用相對應(yīng)的Cordovas事件通知機(jī)制來對用戶的訪問數(shù)據(jù)進(jìn)行保存,等到Activtiy 和WebView重新創(chuàng)建的時(shí)候可以恢復(fù)到上一次離開的狀態(tài)。
比如用戶想上傳一張圖片到服務(wù)器中,這個(gè)時(shí)候會(huì)有如下的操作步驟:
- 點(diǎn)擊界面的圖片選擇按鈕跳轉(zhuǎn)到Android原生相機(jī)拍照界面(這個(gè)時(shí)候Cordova程序停止運(yùn)行并且退到后臺(tái))
- 用戶完成拍照并保存圖片
- Android系統(tǒng)保存完拍照圖片后關(guān)閉相機(jī)應(yīng)用(這個(gè)時(shí)候系統(tǒng)會(huì)將Cordova程序推倒前臺(tái)顯示)
- 用戶回到上一次離開的界面
然而,以上的使用流程如果在一些內(nèi)存很低的手機(jī)上的時(shí)候有可能會(huì)執(zhí)行出錯(cuò)(不完整),就比如系統(tǒng)把退到后臺(tái)的程序給結(jié)束了,這個(gè)時(shí)候就變成:
- 點(diǎn)擊界面的圖片選擇按鈕跳轉(zhuǎn)到Android原生相機(jī)拍照界面(這個(gè)時(shí)候Cordova程序停止運(yùn)行并且退到后臺(tái))
- 用戶完成拍照并保存圖片
- Android系統(tǒng)保存完拍照圖片后關(guān)閉相機(jī)應(yīng)用(這個(gè)時(shí)候系統(tǒng)會(huì)將Cordova程序推倒前臺(tái)顯示)
- 程序重新運(yùn)行回到登錄界面
所以針對上面的這種場景,Cordova增加了對插件的事件回調(diào)方法:
當(dāng)程序退到后臺(tái)的時(shí)候/或者從后臺(tái)切換到前臺(tái)的時(shí)候,Cordova可以根據(jù)回調(diào)事件來判斷是否需要保存數(shù)據(jù)/恢復(fù)用戶數(shù)據(jù)
比如Cordova調(diào)用頁面重新加載的事件通知格式如下:
{
action: "resume",
pendingResult: {
pluginServiceName: string,
pluginStatus: string,
result: any
}
}
- pluginServiceName : 插件的名字(比如我們剛才定義的YuntxSDK),也就是我們在
plugin.xml
文件中的<name>
標(biāo)簽中配置的值 - pluginStatus:插件的狀態(tài)
- result:
其中插件的狀態(tài)pluginStatus
值有如下幾種:
"OK"
插件是否調(diào)用成功"No Result"
插件調(diào)用結(jié)束并且無返回值"Error"
插件調(diào)用發(fā)生錯(cuò)誤-
其他可能的錯(cuò)誤
"Class not found"
"Illegal access"
"Instantiation error"
"Malformed url"
"IO error"
"Invalid action"
"JSON error"
所以我們按照上面的格式在Activity的生命周期方法中根據(jù)當(dāng)前的Activity狀態(tài)來傳相對應(yīng)的字符串,告訴Cordova我們當(dāng)前的狀態(tài),這樣Cordova就可以對插件的一些數(shù)據(jù)做保存,等到程序在后臺(tái)被結(jié)束的時(shí)候再次返回后可以恢復(fù)到離開狀態(tài)。
使用方法
下面是Cordova官方提供的示例代碼,告訴我們?nèi)绾问褂?code>resume和pause
事件來管理狀態(tài),以及如何根據(jù)resume的返回值來回復(fù)當(dāng)前的activity狀態(tài)。
// 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();
與之對應(yīng)的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系統(tǒng)設(shè)置/開發(fā)者選項(xiàng)/中提高了一些設(shè)置api來模擬低內(nèi)存狀態(tài),在開發(fā)者選項(xiàng)中,我們將不保留活動(dòng)
設(shè)置為啟用狀態(tài)來模擬低內(nèi)存的場景,這樣我們運(yùn)行上面的程序打開系統(tǒng)相機(jī)界面的時(shí)候,等我們拍完照片返回的時(shí)候,系統(tǒng)已經(jīng)將我們的插件程序結(jié)束并且重新初始化了。