抖音-JSBridge

抖音 JSBridge(旧版,现在统一使用via)
 
 
一、JSBridge是什么
  1. 主流App开发模式
  • Native App:传统原生APP开发模式。Android基于Java语言,底层调用Google的API,iOS基于OC或者Swift语言,底层调用ios官方提供的API。
  • Web App:网站开发模式。将页面部署在服务器上,用户使用浏览器访问,一般泛指 SPA(Single Page Application)模式开发出的网站。
  • Hybrid App:半Native半web混合开发模式。介于Web App、Native App两者之间,兼具Native良好交互体验和Web页跨平台开发优势。
  • React Native App:用JS写出的原生应用。

  2.Native vs Web

 
Native
Web
优点
可以调用系统底层API 交互顺畅,转场平滑 数据安全,稳定
跨多平台,表现力强 迭代快 即时上线,无需发版
缺点
任何改动需要发版(安卓热更新除外) 两端各一套实现方式,无法跨平台开发
无法调用系统底层API 性能取决于容器 安全性稳定性相对较弱
 
 
Native App
Web App
Hybrid App
React Native App
原生功能体验
优秀
良好
接近优秀
渲染性能
非常快
接近快
是否支持设备底层访问
支持
不支持
支持
支持
网络要求
支持离线
依赖网络
支持离线(资源存本地情况)
支持离线
更新复杂度
高(几乎总是通过应用商店更新)
低(服务器端直接更新)
较低(可以进行资源包更新)
较低(可以进行资源包更新)
编程语言
Android(Java) iOS(OC/Swift)
js+html+css3
js+html+css3
主要使用JS编写 语法规则JSX
社区资源
丰富
丰富(大量前端资源)
有局限(不同的Hybrid相互独立)
丰富(统一的活跃社区)
上手难度
难(不同平台需要单独学习)
简单(写一次,支持不同平台访问)
简单(写一次,运行任何平台)
中等(学习一次,写任何平台)
开发周期
较短
中等
开发成本
昂贵
便宜
较为便宜
中等
跨平台
不跨平台
所有H5浏览器
Android,iOS,h5浏览器
Android,iOS
APP发布
App Store
Web服务器
App Store
App Store

  3.Hybrid App

  • 定义:Hybrid App(混合模式开发的移动应用)底层功能API均由原生容器通过某种方式提供,业务逻辑由H5页面完成,最后原生容器加载H5,从而完成整个业务流程,只需要写一套代码即可,即可达到跨平台效果。
  • 原生容器 :Native中的webview组件,用于加载HTML文件。Android中是webview,iOS7以下有UIWebview,iOS7以上有了WKWebview。
  • hybrid架构:上层为web层,底层为native层,通信靠jsbridge

  4.hybrid核心--JSBridge技术:构建了 Native 和非 Native 间消息双向通信的通道

  • JS 向 Native 发送消息 : 调用相关功能、通知 Native JS当前状态。
  • Native 向 JS 发送消息 : 回溯调用结果、消息推送、通知 JS 当前 Native 的状态。
 
二、JSBridge实现原理
JSBridge是一种通用的交互理念,多种设计方式都可以实现,思路也不尽相同。
  1. JS 调用 Native 的几种通信方案
    • 特殊url scheme假跳转的请求拦截:h5发出一条跳转请求,其中跳转目的地是一个非法的不存在的地址,客户端拦截并分析请求从而调用native方法。
    • 优点: 兼容性好。这是所有JS调用Native的通信方式里,唯一同时支持安卓webview/苹果UIWebView/WKWebView的通信方式。
    • 缺点:webview会把调用封装为请求,时延达到200ms~400ms;跳转的URL存在长度限制。
    • JS上下文注入API:通过 WebView 提供的接口,向JS运行的Context(window)中直接注入对象或者方法,JavaScript调用时能直接执行相应的Native代码逻辑。
    • 优点: 由于同步返回调用速度非常快,参照alert。
    • 缺点: 低版本iOS系统不支持此方式;安卓 4.2 之前,注入JavaScript的接口是 addJavascriptInterface,存在安全漏洞,4.2 后引入JavascriptInterface新接口做替代,所以存在兼容性问题。
 

  2.请求拦截假跳转通信方式详解

  • url地址分为几部分:
  协议://域名/路径?参数 
  aweme://profile/?douyin_id=88 (假跳转)
 
  • JS发起跳转3种方式:
  1)在HTML中用a标签直接填写假请求地址
  <a href="aweme://profile/?douyin_id=88">A标签</a>
  2) 原地跳转:在JS中用location.href
  location.href = 'aweme://profile/?douyin_id=88'
  3) iframe跳转:在JS中创建一个iframe,插入dom之中进行跳转
  $('body').append('<iframe src="' + 'aweme://profile/?douyin_id=88' + '" style="display:none"></iframe>');
 
  • 拦截规则:因为客户端内打开H5页面的webview容器,会无差别拦截h5页面发送所有请求,真正的url地址会正常跳转,而协议域名符合一定规则的url地址则会被客户端拦截,拦截下来的url不会导致webview继续跳转,因此用户完全没有感知。我们可以利用这个条件,定义一些scheme规则,客户端读取伪协议域名的部分作为通信识别,如bytedance:// , snssdk1128://, aweme:// 可与正常协议做区分,读取路径作为指令识别,读取参数作为数据,并根据约定调用对应的native原生代码。
 
  • 客户端请求拦截:不同种类webview通过不同方法,都实现了这样的流程:首先根据协议/域名判断是否需要拦截此调用,若需要则取出路径,并匹配出指令,传入js携带参数数据调用相应native方法,停止webview的继续请求。
  • 1)安卓 shouldOverrideUrlLoading
  @Override
  public boolean shouldOverrideUrlLoading(WebView view, String url) {
    //1 根据url,判断是否是所需要的拦截的调用 判断协议/域名
    if (是){
      //2 取出路径,确认要发起的native调用的指令是什么
      //3 取出参数,拿到JS传过来的数据
      //4 根据指令调用对应的native方法,传递数据
      return true;
    }
    return super.shouldOverrideUrlLoading(view, url);
  }
  2)UIWebView ​webView:shouldStartLoadWithRequest:navigationType:
    (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{
      //1 根据url,判断是否是所需要的拦截的调用 判断协议/域名
      if (是){
        //2 取出路径,确认要发起的native调用的指令是什么
        //3 取出参数,拿到JS传过来的数据
        //4 根据指令调用对应的native方法,传递数据
        return NO;
        //确认拦截,拒绝WebView继续发起请求
      }
      return YES;
    }
  3)WKWebView webView:decidePolicyForNavigationAction:decisionHandler:
    (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
      //1 根据url,判断是否是所需要的拦截的调用 判断协议/域名
      if (是){
        //2 取出路径,确认要发起的native调用的指令是什么
        //3 取出参数,拿到JS传过来的数据
        //4 根据指令调用对应的native方法,传递数据
 
        //确认拦截,拒绝WebView继续发起请求
        decisionHandler(WKNavigationActionPolicyCancel);
      }else{
        decisionHandler(WKNavigationActionPolicyAllow);
      }
      return YES;
    }
 
 

  3.Native调用 Js

  • 使用evaluatingJavaScript 执行JS代码:
