• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

Cordova安卓和JS交互原理

武飞扬头像
方大可
帮助1

Cordova安卓与JS交互原理

JS调用Android

JS通过引用调用插件访问Android代码:

export const exec = (plguinName, functionName, params, success, failed) => {
    try {
        window.cordova
            .require('cordova/channel')
            .onCordovaReady.subscribe(function () {
                window.cordova.exec(success, failed, plguinName, functionName, params);
            });
    } catch (e) {
        console.log('exec:'   e);
    }
};

具体实现有两种形式,一个是jsBridge,另一个是webView拦截prompt,默认使用jsBridge。

jsBridge形式

jsBridge要求Android4.3以上,所以低于此版本应该使用prompt形式。对于jsBridge形式,SystemExposedJsApi中定义了三种jsBridge方法:

class SystemExposedJsApi implements ExposedJsApi {
    private final CordovaBridge bridge;

    SystemExposedJsApi(CordovaBridge bridge) {
        this.bridge = bridge;
    }

    @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);
    }
}

// 进行设置
SystemExposedJsApi exposedJsApi = new SystemExposedJsApi(bridge);
webView.addJavascriptInterface(exposedJsApi, "_cordovaNative");

通过exec直接就能实现JS到Android的交互。

prompt形式

JS除了通过jsBridge向Android插件发起调用,还能通过prompt形式实现更强大功能。

Android的WebChromeClient会拦截onJsPrompt方法,如果包含要传递的信息就会被Android处理,如果不包含就不拦截:

    @Override
    public boolean onJsPrompt(WebView view, String origin, String message, String defaultValue, final JsPromptResult result) {
        // Unlike the @JavascriptInterface bridge, this method is always called on the UI thread.
        String handledRet = parentEngine.bridge.promptOnJsPrompt(origin, message, defaultValue);
        if (handledRet != null) {
            // handledRet是插件返回结果 => 所以必需要有结果,不然到onJsPrompt默认实现
            result.confirm(handledRet);
        } else {
            // 弹AlertDialog对话框?替代onJsPrompt默认实现
            dialogsHelper.showPrompt(message, defaultValue, (success, value) -> {
                if (success) {
                    result.confirm(value);
                } else {
                    result.cancel();
                }
            });
        }
        return true;
    }

prompt形式不仅仅可以发起对插件的方法的请求,还包含其他几种功能,下面是Android能处理的几种形式:

public String promptOnJsPrompt(String origin, String message, String defaultValue) {
    // 请求插件
    if (defaultValue != null && defaultValue.length() > 3 && defaultValue.startsWith("gap:")) {...}
    
    // 修改BridgeMode,Android访问JS的模式
    else if (defaultValue != null && defaultValue.startsWith("gap_bridge_mode:")) {...}
    
    // 拉取Android消息队列内的消息(插件结果、要执行的JS)
    else if (defaultValue != null && defaultValue.startsWith("gap_poll:")) {...}
    
    // 初始化prompt形式传递方式
    else if (defaultValue != null && defaultValue.startsWith("gap_init:")) {...}
}

结果处理

JS调用插件后,无论是jsBridge形式还是prompt形式,结果都不是立即拿到的,而是会收到一个一个消息,在JS中也不是立即处理插件返回结果的,而是以一个去处理储存的消息:

// cordova-android/cordvoa-js-src/exec.js
function processMessages() {
    // Check for the reentrant case.
    if (isProcessing) {
        return;
    }
    if (messagesFromNative.length === 0) {
        return;
    }
    isProcessing = true;
    try {
        var msg = popMessageFromQueue();
        // 未完全拉取完Android的消息队列的标记,继续拉取
        if (msg == '*' && messagesFromNative.length === 0) {
            nextTick(pollOnce);
            return;
        }
        processMessage(msg);
    } finally {
        isProcessing = false;
        if (messagesFromNative.length > 0) {
            nextTick(processMessages);
        }
    }
}

Android调用JS

JS通过调用插件的形式访问安卓代码,安卓同样支持访问JS代码,而且支持四种方式,对应四种BridgeMode:

    /** Uses webView.evaluateJavascript to execute messages. */ 
    // 通过webView.evaluateJavascript实现
    public static class EvalBridgeMode extends BridgeMode {...}
    
    /** Uses webView.loadUrl("javascript:") to execute messages. */
    // 通过webView.loadUrl("javascript:")实现
    public static class LoadUrlBridgeMode extends BridgeMode {...}
    
    /** Uses JS polls for messages on a timer.. */
    // 通过JS定期拉取消息队列的消息实现
    public static class NoOpBridgeMode extends BridgeMode {...}
    
    /** Uses online/offline events to tell the JS when to poll for messages. */
    // 通过webView设置online/offline事件,触发JS去拉取消息
    public static class OnlineEventsBridgeMode extends BridgeMode {...}

下面看下对应的JS代码。

EvalBridgeMode

EvalBridgeMode通过webView的evaluateJavascript方法执行JS脚本,这里需要安卓版本 4.4 以上,并且可以带返回值。

平常使用的话,需要H5提供一个可以执行的JS方法:

mWebView.evaluateJavascript("testReturn(1,2)", value ->
                        Log.e("TAG", "onReceiveValue value = "   value));

但是在Cordova中,JS应该先通过prompt方式,设置好EvalBridgeMode模式:

