JSBridge深度剖析

概述

做过混合开发的人都知道Ionic和PhoneGap之类的框架,这些框架在web基础上包装一层Native,然后通过Bridge技术的js调用本地的库。

在讲JSBridge技术之前,我们来看一下传统的实现方式。

Android端

Native调JS

native调用js比较简单,只要遵循:"javascript: 方法名('参数,需要转为字符串')"的规则即可。

在4.4之前,调用的方式:

// mWebView = new WebView(this);
mWebView.loadUrl("javascript: 方法名('参数,需要转为字符串')"); 

//ui线程中运行
 runOnUiThread(new Runnable() {
        @Override
        public void run() {
            mWebView.loadUrl("javascript: 方法名('参数,需要转为字符串')");
            Toast.makeText(Activity名.this, "调用方法...", Toast.LENGTH_SHORT).show();
        }
});

4.4以后(包括4.4),使用以下方式:

mWebView.evaluateJavascript("javascript: 方法名('参数,需要转为字符串')", new ValueCallback() {
        @Override
        public void onReceiveValue(String value) {
            //这里的value即为对应JS方法的返回值
        }
});

说明:

  • 4.4之前Native通过loadUrl来调用JS方法,只能让某个JS方法执行,但是无法获取该方法的返回值
  • 4.4之后,通过evaluateJavascript异步调用JS方法,并且能在onReceiveValue中拿到返回值
  • 不适合传输大量数据(大量数据建议用接口方式获取)
  • mWebView.loadUrl("javascript: 方法名('参数,需要转为字符串')");函数需在UI线程运行,因为mWebView为UI控件

JS调Native

Js调用Native需要对WebView设置@JavascriptInterface注解,这里有个漏洞,后面会给大家说明。要想js能够Native,需要对WebView设置以下属性。

WebSettings webSettings = mWebView.getSettings();
 //Android容器允许JS脚本
webSettings.setJavaScriptEnabled(true);
//Android容器设置侨连对象
mWebView.addJavascriptInterface(getJSBridge(), "JSBridge");

这里我们看到了getJSBridge(),Native中通过addJavascriptInterface添加暴露出来的JS桥对象,然后再该对象内部声明对应的API方法。

private Object getJSBridge(){
    Object insertObj = new Object(){
        @JavascriptInterface
        public String foo(){
            return "foo";
        }  

        @JavascriptInterface
        public String foo2(final String param){
            return "foo2:" + param;
        }  

    };
    return insertObj;
}

那么Html怎么调用Native的方法呢?

//调用方法一
window.JSBridge.foo(); //返回:'foo'
//调用方法二
window.JSBridge.foo2('test');//返回:'foo2:test'

说明:

  • 在Android4.2以上(api17后),暴露的api要加上注解@JavascriptInterface,否则会找不到方法。
  • 在api17以前,addJavascriptInterface有风险,hacker可以通过反编译获取Native注册的Js对象,然后在页面通过反射Java的内置 静态类,获取一些敏感的信息和破坏
  • JS调用Native暴露的api,并且能得到相应返回值

注:说到WebView中接口隐患的问题,这里大家可以参考WebViw漏洞利用,不过Android发展到现在,这个漏洞基本没有了。

iOS端

Native调JS

Native调用js的方法比较简单,Native通过stringByEvaluatingJavaScriptFromString调用Html绑定在window上的函数。不过应注意Oc和Swift的写法。

//Swift
webview.stringByEvaluatingJavaScriptFromString("方法名(参数)")
//OC
[webView stringByEvaluatingJavaScriptFromString:@"方法名(参数);"];

说明:

  • Native调用JS方法时,能拿到JS方法的返回值
  • 不适合传输大量数据(大量数据建议用接口方式获取)

JS调Native

Native中通过引入官方提供的JavaScriptCore库(iOS7以上),然后可以将api绑定到JSContext上(然后Html中JS默认通过window.top.*可调用)。

引入官方的库文件

#import <JavaScriptCore/JavaScriptCore.h>

Native注册api函数(OC)

-(void)webViewDidFinishLoad:(UIWebView *)webView{
    [self hideProgress];
    [self setJSInterface];
}

-(void)setJSInterface{

    JSContext *context =[_wv valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];

    // 注册名为foo的api方法
    context[@"foo"] = ^() {

        //获取参数
        NSArray *args = [JSContext currentArguments];
        NSString *title = [NSString stringWithFormat:@"%@",[args objectAtIndex:0]];
        //做一些自己的逻辑
        //返回一个值  'foo:'+title
        return [NSString stringWithFormat:@"foo:%@", title];
    };
}               

Html中JS调用Native方法

window.top.foo('test');

