cordova研习笔记(一) —— 初试牛刀之cordova.js概要

前言

来新公司的第一个任务,研究hybrid App中间层实现原理,作中间层插件开发。这个任务挺有意思,也颇有挑战性,以前在DCloud虽然作过5+ App开发,可是中间层的东西确实涉及很少。本系列文章属于系列开篇cordova学习笔记,本文主要是从零开始搭建一个cordova工程,并了解cordova开发的基本内容。html

建立第一个App

Apache Cordova是一个开源的移动开发框架。容许你用标准的web技术-HTML5,CSS3和JavaScript作跨平台开发。 应用在每一个平台的具体执行被封装了起来,并依靠符合标准的API绑定去访问每一个设备的功能,好比说:传感器、数据、网络状态等。java

cordova应用架构

Cordova官网:http://cordova.apache.org/
Cordova中文网:http://cordova.axuer.com/
Cordova中文站:http://www.cordova.org.cn/android

1.安装 Cordova CLI

npm install -g cordova

安装完成能够经过cordova -v查看版本号,本文是在V6.5.0下构建。ios

2.新建项目

cordova create <PATH> [ID [NAME [CONFIG]]] [options]

Create a Cordova project:git

  • PATH —— 项目路径
  • ID —— app 包名 - used in <widget id>
  • NAME —— app 名称
  • CONFIG —— 配置文件地址 json string whose key/values will be included in [PATH]/.cordova/config.json

Options:github

  • --template=<PATH|NPM PACKAGE|GIT URL> ... use a custom template located locally, in NPM, or GitHub.
  • --copy-from|src=<PATH> .................. deprecated, use --template instead.
  • --link-to=<PATH> ........................ symlink to custom www assets without creating a copy.

Example:web

cordova create hello-cordova io.zhaomenghuan HelloCordova

这将会为你的cordova应用创造必须的目录。默认状况下,cordova create命令生成基于web的应用程序的骨骼,项目的主页是 www/index.html 文件。apache

图为建立好的项目

3.添加平台

全部后续命令都须要在项目目录或者项目目录的任何子目录运行:npm

cd hello-cordova

给你的App添加目标平台。咱们将会添加iosandroid平台,并确保他们保存在了config.xml中:json

cordova platform add ios --save
cordova platform add android --save

运行add或者remove平台的命令将会影响项目platforms的内容,在这个目录中每一个指定平台都有一个子目录。

注意:在你使用CLI建立应用的时候, 不要 修改/platforms/目录中的任何文件。当准备构建应用或者从新安装插件时这个目录一般会被重写。

检查你当前平台设置情况:

cordova platform is

Installed platforms:
  android 6.1.2
Available platforms:
  amazon-fireos ~3.6.3 (deprecated)
  blackberry10 ~3.8.0
  browser ~4.1.0
  firefoxos ~3.6.3
  webos ~3.7.0
  windows ~4.4.0
  wp8 ~3.8.2 (deprecated)

安装构建先决条件:
要构建和运行App,你须要安装每一个你须要平台的SDK。另外,当你使用浏览器开发你能够添加 browser平台,它不须要任何平台SDK。

检测你是否知足构建平台的要求:

cordova requirements

Requirements check results for android:
Java JDK: installed 1.8.0
Android SDK: installed true
Android target: installed android-7,android-8,android-9,android-10,android-11,android-12,android-13,android-14,android-15,android-16,android-17,android-18,android-19,android-20,android-21,android-22,android-23,android-24,android-25
Gradle: installed

初次使用咱们可能会遇到下面的报错:

Error: Failed to find 'ANDROID_HOME' environment variable. Try setting setting it manually.
Failed to find 'android' command in your 'PATH'. Try update your 'PATH' to include path to valid SDK directory.

这是由于咱们没有配置环境变量:

  • 设置JAVA_HOME环境变量,指定为JDK安装路径
  • 设置ANDROID_HOME环境变量,指定为Android SDK安装路径
  • 添加Android SDK的tools和platform-tools目录到你的PATH

对于android平台下的环境配置在这里再也不赘述,具体能够参考:

4.构建App

默认状况下, cordova create生产基于web应用程序的骨架,项目开始页面位于www/index.html
 文件。任何初始化任务应该在www/js/index.js文件中的deviceready事件的事件处理函数中。

运行下面命令为全部添加的平台构建:

cordova build

你能够在每次构建中选择限制平台范围 - 这个例子中是android

