美文网首页hybird
Android Cordova 源码走读(2019年6.0.1版

Android Cordova 源码走读(2019年6.0.1版

作者: summerlines | 来源:发表于2020-09-16 16:56 被阅读0次

前记:这里将19年中的Android Cordova主要源码走读分享出来,后期目标基于原有的cordova 框架扣除多余的代码,包括 js/java文件等 同时扩展容器的可选择性及 结合aop功能简化native 注册机制,合并同ios的使用注册文件模版为单一注入引擎文件,原有的native 到js的bridge使用方式不变,以此作为对之前技术输出的总结

1.Cordova SDK整体结构

cordova_whole.png

2.Cordova native端加载流程图

cordova_load.png

note: native插件的注册h5容器页面【不管是Activity 还是 Fragment 在初始化onCreate/onCreateView时执行注入native插件注入】
a. 解析并读取 corodva_plugins.xml中关于插件的信息生成PluginEntry集合
b. 在loadUrl【加载h5页面前】初始化好webview并根据PluginEntry中的onLoad值来确定是否提前初始化对应的插件
c.loadUrl执行,加载h5启动页到初始化好的webview中
d.其他onLoad 为false的大多数插件都是在具体js执行对应native插件能力时通过反射完成初始化并最终调用native 插件中的原生能力

2.1 注入native端xml文件中声明的 plugin【xml提供了后续扩展解耦plugin添加的入口】,解析xml并用全局map维护,指定onload为true的先行初始化,其他native端plugin 懒加载
// CordovaActivity 的onCreate
//这里是corodva native插件的懒加载注入入口
        loadConfig(pluginEntity);
    // 加载xml中的native plugin入口  
    @SuppressWarnings("deprecation")
        protected void loadConfig(ArrayList<PluginEntry> list) {
            ConfigXmlParser parser = new ConfigXmlParser();
            if (!CollectionUtils.isEmpty(list)) {
                parser.setDynamicEntity(list);
            }
            parser.parse(this);
            preferences = parser.getPreferences();
            preferences.setPreferencesBundle(getIntent().getExtras());
            launchUrl = parser.getLaunchUrl();
            //xml 解析得到native xml中声明的 plugin 包装对象map 并在下面loadUrl的 init方法中传递到PluginManager中
            pluginEntries = parser.getPluginEntries();
            Config.parser = parser;
        }
2.2 init() : 初始化WebView 对应代理对象重载,设置webview的属性(settings),暴露native 对象给js作为后续交互的bridge
public void loadUrl(String url) {
    if (appView == null) {
        init();
    }

    // If keepRunning
    this.keepRunning = preferences.getBoolean("KeepRunning", true);

    appView.loadUrlIntoView(url, true);
}



// com.xxx.xxx.core.CordovaActivity#init 方法提供了子类重写init方法
protected void init() {
        appView = makeWebView();
        createViews();
        if (!appView.isInitialized()) {
            appView.init(cordovaInterface, pluginEntries, preferences);
        }
        cordovaInterface.onCordovaInit(appView.getPluginManager());

        // Wire the hardware volume controls to control media if desired.
        String volumePref = preferences.getString("DefaultVolumeStream", "");
        if ("media".equals(volumePref.toLowerCase(Locale.ENGLISH))) {
            setVolumeControlStream(AudioManager.STREAM_MUSIC);
        }
    }



// ContainerActivity#makeWebView
@Override
    protected CordovaWebView makeWebView() {

            mSystemWebView = new NormalWebView(this);
            mEngine = new SystemWebViewEngine((NormalWebView)mSystemWebView);
            mFlWeb.addView((NormalWebView)mSystemWebView);

        mCordovaWebView = new CordovaWebViewImpl(mEngine);
        mSystemWebView.resetUserAgent( " " + userAgent);
        //这里抽象了 IWebview 【便于替换X5Webview等其他扩展WebView的实现】
        mSystemWebView.resetWebViewClient(this, mEngine);
        //同理在NormalWebview中注入了重写的WebViewClient和 WebChromeClient
        mSystemWebView.resetWebChromeClient(this, mEngine);
        return mCordovaWebView;
    }
2.3 证书校验说明
//证书校验调用入口
com.xxx.RedirectSystemWebViewClient#onReceivedSslError

@Override
            public void onReceivedSslError(WebView webView, SslErrorHandler handler, SslError sslError) {
//                super.onReceivedSslError(webView, handler, sslError); 注释父类实现
            //入口封装传入参数确定是否证书校验
                if (isCheck()) {
                    //检验证书颁发机构以及证书的合法性 证书确实是发给指定域名的且为当前设备的根证书所信任
                        if (SSLChecker.checkSSLCNCertOCFT(sslError.getCertificate())) {
                            handler.proceed();  // 如果证书一致,忽略错误
                        } else {
                            LogUtil.w("onReceivedSslError", "onReceivedSslError" + "cancel" + sslError);
                            handler.cancel();
                        }
//                    }
                } else {
                    handler.proceed();
                }
                if (client != null) {
                    client.onReceivedSslError();
                }
            }
        });





//init方法调用到engine 的 init方法, 初始化WebView相关设置,最后调用到 com.xxx.xxx.engine.SystemWebViewEngine#exposeJsInterface

