简易jsbridge设计

305 阅读3分钟

1.初衷

解决请求范式 (仿微信):

wx.getLocation({
  type: 'wgs84', // 默认为wgs84的gps坐标
  success: function (res) {
    var latitude = res.latitude; // 纬度,浮点数,范围为90 ~ -90
    var longitude = res.longitude; // 经度,浮点数,范围为180 ~ -180。
  
  }
});

实现目标请求方式

 jlpay.getLocation({
   'type':'gps084',
   succ:function (res) { 
   }
 }); 

直接试下,走起

尝试1 , 直接尝试接收JsonObject,报错

webView.addJavascriptInterface(new JsInterace(), "jsbridge");
@JavascriptInterface
public void getLocation(String content) {
    showMsg(" 客户端收到消息:" + content);
}
window.jsbridge.getLocation({'type':'gps084',succ:function(res){}}); 
客户端收到消息: undefined

尝试2, 参数分开调用,原生收到的消息 客户端收到消息:undefined

@JavascriptInterface
public void getLocation(String method,String callback) {
   showMsg(" 客户端收到消息:" + method+" + "+callback);
}
window.jlpay.getLocation("wgs84",function(res){});
收到结果: 原生收到的消息 客户端收到消息:wgs84 + undefined 

2.设计思路

2.1基础参考
2.1.1. web端调用原生代码方法
1) js调用

webView注入 js对象

webView.addJavascriptInterface(new JsInterace(), "jsbridge")

public class JsInterace {        
  @JavascriptInterface
  public void send(String msg) {} 
}

web 端调用

window.jsbridge.send("hello");
2)scheme方式调用

web端scheme调用

document
.getElementById('hello')
.addEventListener('click', function () {
   window.location.href = 'jlmerchant://hello?
   jsCallBack=hello&text=helloWorld'
})

原生在webView接收

@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
     return super.shouldOverrideUrlLoading(view, url);    
}
2.1.2 原生调用web端方法

方法一 loadUrl

String js_content = "jlpay.receive('客户端收到了')"
webView.loadUrl("javascript:"+js_content)

方法二 evaluateJavascript(api 19+)

webView.evaluateJavascript(js_content,null);
2.3.设计思路(jsbridge)

问题:无法接收 JsonObject 或 Function对象

方案:设计 jsbridge 中转

5步走

web调用--> jsbridge转发 -->原生处理-->jsbridge接收-->回调web端

第1步.web调用。

jlpay.getLocation({
   'type':'gps084',
   success:function (res) {}
}); 

第2步.jsbridge转发

 //发送消息的通道, method(方法名),message(消息体)
  function getLocation(data){
    //取到回调对象
    var success = data.success;
    //为当前callback生成一个callbackId
    callbackId = 'cb_id_' + (callbackUnionId++);
    //保存当前方法的callback对象
    responseCallbacks[callbackId]=success;
    //调用原生方法
    jlpay.getLocation(JSON.stringify(data),callbackId);
  }
  

第3步. java处理及回调。

    @JavascriptInterface
    public void getLocation(String msg, String callbackId) {
        String js_content = 
            String.format("javascript:jsbridge.receive('%s')"
            ,locationData());
        //回调回jsbridge
        webView.loadUrl(js_content);
    }
    
    //模拟定位信息
    private void locationData(String callbackId) {
       JSONObject object = new JSONObject();
        try {
            object.put("callbackId", callbackId);
            JSONObject location = new JSONObject();
            location("longitude", 35.24324324);
            location("latitude", 135.24324324);
            object.put("data", location);
        } catch (JSONException e) {
            e.printStackTrace();
        }
        retrun object.toString();
     }
                                

第4步. jsbridge.js接收并回调web端

    function receive(messageJson){
       var message =  JSON.parse(messageJson);
       var callbackId = message.callbackId;
       var data = message.data;
    }

第5步. 回调web端

//找到存储的callback 并回调
responseCallbacks[callbackId](data);

3. one more thing (扩展方案)

项目中App-scheme方案

1.web 端调用