cordova build android

注意:首次使用时,命令行提示 Downloading https://services.gradle.org/distributions/gradle-2.14.1-all.zip,是在下载对应的gradle并自动解压安装,根据网络情况,可能耗时极长,且容易报错。

使用Cordova编译Android平台程序提示:Could not reserve enough space for 2097152KB object heap。

Error occurred during initialization of VM
Could not reserve enough space for 2097152KB object heap

大致的意思是系统内存不够用,建立VM失败。试了网上好几种方法都不行,最后这个方法能够了:

开始->控制面板->系统->高级设置->环境变量->系统变量
新建变量:
变量名: _JAVA_OPTIONS
变量值: -Xmx512M

5.运行App

咱们有多种方式运行咱们的App,在不一样场景下使用不一样的方式有助于咱们快速开发和测试咱们的应用。

在命令行运行下面的命令,会从新构建App并能够在特定平台的模拟器上查看:

cordova emulate android

你能够将你的手机插入电脑,在手机上直接测试App:

cordova run android

在进行打包操做前,咱们能够经过建立一个本地服务预览app UI,使用指定的端口或缺省值为8000运行本地Web服务器www/assets。访问项目:http://HOST_IP:PORT/PLATFORM/www

cordova serve [port]

参考文档:

6.安装插件

cordova的强大之处在于咱们能够经过安装插件,拓展咱们web工程的能力,好比调用系统底层API来调用设备上的底层功能,如摄像头、相册。经过cordova plugin命令实现插件管理。

能够在这里搜索须要的插件:Cordova Plugins

cordova {plugin | plugins} [
    add <plugin-spec> [..] {--searchpath=<directory> | --noregistry | --link | --save | --browserify | --force} |
    {remove | rm} {<pluginid> | <name>} --save |
    {list | ls} |
    search [<keyword>] |
    save |
]

添加插件:

cordova plugin add <plugin-spec> [...]

移除插件:

cordova plugin remove [...]

7.平台为中心的工做流开发App

上面咱们是在跨平台(CLI)的工做流进行,原则上若是咱们不须要本身写原生层自定义组件,咱们彻底能够只在CLI上完成咱们的工做,固然若是须要进一步深刻了解cordova native与js的通讯联系,咱们须要切换到平台为中心的工做流,即将咱们的cordova工程导入到原生工程。例如:咱们可使用android studio导入咱们新建的cordova工程。

自定义插件开发

官方推荐的插件遵循相同的目录结构,根目录下是plugin.xml配置文件,src目录下放平台原生代码,www下放js接口代码,基本配置方法和代码结构由必定规律,咱们使用plugman能够生成一个插件模板,改改就能够写一个自定义插件。

1.安装 plugman ,使用 plugman 建立插件模板

npm install -g plugman

好比这里咱们建立一个nativeUI的插件:

plugman create --name NativeUI --plugin_id cordova-plugin-nativeui --plugin_version 0.0.1

参数介绍:
pluginName: 插件名字:NativeUI
pluginID: 插件id : cordova-plugin-nativeui
oversion: 版本 : 0.0.1
directory:一个绝对或相对路径的目录,该目录将建立插件项目
variable NAME=VALUE: 额外的描述,如做者信息和相关描述

进入插件目录

cd NativeUI

给 plugin.xml 增长Android平台

plugman platform add --platform_name android

生成的插件文件结构为:

NativeUI:

├── src
    └── android
        └── NativeUI.java
├── www
    └── NativeUI.js
└── plugin.xml

2.修改配置文件

plugin.xml文件字段含义:

元素 描述
plugin 定义命名空间,ID和插件版本。应该用定义在http://apache.org/cordova/ns/...命名空间。plugin的ID在输入cordova plugins命令时在插件列表中显示。
name 定义插件的名字。
description 定义插件的描述信息。
author 定义插件做者的名字。
keywords 定义与插件相关的关键字。Cordova研发组创建了公开、可搜索的插件仓库,添加的关键字能在你把插件提交到仓库后帮助被发现。
license 定义插件的许可。
engines 用来定义插件支持的Cordova版本。再添加engine元素定义每一个支持的Cordova版本。
js-module 指js文件名,而这个文件会自动以<script>标签的形式添加到Cordova项目的起始页。经过在js-module中列出插件,能够减小开发者的工做。
info 它是另外一个除了description外说插件信息的地方。
<?xml version='1.0' encoding='utf-8'?>
<plugin id="cordova-plugin-nativeui" version="0.0.1" xmlns="http://apache.org/cordova/ns/plugins/1.0" xmlns:android="http://schemas.android.com/apk/res/android">
    <name>NativeUI</name>
    <js-module name="NativeUI" src="www/NativeUI.js">
        <clobbers target="agree.nativeUI" />
    </js-module>
    <platform name="android">
        <config-file parent="/*" target="res/xml/config.xml">
            <feature name="NativeUI">
                <param name="android-package" value="cn.com.agree.nativeui.NativeUI" />
            </feature>
        </config-file>
        <config-file parent="/*" target="AndroidManifest.xml"></config-file>
        <source-file src="src/android/NativeUI.java" target-dir="src/cn/com/agree/nativeui" />
    </platform>