//1.WebView相关设置
initWebViewSettings();
//2.暴露对象给js rename:“_cordovaNative”
private static void exposeJsInterface(WebView webView, CordovaBridge bridge) {
        if ((Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1)) {
            LOG.i(TAG, "Disabled addJavascriptInterface() bridge since Android version is old.");
            // Bug being that Java Strings do not get converted to JS strings automatically.
            // This isn't hard to work-around on the JS side, but it's easier to just
            // use the prompt bridge instead.
            return;
        }
        SystemExposedJsApi exposedJsApi = new SystemExposedJsApi(bridge);
        //webview注入native端 SystemExposedJsApi的对象 ,并以 _cordovaNative为名在js端使用
        webView.addJavascriptInterface(exposedJsApi, "_cordovaNative");
    }
2.4 重载代理接口处理动态拦截部分[当前展示为非动态加载代码块]
 @Override
    public WebResourceResponse shouldInterceptRequest(WebView view, String url) {

        Context context = view.getContext();
        if(url.endsWith(".js") && url.contains("local_intercept")) {
            int intercept = url.indexOf("local_intercept");
            url = url.substring(intercept+"local_intercept/".length(),url.length());
            return getWebResourceResponse(url, context);
        }
        return super.shouldInterceptRequest(view, url);
    }

 @SuppressLint("NewApi")
    private WebResourceResponse getWebResourceResponse(String url, Context context){
        WebResourceResponse res = null;
        try {
            InputStream instream = context.getResources().getAssets().open(
                    url);
            res = new WebResourceResponse("text/javascript",
                    "UTF-8", instream);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return res;
    }
3.Cordova js部分流程图
cordova_js_init.png

note:js插件的注册
js插件的注册从loadUrl即 入口index页面加载开始【这里以容器的测试页面为例子说明】 核心点是 : cordova.js 源码解读

3.1 声明 cordova 核心能力引用入口api package/import 和 define/require 执行Cordova.js文件加载

js从上至下解析执行,corodva.js 中的 define 先行,声明 cordova.js文件中内置的初始化插件
包括 
cordova/
channel/
nativeapiprovider/
base64/
init/
modulemapper/
platform/
app/
pluginloader/
utils/
等其他自带基础组件
var require, //类java的package和import全局声明 
    define;

(function () {
    var modules = {},
    // Stack of moduleIds currently being built.
        requireStack = [],
    // Map of module ID -> index into requireStack of modules currently being built.
        inProgressModules = {},
        SEPARATOR = ".";



 3 执行modules中的item module(在第1步中有声明定义)这里执行factory对应的 function(require, exports, module){...} 方法调用a/b 栈回调退出b/a 将module.exports赋给require点使用 
 require的作用:require后立即使用export出来的能力
               require对应插件,执行插件中的代码
 
 function build(module) {
        var factory = module.factory,
            //将require的能力传递到js插件中使用
            localRequire = function (id) {
                var resultantId = id;
                //Its a relative path, so lop off the last portion and add the id (minus "./")
                if (id.charAt(0) === ".") {
                    resultantId = module.id.slice(0, module.id.lastIndexOf(SEPARATOR)) + SEPARATOR + id.slice(2);
                }
                return require(resultantId);
            };
        module.exports = {};
        //移除要exports的js插件对象绑定的 factory【因为已经exports 下次require时,直接对外输出,不再重复build流程避免循环 见:[X] 处】
        delete module.factory;
        factory(localRequire, module.exports, module); [b]
        //绑定当前require的js 插件的factory到js插件所映射的module对象中【后续再绑定到window上供h5全局调用】
        return module.exports;
    }

 2 此id为define func 的第一个参数 id 即 插件数组js文件中单个item的id
 require = function (id) {
        if (!modules[id]) {
          //首先你得先define到modules数组中才能使用
            throw "module " + id + " not found";
        } else if (id in inProgressModules) {
          //说明js插件存在循环require【应杜绝,死循环】
            var cycle = requireStack.slice(inProgressModules[id]).join('->') + '->' + id;
            throw "Cycle in require graph: " + cycle;
        }
        // require->build js插件后
        if (modules[id].factory) {[X1]
            try {
                inProgressModules[id] = requireStack.length;
                requireStack.push(id);
                return build(modules[id]);  [a]
            } finally {
              //移除维护的额外数组js插件元素,确保当前的js require的正常运行
                delete inProgressModules[id];
                requireStack.pop();
            }
        }
        //执行完factory内exports的导出后,跳过[X1],直接返回前面已绑定的exports
        return modules[id].exports; [X2]
    };

  1 声明初始化modules数组中的 插件对象【可对比具体的插件define得知属性id 和 factory id在后面中有判断是否有加载中判断 factory作为整个js export 能力的入口】
  define = function (id, factory) {
        if (modules[id]) {
            throw "module " + id + " already defined";
        }

        modules[id] = {
            id: id,
            factory: factory
        };
    };

    define.remove = function (id) {
        delete modules[id];
    };

    define.moduleMap = modules;
})();
3.2 Cordova.js文件末尾调用:window.cordova = require('cordova'); 启动整个cordova js的环境初始化

a).初始化cordova加载环境,包括native和js的交互,事件监听注册以及在后续可能使用的api声明
b).将cordova主默认js插件添加到全局可用
c).加载外置插件plugin_list等js中的插件【动态加载js文件到 document,加载类cordova.js 中的各子js插件 包括 define 使用时的require 以及factory的exports 】

