导语

现在大多数App与H5的交互越来越多,jsBridge是一个能使webView和js交互的通信方式,本文只对https://github.com/lzyzsd/JsBridge(以下涉及到的jsBridge源码都是出自这个框架)进行分析,只要你懂得了其中的原理,你也可以封装一个jsBridge。不过在介绍jsBridge的原理前,我会简单介绍下原始的webView与js交互以及为什么要用jsBridge。

一、WebView与js交互

原始的js交互非常简单容易理解,直接给出一段客户端的代码。

//开启支持js交互

mWebView.getSettings().setJavaScriptEnabled(true);

//添加js回调接口,第一个参数是我们本地写的一个专门提供方法给H5的js对象;第二个参数是双方规定好的命名,只有注册的名称和H5那边对应才可交互。

mWebView.addJavascriptInterface(new JSRequest(), "jsRequest");

class JSRequest{

@JavascriptInterface //只有加了这个注解的方法才能被h5调用

public void actionFromH5(){

Log.v("JSRequest","H5调用了该方法");

}

}

// 本地调用H5的方法用loadUrl实现,actionFromNative是在H5里实现的一个方法

mWebView.loadUrl("javascript:actionFromNative()");

二、WebView的js对象注入漏洞

webView的js对象注入的方式非常简单,可是为什么建议使用jsBridge呢?因为该方式存在安全隐患。上述提到本地方法加了@JavascriptInterface注解才能被h5调用,这个是在Android4.2之后加的,是为了避免恶意js代码获取本地信息,如SD卡中的用户信息。但是@JavascriptInterface无法兼容4.2以前的版本,所以4.2之前的系统都有被随时侵入获取信息的可能。

那么js是如何做到的?答案是反射。4.2之前没有加@JavascriptInterface的情况下,js是可以通过你注入的js对象(addJavascriptInterface的第一个参数)直接拿到getClass(这个方法是基类Object的方法),然后再拿到Runtime对象用来执行一些命令。原理大概就是这样,如果想具体了解如何实现的,请阅读WebView的Js对象注入漏洞解决方案。

三、jsBridge源码分析

jsBridge的最大作用就是解决了WebView的安全隐患,任何版本的系统都是适用的。还是一样,下面先介绍下jsBridge的用法,一些配置我就不介绍了,直接拿主干部分。

//一些初始化代码就不展示了

······································

// 第一个参数在本地注册一个叫"submitFromWeb"的方法供H5调用,

// 第二个参数是实现了BridgeHandler接口的匿名类用来回调。

webView.registerHandler("submitFromWeb", new BridgeHandler() {

@Override

public void handler(String data, CallBackFunction function) {

// 这里的data是H5传给本地的数据,function.onCallBack是回调给H5的字符串数据

Log.i(TAG, "handler = submitFromWeb, data from web = " + data);

function.onCallBack("submitFromWeb exe, response data 中文 from Java");

}

});

// 第一个参数是H5页面注册的一个名为"functionInJs"的方法

// 第二个参数是客户端本地传给H5的字符串

// 第三个参数是实现回调接口的匿名内部类

webView.callHandler("functionInJs", "data from Java", new CallBackFunction() {

@Override

public void onCallBack(String data) {

// TODO Auto-generated method stub

// data是H5返回给客户端的数据

Log.i(TAG, "reponse data from js " + data);

}

});

3.1 H5调客户端

jsBridge的源码是很少的,理解起来不是那么困难,只要一步步往下走就好了,首先我们从registerHandler出发:

// BridgeWebView.java

public void registerHandler(String handlerName, BridgeHandler handler) {

if (handler != null) {

messageHandlers.put(handlerName, handler);// 每个回调接口都对应一个key值(也就是你命名的方法名)

}

}

registerHandler方法就是这么简单,客户端操作已经到此结束了。我认为jsBridge最神奇的地方就是WebViewJavaScriptBridge.js这个js文件,对于不熟悉H5开发的同学可能有点看不懂(包括我),但是其实这个js文件的内容和BridgeWebView.java非常类似,大概看懂几个重要方法的作用即可。下面是一段H5调用客户端方法的代码:

// demo.html

