添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

JsBridge( github地址 )为混合式应用native与h5的通讯提供安全而方便的桥接。

JsBridge是通过 url拦截 的方式实现的。

本次源码解析分为三篇,本篇为源码分析第一篇,其他两篇请见:
android JS与Native通讯方案汇总
JsBridge源码详解(一) JS与Native通讯过程(附详细流程图)
JsBridge源码详解(二) Native与JS通讯过程(附详细流程图)


由于框架功能分为js与native通讯和native与js通讯两部分,所以分两个流程分别来分析,本篇是源码解析的第一篇,先看js与native通讯部分。

通讯前准备

通讯的实现需要注入一段js代码,js代码的注入在页面加载完毕也就是WebViewClent的onPageFinished方法中

@Override
    public void onPageFinished(WebView view, String url) {
        super.onPageFinished(mWebView, url);
        //加载本地通讯桥接的js文件
        JsBridgeUtil.webViewLoadLocalJs(view, BridgeX5WebView.toLoadJs);

其中JsBridgeUtil的webViewLoadLocalJs方法负责注入js片段,其代码如下

public static void webViewLoadLocalJs(WebView view, String path) {
        String jsContent = assetFile2Str(view.getContext(), path);
        view.loadUrl("javascript:" + jsContent);

负责通讯的js片段以文件形式放在asserts目录下,assetFile2Str将文件以流的方式读取出来转换成字符串格式,然后通过loadUrl就加载了通讯的js片段,js的具体内容下面再一一分析。

至此,通讯的准备工作就完成了,下面来看看具体的通讯实现。

js调用native有个前提条件,就是上面讲到的js片段必须注入完毕,所以这里有部分逻辑是对该部分准备工作的监听,负责通讯的js通过发送事件的方式告诉调用方js:

 var readyEvent = doc.createEvent('Events');
    readyEvent.initEvent('WebViewJavascriptBridgeReady');
    readyEvent.bridge = WebViewJavascriptBridge;
    doc.dispatchEvent(readyEvent);

调用方js可以通过如下方式来监听该事件

function connectWebViewJavascriptBridge(callback) {
            if (window.WebViewJavascriptBridge) {
                // 准备完毕,可以通讯了
            } else {
                document.addEventListener(
                    'WebViewJavascriptBridgeReady'
                    , function() {
                        // 准备完毕,可以通讯了
                    false

监听到桥js加载完毕(步骤1),接下来就可以通讯了,调用方js通过调用桥js的callHandler函数来发起通讯(步骤2),来看一个获取H5所运行的设备的信息例子:

function getDeviceInfo(){
             window.WebViewJavascriptBridge.callHandler(
                'NativeHandler'
                , {'funcName'  : 'getDeviceInfo'}
                , function(responseData) {
                    document.getElementById("show").innerHTML = "getDeviceInfo involved,responseData from native is " + responseData

各个参数的作用后面会讲到,先来看一下callHandler的实现

function callHandler(handlerName, data, responseCallback) {
        _doSend({
            handlerName: handlerName,
            data: data
        }, responseCallback);

callHandler调用了内部doSend方法

 function _doSend(message, responseCallback) {
        if (responseCallback) {
            var callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime();
            responseCallbacks[callbackId] = responseCallback;
            message.callbackId = callbackId;
        sendMessageQueue.push(message);
        messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;

dosend方法先是判断是否需要有结果回调,如果有则生成唯一的回调id并将该id与回调方法一起保存进map以便调用结束后通知回调方法。回调id的生成用一个自增长的整数和当前时间戳组成。唯一的id会被放进传递给native的message对象中,使得native执行完毕之后可以告诉js应该将结果返回给哪个id对应的回调方法。

需要注意的是,dosend的方法并没有直接将调用者发送来的数据告诉native,而是将message对象保存进待发送消息队列sendMessageQueue,然后改变iframe标签的src来通知native有消息要取,这是怎么做到的呢?

首先这里要简单介绍下iframe:

iframe 标签规定一个内联框架

一个内联框架被用来在当前 HTML 文档中嵌入另一个文档

iframe能在当前页面加载其他的页面,也就是说制定了其src之后会触发WebViewClient的shouldOverrideUrlLoading方法。

看一下iframe的初始化

var doc = document;
_createQueueReadyIframe(doc);
 function _createQueueReadyIframe(doc) {
        messagingIframe = doc.createElement('iframe');
        messagingIframe.style.display = 'none';
        doc.documentElement.appendChild(messagingIframe);

在documentElement上增加了一个不可见的iframe标签,使用该标签来触发通讯。那么究竟是如何通讯的呢?

看一下native的WebViewClient的shouldOverrideUrlLoading方法(步骤3

@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
    if (url.startsWith(JsBridgeUtil.YY_RETURN_DATA)) {
        try {
            url = URLDecoder.decode(url, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            LogUtil.e("decode url fail:" + url, e);
        mWebView.handlerReturnData(url);
        return true;
    } else if (url.startsWith(JsBridgeUtil.YY_OVERRIDE_SCHEMA)) {
        try {
            url = URLDecoder.decode(url, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            LogUtil.e("decode url fail:" + url, e);
        mWebView.flushMessageQueue();
        return true;
    } else {
        return super.shouldOverrideUrlLoading(view, url);

其中JsBridgeUtil.YY_RETURN_DATA 为 ”yy://return/“这部分用来处理从js端获取到的返回数据。

JsBridgeUtil.YY_OVERRIDE_SCHEMA为”yy://“ 刚刚桥js的doSend方法改变的iframe的src为CUSTOM_PROTOCOL_SCHEME + ‘: //’ + QUEUE_HAS_MESSAGE为”yy://fetchQuere“,会匹配仅第二个条件,并执行flushMessageQueue方法(步骤4

public void flushMessageQueue() {
    if (Thread.currentThread() == Looper.getMainLooper().getThread()) {
        loadUrl(JsBridgeUtil.JS_FETCH_QUEUE_FROM_JAVA, new CallBackFunction() {
            @Override
            public void onCallBack(String data) {
              // callback detail ignored

flushMessageQueue主要做了两件事,一是判断当前线程是否是主线程,只有主线程才会继续执行后面的逻辑。二是调用loadUrl方法,loadUrl方法需要两个参数,第一个是要加载url地址,第二个是一个回调,回调的内容暂且不关注,后面再说。继续看loadUrl方法

 public void loadUrl(String jsUrl, CallBackFunction returnCallback) {
        this.loadUrl(jsUrl);
        responseCallbacks.put(JsBridgeUtil.parseFunctionName(jsUrl), returnCallback);
    public static String parseFunctionName(String jsUrl) {
        return jsUrl.replace("javascript:WebViewJavascriptBridge.", "").replaceAll("\\(.*\\);", "");

上面部分逻辑一方面执行js的调用,另一方面将回调与该js的调用关联起来。调用loadUrl的时候传入的JsBridgeUtil.JS_FETCH_QUEUE_FROM_JAVA实际为"javascript:WebViewJavascriptBridge._fetchQueue();",由此可以知道现在调用的是桥js的_fetchQueue()方法,那我们接着去分析_fetchQueue(步骤5

function _fetchQueue() {
    var messageQueueString = JSON.stringify(sendMessageQueue);
    sendMessageQueue = [];
    //android can't read directly the return data, so we can reload iframe src to communicate with java
    bizMessagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://return/_fetchQueue/' + encodeURIComponent(messageQueueString);

_fetchQueue逻辑很简单,只是将前面放到messageQueue里面的消息转变成json格式并将queue置空,然后再次按照定义好的规则拼接url放置到iframe上去,这次拼接出来的内容大致是 ”yy://return/_fetchQueue/{jsonData}“,一定看上去很眼熟吧,对这次会进入到shouldOverrideUrlLoading的第一个条件并执行webview的handlerReturnData方法。(步骤6

public void handlerReturnData(String url) {
    String functionName = JsBridgeUtil.getFunctionFromReturnUrl(url);
    CallBackFunction f = responseCallbacks.get(functionName);
    String data = JsBridgeUtil.getDataFromReturnUrl(url);
    if (f != null) {
        f.onCallBack(data);
        responseCallbacks.remove(functionName);
public static String getFunctionFromReturnUrl(String url) {
    String temp = url.replace(YY_RETURN_DATA, EMPTY_STR); //YY_RETURN_DATA="yy://return/"
    String[] functionAndData = temp.split(SPLIT_MARK); //SPLIT_MARK="/"
    if (functionAndData.length >= 1) {
        return functionAndData[0];
    return null;

handlerReturnData先是通过操作字符串获取方法名,在这里获取到的正是_fetchQueue ,可想而知从回调map中取出key为_fetchQueue的回调正式刚才调用fetchQueue方法时我们忽略具体实现的那个回调,现在让我们来看下这个回调到底干了什么:(步骤7)

@Override
public void onCallBack(String data) {
  // deserializeMessage 反序列化消息
  List<Message> list = null;
  try {
    list = Message.toArrayList(data);
  } catch (Exception e) {
    e.printStackTrace();
    return;
  if (list == null || list.size() == 0) {
    return;
  for (int i = 0; i < list.size(); i++) {
    Message m = list.get(i);
    String responseId = m.getResponseId();
    // 是否是response  CallBackFunction
    if (!TextUtils.isEmpty(responseId)) {
      CallBackFunction function = responseCallbacks.get(responseId);
      String responseData = m.getResponseData();
      function.onCallBack(responseData);
      responseCallbacks.remove(responseId);
    } else {
      CallBackFunction responseFunction = null;
      // if had callbackId 如果有回调Id
      final String callbackId = m.getCallbackId();
      if (!TextUtils.isEmpty(callbackId)) {
        responseFunction = new CallBackFunction() {
          @Override
          public void onCallBack(String data) {
            Message responseMsg = new Message();
            responseMsg.setResponseId(callbackId);
            responseMsg.setResponseData(data);
            queueMessage(responseMsg);
      } else {
        responseFunction = new CallBackFunction() {
          @Override
          public void onCallBack(String data) {
            // do nothing
      // BridgeHandler执行
      BridgeHandler handler;
      if (!TextUtils.isEmpty(m.getHandlerName())) {
        handler = messageHandlers.get(m.getHandlerName());
      } else {
        handler = defaultHandler;
      if (handler != null) {
        handler.handler(m.getData(), responseFunction);

首先将js传过来的json格式的字符串转换为message列表,如果列表不为空则挨个处理,处理过程大概如下:如果有repsponseId则说明是native调用js的返回结果,由于我们分析的是js调用native所以这条分路暂时不管,看下另外一条,如果callBackId不为空生成一个新的callBackFunction,并将callBackId在返回数据中一并返回,目的是为了方便js找到该callBackId对应的调用方js的callBackFunction,如果callBackId为空则说明这次请求不需要结果回调,则生成一个空实现的回调。这里callBackId是否为空是由最初调用方js决定的,可以参考 步骤2 处获取设备信息的例子。

接着会根据js设置的 BridgeHandler的名字去匹配对应的BridgeHandler,要调用的BridgeHandler的名字也是在最初调用方js那里确定好的,在 步骤2 处例子是 ”NativeHandler“。

那么这些BridgeHandler是在什么时候又是怎样注册进来的呢?这个问题比较简单,注册时机一般在使用webview的activity或者fragment对webview进行初始化的时候,注册方法更简单这里不浪费口舌。

假如根据handlerName能匹配到对应的BridgeHandler(一般都能匹配到,因为BridgeHandler的name都是两端提前商量好的),则执行BridgeHandler的handler方法,然后handler方法会根据事先定义好的规则匹配要执行的方法和传入的参数(如果有的话),执行完之后回调刚才根据callBackId是否为空生成的CallBackFunction (步骤8),例如:

@Override
public void handler(String data, CallBackFunction function) {
    //获取js端要调用的方法名
    String funcName = getFunctionName(data);
    switch (funcName) {
        case "getDeviceInfo":
            function.onCallBack(new Gson().toJson(new DeviceInfo()));
            break;
      case "xxx":
        default:
            function.onCallBack("no such function named: " + funcName);
            break;

BridgeHandler将处理结果通过执行CallBackFunction的onCallBack方法回传给js,通过步骤7中onCallBack的具体实现我们知道具体传递工作交给了queueMessage方法,queueMessage又调用了dispatchMessage方法,源码如下

 private void queueMessage(Message m) {
        if (startupMessage != null) {
            startupMessage.add(m);
        } else {
            dispatchMessage(m);
void dispatchMessage(Message m) {
        String messageJson = m.toJson();
        // 为json字符串转义特殊字符
        messageJson = messageJson.replaceAll("(\\\\)([^utrn])", "\\\\\\\\$1$2");
        messageJson = messageJson.replaceAll("(?<=[^\\\\])(\")", "\\\\\"");
        String javascriptCommand = String.format(MyBridgeUtil.JS_HANDLE_MESSAGE_FROM_JAVA, messageJson);
        // 必须要找主线程才会将数据传递出去
        if (Thread.currentThread() == Looper.getMainLooper().getThread()) {
            this.loadUrl(javascriptCommand);

可见执行结果通过一些格式处理字符转义之后,以”javascript:WebViewJavascriptBridge._handleMessageFromNative(’{resultData}’);“的形式调用桥接js的_handleMessageFromNative方法(步骤9),

function _handleMessageFromNative(messageJSON) {
  console.log(messageJSON);
  if (receiveMessageQueue) {
    receiveMessageQueue.push(messageJSON);
  _dispatchMessageFromNative(messageJSON);

_handleMessageFromNative在将处理结果直接放进messageQueue之后执行了_dispatchMessageFromNative

function _dispatchMessageFromNative(messageJSON) {
        setTimeout(function() {
            var message = JSON.parse(messageJSON);
            var responseCallback;
            //java call finished, now need to call js callback function
            if (message.responseId) {
                responseCallback = responseCallbacks[message.responseId];
                if (!responseCallback) {
                    return;
                responseCallback(message.responseData);
                delete responseCallbacks[message.responseId];
            } else {
                //直接发送
                if (message.callbackId) {
                    var callbackResponseId = message.callbackId;
                    responseCallback = function(responseData) {
                        _doSend({
                            responseId: callbackResponseId,
                            responseData: responseData
                var handler = WebViewJavascriptBridge._messageHandler;
                if (message.handlerName) {
                    handler = messageHandlers[message.handlerName];
                //查找指定handler
                try {
                    handler(message.data, responseCallback);
                } catch (exception) {
                    if (typeof console != 'undefined') {
                        console.log("WebViewJavascriptBridge: WARNING: javascript handler threw.", message, exception);

这段会看着非常眼熟,因为他的逻辑跟 步骤7 中CallBackFunction的实现如出一辙,这也可以理解,因为通讯是双向的,自然两边的实现逻辑也就一致,只是实现的语言不一样。

有了上面 步骤7 的分析,这段就好理解多了,因为 步骤7 中内部生成的CallBackFunction为Message指定了responseId 为js传过去的callBackId,那么我们也就能根据这个callBackId对应到 步骤2 中存入responseCallbacks中的responseCallback,也就是调用方js定义的callBack,在获取设备信息的例子汇总该callBack就是

function(responseData) {
  document.getElementById("show").innerHTML = "getDeviceInfo involved,responseData from native is " + responseData

至此,js调用native的过程我们就分析完了。调用过程在js和native之间来回转换,会有些绕,用图表的方式总结一下可能会更清晰一些:

通讯前准备通讯的实现需要注入一段js代码,js代码的注入在页面加载完毕也就是WebViewClent的onPageFinished方法中@Override public void onPageFinished(WebView view, String url) { super.onPageFinished(mWebView, url); //加载本地通讯... JSBrige系列直通车,由浅入深理解JS-Native的通信过程JSbridge系列解析(一):JS-Native调用方法 JSbridge系列解析(二):lzyzsd/JsBridge使用方法 JSbridge系列解析(三):lzyzsd/JsBridge源码解析 JSbridge系列解析(四):Web发消息给Native代码流程具体分析...
#import "ExampleUIWebViewController.h" #import "WebViewJavascriptBridge.h"@interface ExampleUIWebViewController () @property WebViewJavascriptBridge* bridge; @end@implementation ExampleUIWebViewControl
h5和原生结合开发app越来越流行。其实就是webview 的js调用native的方法。也就是需要搭建一个。这样的早就有人搭建好了,那就是jsbridge。 git地址: https://github.com/lzyzsd/JsBridge.git 其实很简单,那些添加依赖我就不说了。 两种情况: 1.js调用本地的方法: webView.loa...
该插件公开以下方法: cordova . plugins . jsCallNative . run ( success , error ) ; you should define a function called "sayHi()" in the "index.html" when you call cordova.plugins.jsCallNative.run, it will call sayHi() function, also, we can do something in the native when you call the plugin. // when you do something, like demo button click event
在混合开发中,很多情况下我们需要和native进行交互。那么我们怎么和native进行交互呢。。我自己也是研究了很久,最终搞好了一套稳定的,记下当笔记,遇到相同问题的筒子门可以参考一下 1.url传参,在一些简单的页面开发中,可以通过url?key1=value1&key2=value2 进行一些简单的数据交互,但是这种交互是单项的。 而且native筒子很烦躁处理每一条跳转。 2.hy...
# Build, watch for changes and run the application tns run # Build, watch for changes and debug the application tns debug < platform> # Build for production tns build < platform> --env.production ```javascript import React, { useState } from 'react'; import { View, Text, StyleSheet, TouchableOpacity, Image, ScrollView } from 'react-native'; import * as ImagePicker from 'expo-image-picker'; import Constants from 'expo-constants'; const ImagePickerComponent = () => { const [selectedImages, setSelectedImages] = useState([]); const pickImage = async () => { let result = await ImagePicker.launchCameraAsync({ mediaTypes: ImagePicker.MediaTypeOptions.Images, allowsEditing: true, aspect: [4, 3], quality: 1, if (!result.cancelled) { setSelectedImages([...selectedImages, { uri: result.uri }]); return ( <View style={styles.container}> <TouchableOpacity style={styles.button} onPress={pickImage}> <Text style={styles.buttonText}>Take a photo</Text> </TouchableOpacity> <ScrollView horizontal={true}> {selectedImages.map((image, index) => ( <View key={index}> <Image source={{ uri: image.uri }} style={styles.image} /> </View> </ScrollView> </View> const styles = StyleSheet.create({ container: { flex: 1, alignItems: 'center', justifyContent: 'center', paddingTop: Constants.statusBarHeight, backgroundColor: '#ecf0f1', padding: 8, button: { alignItems: 'center', backgroundColor: '#DDDDDD', padding: 10, marginBottom: 10, buttonText: { fontSize: 20, image: { width: 100, height: 100, margin: 5, export default ImagePickerComponent; 这个组件使用了 `expo-image-picker` 库来打开相机拍照,并将拍摄的图片加入到一个数组中。这个数组中的每一个元素都是一个对象,包含了图片的 URI。然后,使用 `ScrollView` 组件将所有的图片展示出来。