setTimeout(function() {
    pluginloader.load(function() {//这个function即为最终回调调用的callback
        channel.onPluginsReady.fire(); [X3]
    });
}, 0);


//改造扩展加载外置js插件方法
exports.load = function(callback) {
    var pathPrefix = findCordovaPath();
    if (pathPrefix === null) {
        console.log('Could not find cordova.js script tag. Plugin loading may fail.');
        pathPrefix = '';
    }
//      injectIfNecessary('cordova/plugin_list', pathPrefix + 'cordova_plugins.js', function() {
//            var moduleList = require("cordova/plugin_list");
//            handlePluginsObject(pathPrefix, moduleList, callback);
//        }, callback);
//    console.log("log", new Date().getTime());
    var cordovaTime;
    var bridgeTime;
    injectIfNecessary('cordova/plugin_list', pathPrefix + 'cordova_plugins.js', function() {                            [X:plugin_list]

            cordovaTime = new Date().getTime();
//             console.log("log",cordovaTime, bridgeTime);
            if(bridgeTime > 0 && cordovaTime>=bridgeTime) {
                var bridgeList = require("cordova/bridge_list");
                var moduleList = require("cordova/plugin_list");
                var pluginList = moduleList.concat(bridgeList);
//                console.log("log", pluginList, new Date().getTime());
                handlePluginsObject(pathPrefix, pluginList, callback);
            }
//        handlePluginsObject(pathPrefix, moduleList, callback);
    }, callback);
//    console.log("log", new Date().getTime());
    injectIfNecessary('cordova/bridge_list', pathPrefix + 'bridge_plugins.js', function() {
      [X:plugin_list]
            bridgeTime = new Date().getTime();
//            console.log("log", bridgeTime, cordovaTime);
            if(cordovaTime>0 && bridgeTime >= cordovaTime) {
                var bridgeList = require("cordova/bridge_list");
                var moduleList = require("cordova/plugin_list");
                var pluginList = moduleList.concat(bridgeList);
//                console.log("log", pluginList, new Date().getTime());
                handlePluginsObject(pathPrefix, pluginList, callback);
            }
        }, callback);
};


});

// file: src/common/pluginloader_b.js
define("cordova/pluginloader_b", function(require, exports, module) {

var modulemapper = require('cordova/modulemapper');

// Handler for the cordova_plugins.js content.
// See plugman's plugin_loader.js for the details of this object.
function handlePluginsObject(moduleList) {
    // if moduleList is not defined or empty, we've nothing to do
    if (!moduleList || !moduleList.length) {
        return;
    }

    // Loop through all the modules and then through their clobbers and merges.
    for (var i = 0, module; module = moduleList[i]; i++) {
        if (module.clobbers && module.clobbers.length) {
            for (var j = 0; j < module.clobbers.length; j++) {
                modulemapper.clobbers(module.id, module.clobbers[j]);
            }
        }

        if (module.merges && module.merges.length) {
            for (var k = 0; k < module.merges.length; k++) {
                modulemapper.merges(module.id, module.merges[k]);
            }
        }

        // Finally, if runs is truthy we want to simply require() the module.
        if (module.runs) {
            modulemapper.runs(module.id);
        }
    }
}

d).外置js插件script脚本加载完成回调执行onScriptLoadingComplete,添加外置插件到modulemapper对象上,最后回调onPluginsReady事件 [x3]

function handlePluginsObject(path, moduleList, finishPluginLoading) {
    // Now inject the scripts.
    var scriptCounter = moduleList.length;

    if (!scriptCounter) {
        finishPluginLoading();
        return;
    }
    function scriptLoadedCallback() {
      //每动态注入 一个moduleList的元素 scriptCounter减1,最后执行onScriptLoadingComplete [X6]节点
        if (!--scriptCounter) {
            onScriptLoadingComplete(moduleList, finishPluginLoading);
        }
    }

    for (var i = 0; i < moduleList.length; i++) {
      //动态注入 moduleList 中 的js文件,完成后回调 scriptLoadedCallback方法
        injectIfNecessary(moduleList[i].id, path + moduleList[i].file, scriptLoadedCallback);
    }
}



// 动态注入js入口 
function injectIfNecessary(id, url, onload, onerror) {
    onerror = onerror || onload;
    if (id in define.moduleMap) {
        onload();
    } else {
        exports.injectScript(url, function() {
            if (id in define.moduleMap) {
                onload();
            } else {
                onerror();
            }
        }, onerror);
    }
}



//动态注入js的实现 加载js完成后回调script的onload方法 最终调用 injectIfNecessary 的 scriptLoadedCallback
exports.injectScript = function(url, onload, onerror) {
    var script = document.createElement("script");
    // onload fires even when script fails loads with an error.
    script.onload = onload; //scriptLoadedCallback 或  [X:plugin_list]
    // onerror fires for malformed URLs.
    script.onerror = onerror;
    script.src = url;
   // console.log('log', script.src);
    document.head.appendChild(script);
};



// [X6] 脚本加载完成后 根据js对象声明的不同属性(clobbers/merges)添加到 modulemapper 对象中

function onScriptLoadingComplete(moduleList, finishPluginLoading) {
    // Loop through all the plugins and then through their clobbers and merges.
    for (var i = 0, module; module = moduleList[i]; i++) {
        if (module.clobbers && module.clobbers.length) {
            for (var j = 0; j < module.clobbers.length; j++) {
                modulemapper.clobbers(module.id, module.clobbers[j]);
            }
        }

        if (module.merges && module.merges.length) {
            for (var k = 0; k < module.merges.length; k++) {
                modulemapper.merges(module.id, module.merges[k]);
            }
        }

        // Finally, if runs is truthy we want to simply require() the module.
        if (module.runs) {
            modulemapper.runs(module.id);
        }
    }

    finishPluginLoading();
}

e).将js插件挂载到window上【plugin_list/bridge_list】后,前置注册的 deviceready 事件fire 最后回调 onDeviceReady方法 js端加载结束