//定义接收方法 
window.hello = function (res) { //处理原生实现的方法}

//调用
document.getElementById('hello').addEventListener('click', 
    function () {
    window.location.href = 
    'jlmerchant://getLocation? jsCallBack=hello&text=helloWorld'
})

2.原生解析并回调

public boolean shouldOverrideUrlLoading(WebView view, String url) {
    //解析scheme接收参数信息
    if (url.startsWith("jlmerchant://")) {
        handleData(url);
        return true;
    }
}

//处理并回调 
//jlmerchant://getLocation?jsCallBack=hello
handleData(String url){  
    Uri uri = Uri.parse(path);
    String host = uri.getAuthority();
    String fuction = uri.getQueryParameter("jsCallBack");
    switch (host) {
    case "getLocation":
        JSONObject locationData=getSuccResponse(context);
        try{                
            locationData.put("longitude","35.48484838");
            locationData.put("latitude","113.83848484");
        } catch (JSONException e) {
            e.printStackTrace();
        }
    、、回调
    webView.loadUrl("javascript:"+fuction+"('" +locationData.toString() + "')");
    break;
}

思考:
Q1-调试及异常处理
function send(data,callback){
   log('jljsbridge 发送数据 :'+data+ 'callback:'+callback);
   var callbackId = 'cb_id_' + (callbackUnionId++);
   log('jljsbridge 的callbackId是:'+callbackId);
   responseCallbacks[callbackId]=callback;
   log('jsBridge 回复消息 完成');
   try{
        window.jlpay.send( JSON.stringify(data),callbackId);
        log('jljsbridge 调用成功');
   }catch(e){
         console.log('jljsbridge 加载错误'+e);
   }
}
Q2-方案对比

jsbridge方案 or scheme方案

Q3-双向调用
   webView.evaluateJavascript(js_content,new ValueCallback<String>(){
        @Override
        public void onReceiveValue(String value) {
        //接收js返回结果
        }
    });
    
Tips:

备注问题:

1.Java端加载js方法,主线程调用,否则

2.注入的时机 //onPageFinished

3.格式化Json

/**
     * 去掉json的特殊字符
     *
     * @param messageJson
     * @return
     */
    public static String formatJson(String messageJson) {
        messageJson = messageJson.replaceAll("(\\\\)([^utrn])", "\\\\\\\\$1$2");
        messageJson = messageJson.replaceAll("(?<=[^\\\\])(\")", "\\\\\"");
        messageJson = messageJson.replaceAll("(?<=[^\\\\])(\')", "\\\\\'");
        messageJson = messageJson.replaceAll("%7B", URLEncoder.encode("%7B"));
        messageJson = messageJson.replaceAll("%7D", URLEncoder.encode("%7D"));
        messageJson = messageJson.replaceAll("%22", URLEncoder.encode("%22"));
        return messageJson;
    }
参考:

jsbridge:github.com/lzyzsd/JsBr…

本文demo示例: github.com/jinbo123200…

附iOS端实现参考:

iOS WKWebView和 h5交互

1.加入js中间处理,将js方法转换成window.webkit.messageHandlers方法发送给app
javascript
window.jlpay = new Object();
var jlpaySuccessObject = {};
var id = 0;
window.jlpay.getLocation = function(object){
    id++;
    try {
        jlpaySuccessObject[id] = object.success;
        var jlpayCallBack = function(res){
            var tempId = res.jlpayId;
            delete res.jlpayId;
            (jlpaySuccessObject[id])(res);
            jlpaySuccessObject[tempId]=null;
            
        }
        window.webkit.messageHandlers.jlGetLocation.postMessage({"type":object.type,
        "callBack":jlpayCallBack.toString(),
        "jlpayId":id});
    } catch(e) {
        alert(e.message)
    }
}
2.WKWebView 加载中间js文件,并监听jsGetLocation方法
 WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
 //加载中间js文件
 [config.userContentController addUserScript:[self userScript]];
 //监听jsGetLocation方法
 [config.userContentController addScriptMessageHandler:self name:@"jsGetLocation"];
3.jsGetLocaiton 回调响应处理,并将处理结果回传给h5
#pragma mark - WKScriptMessageHandler
- (void)userContentController:(nonnull WKUserContentController *)userContentController didReceiveScriptMessage:(nonnull WKScriptMessage *)message {
    
    NSString *messageName = message.name;
    if ([messageName isEqualToString:@"jsGetLocation"]) {
        
        if ([message.body isKindOfClass:[NSDictionary class]]) {
            
            NSDictionary *bodyDict = (NSDictionary *)message.body;
            NSString *type     = bodyDict[@"type"];
            NSString *timeout  = bodyDict[@"timeout"];
            NSString *callBack = bodyDict[@"callBack"];
            NSString *jlpayId  = bodyDict[@"jlpayId"];
            
            __weak typeof(self)weakSelf = self;
            [self getLocation:type timeout:timeout jlpayId:jlpayId callBlk:^(NSString *jsonStr) {
                
                __strong typeof(self)strongSelf = weakSelf;
                [strongSelf callBackStringWithJsonString:jsonStr backFuncStr:callBack callBlk:^(NSString *backFuncString) {
                    //操作完成回调结果给h5
                    [message.webView evaluateJavaScript:backFuncString completionHandler:^(id _Nullable obj, NSError * _Nullable error) {
                        if (error) {
                            JLLogE(@"webview", @"error:%@",error.localizedDescription);
                        }
                        else{
                            JLLogI(@"webview", @"jl callback success");
                        }
                    }];
                }];
            }];
        }
    } 
}