来新公司的第一个任务,研究hybrid App中间层实现原理,作中间层插件开发。这个任务挺有意思,也颇有挑战性,以前在DCloud虽然作过5+ App开发,可是中间层的东西确实涉及很少。本系列文章属于系列开篇cordova学习笔记,本文主要是从零开始搭建一个cordova工程,并了解cordova开发的基本内容。html
Apache Cordova是一个开源的移动开发框架。容许你用标准的web技术-HTML5,CSS3和JavaScript作跨平台开发。 应用在每一个平台的具体执行被封装了起来,并依靠符合标准的API绑定去访问每一个设备的功能,好比说:传感器、数据、网络状态等。java
Cordova官网:http://cordova.apache.org/
Cordova中文网:http://cordova.axuer.com/
Cordova中文站:http://www.cordova.org.cn/android
npm install -g cordova
安装完成能够经过cordova -v
查看版本号,本文是在V6.5.0下构建。ios
cordova create <PATH> [ID [NAME [CONFIG]]] [options]
Create a Cordova project:git
Options:github
Example:web
cordova create hello-cordova io.zhaomenghuan HelloCordova
这将会为你的cordova应用创造必须的目录。默认状况下,cordova create命令生成基于web的应用程序的骨骼,项目的主页是 www/index.html 文件。apache
全部后续命令都须要在项目目录或者项目目录的任何子目录运行:npm
cd hello-cordova
给你的App添加目标平台。咱们将会添加ios
和android
平台,并确保他们保存在了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.
这是由于咱们没有配置环境变量:
对于android平台下的环境配置在这里再也不赘述,具体能够参考:
默认状况下, 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
咱们有多种方式运行咱们的App,在不一样场景下使用不一样的方式有助于咱们快速开发和测试咱们的应用。
在命令行运行下面的命令,会从新构建App并能够在特定平台的模拟器上查看:
cordova emulate android
你能够将你的手机插入电脑,在手机上直接测试App:
cordova run android
在进行打包操做前,咱们能够经过建立一个本地服务预览app UI,使用指定的端口或缺省值为8000运行本地Web服务器www/assets。访问项目:http://HOST_IP:PORT/PLATFORM/www。
cordova serve [port]
参考文档:
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 [...]
上面咱们是在跨平台(CLI)的工做流进行,原则上若是咱们不须要本身写原生层自定义组件,咱们彻底能够只在CLI上完成咱们的工做,固然若是须要进一步深刻了解cordova native与js的通讯联系,咱们须要切换到平台为中心的工做流,即将咱们的cordova工程导入到原生工程。例如:咱们可使用android studio导入咱们新建的cordova工程。
官方推荐的插件遵循相同的目录结构,根目录下是plugin.xml
配置文件,src目录下放平台原生代码,www下放js接口代码,基本配置方法和代码结构由必定规律,咱们使用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
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文档。
cordova-plugin-nativeui
,经过plugman建立插件模板的时候须要指定。-
转换成`.'的对象。这里须要说明的是咱们能够写多个js-module,每一个js-module下能够指定不一样的clobbers。cordova-plugin-nativeui.NativeUI
,这里咱们须要改为符合本身要求的类名,如我这里使用公司的域名:cn.com.agree.nativeui.NativeUI
。须要说明的是这里的类名能够与插件名称不一样。target-dir
须要修改成: src/cn/com/agree/nativeui
,同时须要修改平台下的native部分的代码:如:package cn.com.agree.nativeui;
uses-permission
,如:<config-file target="AndroidManifest.xml" parent="/*"> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> </config-file>
这里咱们以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插件基于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。
一个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中:
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的实现原理和插件机制,也会教你们封装一些经常使用的自定义组件。本文内容基本取材于官方文档,只是借助谷歌翻译以及本身在探索过程当中的一些问题,作了一些增删,若是有任何问题,但愿各位不吝指教。
写文章不容易,也许写这些代码就几分钟的事,写一篇你们好接受的文章或许须要几天的酝酿,而后加上几天的码字,累并快乐着。若是文章对您有帮助请我喝杯咖啡吧!
转载需标注本文原始地址:https://zhaomenghuan.github.io/