// testClick1方法是H5页面点击某个按钮触发的,然后会调客户端的方法。

function testClick1() {

// call native method

// 第一个参数是客户端命名的方法

// 第二个参数是传给客户端的数据

// 第三个参数是客户端返回数据给H5的回调方法

window.WebViewJavascriptBridge.callHandler(

'submitFromWeb'

, {'param': '中文测试'}

, function(responseData) {

document.getElementById("show").innerHTML = "send get responseData from java, data = " + responseData

}

);

}

但是这个callHandler方法不是H5写的,而是客户端本地的WebViewJavaScriptBridge.js文件里的方法,这个文件里的内容是直接可以注入到H5页面(不得不感叹H5的方便之处)。

// WebViewJavaScriptBridge.js

// 提供给H5的js方法

function callHandler(handlerName, data, responseCallback) {

_doSend({

handlerName: handlerName,

data: data

}, responseCallback);

}

// 对应上面方法里的_doSend,在发送消息队列中加入消息,触发native请求

function _doSend(message, responseCallback) {

// responseCallback按命名理解就是响应回调,也就是说是客户端再传数据给H5的时候用到的

if (responseCallback) {

var callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime();

responseCallbacks[callbackId] = responseCallback;

message.callbackId = callbackId;

}

sendMessageQueue.push(message);

// 我的理解是这行代码会触发WebViewClient中的shouldOverrideUrlLoading,这是交互的关键点

// 返回给客户端的url是"yy://__QUEUE_MESSAGE__/"

messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;

}

上面的注释已经写明H5最终会触发WebViewClient中的shouldOverrideUrlLoading:

// BridgeWebViewClient.java

public boolean shouldOverrideUrlLoading(WebView view, String url) {

if (url.startsWith(BridgeUtil.YY_RETURN_DATA)) { // url开头是否是"yy://return/_fetchQueue/",说明是H5要返回数据给客户端了

webView.handlerReturnData(url);

return true;

} else if (url.startsWith(BridgeUtil.YY_OVERRIDE_SCHEMA)) { //url开头是否是"yy://__QUEUE_MESSAGE__/",说明H5要调用客户端了。

webView.flushMessageQueue();

return true;

} else {

return super.shouldOverrideUrlLoading(view, url);

}

}

上面的代码已经走到H5调用客户端了,接下去跟进webView.flushMessageQueue()看看:

// BridgeWebView.java

