cordova源碼解析

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

架構圖:


圖片.png

在看之前提出幾個問題:

  • 啟動流程,初始化流程
  • 配置的加載流程,plugin的啟動、初始化流程
  • 如何通訊,js到native,native到js

看總體的類圖:

圖片.png

有一些線沒畫出來,比如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種:

  1. 通過WebView的loadUrl()
  2. 通過WebView的evaluateJavascript()

對于JS調用Android代碼的方法有3種:

  1. 通過WebView的addJavascriptInterface()進行對象映射 。這樣js就可以直接通過映射的對象調用native的代碼了。(但是這個方法在一些安卓版本里有任意執(zhí)行漏洞)
  2. 通過 WebViewClient 的shouldOverrideUrlLoading ()方法回調攔截 url 。(主要是攔截對應的url做一些操作,比如攔截到一些特殊的url或者url帶了一些特殊的參數(shù),從而調用native的代碼,也就是相當于js調用了native的代碼。)
  3. 通過 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

參考文檔:

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容