//([x3]调用)channel.join
Channel.prototype.fire = function(e) {
    var fail = false,
        fireArgs = Array.prototype.slice.call(arguments);
    // Apply stickiness.
    if (this.state == 1) {
        this.state = 2;
        this.fireArgs = fireArgs;
    }
    if (this.numHandlers) {
        // Copy the values first so that it is safe to modify it from within
        // callbacks.
        var toCall = [];
        for (var item in this.handlers) {//handlers中存储了前置subscribe的func 即 之前注入到 channel 的 join方法[X4],这里push后 toCall同步 handlers内元素
            toCall.push(this.handlers[item]); 
        }
        for (var i = 0; i < toCall.length; ++i) {
          //执行channel中的join方法【可延伸:js中 apply 和 call的区别】
            toCall[i].apply(this, fireArgs);
        }
        if (this.state == 2 && this.numHandlers) {
            this.numHandlers = 0;
            this.handlers = {};
            this.onHasSubscribersChange && this.onHasSubscribersChange();
        }
    }
};



[X5]
channel.join(function() {
  //挂载外置js插件到window上
    modulemapper.mapModules(window);

    platform.initialize && platform.initialize();

    // Fire event to notify that all objects are created
    channel.onCordovaReady.fire();

    // Fire onDeviceReady event once page has fully loaded, all
    // constructors have run and cordova info has been received from native
    // side.
    channel.join(function() {
        require('cordova').fireDocumentEvent('deviceready');
    }, channel.deviceReadyChannelsArray);

}, platformInitChannelsArray);



    channel = {
        /**
         * Calls the provided function only after all of the channels specified
         * have been fired. All channels must be sticky channels.
         */
        join: function(h, c) {
            var len = c.length,
                i = len,
                f = function() {
                  //数组中的事件fire后回调用,最后一个元素即onPluginReady fire时 调用 [X5]注入的func->h()
                    if (!(--i)) h();
                };
            for (var j=0; j<len; j++) {
                if (c[j].state === 0) {
                    throw Error('Can only use join with sticky channels.');
                }
              //事件订阅,fire时触发f->func【这里为channel.join调用时的func】
                c[j].subscribe(f);
            }
            if (!len) h();
        },
        create: function(type) {
            return channel[type] = new Channel(type, false);
        },
        createSticky: function(type) {
            return channel[type] = new Channel(type, true);
        },

        /**
         * cordova Channels that must fire before "deviceready" is fired.
         */
        deviceReadyChannelsArray: [],
        deviceReadyChannelsMap: {},

        /**
         * Indicate that a feature needs to be initialized before it is ready to be used.
         * This holds up Cordova's "deviceready" event until the feature has been initialized
         * and Cordova.initComplete(feature) is called.
         *
         * @param feature {String}     The unique feature name
         */
        waitForInitialization: function(feature) {
            if (feature) {
                var c = channel[feature] || this.createSticky(feature);
                this.deviceReadyChannelsMap[feature] = c;
                this.deviceReadyChannelsArray.push(c);
            }
        },

        /**
         * Indicate that initialization code has completed and the feature is ready to be used.
         *
         * @param feature {String}     The unique feature name
         */
        initializationComplete: function(feature) {
            var c = this.deviceReadyChannelsMap[feature];
            if (c) {
                c.fire();
            }
        }
    };



//这里的obj 为传递过来的window 即完成了最终exports出来的js能力挂载到window上
function clobber(obj, key, value) { 
    exports.replaceHookForTesting(obj, key);
    var needsProperty = false;
    try {
        obj[key] = value;
    } catch (e) {
        needsProperty = true;
    }
    // Getters can only be overridden by getters.
    if (needsProperty || obj[key] !== value) {
        utils.defineGetter(obj, key, function() {
            return value;
        });
    }
}
3.3 各Cordova 内部插件简要说明【未补充完全】

