jsbridge实现及原理_jsBridge原理解析
导语
现在大多数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原理解析相关推荐
- 视觉SLAM开源算法ORB-SLAM3 原理与代码解析
来源:深蓝学院,文稿整理者:何常鑫,审核&修改:刘国庆 本文总结于上交感知与导航研究所科研助理--刘国庆关于[视觉SLAM开源算法ORB-SLAM3 原理与代码解析]的公开课. ORB-SLA ...
- ORB-SLAM / ORB-SLAM2原理解读+代码解析(汇总了资料,方便大家学习)
注释:本文非原创,初学搜集了很多资料附上链接,方便初学者学习,避免盲目搜索浪费时间. 目录 官方代码链接 代码框架思维导图 参考解读 参考链接- -一步步带你看懂orbslam2源码 ORB-SLAM ...
- Dubbo原理和源码解析之服务引用
github新增仓库 "dubbo-read"(点此查看),集合所有<Dubbo原理和源码解析>系列文章,后续将继续补充该系列,同时将针对Dubbo所做的功能扩展也进行 ...
- Git内部原理之深入解析Git的引用和包文件
一.Git 分支本质 如果对仓库中从一个提交(比如 1a410e)开始往前的历史感兴趣,那么可以运行 git log 1a410e 这样的命令来显示历史,不过需要记得 1a410e 是查看历史的起点提 ...
- 音视频开发(7)---流媒体服务器原理和架构解析
流媒体服务器原理和架构解析 多媒体数据文件 一个完整的多媒体文件是由音频和视频两部分组成的,H264.Xvid等就是视频编码格式,MP3.AAC等就是音频编码格式,字幕文件只是附加文件.目前大部分的播 ...
- java+cache使用方法_JVM代码缓存区CodeCache原理及用法解析
一. CodeCache简介 从字面意思理解就是代码缓存区,它缓存的是JIT(Just in Time)编译器编译的代码,简言之codeCache是存放JIT生成的机器码(native code).当 ...
- Tensorflow2.0---SSD网络原理及代码解析(二)-锚点框的生成
Tensorflow2.0-SSD网络原理及代码解析(二)-锚点框的生成 分析完SSD网络的原理之后,一起来看看代码吧~ 代码转载于:https://github.com/bubbliiiing/ss ...
- 流媒体服务器原理和架构解析
流媒体服务器原理和架构解析 多媒体数据文件 一个完整的多媒体文件是由音频和视频两部分组成的,H264.Xvid等就是视频编码格式,MP3.AAC等就是音频编码格式,字幕文件只是附加文件.目前大部分的播 ...
- Spark SQL架构工作原理及流程解析
Spark SQL架构工作原理及流程解析,spark sql从shark发展而来,Shark为了实现Hive兼容,在HQL方面重用了Hive中HQL的解析.逻辑执行计划翻译.执行计划优化等逻辑. Sp ...
最新文章
- svn trunk branches tags 的用法
- 数字图像处理:第十五章 图象分割
- mysql启动warning: World-writable config file
- cookies可以跨域了~单点登陆(a.com.cn与b.com.cn可以共享cookies)
- Linux交换Esc和Caps
- Python命令行自动补全和记录历史命令
- 垃圾收集器与内存分配策略(五)之垃圾日志与常见参数
- Sql Server 开窗函数Over()的使用
- matlab for循环太慢,Matlab中每个for循环迭代的速度降低
- 关于java mail 发邮件的问题总结(转)
- 2019蓝桥杯C++B组 年号字串;完全二叉树的权值
- android开发访问百度搜索,Android开发如何添加搜索功能———大神求救啊
- C++11 关键字override和final
- 调用函数,判断一个数是否为素数
- 双眼融合训练一个月_双眼视觉是什么?为什么要进行视功能训练?
- linux暂停命令 pause_Linux-pause(2)的使用(day10)
- ctf镜子里面的世界_一个小编姐姐的CTF入坑之旅
- 基于数字电路交通灯信号灯控制系统设计-基于单片机病房温度监测与呼叫系统设计-基于STM32的无线蓝牙心电监护仪系统设计-基于STM32的智能蓝牙温控风扇控制设计-基于STM32的智能温室控制系统设计
- 一、降维——机器学习笔记——降维(特征提取)
- java唯一id_生成唯一ID的四种办法 程序员必备
热门文章
- .NET简谈设计模式之(策略模式)
- Brave 狮子王浏览器,撕裂式创新?
- Week6:PACT量化
- Mogodb中使用save和insert方式插入数据的区别
- 微信小程序——签到系统(入门级)
- java交叉编译安卓可用,Android开发实践:Android交叉编译工具链的使用
- 学习python-day01-01---转自Python分布式爬虫打造搜索引擎Scrapy精讲
- 股权作用和错误股权结构
- C++ 一维数组实现杨辉三角(Pascar Triangel)
- Gradient has outdated direction syntax. New syntax is like `to left` instead of `right`