说明:

  • iOS7才出现这种方式,在这之前,js无法直接调用Native,只能通过JSBridge方式简介调用
  • JS能调用到已经暴露的api,并且能得到相应返回值
  • iOS原生本身是无法被JS调用的,但是通过引入官方提供的第三方"JavaScriptCore",即可开放api给JS调用

JSBridge

什么是JSBridge

JSBridge:听其取名就是js和Native之前的桥梁,而实际上JSBridge确实是JS和Native之前的一种通信方式。简单的说,JSBridge就是定义Native和JS的通信,Native只通过一个固定的桥对象调用JS,JS也只通过固定的桥对象调用Native。JSBridge另一个叫法及大家熟知的Hybrid app技术。

流程:H5->通过某种方式触发一个url->Native捕获到url,进行分析->原生做处理->Native调用H5的JSBridge对象传递回调。

我们前面讲过了原生的WebView/UIWebView控件已经能够和Js实现数据通信了,那为什么还要JSBridge呢?
其实使用JSBridge有很多方面的考虑:

  • Android4.2以下,addJavascriptInterface方式有安全漏掉
  • iOS7以下,JS无法调用Native
  • url scheme交互方式是一套现有的成熟方案,可以完美兼容各种版本,对以前老版本技术的兼容。

url scheme

url scheme是一种类似于url的链接,是为了方便app直接互相调用设计的。具体来讲如果是系统的url scheme,则打开系统应用,否则找看是否有app注册这种scheme,打开对应app。
注:这种scheme必须原生app注册后才会生效。

而在我们实际的开发中,app不会注册对应的scheme,而是由前端页面通过某种方式触发scheme(如用iframe.src),然后Native用某种方法捕获对应的url触发事件,然后拿到当前的触发url,根据定义好的协议,分析当前触发了那种方法。

JSBridge技术实现

要实现JSBridge,我们需要按以下步骤分析:

  • 第一步:设计出一个Native与JS交互的全局桥对象
  • 第二步:JS如何调用Native
  • 第三步:Native如何得知api被调用
  • 第四步:分析url-参数和回调的格式
  • 第五步:Native如何调用JS
  • 第六步:H5中api方法的注册以及格式

JSBridge的完整流程可总结为:

设计Native与JS交互的全局桥对象

我们规定,JS和Native之间的通信必须通过一个H5全局对象JSbridge来实现。该对象有如下特点:
该对象名为"JSBridge",是H5页面中全局对象window的一个属性,形如:

var JSBridge = window.JSBridge || (window.JSBridge = {});

该对象有如下方法:

  • registerHandler( String,Function )H5调用注册本地JS方法,注册后Native可通过JSBridge调用。调用后会将方法注册到本地变量messageHandlers 中。
  • callHandler( String,JSON,Function )H5调用 调用原生开放的api,调用后实际上还是本地通过url scheme触发。调用时会将回调id存放到本地变量responseCallbacks中
  • _handleMessageFromNative( JSON )Native调用 原生调用H5页面注册的方法,或者通知H5页面执行回调方法

JS调用Native

我们定义好了全局桥对象,可以通过它的callHandler方法来调用原生的api。

callHandler函数内部实现过程

在执行callHandler时,内部经历了以下步骤:

  1. 判断是否有回调函数,如果有,生成一个回调函数id,并将id和对应回调添加进入回调函数集合responseCallbacks中。
  2. 通过特定的参数转换方法,将传入的数据,方法名一起,拼接成一个url scheme
//url scheme的格式如
//基本有用信息就是后面的callbackId,handlerName与data
//原生捕获到这个scheme后会进行分析
var uri = CUSTOM_PROTOCOL_SCHEME://API_Name:callbackId/handlerName?data
  1. 使用内部早就创建好的一个隐藏iframe来触发scheme
//创建隐藏iframe过程
var messagingIframe = document.createElement('iframe');
messagingIframe.style.display = 'none';
document.documentElement.appendChild(messagingIframe);

//触发scheme
messagingIframe.src = uri;

注:正常来说是可以通过window.location.href达到发起网络请求的效果的,但是有一个很严重的问题,就是如果我们连续多次修改window.location.href的值,在Native层只能接收到最后一次请求,前面的请求都会被忽略掉。所以JS端发起网络请求的时候,需要使用iframe,这样就可以避免这个问题。

Native通知api被调用

上一步,我们已经成功在H5页面中触发scheme,那么Native如何捕获scheme被触发呢?

根据系统不同,Android和iOS分别有自己的处理方式。

Android

在Android中(WebViewClient里),通过shouldoverrideurlloading可以捕获到url scheme的触发。

public boolean shouldOverrideUrlLoading(WebView view, String url){
    //如果返回false,则WebView处理链接url,如果返回true,代表WebView根据程序来执行url
    return true;
}

