本文承接上篇《使用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),在這你能夠發佈你的插件的信息。團隊致力於開發一個插件發現系統,以便讓開發者更方便的定位在應用中用到的插件。