各cordova 内部的 js插件能力
cordova:1.提供callbackId/callbacks/callbackStatus变量声明【用于管理插件调用行为的标记声明】
2.提供 callbackSuccess 和 callbackError以及 callbackFromNative方法声明 分别对应:成功/失败/native调用js 能力声明
这里存在boolean变量 keepCallback 决定是否要keep住当前调用行为中使用callback(callback常驻能力的js来源)
channel: 1.提供create/createSticky等事件定义的方法
2.提供事件订阅和取消订阅能力subscribe/unSubscribe
3.提供fire能力来执行所有通过channel完成订阅的方法
【2/3里 依赖 js的prototype 原型能力,创建的具体对象获取传递的prototype 属性能力】
platform: cordova平台启动bootstrap处理
pluginloader: 提供外置js插件的注入方式,包括读取plugin声明的js数组文件并动态创建脚本
init: 1.设置加载超时事件回掉(源码设置5s)
2.添加DOMContentLoaded等事件监听
3.执行 platform 的 bootstrap,onNativeReady事件执行
4.执行pluginloader 插件中的 load方法,即加载外置的js插件(plugin_list和 bridge_list)
exec: 1.声明native和js的交互方式【android 默认对象注入(js调用native),eval_bridge(native调用js)】并提供调用api

4. Cordova功能整体流程图
sdk_interact.png
4.1 js插件是如何找到native插件

以自定义插件调用过程为例:

a)h5触发事件:

// 获取经纬度信息api调用:
function getLocationGPSData(params) {

                    function onSuccess(result) {
                        navigator.notification.alert(JSON.stringify(result), null, '提示','OK');
                    };

                    function onError(error) {
                        navigator.notification.alert(JSON.stringify(error), null, '提示','OK');
                    };
                    XXBridge.call('executeH5InfoAction', params, onSuccess, onError);
                //XXXMessage.executeH5InfoAction(params, onSuccess, onError);
            }

b)代理跳转

    var exec = require('cordova/exec'); 
//exec js 中的 export: module.exports = androidExec;exec方法和 androidExec是映射关系

XXBridge.js: 
 XXXMessage.executeH5InfoAction(paras, suc, err);

XXXMessage.js: 
 executeH5InfoAction: function(params, successCallback, errorCallback) {
            exec(successCallback, errorCallback, "XXXBasicMessagePlugin", "executeH5InfoAction", [params]);
        }

c)触发的事件执行代码

//对应js 触发的 exec 调用到cordova js中 注入的 exec.js androidExec 方法
function androidExec(success, fail, service, action, args) {
  //bridgeSecret ->native to js module [这里默认eval]
    if (bridgeSecret < 0) { 
        // If we ever catch this firing, we'll need to queue up exec()s
        // and fire them once we get a secret. For now, I don't think
        // it's possible for exec() to be called since plugins are parsed but
        // not run until until after onNativeReady.
        throw new Error('exec() called without bridgeSecret');
    }
    // Set default bridge modes if they have not already been set.
    // By default, we use the failsafe, since addJavascriptInterface breaks too often
  //js to native 默认是 注入JS_OBJECT方式调用 
    if (jsToNativeBridgeMode === undefined) {
        androidExec.setJsToNativeBridgeMode(jsToNativeModes.JS_OBJECT);
    }

    // If args is not provided, default to an empty array
    args = args || [];

    // Process any ArrayBuffers in the args into a string.
  //
    for (var i = 0; i < args.length; i++) {
        if (utils.typeName(args[i]) == 'ArrayBuffer') {
            args[i] = base64.fromArrayBuffer(args[i]);
        }
    }
    //callbackId生成 并将生成的callbackId及成功失败回调以对象存储
    var callbackId = service + cordova.callbackId++,
        argsJson = JSON.stringify(args);
    if (success || fail) {
        cordova.callbacks[callbackId] = {success:success, fail:fail};
    }
    //1.根据注入的调用方式完成 js到native的调用 这里exec对应android端的_cordovaNative 
  //2.nativeApiProvider.get() 为 nativeApi 即 _cordovaNative 
  //3.msgs 是 native能力实现后的返回值【无callback事件回执时为undefined】
    var msgs = nativeApiProvider.get().exec(bridgeSecret, service, action, callbackId, argsJson);
    // If argsJson was received by Java as null, try again with the PROMPT bridge mode.
    // This happens in rare circumstances, such as when certain Unicode characters are passed over the bridge on a Galaxy S2.  See CB-2666.
    if (jsToNativeBridgeMode == jsToNativeModes.JS_OBJECT && msgs === "@Null arguments.") {
        androidExec.setJsToNativeBridgeMode(jsToNativeModes.PROMPT);
        androidExec(success, fail, service, action, args);
        androidExec.setJsToNativeBridgeMode(jsToNativeModes.JS_OBJECT);
    } else if (msgs) {
      //存储callback回来的msg到数组中
        messagesFromNative.push(msgs);
        // Always process async to avoid exceptions messing up stack.
      //异步方法执行 指向前述声明的Promise.then(func) 这里func为 processMessage()
        nextTick(processMessages);
    }
}

//以下为nativeApi 指向注入到js的native对象说明
var nativeApi = this._cordovaNative || require('cordova/android/promptbasednativeapi');
var currentApi = nativeApi;

module.exports = {
    get: function() { return currentApi; },
    setPreferPrompt: function(value) {
        currentApi = value ? require('cordova/android/promptbasednativeapi') : nativeApi;
    },
    // Used only by tests.
    set: function(value) {
        currentApi = value;
    }
};

d)回调的消息处理

function processMessages() {
    // Check for the reentrant case.
    if (isProcessing) {
        return;
    }
    if (messagesFromNative.length === 0) {
        return;
    }
    isProcessing = true;
    try {
      //从前面的messagesFromNative中pop出msg
        var msg = popMessageFromQueue();
        // The Java side can send a * message to indicate that it
        // still has messages waiting to be retrieved.
        if (msg == '*' && messagesFromNative.length === 0) {
            nextTick(pollOnce);
            return;
        }
      //根据native同js协定的传递消息格式处理信息
        processMessage(msg);
    } finally {
        isProcessing = false;
        if (messagesFromNative.length > 0) {
            nextTick(processMessages);
        }
    }
}

function popMessageFromQueue() {
  //从数组中移除第一项并返回
    var messageBatch = messagesFromNative.shift();
    if (messageBatch == '*') {
        return '*';
    }
    //从msg中取出长度以外的信息并返回
    var spaceIdx = messageBatch.indexOf(' ');
    var msgLen = +messageBatch.slice(0, spaceIdx);//msg的长度
    var message = messageBatch.substr(spaceIdx + 1, msgLen);
    messageBatch = messageBatch.slice(spaceIdx + msgLen + 1);
    if (messageBatch) {
        messagesFromNative.unshift(messageBatch);
    }
    return message;
}

// Processes a single message, as encoded by NativeToJsMessageQueue.java.
function processMessage(message) {
    var firstChar = message.charAt(0);
    if (firstChar == 'J') {
        // This is deprecated on the .java side. It doesn't work with CSP enabled.
        eval(message.slice(1));
      //S means success & F means False 对应后面方法调用的 success字段
    } else if (firstChar == 'S' || firstChar == 'F') {
        var success = firstChar == 'S';
        var keepCallback = message.charAt(1) == '1';
        var spaceIdx = message.indexOf(' ', 2);
        var status = +message.slice(2, spaceIdx);
        var nextSpaceIdx = message.indexOf(' ', spaceIdx + 1);
        var callbackId = message.slice(spaceIdx + 1, nextSpaceIdx);
      //获取回执信息中的有效信息【去除标志位 插件对象名toString:callbackId】
      第一位表示处理规则:第二位表示callback是否常驻:第三位表示callback的状态值用于callbackFromNative:两处空格之间为callbackId
      //example:"S01 XXXGeoPlugin559127509 sOK"
        var payloadMessage = message.slice(nextSpaceIdx + 1);//获取传递过来的原信息【可理解为类tcp/ip协议中的各种协议头封装后的解析过程,这里的原信息即可对应数据包】
        var payload = [];
        buildPayload(payload, payloadMessage);
        cordova.callbackFromNative(callbackId, success, status, payload, keepCallback);
    } else {
        console.log("processMessage failed: invalid message: " + JSON.stringify(message));
    }
}

// 执行cordova中声明的callbackFromNative,isSuccess对应前面的S/F标识位
callbackFromNative: function(callbackId, isSuccess, status, args, keepCallback) {
        try {
            var callback = cordova.callbacks[callbackId];
          //基本逻辑是在js端存在callbackId对应的callback,成功执行success失败执行fail
            if (callback) {
                if (isSuccess && status == cordova.callbackStatus.OK) {
                  // null 或 undefined 时会自动指向全局对象 apply即执行success func
                    callback.success && callback.success.apply(null, args);
                } else if (!isSuccess) {
                    callback.fail && callback.fail.apply(null, args);
                }
                /*
                else
                    Note, this case is intentionally not caught.
                    this can happen if isSuccess is true, but callbackStatus is NO_RESULT
                    which is used to remove a callback from the list without calling the callbacks
                    typically keepCallback is false in this case
                */
                // Clear callback if not expecting any more results
                if (!keepCallback) {
                    delete cordova.callbacks[callbackId];
                }
            }
        }
        catch (err) {
            var msg = "Error in " + (isSuccess ? "Success" : "Error") + " callbackId: " + callbackId + " : " + err;
            console && console.log && console.log(msg);
            cordova.fireWindowEvent("cordovacallbackerror", { 'message': msg });
            throw err;
        }
    }

java代码层面对应的Status说明【对应js中传递拼接的 status字段,说明当前的action是成功还是失败以及对应状态】

public enum Status {
        NO_RESULT,
        OK,
        CLASS_NOT_FOUND_EXCEPTION,
        ILLEGAL_ACCESS_EXCEPTION,
        INSTANTIATION_EXCEPTION,
        MALFORMED_URL_EXCEPTION,
        IO_EXCEPTION,
        INVALID_ACTION,
        JSON_EXCEPTION,
        ERROR
    }

javascript中的Status声明

callbackId: Math.floor(Math.random() * 2000000000),
    callbacks:  {},
    callbackStatus: {
        NO_RESULT: 0,
        OK: 1,
        CLASS_NOT_FOUND_EXCEPTION: 2,
        ILLEGAL_ACCESS_EXCEPTION: 3,
        INSTANTIATION_EXCEPTION: 4,
        MALFORMED_URL_EXCEPTION: 5,
        IO_EXCEPTION: 6,
        INVALID_ACTION: 7,
        JSON_EXCEPTION: 8,
        ERROR: 9
    }
4.2 native端代码,接收js事件 转发行为到对应子plugin 并产生回调信息传给js【eval】

a)js执行androidExec 调用到native端

[var msgs = nativeApiProvider.get().exec(bridgeSecret, service, action, callbackId, argsJson);]

// com.xxx.xxx.core.PluginManager#exec    
public void exec(final String service, final String action, final String callbackId, final String rawArgs) {
    //懒加载,根据协定传递指定key为service,初始化对应plugin【已初始化则直接取出】
        CordovaPlugin plugin = getPlugin(service);
        if (plugin == null) {
            LOG.d(TAG, "exec() call to unknown plugin: " + service);
//            PluginResult cr = new PluginResult(PluginResult.Status.CLASS_NOT_FOUND_EXCEPTION);
//            app.sendPluginResult(cr, callbackId);
            return;
        }
    //根据js端生成的callbackId 生成 native端的 CallbackContenxt对象,这个对象是native 回调js端,传递数据的起点
        CallbackContext callbackContext = new CallbackContext(callbackId, app);
        try {
            long pluginStartTime = System.currentTimeMillis();
            // 子plugin实现execute的返回值,action对应事件名,这里通过java多态找到CordovaPlugin的子实现类【即service映射的类】并execute
            boolean wasValidAction = plugin.execute(action, rawArgs, callbackContext);
            long duration = System.currentTimeMillis() - pluginStartTime;
            //提示在主线程执行过长时间,建议使用线程池异步
            if (duration > SLOW_EXEC_WARNING_THRESHOLD) {
                LOG.w(TAG, "THREAD WARNING: exec() call to " + service + "." + action + " blocked the main thread for " + duration + "ms. Plugin should use CordovaInterface.getThreadPool().");
            }//无效行为回调
            if (!wasValidAction) {
                PluginResult cr = new PluginResult(PluginResult.Status.INVALID_ACTION);
                callbackContext.sendPluginResult(cr);
            }
        } catch (JSONException e) {
            //异常回调
            PluginResult cr = new PluginResult(PluginResult.Status.JSON_EXCEPTION);
            callbackContext.sendPluginResult(cr);
        } catch (Exception e) {
            LOG.e(TAG, "Uncaught exception from plugin", e);
            callbackContext.error(e.getMessage());
        }
    }

b)事件行为分发到对应CordovaPlugin子类

//com.xxx.xxx.core.CordovaPlugin#execute(java.lang.String, java.lang.String, com.xxx.xxx.core.CallbackContext) 
public boolean execute(String action, String rawArgs, CallbackContext callbackContext) throws JSONException {
        JSONArray args = new JSONArray(rawArgs);
        return execute(action, args, callbackContext);
    }

//com.xxx.xxx.core.CordovaPlugin#execute(java.lang.String, com.xxx.xxx.core.CordovaArgs, com.xxx.xxx.core.CallbackContext)
  public boolean execute(String action, CordovaArgs args, CallbackContext callbackContext) throws JSONException {
        mMCallbackContext = callbackContext;
        return true;
    }

c)子类cordovaPlugin执行并产生回调信息返回【&信息包装】

//任意CordovaPlugin子类完成事件触发即执行execute后,通过方法传递的 CallbackContext回调
...
  public void success(JSONObject message) {
        sendPluginResult(new PluginResult(PluginResult.Status.OK, message));
    }
...//还有很多其他重载的success方法,可以兼容传递不同类型的数据类型,最终都会在PluginResult中完成封装,转换成最终传递到js的 payload string msg



public void sendPluginResult(PluginResult pluginResult) {
        synchronized (this) {
            if (finished) {
                LOG.w(LOG_TAG, "Attempted to send a second callback for ID: " + callbackId + "\nResult was: " + pluginResult.getMessage());
                return;
            } else {
        //如果执行过一次的事件 【finised 为 true】 下次不会再执行,如果keepcallback则可重复调用执行,回执到js端message process后也不会被移除全局的js callback数组【所以保持了两端的同步keepcallback能力】
                finished = !pluginResult.getKeepCallback();
            }
        }
        LogUtil.d("xxx", "sendPluginResult: "+callbackId+"-----"+pluginResult );
        webView.sendPluginResult(pluginResult, callbackId);
    }



//com.xxx.xxx.core.CordovaWebViewImpl#sendPluginResult
 @Override
    public void sendPluginResult(PluginResult cr, String callbackId) {
        // nativeToJsMessageQueue native生成发送到js端的消息维护类
        nativeToJsMessageQueue.addPluginResult(cr, callbackId);
    }

public void addPluginResult(PluginResult result, String callbackId) {
        if (callbackId == null) {
            LogUtil.e(LOG_TAG, "Got plugin result with no callbackId", new Throwable());
            return;
        }
        // Don't send anything if there is no result and there is no need to
        // clear the callbacks.
        boolean noResult = result.getStatus() == PluginResult.Status.NO_RESULT.ordinal();
        boolean keepCallback = result.getKeepCallback();
        if (noResult && keepCallback) {
            return;
        }
     // 将PluginResult 和 callbackId 封装成 JsMessage对象 [ JsMessage 是 NativeToJsMessageQueue 的内部类,JsMessage用于生成协议规则message的类]
        JsMessage message = new JsMessage(result, callbackId);
        if (FORCE_ENCODE_USING_EVAL) {
            StringBuilder sb = new StringBuilder(message.calculateEncodedLength() + 50);
            message.encodeAsJsMessage(sb);
            message = new JsMessage(sb.toString());
        }

        enqueueMessage(message);
    }

 private void enqueueMessage(JsMessage message) {
        synchronized (this) {
            if (activeBridgeMode == null) {
                LOG.d(LOG_TAG, "Dropping Native->JS message due to disabled bridge");
                return;
            }
            queue.add(message);
            if (!paused) {
                activeBridgeMode.onNativeToJsMessageAvailable(this);
            }
        }
    }


        @Override
        public void onNativeToJsMessageAvailable(final NativeToJsMessageQueue queue) {
            cordova.getActivity().runOnUiThread(new Runnable() {
                public void run() {
                    //这里在发送信息到js端前,先处理
                    String js = queue.popAndEncodeAsJs();
                    if (js != null) {
                        //最后调用 webview 的 evaluateJavascript方法,将处理过的带协议规则的信息传递到js端
                        engine.evaluateJavascript(js, null);
                    }
                }
            });
        }
    }

//完成封装的api方法
public String popAndEncodeAsJs() {
        synchronized (this) {
            int length = queue.size();
            if (length == 0) {
                return null;
            }
            int totalPayloadLen = 0;
            int numMessagesToSend = 0;
            for (JsMessage message : queue) {
                //计算长度
                int messageSize = message.calculateEncodedLength() + 50; // overestimate.
                //MAX_PAYLOAD_SIZE最大传递信息长度
                if (numMessagesToSend > 0 && totalPayloadLen + messageSize > MAX_PAYLOAD_SIZE && MAX_PAYLOAD_SIZE > 0) {
                    break;
                }
                totalPayloadLen += messageSize;
                numMessagesToSend += 1;
            }
            boolean willSendAllMessages = numMessagesToSend == queue.size();
            StringBuilder sb = new StringBuilder(totalPayloadLen + (willSendAllMessages ? 0 : 100));
            // Wrap each statement in a try/finally so that if one throws it does
            // not affect the next.
            for (int i = 0; i < numMessagesToSend; ++i) {
                //从队列中取出第一条JsMessage对象并移除
                JsMessage message = queue.removeFirst();
                if (willSendAllMessages && (i + 1 == numMessagesToSend)) {
                    message.encodeAsJsMessage(sb);
                } else {
                    sb.append("try{");
                    message.encodeAsJsMessage(sb);
                    sb.append("}finally{");
                }
            }
            if (!willSendAllMessages) {
                sb.append("window.setTimeout(function(){cordova.require('cordova/plugin/android/polling').pollOnce();},0);");
            }
            for (int i = willSendAllMessages ? 1 : 0; i < numMessagesToSend; ++i) {
                sb.append('}');
            }
            String ret = sb.toString();
            return ret;
        }
    }

// 转换成js能够执行的message【并包装回调js端的信息】
void encodeAsJsMessage(StringBuilder sb) {
            if (pluginResult == null) {
                sb.append(jsPayloadOrCallbackId);
            } else {
                int status = pluginResult.getStatus();
                boolean success = (status == PluginResult.Status.OK.ordinal()) || (status == PluginResult.Status.NO_RESULT.ordinal());
                // js端执行 cordova#callbackFromNative 该方法去处理包装的信息
                sb.append("cordova.callbackFromNative('")
                  .append(jsPayloadOrCallbackId)
                  .append("',")
                  .append(success)
                  .append(",")
                  .append(status)
                  .append(",[");
                switch (pluginResult.getMessageType()) {
                    case PluginResult.MESSAGE_TYPE_BINARYSTRING:
                        sb.append("atob('")
                          .append(pluginResult.getMessage())
                          .append("')");
                        break;
                    case PluginResult.MESSAGE_TYPE_ARRAYBUFFER:
                        sb.append("cordova.require('cordova/base64').toArrayBuffer('")
                          .append(pluginResult.getMessage())
                          .append("')");
                        break;
                    default:
                    sb.append(pluginResult.getMessage());
                }
                sb.append("],")
                  .append(pluginResult.getKeepCallback())
                  .append(");");
            }
        }

// cordova.callbackFromNative('XXXMessagePlugin1365781441',true,1,["{\"latitude\":31.181945,\"longitude\":121.365825}"],false

js插件的使用和声明:

     {
            "id": "xxxCordova-plugin-api.XXBridge", //对应define的id
            "file": "plugins/xxxCordova-plugin-api/www/XXBridge.js", //对应加载文件的path
            "pluginId": "xxxCordova-plugin-api", //可无 在动态更新插件中使用
            "merges": [
                "XXBridge"
            ]
            //如果是 run 则在crodova中 continue
            //如果是 merge 和 clobbers 则直接挂载,其中当出现新旧对象挂载到window上时 merge标签的对应元素会出现override
         }
5.对比简要说明 Cordova hybird和 ReactNative 差异

Cordova 原名phoneGap 被收购后改名为Cordova, 包括 native端 jsbridge 和 js端三部分,页面加载通过webView作为容器,通过js完成主逻辑控制,native提供原生端的能力支持
ReactNative 则包括 native runtime、 jsbridge 、js runtime,不依赖于WebView来展示页面而是使用原生控件,只不过是通过js脚本语言指定协议规则同native runtime端交互生成 映射的native 端view 添加到原生页面 窗口上

相关文章

网友评论

    本文标题:Android Cordova 源码走读(2019年6.0.1版

    本文链接:https://www.haomeiwen.com/subject/itqhyktx.html