相比于 JavaScript 调用 Native, Native 调用 JavaScript 较为简单,毕竟不管是 iOS 的 UIWebView 还是 WKWebView,还是 Android 的 WebView 组件,都以子组件的形式存在于 View/Activity 中,Native想要调用JS的时候,可以把数据与调用的JS函数,通过字符串拼接成JS代码,交给WebView进行执行,直接调用相应的 API 即可执行拼接 JavaScript 字符串。所以设计成为暴露一个如JSBridge的对象供native调用。
 
  • 举个例子
1)JS中存在函数jsfunction()
function jsfunction(data){
  console.log(JSON.parse(data))
  //1 识别客户端传来的数据
  //2 对数据进行分析,从而调用或执行其他逻辑
}
2)客户端用OC拼接字符串,拼出js代码,并用json传递数据
//data是一个字典,把字典序列化
NSString *paramsString = [self _serializeMessageData:data];
NSString* javascriptCommand = [NSString stringWithFormat:@"jsfunction('%@');", paramsString];
//要求必须在主线程执行JS
if ([[NSThread currentThread] isMainThread]) {
  [self.webView evaluateJavaScript:javascriptCommand completionHandler:nil];
} else {
  __strong typeof(self)strongSelf = self;
  dispatch_sync(dispatch_get_main_queue(), ^{
    [strongSelf.webView evaluateJavaScript:javascriptCommand completionHandler:nil];
  });
}
这段代码只是用来拼接出这个字符串:jsfunction('{data:xxx,data2:xxx}');
再用evaluatingJavaScript或loadUrl对js代码进行执行,即完成了native对js方法的调用
 

  4.通信过程整理

  • S1. 客户端内H5里发送请求scheme,aweme://profile?douyin_id=233;
  • S2. 客户端内webview容器拦截所有请求,挨个请求做字符串匹配,判断是否以 aweme:// 开头;
  • S3. scheme命中匹配,客户端拆解出后面的参数得到操作名和对应的操作参数;
  • S4. 客户端执行对应的操作,打开个人profile页;
  • S5. Native功能调用完毕。
 

  5.回调设计

  • 场景问题:例如分享成功后增加积分等场景,h5网页如何知道客户端执行完毕,并执行相应回调呢?
  • 参照JSONP信息传递的执行过程:
JSONP利用<script>标签没有跨域限制的特点来达到与第三方通讯的目的
调用方需要提供一个回调函数 比如cb20180725 来接收数据:
  • 第三方包装callback参数作为函数名来包裹住响应的JSON数据如cb20180725({"param": “1111”})
  • 1. 在window上生成挂载一个随机名的全局函数,函数里执行success的回调函数;
  • 2.创建script标签,载入请求地址;
  • 3.服务端返回一段执行window上cb20180725函数的js代码: cb20180725({"param": “1111”})
  • 4.浏览器中执行完成jsonp回调。
 
  • JSBridge回调过程:
 
  • 1,H5发起scheme请求之前,随机生成一个callback_id,挂到window上;
  • 2,在callback_id指向的唯一函数里,执行想要的回调函数
  • 3,H5发送请求scheme,bytedance://profile?douyin_id=233&callback_id=dy20180724;
  • 4,客户端内webview容器拦截所有请求,挨个请求做字符串匹配,匹配是否以 bytedance:// 开头;
  • 5, scheme命中匹配,客户端拆解出后面的参数得到操作名和对应的操作参数;
  • 6,客户端执行对应的操作,打开个人profile页;
  • 7,执行完对应操作之后,客户端调用webview接口执行回调 javascript:dy20180724(json_data)
关键点:生成回调函数callback_id,回调函数存储(挂载在window)
原文地址:https://www.cnblogs.com/zhenjianyu/p/13284085.html