void flushMessageQueue() {

if (Thread.currentThread() == Looper.getMainLooper().getThread()) {

loadUrl(BridgeUtil.JS_FETCH_QUEUE_FROM_JAVA, new CallBackFunction() {

@Override

public void onCallBack(String data) {

// 先不要看这里,因为代码还没走到这一步,等回调的时候才会走这里,下面会有提示再来看。(省略部分代码)

List list = null;

try {

list = Message.toArrayList(data);// 解析H5传过来的Json数据

} catch (Exception e) {

e.printStackTrace();

return;

}

for (int i = 0; i < list.size(); i++) {

Message m = list.get(i);

String responseId = m.getResponseId();

// 如果是客户端调用H5方法则会有responseId这个值,也就是webView.callHandler

if (!TextUtils.isEmpty(responseId)) {

CallBackFunction function = responseCallbacks.get(responseId);

String responseData = m.getResponseData();

function.onCallBack(responseData);// 回调到webView.callHandler里面的回调方法

responseCallbacks.remove(responseId);

} else {// H5调用客户端会走这里

CallBackFunction responseFunction = null;

final String callbackId = m.getCallbackId();// 一般情况下都是有callbackId的,这是H5那边设置的

if (!TextUtils.isEmpty(callbackId)) {

// 这里实现的回调接口是提供给客户端再次去和H5交互的机会,对应webView.registerHandler(name,handler)里面的function

responseFunction = new CallBackFunction() {

@Override

public void onCallBack(String data) {

Message responseMsg = new Message();

responseMsg.setResponseId(callbackId);// js传过来的callbackId赋值给responseId回传给js,这样就可以配对了。

responseMsg.setResponseData(data);

queueMessage(responseMsg);// 向H5发送消息

}

};

}

BridgeHandler handler;

if (!TextUtils.isEmpty(m.getHandlerName())) {

handler = messageHandlers.get(m.getHandlerName());

}

if (handler != null){// 客户端只有registerHandler后取出来的handler才不为null

// 这一步就是调到了webView.registerHandler(name,handler)第二个参数BridgeHandler里了

handler.handler(m.getData(), responseFunction);

}

}

}

}

}

}

public void loadUrl(String jsUrl, CallBackFunction returnCallback) {

this.loadUrl(jsUrl); // 加载jsUrl="javascript:WebViewJavascriptBridge._fetchQueue();"

// 键值对形式存放响应回调接口,这里的key是"_fetchQueue"

responseCallbacks.put(BridgeUtil.parseFunctionName(jsUrl), returnCallback);

}

现在整理下发现H5第一次调客户端时只是实现一个回调方法(当然这个回调方法非常重要),然后用键值对的方式存储之后供下次配对。客户端会再一次loadUrl加载本地js文件中的_fetchQueue()方法:

// WebViewJavaScriptBridge.js

function _fetchQueue() {

var messageQueueString = JSON.stringify(sendMessageQueue);

sendMessageQueue = [];

// 会触发客户端的shouldOverrideUrlLoading,传递url的形式是:"yy://return/_fetchQueue/"+H5给客户端的数据

messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://return/_fetchQueue/' + encodeURIComponent(messageQueueString);

}

shouldOverrideUrlLoading的代码已经在之前贴出过,这里就不再贴出,然后会调用webView.handlerReturnData(url):

void handlerReturnData(String url) {

String functionName = BridgeUtil.getFunctionFromReturnUrl(url);// 拿到functionName="_fetchQueue"

CallBackFunction f = responseCallbacks.get(functionName);// 拿到的key就是为配对键值对的啊!还记得上面存储过了吗?

String data = BridgeUtil.getDataFromReturnUrl(url);// 拿到H5给客户端的数据

if (f != null) {

f.onCallBack(data);// 回调

responseCallbacks.remove(functionName);

return;

}

}

f.onCallBack(data)就是在flushMessageQueue实现的那个回调方法啊,所以这个时候就要回去看看那个方法里面具体做了什么(重点已注释),到此为止H5调客户端的方法流程基本已经走完,queueMessage(responseMsg)方法就不再具体讲了,作用就是向H5发消息(类似于客户端调用H5方法,但是有区别)。

3.2 客户端调用H5方法

我觉得再从源码一步步讲解是没什么意义的,只要理解了H5调用客户端方法就可以了,因为流程和H5调用客户端方法是相反的,也就是说WebViewJavaScriptBridge.js和BridgeWebView.java是功能相似的不同语言所写的文件,接下来我通过一张流程图过一遍客户端调用H5方法 :

客户端调H5方法.png

总结

现在的App开发熟练使用WebView以及和js交互是很有必要的,jsBridge的实现也不复杂,只要和H5定好协议,完全可以自己写一个jsBridge通信方式的框架。而且多阅读源码有助于自己的提升,从这些简单而精妙的源码入手是再合适不过了。

jsbridge实现及原理_jsBridge原理解析相关推荐

  1. 视觉SLAM开源算法ORB-SLAM3 原理与代码解析

    来源:深蓝学院,文稿整理者:何常鑫,审核&修改:刘国庆 本文总结于上交感知与导航研究所科研助理--刘国庆关于[视觉SLAM开源算法ORB-SLAM3 原理与代码解析]的公开课. ORB-SLA ...

  2. ORB-SLAM / ORB-SLAM2原理解读+代码解析(汇总了资料,方便大家学习)

    注释:本文非原创,初学搜集了很多资料附上链接,方便初学者学习,避免盲目搜索浪费时间. 目录 官方代码链接 代码框架思维导图 参考解读 参考链接- -一步步带你看懂orbslam2源码 ORB-SLAM ...

  3. Dubbo原理和源码解析之服务引用

    github新增仓库 "dubbo-read"(点此查看),集合所有<Dubbo原理和源码解析>系列文章,后续将继续补充该系列,同时将针对Dubbo所做的功能扩展也进行 ...

  4. Git内部原理之深入解析Git的引用和包文件

    一.Git 分支本质 如果对仓库中从一个提交(比如 1a410e)开始往前的历史感兴趣,那么可以运行 git log 1a410e 这样的命令来显示历史,不过需要记得 1a410e 是查看历史的起点提 ...

  5. 音视频开发(7)---流媒体服务器原理和架构解析

    流媒体服务器原理和架构解析 多媒体数据文件 一个完整的多媒体文件是由音频和视频两部分组成的,H264.Xvid等就是视频编码格式,MP3.AAC等就是音频编码格式,字幕文件只是附加文件.目前大部分的播 ...

  6. java+cache使用方法_JVM代码缓存区CodeCache原理及用法解析

    一. CodeCache简介 从字面意思理解就是代码缓存区,它缓存的是JIT(Just in Time)编译器编译的代码,简言之codeCache是存放JIT生成的机器码(native code).当 ...

  7. Tensorflow2.0---SSD网络原理及代码解析(二)-锚点框的生成

    Tensorflow2.0-SSD网络原理及代码解析(二)-锚点框的生成 分析完SSD网络的原理之后,一起来看看代码吧~ 代码转载于:https://github.com/bubbliiiing/ss ...

  8. 流媒体服务器原理和架构解析

    流媒体服务器原理和架构解析 多媒体数据文件 一个完整的多媒体文件是由音频和视频两部分组成的,H264.Xvid等就是视频编码格式,MP3.AAC等就是音频编码格式,字幕文件只是附加文件.目前大部分的播 ...

  9. Spark SQL架构工作原理及流程解析

    Spark SQL架构工作原理及流程解析,spark sql从shark发展而来,Shark为了实现Hive兼容,在HQL方面重用了Hive中HQL的解析.逻辑执行计划翻译.执行计划优化等逻辑. Sp ...

最新文章

  1. svn trunk branches tags 的用法
  2. 数字图像处理:第十五章 图象分割
  3. mysql启动warning: World-writable config file
  4. cookies可以跨域了~单点登陆(a.com.cn与b.com.cn可以共享cookies)
  5. Linux交换Esc和Caps
  6. Python命令行自动补全和记录历史命令
  7. 垃圾收集器与内存分配策略(五)之垃圾日志与常见参数
  8. Sql Server 开窗函数Over()的使用
  9. matlab for循环太慢,Matlab中每个for循环迭代的速度降低
  10. 关于java mail 发邮件的问题总结(转)
  11. 2019蓝桥杯C++B组 年号字串;完全二叉树的权值
  12. android开发访问百度搜索,Android开发如何添加搜索功能———大神求救啊
  13. C++11 关键字override和final
  14. 调用函数,判断一个数是否为素数
  15. 双眼融合训练一个月_双眼视觉是什么?为什么要进行视功能训练?
  16. linux暂停命令 pause_Linux-pause(2)的使用(day10)
  17. ctf镜子里面的世界_一个小编姐姐的CTF入坑之旅
  18. 基于数字电路交通灯信号灯控制系统设计-基于单片机病房温度监测与呼叫系统设计-基于STM32的无线蓝牙心电监护仪系统设计-基于STM32的智能蓝牙温控风扇控制设计-基于STM32的智能温室控制系统设计
  19. 一、降维——机器学习笔记——降维(特征提取)
  20. java唯一id_生成唯一ID的四种办法 程序员必备

热门文章

  1. .NET简谈设计模式之(策略模式)
  2. Brave 狮子王浏览器,撕裂式创新?
  3. Week6:PACT量化
  4. Mogodb中使用save和insert方式插入数据的区别
  5. 微信小程序——签到系统(入门级)
  6. java交叉编译安卓可用,Android开发实践:Android交叉编译工具链的使用
  7. 学习python-day01-01---转自Python分布式爬虫打造搜索引擎Scrapy精讲
  8. 股权作用和错误股权结构
  9. C++ 一维数组实现杨辉三角(Pascar Triangel)
  10. Gradient has outdated direction syntax. New syntax is like `to left` instead of `right`