androidExec.setNativeToJsBridgeMode = function(mode) {
    if (mode == nativeToJsBridgeMode) {
        return;
    }
    if (nativeToJsBridgeMode == nativeToJsModes.POLLING) {
        pollEnabled = false;
    }

    nativeToJsBridgeMode = mode;
    // Tell the native side to switch modes.
    // Otherwise, it will be set by androidExec.init()
    if (bridgeSecret >= 0) {
        nativeApiProvider.get().setNativeToJsBridgeMode(bridgeSecret, mode);
    }

    if (mode == nativeToJsModes.POLLING) {
        pollEnabled = true;
        setTimeout(pollingTimerFunc, 1);
    }
};

然后通过CoreAndroid插件去发送JS脚本,它会封装成消息,并放到消息队列:

private void sendJavascriptEvent(String event) {
    if (appPlugin == null) {
        appPlugin = (CoreAndroid) pluginManager.getPlugin(CoreAndroid.PLUGIN_NAME);
    }

    if (appPlugin == null) {
        LOG.w(TAG, "Unable to fire event without existing plugin");
        return;
    }
    appPlugin.fireJavascriptEvent(event);
}

消息经过消息队列的处理,最终调用evaluateJavascript去执行:

public static class EvalBridgeMode extends BridgeMode {
    private final CordovaWebViewEngine engine;
    private final CordovaInterface cordova;

    public EvalBridgeMode(CordovaWebViewEngine engine, CordovaInterface cordova) {
        this.engine = engine;
        this.cordova = cordova;
    }

    @Override
    public void onNativeToJsMessageAvailable(final NativeToJsMessageQueue queue) {
        cordova.getActivity().runOnUiThread(new Runnable() {
            public void run() {
                String js = queue.popAndEncodeAsJs();
                if (js != null) {
                    engine.evaluateJavascript(js, null);
                }
            }
        });
    }
}

LoadUrlBridgeMode

LoadUrlBridgeMode执行顺序和上面类似,只不过方法换成了loadUrl:

@Override
public void onNativeToJsMessageAvailable(final NativeToJsMessageQueue queue) {
    cordova.getActivity().runOnUiThread(new Runnable() {
        public void run() {
            String js = queue.popAndEncodeAsJs();
            if (js != null) {
                engine.loadUrl("javascript:"   js, false);
            }
        }
    });
}

NoOpBridgeMode

NoOpBridgeMode表示Android不进行操作,由JS定期拉取消息队列的消息,并进行处理:

function pollingTimerFunc() {
    if (pollEnabled) {
        pollOnce();
        setTimeout(pollingTimerFunc, 50);
    }
}

就是使用setTimeout定期执行,50毫秒查询一次Android的message,message中有要执行的JS。不过JS中设置默认不启动定期查询模式。

OnlineEventsBridgeMode

OnlineEventsBridgeMode比较复杂点,它会通过online/offline事件,通知JS去取消息.

只要有新的消息存入消息队列,只要反转下online:

@Override
public void notifyOfFlush(final NativeToJsMessageQueue queue, boolean fromOnlineEvent) {
    if (fromOnlineEvent && !ignoreNextFlush) {
        online = !online;
    }
}

然后触发onNativeToJsMessageAvailable方法,如果队列中有消息就会通过setNetworkAvailable方法,去修改webView的online/offline状态:

@Override
public void onNativeToJsMessageAvailable(final NativeToJsMessageQueue queue) {
    // 增加了一个代理
    delegate.runOnUiThread(new Runnable() {
        public void run() {
            if (!queue.isEmpty()) {
                ignoreNextFlush = false;
                delegate.setNetworkAvailable(online);
            }
        }
    });
}  

// SystemWebViewEngine创建的OnlineEventsBridgeMode
nativeToJsMessageQueue.addBridgeMode(new NativeToJsMessageQueue.OnlineEventsBridgeMode(new NativeToJsMessageQueue.OnlineEventsBridgeMode.OnlineEventsBridgeModeDelegate() {
            @Override
            public void setNetworkAvailable(boolean value) {
                webView.setNetworkAvailable(value);
            }
            @Override
            public void runOnUiThread(Runnable r) {
                SystemWebViewEngine.this.cordova.getActivity().runOnUiThread(r);
            }
        }));

在JS中监听online/offline事件,然后通过prompt方式去拉取消息,消息中就有要执行的JS。

function hookOnlineApis() {
    function proxyEvent(e) {
        cordova.fireWindowEvent(e.type);
    }
    // The network module takes care of firing online and offline events.
    // It currently fires them only on document though, so we bridge them
    // to window here (while first listening for exec()-releated online/offline
    // events).
    window.addEventListener('online', pollOnceFromOnlineEvent, false);
    window.addEventListener('offline', pollOnceFromOnlineEvent, false);
    cordova.addWindowEventHandler('online');
    cordova.addWindowEventHandler('offline');
    document.addEventListener('online', proxyEvent, false);
    document.addEventListener('offline', proxyEvent, false);
}

hookOnlineApis();

总结

JS能够通过jsBridge和prompt方式调用Android的插件,执行相关代码。但是无论是插件结果,还是Android要执行的JS代码,都会封装成JsMessage,放到消息队列里面,需要JS一条一条去拉取,然后处理消息。

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgiieaj
系列文章
更多 icon
同类精品
更多 icon
继续加载