本文承接上篇《使用Cordova API开发(下)》。javascript
前面讨论的工具和插件都是Cordova框架一部分,但若是框架缺乏相应的插件能够自已构建。html
3.0之后由plugman和CLI提供的功能让插件有所改变。接下来将会讨论如何建立只有js的插件,还有Android的Native插件,其余平台的构建过程基本也是同样的。java
在构建以前先解析下插件的结构。Cordova有大量的关于如何构建插件的文档。"Plugin Development Guide"说明了如何为插件建立js接口,还有为每一个移动平台建立native插件组件各自的指南。android
Cordova插件是一组扩展或提高Cordova应用功能的文件。开发者向项目中添加插件,经过提供的js接口和插件交互。插件中包括一个叫plugin.xml的配置文件,一个或多个js文件,加上一些native代码文件(可选),库文件,和相关的内容(HTML, CSS和其余内容文件)。git
plugin.xml文件描述了插件而且告诉CLI复制哪些部分的文件,这些文件在每一个平台上都不同。plugin.xml中还有配置,由CLI用来设置平台方面的config.xml配置。plugin.xml文件中有许多可用选项。github
一个插件中至少有一个js源文件,用来定义插件公开的方法、对象和属性。能够把全部的都封装到一个文件或多个。也能够把其余js类库打包(如jQuery)。web
除了上述两种文件,剩下的文件主要用来定义插件。通常来讲,插件要有每一个支持平台的一个或多个native源码文件。它们上面还包括其余native文件(源码或预编译的)或内容(图像文件,样式表,HTML文件)。apache
构建插件的一个好参考是有大量的可用的示例。项目中下载的插件是zip包,能够解压并分析如何构建。一个方法是修改现有插件来知足需求。json
Cordova插件不必定要有native代码,能够彻底由js代码组成。在写native插件以前开始写个简单的插件用来讲明插件结构和格式。数组
给下面这个例子中的插件起名叫"Meaning of Life"。建立插件前首先建立一个叫"mol"的文件夹(Meaning of Life"的缩写),在其中建立一个描述插件的plugin.xml文件,代码以下,它是由其余插件的plugin.xml复制过来修改的。
<?xml version="1.0" encoding="UTF-8"?> <plugin xmlns="http://apache.org/cordova/ns/plugins/1.0" id="com.learningplugin.meaningoflife" version="1.0.0"> <name>mol</name> <description>Calculates the meaning of the life</description> <author>Zhang San</author> <keywords>mol, meaning of life</keywords> <license>Apache 2.0 License</license> <engines> <engine name="cordova" version="5.0.0" /> </engines> <js-module src="mol.js" name="mol"> <clobbers target="mol" /> </js-module> <info>This plugin exists to demonstrate how to build a simple, Javascript-Only plugin for Apache Cordova.</info> </plugin>
plugin.xml中一些内容做为文档说明做者和目的。 其余的用于驱动plugman或Cordova CLI插件安装过程。下表列出不一样的plugin.xml元素。
元素 | 描述 |
---|---|
plugin | 定义命名空间,ID和插件版本。应该用定义在*http://apache.org/cordova/ns/plugins/1.0*命名空间。plugin的ID在输入*cordova plugins*命令时在插件列表中显示。 |
name | 定义插件的名字。 |
description | 定义插件的描述信息。 |
author | 定义插件做者的名字。 |
keywords | 定义与插件相关的关键字。Cordova研发组创建了公开、可搜索的插件仓库,添加的关键字能在你把插件提交到仓库后帮助被发现。 |
license | 定义插件的许可。例中是Cordova默认的许可,还能够是许可描述或许可期限的连接。 |
engines | 用来定义插件支持的Cordova版本。再添加*engine*元素定义每一个支持的Cordova版本。 |
js-module | 指js文件名,而这个文件会自动以`<script`>标签的形式添加到Cordova项目的起始页。经过在*js-module*中列出插件,能够减小开发者的工做。*clobbers*元素说明了*module.exports*自动添加到*window*对象,让插件方法可以在窗口级别使用。 |
info | 它是另外一个除了*description*外说插件信息的地方。 |
接下来在mol文件夹中定义一个叫mol.js的文件。代码以下,它简单的建立了一个mol对象,而后定义了一个calculateMOL方法计算"生活的意义"。最后一行输出了mol对象,它等于对象和相关函数以便使用插件的应用调用。
var mol = { calculateMOL: function() { return 42; } }; module.exports = mol;
这样就完成了建立一个简单插件的工做。为了证实它能起做用,建立一个简单的MoL Demo应用。首先在命令行工具中进入开发文件夹,输入如下CLI命令:
cordova creat simplePlugin com.learningplugin.simplePlugin SimplePlugin cd simplePlugin cordova platform add android cordova plugin add your_plugin_location
建立一个名叫simpleplugin.html的页面,其中有一个按钮,点击后执行doMOL函数,调用了mol.calculateMOL()并用alert对话框显示结果。代码以下:
<!DOCTYPE html> <html lang="en"> <head> <meta http-equiv="Content-Type" content="text/html;charset=utf-8"> <meta name="viewport" content="user-scalable=no,initial-scale=1,maximum-scale=1,minimum-scale=1,width=device-width;"> <script src="cordova.js"></script> <script type="text/javascript" charset="utf-8"> function onBodyLoad() { document.addEventListener("deviceready", onDeviceReady, false); }; function onDeviceReady() { } function doMol() { var res = mol.calculateMOL(); alert("Meaning of Life =' + res); } </script> <title>Meaning of Life Demo</title> </head> <body onload="onBodyLoad()"> <h1>MoL Demo</h1> <p> This is a Cordova application that uses my custom Meaning of Life plugin. </p> <button onclick="doMOL();"> Calculate Meaning of Life </button> </body> </html>
运行结果以下图:
下面的例子用来讲明如何建立一个Native插件,它向Cordova容器公开了一些native电话API.参考了Android SDK文档找到一些简单有趣的可供公开的接口。接下来几个部分是如何组织plugin代码;知道插件如何工做后向插件添加其余API或较复杂的API会很容易。
首先建立一个叫"carrier"的插件的文件夹,其中有js接口定义和plugin.xml。还建立了一个叫src的子文件夹并向其中建立了一个名叫"android"的文件夹用来存放native代码。这么作不是必要的但显得有条理。
在写native代码前要定义向Cordova应用公开的js接口。建立方法跟上个例子同样,这个例子中建立一个叫carrier的对象,在其中定义了一个或多个能被Cordova应用调用的方法,本例中将公开getCarrierName和getCountryCode方法。
不像上个MoL插件,这些方法不作计算,也不直接返回任何值;相反,它们调用cordova.exec方法,由它向native代码要控制权。这就是著名的Javscript-to-native桥接,可让Cordova应用执行native API。native代码执行完后,调用回调函数并把结果返回给js对象。
cordova.exec方法声明以下:
cordova.exec(successCallback, errorCallback, 'Pluginobject', 'pluginMethod', [arguments]);
successCallback和errorCallback参数是插件方法调用成功或失败时执行的函数名。'Pluginobject'参数是一个字符串用于识别包括被调用方法的native对象,'pluginMethod'参数是一个字符串,用来识别要执行的方法,最后的'arguments'是一个可选的参数数组,用来传递给pluginMethod。
例子中的getCarrierName和getCountryCode方法不须要任何参数,arguments参数为空并表示为"[]"。
下面是carrier.js的代码,插件的js接口。它开始用声明了一个cordova对象,指向加载的公开exec方法的cordova js库。接下来建立carrier对象,定义两个方法。每一个方法都调用了cordova.exec并传入了cordova对象必要的函数名、对象和方法名,用来定位正确的完成功能的native对象和方法。文件最后是module.exports赋值,让carrier对象向Cordova应用公开。代码以下:
var cordova = require('cordova'); var carrier = { getCarrierName : function(successCallback, errorCallback) { cordova.exec(successCallback, errorCallback, 'CarrierPlugin', 'getCarrierName', []); }, getCountryCode : function(successCallback, errorCallback) { cordova.exec(successCallback, errorCallback, 'CarrierPlugin', 'getCountryCode', []); } }; module.exports = carrier;
接下来就能够写插件的native部分的代码了。先写js仍是native部分不重要,通常来讲比较复杂的插件先写native会更容易。
能够经过建立一个Cordova项目建立插件,而后向应用中写Java类和js接口文件。应用中插件能够工做后再把它们抽取出来放在单独的文件夹用来发布。照这么作的话,能够为插件建立一个文件夹,而后建立一个新的Cordova项目,并使用CLI把插件添加进去。这样就能够独立开发插件并同时测试plugin.xml了。
这个插件将向Cordova应用返回的信息来自于电话API。使用这个API前应用必须引入Context和TelephoneManager类:
import android.content.context; import android.telephony.TelephonyManager;
而后在应用里定义一个对象实例来公开一些方法, 咱们用这些方法调用carrier名和国家代码:
tm = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
要肯定carrier名,插件会调用tm.getSimOperatorName()方法,要得到国家代码会调用tm.getSimCountryIso()。
下面的表列出了用于Android插件的Java代码;它定义了一个叫CarrierPlugin的简单的类,这个类公开了exec方法,它是向js的cordova.exec调用执行的内容。
这个类定义了两个常量ACTION_GET_CARRIER和ACTION_GET_COUNTRY_CODE,用来肯定Cordova应用调用哪一个方法。这样作在之后更改方法名时更容易。
接下来类定义了一个tm对象,它首先调用了super.initialize,这个方法让cordova对象适当的初始化。没有这个方法Java代码就不知道Cordova容器。接下来代码得到了一个当前应用上下文的句柄,用它把tm对象传递给由Telephony API公开的服务。
接下来Java代码重载了exec方法并实现了直接处理来自Cordova应用的调用的代码。在这里实现了一个单独的操做,它肯定了调用了哪一个动做(能过比较由调用cordova.exec传递的动做名和开始定义的常触发相应的动做)。
若是exec方法肯定请求的是getCarrierName动做,以后它调用了Android的getSimOperatorName方法,并经过调用callbackContext.success()方法把结果传回Cordova应用。若是请求的是getCountryCode动做,以后调用Android的getSimCountryIso()方法并经过callbackContext.success()方法把结果传回Cordova应用。
若是过程当中某处失败了,代码执行callbackContext.error并把适当的错误消息或错误对象传回用来指出出了什么错。
package com.learningplugin.carrier; import org.apache.cordova.CordovaInterface; import org.apache.cordova.CallbackContext; import org.apache.cordova.CordovaPlugin; import org.apache.cordova.CordovaWebView; import android.content.Context; import android.telephony.TelephonyManager; import org.json.JSONArray; import org.json.JSONException; public class CarrierPlugin extends CordovaPlugin { public static final String ACTION_GET_CARRIER_NAME = "getCarrierName"; public static final String ACTION_GET_COUNTRY_CODE = "getCountryCode"; public TelephonyManager tm; public void initialize(CordovaInterface cordova, CordovaWebView webView) { super.initialize(cordova, webView); Context context = this.cordova.getActivity().getApplicationContext(); tm = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE); } @Override public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException { try { if (ACTION_GET_CARRIER_NAME.equals(action)) { callbackContext.success(tm.getSimOperatorName()); return true; } else { if (ACTION_GET_COUNTRY_CODE.equals(action)) { callbackContext.success(tm.getSimCountryIso()); return true; } } callbackContext.error("Invalid Action"); return false; } catch (Exception e) { System.err.println("Exception: " + e.getMessage()); callbackContext.error(e.getMessage()); return false; } } }
以上是全部须要的编码。在使用CLI添加新插件以前还须要建立一个plugin.xml文件,它和前边MoL示例类似,但其中由于多了native组件,还须要些额外的设置。内容以下:
<?xml version="1.0" encoding="utf-8"?> <plugin xmlns="http://apache.org/cordova/ns/plugins/1.0" id="com.learningplugin.carrier" version="1.0.0"> <name>Carrier</name> <author>Zhang San</author> <description>Expose mobile carrier related values to Cordova application.</description> <keywords>carrier</keywords> <license>Apache 2.0 License</license> <engines> <engine name="cordova" version="5.0.0" /> </engines> <js-module src="carrier.js" name="carrier"> <clobbers target="carrier" /> </js-module> <platform name="android"> <source-file src="src/android/CarrierPlugin.java" target-dir="src/com/cordovalearingplugin/carrier" /> <config-file target="res/xml/config.xml" parent="/*"> <feature name="CarrierPlugin"> <param name="android-package" value="com.cordovalearingplugin.carrier.CarrierPlugin"/> </feature> </config-file> <config-file target="AndroidManifest.xml"> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> </config-file> </platform> </plugin>
文件中js-module元素定义了js的名字,它将在应用开始时自动加载。它定义了向Cordova公开的js接口。clobbers元素指明了js对象赋值给加载的js对象。本例中,Carrier插件经过一个carrier对象向Cordova应用公开。
<js-module src="carrier.js" name="carrier"> <clobbers target="carrier" /> </js-module>
Cordova应用经过carrier对象访问getCarrierName方法:
carrier.getCarrierName(onSuccess, onFailure);
和前边例子的plugin.xml不一样地方是platform节:
<platform name="android"> <source-file src="src/android/CarrierPlugin.java" target-dir="src/com/cordovalearingplugin/carrier" /> <config-file target="res/xml/config.xml" parent="/*"> <feature name="CarrierPlugin"> <param name="android-package" value="com.cordovalearingplugin.carrier.CarrierPlugin"/> </feature> </config-file> <config-file target="AndroidManifest.xml"> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> </config-file> </platform>
它定义了某个移动平台专用的设置,包括了相关native代码的设置。能够有一个或多个平台。里边的source-file元素指出了一个或多个Android native源代码文件,当插件安装时由CLI安装。下面的例子指示plugman或CLI复制CarrierPlugin.java文件到Cordova项目Android平台文件夹的*src/com/cordovalerningplugin/carrier文件夹中。
<source-file src="src/android/CarrierPlugin.java" target-dir="src/com/cordovalearingplugin/carrier" />
config-file元素定义了在插件安装过程当中的改动。在例子中,一个叫CarrierPlugin的特性添加到Android项目的config.xml文件中,指向Java类com.cordovalearningplugin.carrier.CarrierPlugin:
<config-file target="res/xml/config.xml" parent="/*"> <feature name="CarrierPlugin"> <param name="android-package" value="com.cordovalearingplugin.carrier.CarrierPlugin"/> </feature> </config-file>
platform最后一个元素定义了另外一个配置文件设置。在Android上访问Telephony API须要特别受权。任何要用API的应用都必须向应用的AndroidManifest.xml文件添加入口。这里就要向清单添加android.permission.READ_PHONE_STATE许可:
<config-file target="AndroidManifest.xml"> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> </config-file>
为了测试插件建立一个简单的应用。打开命令行,输入如下命令:
cordova create nativePlugin com.cordovalearningplugin.nativePlugin NativePlugin cd nativePlugin cordova platform add android cordova plugin add your_carrier_plugin_location
在应用的index.html中显示两个按钮,一个调用getCarrierName另外一个调用getCountryCode。执行相同的execute函数来显示两个方法的结果,代码以下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Carrier Demo</title> <meta http-equiv="Content-Type" content="text/html;charset=utf-8"> <meta name="viewport" content="user-scalable=no,initial-scale=1,maximum-scale=1,minimum-scale=1,width=device-width"> <script type="text/javascript" charset="utf-8" src="cordova.js"></script> <script type="text/javascript" charset="utf-8"> function onBodyLoad() { document.addEventListener("deviceready", onDeviceReady, false); }; function onDeviceReady() { }; function doSomething1() { carrier.getCarrierName(onSuccess, onFailure); } function doSomething2() { carrier.getCountryCode(onSuccess, onFailure); } function onSuccess(result) { var resStr = "Result: " + result; } function onFailure(err) { console.log("onFailure: " + JSON.stringify(err)); alert("Failure: " + err); } </script> </head> <body onload="onBodyLoad()"> <h1>Carrier Demo</h1> <p> This is a Cordova application that uses my fancy new Carrier Plugin. </p> <button onclick="doSomething1();">GetCarrierName</button> <button onclick="doSomething2()">GetCountryCode</button> </body> </html>
程序运行后以下图1,点各按钮的弹出信息见图2和3。
部署插件的时候有几种方法。能够把插件文件夹打包成zip格式(插件文件夹包括plugin.xml文件和全部的源代码文件)。用户拿到以后先解压到一个文件夹,再使用cordova plugin add安装。
还能够经过gitHub发布,你能够建立GitHub账号并把插件放在里边。文件升级时可让其余开发者知道这一消息。PhoneGap开发团队建立了一个仓库(https://github.com/phonegap/phonegap-plugins),在这你能够发布你的插件的信息。团队致力于开发一个插件发现系统,以便让开发者更方便的定位在应用中用到的插件。