</plugin>

这个配置文件有几个地方很关键,一开始没有认真看,将插件导进工程跑的时候各类问题,十分头痛,不得不从新认真看看plugin.xml文档

  • id:原则上没有严格规定,参考官方插件写法,这里我也写的是cordova-plugin-nativeui,经过plugman建立插件模板的时候须要指定。
  • name:插件名称。
  • clobbers->target:用于指定插入module.exports的窗口对象下的命名空间,也就是用户调用该插件时的js层暴露的顶层对象。这个很关键,虽然能够任意指定,可是涉及到咱们调用插件的属性或者方法,因此须要特别关注。plugman默认生成的是将id中的-转换成`.'的对象。这里须要说明的是咱们能够写多个js-module,每一个js-module下能够指定不一样的clobbers。
  • feature -> param - > value 标识了实际提供服务的Native类别名称,这里直接定位至具体类,然而上述经过plugman生成模板的时候中没有指定NativeUI的包名,会自定生成cordova-plugin-nativeui.NativeUI,这里咱们须要改为符合本身要求的类名,如我这里使用公司的域名:cn.com.agree.nativeui.NativeUI。须要说明的是这里的类名能够与插件名称不一样。
  • source-file -> target-dir 同理target-dir须要修改成: src/cn/com/agree/nativeui,同时须要修改平台下的native部分的代码:如:package cn.com.agree.nativeui;
  • platform -> config-file下能够指定程序所需的权限uses-permission,如:
<config-file target="AndroidManifest.xml" parent="/*">
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
</config-file>

3.导入到平台工程中的目录结构

这里咱们以android平台为例:

cordova.js在建立Android工程的时候,是从cordova的lib目录下Copy到platforms\android\assets\www\cordova.js的。同时备份到platforms\android\platform_www\cordova.js。下一篇文章我会试着读一下cordova.js的源码,这里对cordova.js暂不作深刻探究。

这里咱们主要关心几个地方,咱们的原生代码在src目录下,assets/www目录下是咱们的web 程序。www目录下的plugins文件夹就是咱们的插件js部分,cordova_plugins.js是根据plugins文件夹的内容生成的。

cordova_plugins.js的总体结构:

cordova.define('cordova/plugin_list', function(require, exports, module) {
module.exports = [
    {
        "id": "cordova-plugin-nativeui.NativeUI",
        "file": "plugins/cordova-plugin-nativeui/www/NativeUI.js",
        "pluginId": "cordova-plugin-nativeui",
        "clobbers": [
            "agree.nativeUI"
        ]
    },
    ...
];
module.exports.metadata = 
// TOP OF METADATA
{
    "cordova-plugin-nativeui": "0.0.1",
    ...
};
// BOTTOM OF METADATA
});

Android插件开发指南

Android插件基于Cordova-Android,它是基于具备Javscript-to-native桥接的Android WebView构建的。 Android插件的本机部分至少包含一个扩展CordovaPlugin类的Java类,并重写其一个执行方法。

插件类映射

插件的JavaScript接口使用cordova.exec方法,以下所示:

cordova.exec(<successFunction>, <failFunction>, <service>, <action>, [<args>]);
  • function(winParam) {}: 成功回调
  • function(error) {}: 错误回调
  • service: 原生层服务名称
  • action: js层调用方法名
  • [args]: js层传递到原生层的数据

这将WebView的请求传递给Android本机端,有效地在服务类上调用action方法,并在args数组中传递其余参数。不管您将插件分发为Java文件仍是做为本身的jar文件,必须在Cordova-Android应用程序的res / xml / config.xml文件中指定该插件。 有关如何使用plugin.xml文件注入此要素的详细信息,请参阅应用程序插件:

<feature name="<service_name>">
    <param name="android-package" value="<full_name_including_namespace>" />
</feature>

插件初始化及其生命周期

一个插件对象的一个实例是为每一个WebView的生命建立的。 插件不会被实例化,直到它们被JavaScript的调用首次引用为止,除非在config.xml中将具备onload name属性的<param>设置为“true”。

<feature name="Echo">
    <param name="android-package" value="<full_name_including_namespace>" />
    <param name="onload" value="true" />
</feature>

插件使用 initialize 初始化启动:

@Override
public void initialize(CordovaInterface cordova, CordovaWebView webView) {
    super.initialize(cordova, webView);
    // your init code here
}

插件还能够访问Android生命周期事件,并能够经过扩展所提供的方法(onResume,onDestroy等)来处理它们。 具备长时间运行请求的插件,媒体播放,侦听器或内部状态等背景活动应实现onReset()方法。 当WebView导航到新页面或刷新时,它会执行,这会从新加载JavaScript。

编写Android Java插件

一个JavaScript调用触发对本机端的插件请求,而且相应的Java插件在config.xml文件中正确映射,但最终的Android Java Plugin类是什么样的? 使用JavaScript的exec函数发送到插件的任何东西都被传递到插件类的execute方法中。

插件的JavaScript不会在WebView界面的主线程中运行; 而是在WebCore线程上运行,执行方法也是如此。 若是须要与用户界面进行交互,应该使用Activity的runOnUiThread方法。

若是不须要在UI线程上运行,但不但愿阻止WebCore线程,则应使用cordova.getThreadPool()得到的Cordova ExecutorService执行代码。

...
@Override
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
    if (action.equals("toast")) {
        this.toast(args.getString(0));
        return true;
    }
    return false;
}

/**
 * Builds and shows a native Android toast with given Strings
 *
 * @param message The message the toast should display
 */
private void toast(final String message) {
    final CordovaInterface cordova = this.cordova;
    if (message != null && message.length() > 0) {
        final int duration = Toast.LENGTH_SHORT;
        Runnable runnable = new Runnable() {
            public void run() {
                Toast toast = Toast.makeText(cordova.getActivity().getApplicationContext(), message, duration);
                toast.show();
            }
        };
        cordova.getActivity().runOnUiThread(runnable);
    }
}
...

js部分的代码:

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

module.exports = {
    toast: function(message) {
        exec(null, null, 'NativeUI', 'toast', [message]);
    }
}

callbackContext.success能够将原生层字符串做为参数传递给JavaScript层的成功回调,callbackContext.error能够将给JavaScript层的错误回调函数传递参数。

添加依赖库

若是你的Android插件有额外的依赖关系,那么它们必须以两种方式之一列在plugin.xml中:

  • 首选的方法是使用<framework />标签(有关详细信息,请参阅插件规范)。以这种方式指定库能够经过Gradle的依赖管理逻辑来解决。这容许诸如gson,android-support-v4和google-play-services之类的经常使用库被多个插件使用而没有冲突。
  • 第二个选项是使用<lib-file />标签来指定jar文件的位置(有关更多详细信息,请参阅插件规范)。 只有当您肯定没有其余插件将依赖于您所引用的库(例如,该库特定于您的插件)时,才应使用此方法。 不然,若是另外一个插件添加了相同的库,则可能致使插件用户形成构建错误。 值得注意的是,Cordova应用程序开发人员不必定是本地开发人员,所以本地平台构建错误可能特别使人沮丧。

Android集成

Android具备Intent系统,容许进程相互通讯。插件能够访问CordovaInterface对象,能够访问运行应用程序的Android Activity。 这是启动新的Android Intent所需的上下文。 CordovaInterface容许插件为结果启动Activity,并为Intent返回应用程序时设置回调插件。

从Cordova 2.0开始,插件没法再直接访问上下文,而且旧的ctx成员已被弃用。 全部的ctx方法都存在于Context中,因此getContext()和getActivity()均可以返回所需的对象。

运行权限(Cordova-Android 5.0.0+)

Android 6.0 "Marshmallow" 引入了新的权限模型,用户能够根据须要启用和禁用权限。这意味着应用程序必须将这些权限更改处理为未来,这是Cordova-Android 5.0.0发行版的重点。

就插件而言,能够经过调用权限方法来请求权限,该签名以下:

cordova.requestPermission(CordovaPlugin plugin, int requestCode, String permission);

为了减小冗长度,将此值分配给本地静态变量是标准作法:

public static final String READ = Manifest.permission.READ_CONTACTS;

定义requestCode的标准作法以下:

public static final int SEARCH_REQ_CODE = 0;

而后,在exec方法中,应该检查权限:

if(cordova.hasPermission(READ)) {
    search(executeArgs);
} else {
    getReadPermission(SEARCH_REQ_CODE);
}

在这种状况下,咱们只需调用requestPermission:

protected void getReadPermission(int requestCode) {
    cordova.requestPermission(this, requestCode, READ);
}

这将调用该活动并引发提示出现要求该权限。 一旦用户拥有权限,结果必须使用onRequestPermissionResult方法处理,每一个插件应该覆盖该方法。 一个例子能够在下面找到:

public void onRequestPermissionResult(int requestCode, String[] permissions, int[] grantResults) throws JSONException {
    for(int r:grantResults) {
        if(r == PackageManager.PERMISSION_DENIED) {
            this.callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, PERMISSION_DENIED_ERROR));
            return;
        }
    }
    switch(requestCode) {
        case SEARCH_REQ_CODE:
            search(executeArgs);
            break;
        case SAVE_REQ_CODE:
            save(executeArgs);
            break;
        case REMOVE_REQ_CODE:
            remove(executeArgs);
            break;
    }
}

上面的switch语句将从提示符返回,而且根据传入的requestCode,它将调用该方法。 应该注意的是,若是执行不正确地处理权限提示可能会堆叠,而且应该避免这种状况。

除了要求得到单一权限的权限以外,还能够经过定义权限数组来请求整个组的权限,如同Geolocation插件所作的那样:

String [] permissions = { 
  Manifest.permission.ACCESS_COARSE_LOCATION,
  Manifest.permission.ACCESS_FINE_LOCATION 
};

而后当请求权限时,须要完成的全部操做以下:

cordova.requestPermissions(this, 0, permissions);

这将请求数组中指定的权限。 提供公开访问的权限阵列是一个好主意,由于可使用插件做为依赖关系使用,尽管这不是必需的。

启动其余活动

若是你的插件启动将Cordova活动推送到后台的活动,则须要特别考虑。 若是设备运行内存不足,Android操做系统将在后台销毁活动。在这种状况下,CordovaPlugin实例也将被销毁。 若是您的插件正在等待其启动的活动的结果,则当Cordova活动返回到前台并得到结果时,将建立一个新的插件实例。 可是,插件的状态不会自动保存或恢复,插件的CallbackContext将丢失。 CordovaPlugin能够实现两种方法来处理这种状况:

/**
 * Called when the Activity is being destroyed (e.g. if a plugin calls out to an
 * external Activity and the OS kills the CordovaActivity in the background).
 * The plugin should save its state in this method only if it is awaiting the
 * result of an external Activity and needs to preserve some information so as
 * to handle that result; onRestoreStateForActivityResult() will only be called
 * if the plugin is the recipient of an Activity result
 *
 * @return  Bundle containing the state of the plugin or null if state does not
 *          need to be saved
 */
public Bundle onSaveInstanceState() {}

/**
 * Called when a plugin is the recipient of an Activity result after the
 * CordovaActivity has been destroyed. The Bundle will be the same as the one
 * the plugin returned in onSaveInstanceState()
 *
 * @param state             Bundle containing the state of the plugin
 * @param callbackContext   Replacement Context to return the plugin result to
 */
public void onRestoreStateForActivityResult(Bundle state, CallbackContext callbackContext) {}

总结

cordova 是否可以发挥出它出彩的一面仍是源于咱们对原生的熟练程度,只有对原生足够熟练,对cordova的运行机制足够熟悉才能作出一个相对比较使人满意的App,后面的文章我会尝试阅读cordova的源码,深刻解析cordova的实现原理和插件机制,也会教你们封装一些经常使用的自定义组件。本文内容基本取材于官方文档,只是借助谷歌翻译以及本身在探索过程当中的一些问题,作了一些增删,若是有任何问题,但愿各位不吝指教。

写文章不容易,也许写这些代码就几分钟的事,写一篇你们好接受的文章或许须要几天的酝酿,而后加上几天的码字,累并快乐着。若是文章对您有帮助请我喝杯咖啡吧!

clipboard.png

转载需标注本文原始地址:https://zhaomenghuan.github.io/

相关文章
相关标签/搜索