iOS

iOS中,UIWebView有个特性:在UIWebView内发起的所有网络请求,都可以通过delegate函数在Native层得到通知。这样,我们可以在webview中捕获url scheme的触发(原理是利用 shouldStartLoadWithRequest)

- (BOOL)webView:(UIWebView )webView shouldStartLoadWithRequest:(NSURLRequest )request navigationType:(UIWebViewNavigationType)navigationType {
    NSURL *url = [request URL];

    NSString *requestString = [[request URL] absoluteString];
    //获取利润url scheme后自行进行处理

分析url-参数和回调的格式

在前面的步骤中,Native已经接收到了JS调用的方法,那么接下来,原生就应该按照定义好的数据格式来解析数据了,Native接收到Url后,可以按照这种格式将回调参数id、api名、参数提取出来,然后按如下步骤进行。

  1. 根据api名,在本地找寻对应的api方法,并且记录该方法执行完后的回调函数id
  2. 根据提取出来的参数,根据定义好的参数进行转化
  3. 原生本地执行对应的api功能方法
  4. 功能执行完毕后,找到这次api调用对应的回调函数id,然后连同需要传递的参数信息,组装成一个JSON格式的参数
  5. 通过JSBridge通知H5页面回调

Native调用JS

到了这一步,就该Native通过JSBridge调用H5的JS方法或者通知H5进行回调了。其中的messageJSON数据格式根据两种不同的类型。

JSBridge._handleMessageFromNative(messageJSON);

Native通知H5页面进行回调
数据格式为: Native通知H5回调的JSON格式。
Native主动调用H5方法
Native主动调用H5方法时,数据格式是:{handlerName:api名,data:数据,callbackId:回调id}:

  • handlerName String型 需要调用的,h5中开放的api的名称
  • data JSON型 需要传递的数据,固定为JSON格式(因为我们固定H5中注册的方法接收的第一个参数必须是JSON,第二个是回调函数)
  • callbackId String型 原生生成的回调函数id,h5执行完毕后通过url scheme通知原生api成功执行,并传递参数

H5中api方法的注册以及格式

前面有提到Native主动调用H5中注册的api方法,那么h5中怎么注册供原生调用的api方法呢?

JSBridge.registerHandler('testH5Func',function(data,callback){
    alert('测试函数接收到数据:'+JSON.stringify(data));
    callback&&callback('测试回传数据...');
});

如上代码,其中第一个data即原生传过来的数据,第二个callback是内部封装过一次的,执行callback后会触发url scheme,通知原生获取回调信息.

完善JSBridge方案

github上有一个开源项目,它里面的JSBridge做法在iOS上进一步优化了,所以参考他的做法,这里进一步进行了完善。地址marcuswestin/WebViewJavascriptBridge

JSBridge对象图解:

JSBridge实现完整流程:

总结

那么我们在实际的开发中,如何针对Android和iOS的不同情况,统一出一种完整的方案。

另类实现:不采用url scheme方式

前面提到的JSBridge都是基于url scheme的,但其实如果不考虑Android4.2以下,iOS7以下,其实也可以用另一套方案的。

  • Native调用JS的方法不变
  • JS调用Native是不再通过触发url scheme,而是采用自带的交互
    具体来讲:
    Android中,原生通过 addJavascriptInterface开放一个统一的api给JS调用,然后将触发url scheme步骤变为调用这个api,其余步骤不变。

OS中,原生通过JavaScriptCore里面的方法来注册一个统一api,其余和Android中一样。

时间: 2024-10-21 20:59:05

JSBridge深度剖析的相关文章

深度剖析百度百科单页高排名的内因

百度百科在seoer心中一直是个神话,单页高排名.如何能使自己的网站做到向百科这样的优,走进百科代码,深度剖析百度百科单页高排名的内因. 一.Title Description Keywords如何写   从这个图片中可以看出,百度百科提倡精简不臃肿的title书写方式,而不是现在大家追求的关键词堆叠,其次大家可能也发现了,description和keywords都已经不存在了,只有标题,显示在搜索引擎中的描述都是自动提取网页的前一部分.也就是说你可以完全忽略你的后两项. 二.LOGO如何用  

对Ruby VS Java误区的深度剖析

Relevance咨询公司的Stuart Halloway最近编写了一个关于"Ruby vs. Java之怪谈"的系列博客文章 .这个系列文章的灵感,源自他最近从一个从零起步.没有先前约束的Ruby项目转回一个成熟完备的Java 项目后的心得体会.在这个历时多日的项目过程中,Halloway对以下几个"误区"进行了探索: 误区之一:Ruby适合小型项目,而Java更适用于大型的.复杂的项目. 概括起来,Halloway主张,对于小型项目来说,诸如未知因素一类的问题可

DOCTYPE 标签的深度剖析以及使用选择

  <!DOCTYPE>的定义: <!DOCTYPE>声明位于文档中的最前面的位置,处于<html>标签之前.此标签可告知浏览器文档使用哪种HTML或XHTML规范. 该标签可声明三种DTD类型,分别表示严格版本.过渡版本以及基于框架的HTML版本.(假如文档中的标记不遵循doctype声明所指定的DTD,这个文档除了不能通过代码校验之外,还有可能无法在浏览器中正确显示.) <!DOCTYPE>的用法: <!DOCTYPE html PUBLIC &q

大众点评开源分布式监控平台 CAT 深度剖析

一.CAT介绍 CAT系统原型和理念来源于eBay的CAL的系统,CAT系统第一代设计者吴其敏在eBay工作长达十几年,对CAL系统有深刻的理解.CAT不仅增强了CAL系统核心模型,还添加了更丰富的报表.自2014年开源以来,CAT在携程.陆金所.猎聘网.找钢网等多家互联网公司生产环境应用. CAT是一个实时和接近全量的监控系统,它侧重于对Java应用的监控,基本接入了美团点评上海侧所有核心应用.目前在中间件(MVC.RPC.数据库.缓存等)框架中得到广泛应用,为美团点评各业务线提供系统的性能指

深度剖析Struts2远程代码执行漏洞

本文讲的是深度剖析Struts2远程代码执行漏洞, 三月初,安全研究人员发现世界上最流行的JavaWeb服务器框架之一– Apache Struts2存在远程代码执行的漏洞,Struts2官方已经确认该漏洞(S2-046,CVE编号为:CVE-2017-5638)风险等级为高危漏洞. 漏洞描述 该漏洞是由于上传功能的异常处理函数没有正确处理用户输入的错误信息,导致远程攻击者可通过修改HTTP请求头中的Content-Type值,构造发送恶意的数据包,利用该漏洞进而在受影响服务器上执行任意系统命令

深度剖析俄罗斯黑客组织APT29的后门

本文讲的是深度剖析俄罗斯黑客组织APT29的后门,POSTSPY最大程度的利用了内置于Windows系统中的特性来设置隐蔽的后门,这些特性被称为"living off the land"(意思是入侵者使用系统凭据和合法的软件管理工具访问系统,感染和收集有价值的数据).POSHSPY使用WMI来存储后门代码,使其对不熟悉WMI机制的人不可见.PowerShell只有在合法系统过程能被执行,如果是恶意代码执行,能通过加强日志分析或者内存就可以发现. POSHSPY中的特点,如罕见的后门信标

深度剖析Petya病毒:反社会人格的恶性病毒 只为破坏不为牟利

本文讲的是深度剖析Petya病毒:反社会人格的恶性病毒 只为破坏不为牟利, 一.概述 6月27日晚间,代号为"Petya"的勒索病毒肆虐全球,根据外国媒体报道,俄罗斯石油公司Rosneft.乌克兰国家储蓄银行和政府系统都受到了攻击,仅俄.乌两国就有80多家公司被该病毒感染,就连乌克兰副总理的电脑也不幸中招.其他受影响的国家包括英国.印度.荷兰.西班牙.丹麦等. 经过深度分析,火绒安全团队惊讶地发现,Petya和以往的勒索病毒有很大不同–病毒作者精心设计制作了传播.破坏的功能模块,勒索赎

block深度剖析

title: block深度剖析 date: 2016-04-18 23:46:43 tags: block 分两部分内容来剖析block: 怎么用 为什么这么用 block的堆栈 分类: 根据Block在内存中的位置分为三种类型NSGlobalBlock,NSStackBlock, NSMallocBlock. NSGlobalBlock:类似函数,位于text段: NSStackBlock:位于栈内存,函数返回后Block将无效: NSMallocBlock:位于堆内存.需要开发者进行释放.

《Photoshop混合模式深度剖析》—第2章构建参考图像

构建参考图像 Photoshop混合模式深度剖析 实现色彩混合或是其他效果的方法有很多,很多人更习惯于处理真实的照片,但我更喜欢通过抽象的示意图来解决问题.因此,我需要建立示例文件,并调整各种参数以实现预期的效果,这样就不会受到真实照片中复杂色彩的干扰.使用这种方法的关键是,注意操作和根据色彩及其他调整而生成的结果之间的关系.尽管可以设计许多其他文件和方案,但最好的测试方法通常是处理自己想要调整的图像.将参考文件放在触手可及之处,或者是在灵感来临时构建参考文件,这是更好地处理具体图像的一种方式.