Cordova安卓与JS交互原理

Cordova安卓与JS交互原理

JS调用Android

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

js 复制代码
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方法:

java 复制代码
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处理,如果不包含就不拦截:

java 复制代码
    @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能处理的几种形式:

java 复制代码
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中也不是立即处理插件返回结果的,而是以一个去处理储存的消息:

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:

java 复制代码
    /** 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方法:

java 复制代码
mWebView.evaluateJavascript("testReturn(1,2)", value ->
                        Log.e("TAG", "onReceiveValue value = " + value));

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

js 复制代码
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脚本,它会封装成消息,并放到消息队列:

java 复制代码
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去执行:

java 复制代码
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:

js 复制代码
@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定期拉取消息队列的消息,并进行处理:

scss 复制代码
function pollingTimerFunc() {
    if (pollEnabled) {
        pollOnce();
        setTimeout(pollingTimerFunc, 50);
    }
}

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

OnlineEventsBridgeMode

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

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

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

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

java 复制代码
@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。

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一条一条去拉取,然后处理消息。

相关推荐
安东尼肉店6 小时前
Android compose屏幕适配终极解决方案
android
2501_916007476 小时前
HTTPS 抓包乱码怎么办?原因剖析、排查步骤与实战工具对策(HTTPS 抓包乱码、gzipbrotli、TLS 解密、iOS 抓包)
android·ios·小程序·https·uni-app·iphone·webview
feiyangqingyun7 小时前
基于Qt和FFmpeg的安卓监控模拟器/手机摄像头模拟成onvif和28181设备
android·qt·ffmpeg
用户20187928316712 小时前
ANR之RenderThread不可中断睡眠state=D
android
煤球王子12 小时前
简单学:Android14中的Bluetooth—PBAP下载
android
小趴菜822712 小时前
安卓接入Max广告源
android
齊家治國平天下12 小时前
Android 14 系统 ANR (Application Not Responding) 深度分析与解决指南
android·anr
ZHANG13HAO12 小时前
Android 13.0 Framework 实现应用通知使用权默认开启的技术指南
android
【ql君】qlexcel12 小时前
Android 安卓RIL介绍
android·安卓·ril
写点啥呢12 小时前
android12解决非CarProperty接口深色模式设置后开机无法保持
android·车机·aosp·深色模式·座舱