因為項目使用了Cordova,也使用了很長時間。至於有很多hybride框架,為什么我們使用Cordova,這里不做過多的敘述,我們也是根據項目需求來選定的,需要及時更新、還要輸出別人SDK等。沒有最好,只有適合項目的。
之前整理文檔的時候查看了Cordova官方文檔及一些博客,也總結了自己的思考。站在大牛的肩膀上學習總結,自己才能在成為大牛的道路上快一點。最近需要優化下容器,所以再次梳理了一遍原理代碼。
Cordova的基本架構如下圖:
例如:Cordova就是先獲取config.xml中的setting項,並保存下來,並初始化一些類為webview加載提供需要。webview加載鏈接的時候會注入cordova的一些js,還有相對應的Native的Cordova類,這些就是核心的一些東西。都完成后可以說我們的一個容器就可以使用了。其中的細節我們接下來再細分講解。
下面我們來說下具體的JS和iOSNative交互原理:
圖中表示的是默認的JS和Native通信的方法(采用iframe)
1.保存Cordova_plugin.js的 插件文件名字和地址。
2.插件的API呼出時,通過調用Cordova的exec模塊將API的參數保存在CommandQueue的隊列中。CallBack則保存在JS側的callbacks map里面。
3.添加一個空的iframe,iframe的src則指向gap://ready
4.3的iframe的src設置以后,NATIVE側UIWebviewDelegate#shouldStartLoadWithRequest則被呼出來。
5.Webview的Delegatet判斷gap://ready的情況下,則執行commandDelegate的處理。
6.commandDelegate則從JS側取出API的參數,內部實現則是通過 UIWebview#stringByEvaluatingJavaScriptFromString的返回值 取得CommandQueue里面的參數轉換成JSON數據。
7.根據6的插件,執行NATIVE定義的插件實例。
8.插件中,有CallBack的情況下,成功失敗的結果通過UIWebview#stringByEvaluatingJavaScriptFromString執行JS,JS端則根據傳過來的CallBackId,從callbacks map取出回調函數並執行。
以上這就是一個H5與Native大致的交互回環流程。
看到這里相信對Cordova的架構及調用交互流程有了個大致的理解。我們接下來看下具體代碼相關解析。
配置注析:
我們的Cordova的webview容器由兩個部分組成,H5相關的JSs(cordova.js、cordova_plugins.js、exec.js、plugin.js)等、Html、css和Native的CDV的類組成(CDV、CDVCommandQueue、CDVCommandDelegate、CDVCommandDelegateImpl、CDVPlugin、CDVViewController)等。
config.xml信息:
<feature name="HandleOpenUrl"> <param name="ios-package" value="CDVHandleOpenURL" /> <param name="onload" value="true" /> </feature> <feature name="CDVWKWebViewEngine"> <param name="ios-package" value="CDVWKWebViewEngine" /> </feature>
//是否在線播放 <preference name="AllowInlineMediaPlayback" value="false" /> //web緩存 <preference name="BackupWebStorage" value="cloud" /> //是否顯示滾動原始位置 <preference name="DisallowOverscroll" value="false" /> <preference name="EnableViewportScale" value="false" /> <preference name="KeyboardDisplayRequiresUserAction" value="true" /> <preference name="MediaPlaybackRequiresUserAction" value="false" /> <preference name="SuppressesIncrementalRendering" value="false" /> <preference name="SuppressesLongPressGesture" value="false" /> <preference name="Suppresses3DTouchGesture" value="false" /> <preference name="GapBetweenPages" value="0" /> <preference name="PageLength" value="0" /> <preference name="PaginationBreakingMode" value="page" /> <preference name="PaginationMode" value="unpaginated" /> <preference name="CordovaWebViewEngine" value="CDVWKWebViewEngine" />
preference解析出來會放到settings這個字典里,name是key,value是value。這些事一些配置信息
<content src="index.html" /> <access origin="*" /> <allow-intent href="http://*/*" /> <allow-intent href="https://*/*" /> <allow-intent href="tel:*" /> <allow-intent href="sms:*" /> <allow-intent href="mailto:*" /> <allow-intent href="geo:*" /> <allow-intent href="itms:*" /> <allow-intent href="itms-apps:*" />
content是需要請求的url地址, access origin 通過的類型,allow-intent 跳轉的類型。
cordova_plugins.js解析:
cordova.define('cordova/plugin_list', function(require, exports, module) { module.exports = [ { "id": "cordova-plugin-wkwebview-engine.ios-wkwebview-exec", "file": "plugins/cordova-plugin-wkwebview-engine/src/www/ios/ios-wkwebview-exec.js", "pluginId": "cordova-plugin-wkwebview-engine", "clobbers": [ "cordova.exec" ] }, { "id": "cordova-plugin-wkwebview-engine.ios-wkwebview", "file": "plugins/cordova-plugin-wkwebview-engine/src/www/ios/ios-wkwebview.js", "pluginId": "cordova-plugin-wkwebview-engine", "clobbers": [ "window.WkWebView" ] } ]; module.exports.metadata = // TOP OF METADATA { "cordova-plugin-whitelist": "1.3.3", "cordova-plugin-wkwebview-engine": "1.1.4" }; // BOTTOM OF METADATA });
id:對外唯一標示id
file:js路徑
pluginid:plugin的唯一id(類似pod的時候的name)更新的時候需要對應。
clobbers:命名類似於(類名)
ios-wkwebview.js
cordova.define("cordova-plugin-wkwebview-engine.ios-wkwebview", function(require, exports, module) { var exec = require('cordova/exec'); var WkWebKit = { allowsBackForwardNavigationGestures: function (allow) { exec(null, null, 'CDVWKWebViewEngine', 'allowsBackForwardNavigationGestures', [allow]); } }; module.exports = WkWebKit; });
注意:這里的define與id要一致,feature中的name要與Native的類名一致,ios-wkwebview.js中的CDVWKWebViewEngine也要與feature中的name一致。否則會映射不到造成報錯。
Debug斷點整個流程代碼后,我們會看到整個代碼調用流程是這樣的:
1.配置管理
CDVViewController容器
-->ConfigParser解析 config_loan.xml 獲取到onload = true的startupPluginNames ,再獲取到plugin的name(key),再在pluginsMap中獲取到 value使用反射init初始化 namePlugin ,然后注冊該plugin(賦值屬性、協議)保存到pluginObjects。
2.H5容器注入JS
加載url鏈接,會注入cordova相關的js包含公共的核心js和我們對應寫的customjss。
3.點擊webviewH5觸發事件
點擊H5 plugin api
-->JSmethod execProxy
--> JSmethod cordovaExec
--> JSmethod iOSExec 配置生成事件唯一標示JSONString(successCallback, failCallback, service, action, actionArgs)
--> JSmethod massageArgsJsToNative
--> JSmethod pokeNative()
--> NativeMethod shouldStartLoadWithRequest (gap)
--> NativeMethod fetchCommandsFromJs
--> NativeMethod evaluateJavaScript
:js(cordova.require('cordova/exec').nativeFetchMessages())
--> JSmethod execProxy.nativeFetchMessages
--> 回調通知NativeMethod evaluateJavaScript completionHandler
--> NativeMethod enqueueCommandBatch
--> NativeMethod 轉換成QHInvokedUrlCommand
--> NativeMethod- (BOOL)execute:(QHInvokedUrlCommand*)command
--> NativeMethod getCommandInstance registerPlugin init對應的object
--> NativeMethod 轉換成QHPlugin
--> NativeMethod runtime 調用方法
--> 調用Native的plugin的Method方法。
4.Native處理完成方法后回調給H5,send message to WebViewH5
--> NativeMethod sendPluginResult
--> NativeMethod evalJsHelper
--> NativeMethod evalJsHelper2
-->JSmethod iOSExec.nativeCallback
--> JSmethod callbackFromNative
--> JSmethod callback.success\callback.fail
以上就是整個調用流程代碼調用的順序了。由於流程分的太細所以說就沒畫時序圖。
以下是其他博主的時序圖僅供參考:
