cordova是很多公司用來做hybrid方案的框架,當然會根據自己的業(yè)務需求加入一些自己的改動,現(xiàn)在公司也要用,于是把cordova安卓端的代碼看了一遍。
架構圖:

在看之前提出幾個問題:
- 啟動流程,初始化流程
- 配置的加載流程,plugin的啟動、初始化流程
- 如何通訊,js到native,native到js
看總體的類圖:

有一些線沒畫出來,比如SystemWebViewEngin和SystemWebview都是含有一個parent(對前一個對象的)引用的。比如SystemWebViewEngin的parent是CordovaWebView,SystemWebview的parent是SystemWebViewEngin。SystemWebView和SystemWebViewEngin以及CordovaWebViewImpl都含有一個CordovaInterface的引用,以及pluginManager(SystemWebView沒有plguinManager的引用)。
主要的類介紹:
-
CordovaInterface:主要是plugin以及webview中操作UI的接口,可以直接通過
CordovaInterface獲取到Activity,比如權限請求,startActivity等等。在plugin中也有這個對象的引用,所以可以在對于的plugin中調用UI相關的東西。 -
PluginManager: 主要是統(tǒng)一管理和調用對應的插件,他對外提供了一個exec方法。這個方法可以統(tǒng)一調用對應的plugin。還可以通過pluginManager控制插件的生命周期,比如onPause,onStop等等。 -
CordovaWebViewImpl: 實現(xiàn)了CordovaWebView的接口, 主要是對外提供這個組件的接口,內部集成和管理其他的部分,比如webviewEngin等等。他對外提供了比如loadUrl等等接口。 -
SystemWebViewEngine: 這個類是用來解耦真實Webview和他的調用方,這樣有兩個好處??梢詫ν馓峁└屿`活的API,分離真實的WebView這樣可以縮小對外開放的api。 -
SystemWebView:這個類是真正的Webview,繼承自WebView。這里做一些webview的初始化工作,比如setWebViewClient,setWebChromeClient等等。 -
NativeToJsMessageQueue: 這個類用來管理發(fā)送給webview的消息的。主要用來向webview發(fā)送消息。他有幾種模式,比如通過loadUrl來執(zhí)行js的,通過evaluateJavascript來執(zhí)行url的等等。這些模式可以動態(tài)設置。
js如何與native通訊
先來復習一下js與native通訊的方式:
對于Android調用JS代碼的方法有2種:
- 通過WebView的loadUrl()
- 通過WebView的evaluateJavascript()
對于JS調用Android代碼的方法有3種:
- 通過WebView的addJavascriptInterface()進行對象映射 。這樣js就可以直接通過映射的對象調用native的代碼了。(但是這個方法在一些安卓版本里有任意執(zhí)行漏洞)
- 通過 WebViewClient 的shouldOverrideUrlLoading ()方法回調攔截 url 。(主要是攔截對應的url做一些操作,比如攔截到一些特殊的url或者url帶了一些特殊的參數(shù),從而調用native的代碼,也就是相當于js調用了native的代碼。)
- 通過 WebChromeClient 的onJsAlert()、onJsConfirm()、onJsPrompt()方法回調攔截JS對話框alert()、confirm()、prompt() 消息。也可以實現(xiàn)js調用native的代碼和邏輯。
native如何調用js。
native調用js的時候,native的調用會被封裝成一個message,通過NativeToJsMessageQueue來發(fā)送和調用。比如有一個方法addJavaScript可以執(zhí)行js代碼。但是addJavaScript這個方法已經不推薦使用,推薦使用addPluginResult這個方法。這個方法封裝了需要傳遞一個PluginResult,和一個callbackId,作為發(fā)送到js那邊的消息。因為大多數(shù)時候native需要調用js都是作為js調用native的callback來調用從而返回一些native這邊的結果的。
在通過addPluginResult這個方法往將需要發(fā)送的消息封裝成一個JsMessage然后調用enqueueMessage這個方法,這個方法底部會通過onNativeToJsMessageAvailable方法通知有消息來了,然后不同的BridgeMode會做出不同的反應,比如這里有LoadUrlBridgeMode會通過webview的loadUrl方法運行js代碼,EvalBridgeMode這個模式會通過webview的evaluateJavascript方法運行js代碼等等。這樣就完成了native對js的調用。
這里對js的調用做了一個封裝,讓調用更加靈活和可擴展。
js如何調用native。
js調用native主要使用的是addJavascriptInterface的對象映射。但是對于有些安卓版本這個方式會有任意執(zhí)行漏洞,所以會有一個bridgeSecret用來驗證安全性。
js會先調用prompt來初始化bridgeSecret,在native端生成bridgeSecret并且傳遞到js端,然后每次js調用JavascriptInterface的時候都會帶上這個secret來驗證安全性。
看addJavascriptInterface這種方式注冊的給js調用的接口。
@SuppressLint("AddJavascriptInterface")
private static void exposeJsInterface(WebView webView, CordovaBridge bridge) {
SystemExposedJsApi exposedJsApi = new SystemExposedJsApi(bridge);
webView.addJavascriptInterface(exposedJsApi, "_cordovaNative");
}
看SystemExposedJsApi類提供給js的方法:
@JavascriptInterface
public String exec(int bridgeSecret, String service, String action, String callbackId, String arguments) throws JSONException, IllegalAccessException {
return bridge.jsExec(bridgeSecret, service, action, callbackId, arguments);
}
@JavascriptInterface
public void setNativeToJsBridgeMode(int bridgeSecret, int value) throws IllegalAccessException {
bridge.jsSetNativeToJsBridgeMode(bridgeSecret, value);
}
@JavascriptInterface
public String retrieveJsMessages(int bridgeSecret, boolean fromOnlineEvent) throws IllegalAccessException {
return bridge.jsRetrieveJsMessages(bridgeSecret, fromOnlineEvent);
}
再看prompt方法的調用鏈。
在SystemWebChromeClient里面,會先調用bridge看是否要處理,如果bridge處理了這個prompt,則返回,否則調用dialogHelper處理。
在bridge里會有一系列處理js命令的邏輯
CordovaBridge.java
public String promptOnJsPrompt(String origin, String message, String defaultValue) {
if (defaultValue != null && defaultValue.length() > 3 && defaultValue.startsWith("gap:")) {
//...
return "";
}
// Sets the native->JS bridge mode.
else if (defaultValue != null && defaultValue.startsWith("gap_bridge_mode:")) {
//....
return "";
}
// Polling for JavaScript messages
else if (defaultValue != null && defaultValue.startsWith("gap_poll:")) {
//...
return "";
}
else if (defaultValue != null && defaultValue.startsWith("gap_init:")) {
// Protect against random iframes being able to talk through the bridge.
// Trust only pages which the app would have been allowed to navigate to anyway.
if (pluginManager.shouldAllowBridgeAccess(origin)) {
// Enable the bridge
int bridgeMode = Integer.parseInt(defaultValue.substring(9));
jsMessageQueue.setBridgeMode(bridgeMode);
// Tell JS the bridge secret.
int secret = generateBridgeSecret();
return ""+secret;
} else {
LOG.e(LOG_TAG, "gap_init called from restricted origin: " + origin);
}
return "";
}
return null;
}
這里會有幾個命令:
-
gap:開頭:則調用js那邊傳過來的命令,并且執(zhí)行對應的plugin。 -
gap_bridge_mode:開頭,設置JS bridge mode。 -
gap_poll:開頭,獲取所有需要發(fā)送給js的消息。 -
gap_init:開頭,初始化bridgeSecret。
加載plugin的時候會初始化plugin嗎?
有一個onload的標記位,true的時候在初始化plguinManager的時候就會初始化,否則用到的時候才會初始化。
plugin是如何加載和初始化的?
在CordovaActivity的onCreate里初始化的,解析xml獲取到preference設置和pluginEntry的列表。
cordova 常用命令
安裝cordova
npm install -g cordova
創(chuàng)建工程:
cordova create myApp com.myCompany.myApp myApp
//進入工程目錄
cd myApp
//在工程中可以運行的。
添加平臺
cordova platform add android --save
添加插件
cordova plugin add cordova-plugin-camera --save
查看插件列表
cordova plugin list
移除插件
cordova plguin remove xxx
編譯安卓
cordova build android --verbose
運行安卓
cordova run android
參考文檔: