來新公司的第一個任務,研究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/