请选择 进入手机版 | 继续访问电脑版

热点推荐

查看: 89|回复: 0

Android Cordova 源码走读(2019年6.0.1版)

[复制链接]

该用户从未签到

428

主题

428

帖子

1284

积分

金牌会员

Rank: 6Rank: 6

积分
1284
发表于 2020-9-16 17:35:50 | 显示全部楼层 |阅读模式
前记:这里将19年中的Android Cordova主要源码走读分享出来,后期目标基于原有的cordova 框架扣除多余的代码,包括 js/java文件等 同时扩展容器的可选择性及 结合aop功能简化native 注册机制,合并同ios的使用注册文件模版为单一注入引擎文件,原有的native 到js的bridge使用方式不变,以此作为对之前技术输出的总结
1.Cordova SDK整体结构

2.Cordova native端加载流程图

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 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部分流程图

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);         //绑定当前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.jsdefine("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++) {        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.id, path + moduleList.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 的 scriptLoadedCallbackexports.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++) {        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.joinChannel.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.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; jfunc【这里为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功能整体流程图

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) == 'ArrayBuffer') {            args = base64.fromArrayBuffer(args);        }    }    //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 msgpublic 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 添加到原生页面 窗口上

Java吧 收集整理 java论坛 www.java8.com
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表