cordova源码解析

cordova是很多公司用来做hybrid方案的框架,当然会根据自己的业务需求加入一些自己的改动,现在公司也要用,于是把cordova安卓端的代码看了一遍。

架构图:


cordova源码解析_第1张图片
图片.png

在看之前提出几个问题:

  • 启动流程,初始化流程
  • 配置的加载流程,plugin的启动、初始化流程
  • 如何通讯,js到native,native到js

看总体的类图:

cordova源码解析_第2张图片
图片.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: 主要是统一管理和调用对应的插件,他对外提供了一个exec方法。这个方法可以统一调用对应的plugin。还可以通过pluginManager控制插件的生命周期,比如onPause,onStop等等。
  • CordovaWebViewImpl: 实现了CordovaWebView的接口, 主要是对外提供这个组件的接口,内部集成和管理其他的部分,比如webviewEngin等等。他对外提供了比如loadUrl等等接口。
  • SystemWebViewEngine: 这个类是用来解耦真实Webview和他的调用方,这样有两个好处。可以对外提供更加灵活的API,分离真实的WebView这样可以缩小对外开放的api。
  • SystemWebView:这个类是真正的Webview,继承自WebView。这里做一些webview的初始化工作,比如setWebViewClient,setWebChromeClient等等。
  • NativeToJsMessageQueue: 这个类用来管理发送给webview的消息的。主要用来向webview发送消息。他有几种模式,比如通过loadUrl来执行js的,通过evaluateJavascript来执行url的等等。这些模式可以动态设置。

js如何与native通讯

先来复习一下js与native通讯的方式:

对于Android调用JS代码的方法有2种:

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

对于JS调用Android代码的方法有3种:

  1. 通过WebView的addJavascriptInterface()进行对象映射 。这样js就可以直接通过映射的对象调用native的代码了。(但是这个方法在一些安卓版本里有任意执行漏洞)
  2. 通过 WebViewClient 的shouldOverrideUrlLoading ()方法回调拦截 url 。(主要是拦截对应的url做一些操作,比如拦截到一些特殊的url或者url带了一些特殊的参数,从而调用native的代码,也就是相当于js调用了native的代码。)
  3. 通过 WebChromeClient 的onJsAlert()、onJsConfirm()、onJsPrompt()方法回调拦截JS对话框alert()、confirm()、prompt() 消息。也可以实现js调用native的代码和逻辑。

native如何调用js。

native调用js的时候,native的调用会被封装成一个message,通过NativeToJsMessageQueue来发送和调用。比如有一个方法addJavaScript可以执行js代码。但是addJavaScript这个方法已经不推荐使用,推荐使用addPluginResult这个方法。这个方法封装了需要传递一个PluginResult,和一个callbackId,作为发送到js那边的消息。因为大多数时候native需要调用js都是作为js调用native的callback来调用从而返回一些native这边的结果的。

在通过addPluginResult这个方法往将需要发送的消息封装成一个JsMessage然后调用enqueueMessage这个方法,这个方法底部会通过onNativeToJsMessageAvailable方法通知有消息来了,然后不同的BridgeMode会做出不同的反应,比如这里有LoadUrlBridgeMode会通过webview的loadUrl方法运行js代码,EvalBridgeMode这个模式会通过webview的evaluateJavascript方法运行js代码等等。这样就完成了native对js的调用。

这里对js的调用做了一个封装,让调用更加灵活和可扩展。

js如何调用native。

js调用native主要使用的是addJavascriptInterface的对象映射。但是对于有些安卓版本这个方式会有任意执行漏洞,所以会有一个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那边传过来的命令,并且执行对应的plugin。
  • gap_bridge_mode:开头,设置JS bridge mode。
  • gap_poll:开头,获取所有需要发送给js的消息。
  • gap_init:开头,初始化bridgeSecret。

加载plugin的时候会初始化plugin吗?

有一个onload的标记位,true的时候在初始化plguinManager的时候就会初始化,否则用到的时候才会初始化。

plugin是如何加载和初始化的?

在CordovaActivity的onCreate里初始化的,解析xml获取到preference设置和pluginEntry的列表。

cordova 常用命令

安装cordova
npm install -g cordova

创建工程:
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

参考文档:

  • 讲了部分js的东西:https://blog.csdn.net/Yoryky/article/details/78516291
  • 包含js部分的初始化:https://blog.csdn.net/u010479969/article/details/79529921

你可能感兴趣的:(cordova源码解析)