本文来自阿里巴巴飞猪前端专家@孝瓘,在移动端容器建设有深厚的经验,热爱前端/iOS/Android 开发,从端视角负责过飞猪H5/RN/Weex/小程序/Flutter容器的建设,本文试图从前端视角,向读友揭一揭“App 容器”的面纱,欢迎大伙一起交流。

App 容器,简言之,App 承载某类应用(H5/RN/Weex/小程序/Flutter ...)的运行环境,可主动干预并进行功能扩展,达到丰富能力、优化性能、提升体验的目的,如页面数据预取(prefetch)缩短页面可用耗时、WebAR 将 AR 能力赋予 H5、Native 地图与 H5 复合渲染交互。

数据预取 WebAR 能力 Native 与 H5 复合

本篇主要就 H5 容器(WebView)相关建设进行概要展开。

我们先来做一个类比,通过 H5 的视角简要看一看 Android、iOS,进而更容易理解 WebView 容器建设机理。

内容 H5 Android iOS
窗口 Window PhoneWindow UIWindow
页面控制 Html Activity UIViewController
内容区 body contentView(DecorView) view
常规 UI(容器、文本、图片) div、content、img View、TextView、ImageView UIView、UILabel、UIImageView

通过如上对比可知,Native 与 H5 有很多相通之处的,如:H5 是一个 html 创建一个页面;Android 是一个 Activity 创建一个页面;iOS 是 UIViewController 创建一个页面。不同处在于, Native 本身有完善的页面栈管理机制,在同一个 runtime 环境里控制页面间转换;还可以管理多个窗口(Window),有多线程/进程(仅 Android)辅助合理使用资源保障主线程/进程性能,是 App 的体验。而 H5 本身受运行环境限制,只能在一个窗口里活动,目前缺少同一个 Runtime 内成熟的页面栈管理机制,当前 SPA 方式切换 view 来模拟“页面转场”,已是 WebApp 体验的一种较佳实现了。所以,在 App 里,H5 期望能借助更多的 Native 能力。

在 App 里,是通过 WebView 来访问 h5 页面的,先来看一下 WebView 的官方释义:

A View that displays web pages. This class is the basis upon which you can roll your own web browser or simply display some online content within your Activity. It uses the WebKit rendering engine to display web pages and includes methods to navigate forward and backward through a history, zoom in and out, perform text searches and more.

其实,WebView 在 Android/iOS 两端的实现,都是继承自其 View/UIView 基类。对于 Native 原生来说,WebView 本身通过加载 h5 页面、通过 Chromium/WebKit 内核解析并进行 UI 合成,生成 view,Activity/UIViewController 实例的 View 通过 addView(Android)/addSubview(iOS)将 web view 添加进视图层,UI 合成,然后上屏展示。

我们知道,App 可以使用系统能力,但 WebView(类比浏览器)出于安全等考虑,默认并不提供。我们建设容器的一个主要目的是将 App 这些可用能力开放给容器。下面就谈一谈这部分能力是如何提供出来的,也就是下面要讲的桥通道相关建设。


桥通道

桥有接连之义,将本不互通的二者通过桥连通起来,形象化的描述了“App 能力”以“通道”的形式输送给容器使用,本质上就是如何让我们的 H5 能够使用这些能力。下面分开来看两端的实现思路:

Android

通过  WebView  的  addJavascriptInterface  方法来建立 Java 与 JS 对象映射关系互通。对系统版本要求 4.2+ ,主流 App 基本都基于 5+适配了,兼容低版本的方案就略过了。如下:

  1. 定义与  WebView JS 上下文(即 window 挂载对象)建立映射关系的 Android JavascriptInterface 实现类及 call 方法

    package ...
    ...
    import android.webkit.JavascriptInterface;
    public class JSBridgeChannel {@JavascriptInterface// JS侧JSON.stringify序列化处理options对象public void call(String api, String options){//根据api及传递过来的参数处理Native对应的功能...}
    }
    
  2. 在 WebView 的 loadUrl 时机(保证 H5 JS 运行前桥通道已准备 ok),通过  WebView  的  addJavascriptInterface  方法建立 Android 类实例对象 与 JS 对象 的映射关系

    @Override
    public void loadUrl(String url) {...//…构造函数所需参数;AliJSBridge 为JS映射对象名mWebView.addJavascriptInterface(new JSBridgeChannel(…), "JSBridge");
    }
    
  3. WebView 里 JS 即可同步调用

    window.JSBridge.call("api", JSON.stringify(options));
    

也可以借助 Android 系统 提供的 JS 在调试 Native 代码里面打印日志的 API onConsoleMessage,通过获取 message 来做通讯数据,但不推荐。

iOS

WKWebView 提供了 MessageHandler 方式来处理 JS 与 Native 的数据交互,其优点是同步调用,性能/稳定性更优,更少内存占用。对系统版本要求 iOS 8+,主流 App 基本都基于 9+适配了。

WKWebView 在初始化时:[[WKWebView alloc] initWithFrame:frame configuration:config],其配置参数configuration 是 WKWebViewConfiguration 类型参数,WKWebViewConfiguration 有一个属性叫 userContentController,是WKUserContentController类型参数,WKUserContentController 有个实例方法 [addScriptMessageHandler:name:](https://developer.apple.com/documentation/webkit/wkusercontentcontroller/1537172-addscriptmessagehandler?language=objc),官方释义清晰地说明了,其可建立起 JS 与 Native 的通讯通道:

Adding a script message handler with name name causes the JavaScript function window.webkit.messageHandlers.name.postMessage(messageBody) to be defined in all frames in all web views that use the user content controller.

  1. 在 VC init 时机进行 WKWebView  初始化,创建 WKWebViewConfiguration 对象,配置 MessageHandler 对象。通过addScriptMessageHandler:name: 添加实现 WKScriptMessageHandler 协议的对象,以及被 JS 调用的方法名。注:记得在 VC dealloc 时,通过removeScriptMessageHandlerForName 移除释放。

    WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
    //添加ScriptMessageHandler,即H5侧可以调用的方法bridge
    [configuration.userContentController addScriptMessageHandler:self initWithDelegate:self] name:@"bridge"];
    //创建WKWebView
    [[WKWebView alloc] initWithFrame:frame configuration:configuration];
    
  2. 实现WKScriptMessageHandler协议代理方法,当 JS 通过 window.webkit.messageHandlers 发送 Native 消息时,此方法会被调用

    - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{if ([@"bridge" isEqualToString:message.name]){//提取apiNSString *api = [NSString stringWithString:message.body[@"_api_"]];//根据api及传递过来的参数去调用Native对应的功能...}
    }
    
  3. WebView 里 JS 即可同步调用

    const options = {//注意这里,是一个技巧性的处理方式,可以避免过多的messageHandlers注册_api_: ‘apiName',...
    };
    window.webkit.messageHandlers['bridge'].postMessage(options);
    

统一桥 API 调用方式

如上是两端通过同步方式向 WebView 的 JSContext 上,即 Window 上挂载了 JS 可以调用的对象方法,但两端的使用方式还是有差异。为了能够方便 H5 使用统一,两端还可以分别注入一段兼容封装的 JS 脚本,将两端的桥功能统一成 JSBridge.call(api, options, success, failure) 形式调用。

  • Android 侧 JS 脚本注入(在 mWebView.addJavascriptInterface 之后注入即可):

    String js = "!(_ => {   // JS封装code })();";
    mWebView.evaluateJavascript(js, new ValueCallback<String>() {@Overridepublic void onReceiveValue(String value) {...}
    });
    
  • iOS 侧 JS 脚本注入:

    NSString *js = @"!(_ => {   // JS封装code })();";
    WKUserScript *script = [[WKUserScript alloc] initWithSource: jsinjectionTime:WKUserScriptInjectionTimeAtDocumentStart  // 注入时机按需配置即可:WKUserScriptInjectionTimeAtDocumentStart->刚开始创建 Dom 时;WKUserScriptInjectionTimeAtDocumentEnd -> DomReady时forMainFrameOnly:NO];
    [_webView.configuration.userContentController addUserScript: script];
    

若对更底层的实现有兴趣,可以继续向下深入

  • Android 下,Java 的 JVM 是 C/C++语言开发,V8 编程语言是 C++,提供了数据类型间转换的Value 类及其子类,在其官网 About V8 里也有明确说明:V8 enables any C++ application to expose its own objects and functions to JavaScript code.。与 Java 的JNI 同理。

  • iOS 下,Objective-C 是 C 语言的严格超集,支持 C++,JavaScriptCore 编程语言也是 C++,同样提供了数据类型间转换的JSValue 类,其官网释义也有明确说明:

    A JSValue instance is a reference to a JavaScript value. ... You can also use this class to create JavaScript objects that wrap native objects of custom classes or JavaScript functions whose implementations are provided by native methods or blocks.

  • C++ 几乎又是 C 的超集,在理解 JS 与 Java、OC 交互就明了了。以 V8 示例,比如实现一个 function 让 JS 调用:

    V8 中,有两个模板 (Template) 类:对象模板 (ObjectTemplate) 与 函数模板 (FunctionTemplate) ,用以定义 JavaScript 对象和 JavaScript 函数。

    // function 定义
    Handle<Value> fn(const Arguments& args){//do something
    }
    // JSContext
    Handle<ObjectTemplate> global = ObjectTemplate::New();
    // 将fn Binding到JSContext上,JS便可调用 fn 了
    Handle<FunctionTemplate> fn_template = FunctionTemplate::New(fn);
    global->Set(String::New("fn"), fn_template);
    

通过桥通道支持,H5 便有了 App 级的能力,下面谈一点容器在性能上的优化建设-网络优化。


网络优化

原生 Native 之所以体验平滑,有一关键点,是其页面依赖的静态资源大部分已打进安装包,跟随 App 的安装到了用户本地,节省了网络 IO 开销。顺着这个思路,在 5G 真正平民化之前,网络 IO 消耗,仍是提升性能的一个优化点,这块简要谈一下思路。

主要是两方面:

  1. 静态资源,html/js/css/图片/字体/视频等,通过离线化、预加载、懒加载、开启 WebView 缓存复用等进行文件获取前置备用或复用,当前用户访问页面加载资源时,容器拦截资源网络请求,命中离线或缓存的资源文件并使用。离线化与预加载可直接节省首次网络 IO 性能消耗,其他方式可以节省二次网络 IO 性能消耗。

  • 离线化:即像 Native 一个打进 App 安装包内

  • 预加载:App 启动后,在页面使用前,提前加载资源到用户本地备用

  • 接口数据预取,选择合适的时机在用户访问页面前将接口数据获取到,当用户进入页面时,拦截接口网络请求直接命中本地缓存数据并使用。

  • 这层优化,需要有配套的端远程控制机制与管理后台。合理设计总控机制,管控静态文件的发布、版本/patch 更新、下线等,以及接口数据预取的匹配规则、生命周期管理、静态/动态参数配置处理等。

    我们主要就静态资源网络优化实现,分端简要介绍一下实现思路,主要是对静态资源网络请求进行拦截处理:

    Android

    通过shouldInterceptRequest方法拦截处理(系统要求 4.0+),官方释义清晰明了:

    Notify the host application of a resource request and allow the application to return the data. If the return value is null, the WebView will continue to load the resource as usual. Otherwise, the return response and data will be used.

    @Override
    public WebResourceResponse shouldInterceptRequest(WebView view, String url) {WebResourceResponse response = null;if(// 匹配拦截规则){// 容器生成response// WebResourceResponse(String mimeType, String encoding, int statusCode, String reasonPhrase, Map<String, String> responseHeaders, InputStream data)response =  new WebResourceResponse(...);}return response;
    }
    

    iOS

    受“官方”因素影响,iOS 的实现相对复杂些,了解过其发版审核的同学应该懂得的,这里就略过了。。。

    注意事项

    response 的 header 要处理正确

    1. 要正确处理拦截资源的 content-type

    2. 要正确处理拦截资源的Access-Control-Allow-Origin,避免跨域资源校验问题

    接口数据预取本质上也是拦截识别处理,因为涉及静态/动态参数处理、有效期控制等,要复杂的多,这里就不再展开了。

    下面这块,我们概要性地谈一谈容器增强能力的建设~ WebAR 支持的实现思路:


    WebAR

    在说 WebAR 之前,先看一下 AR。简要讲:AR 是通过启动摄像头获取现实环境,以视频帧的形式将现实环境数据传输给 识别模块 与 绘制模块,识别模块将识别结果数据传递给绘制模块(根据业务配置的识别规则处理成对应的虚拟事物),绘制模块将 现实环境数据与识别结果数据处理后进行展现。

    WebAR 是两个概念,是 Web + AR,在 Web 端提供 AR 能力。Web 自身,依靠 WebGL 实时图形渲染 与 WebRTC 实时视频流处理能力,能够实现 AR 体验。但受其运行环境标准不一、渲染性能差等因素影响,我们在容器侧进行了能力干预支持,以保障其功能与性能体验等。

    在容器侧建设上,将 AR 能力集成到 App 上,再将 AR 能力提供给 WebView 使用(结合上面讲到的桥通道能力理解),以 H5 页的形式呈现。我们 Android 侧接入的是 UCWebView 提供的 AR 能力,使用 WebGL 渲染;iOS 是集成的 ARKit,通过全屏的 OpenGLView 上放置一个背景透明的 WebView 实现的混合渲染。

    两端提供了配置识别模块能力支持。目前识别主要分为两类:一类基于 Maker 的识别(此类是常用的,识别标记图),另一类是基于位置的识别(通俗理解是通过手机传感器的方向和位置增强沉浸感,如位置远显示的内容较小,反之则大)。

    当前小程序正如日中天,其有一项能力是很好的结合了 Native 与 H5,即“同层渲染”,下面我们也“纸上谈兵”一次。不过,我们也已有业务诉求,要做同层渲染的建设,将 Native 的 Video 等原生组件应用到 WebView 上满足业务体验需要。


    同层渲染

    这里讲的同层渲染,是将 Native view 合成到 WebView 上,可以通过 CSS 控制 Native View 在页面的样式。

    以下介绍的两端方案,是基于目前笔者了解到的实现方案里最佳的。

    Android

    需要基于 Chromium 内核扩展自研的 WebView。Chromium 支持 WebPlugin 机制,用来识别解析 dom 标签。其思路:

    1. html 里创建 dom 节点,指定组件类型,供容器识别处理

    2. Chromium 创建 WebPlugin 实例,并生成 RenderLayer,其作用是创建独立的层并返回相应的画布供视图绘制使用

    3. Android 根据识别的 组件类型 ,初始化一个对应的原生组件

    4. Android 将原生组件的视图绘制 到 RenderLayer 所绑定的 SurfaceTexture 上(将 Android 的 UI Toolkit 的视图的数据送到 Texture 供 openGL 绘制使用)

    5. Chromium 将此 RenderLayer 与 Web 页面的 View 进行合成渲染

    iOS

    基于 WKWebView,WK 在内部采用的是分层的方式进行渲染,一般会将多个 dom 节点,合并到一个层上进行渲染。因此,dom 节点和层之间不存在一一对应关系。但是,若将一个 dom 节点的 CSS 属性设置为 “overflow: scroll” 后,WKWebView 便会为其生成一个 WKChildScrollView,且 WebKit 内核已经处理了 WKChildScrollView 与其他 dom 节点之间的层级关系,这时 dom 节点就和层之间有一一对应关系了。所以,同层渲染可基于 WKChildScrollView 实现:

    1. html 里创建 din 节点并设置其 CSS 属性为 overflow: scroll,指定组件类型,供容器识别处理

    2. iOS 查找到该 dom 节点对应的原生 WKChildScrollView 组件

    3. iOS 根据识别的 组件类型 ,初始化一个对应的原生组件

    4. iOS 将原生组件挂载到该 WKChildScrollView 节点上作为其子 view,这样原生组件就被插入到了 webView 上

    5. WebKit 完成渲染


    结语

    先到这了 ????

    关于奇舞周刊

    《奇舞周刊》是360公司专业前端团队「奇舞团」运营的前端技术社区。关注公众号后,直接发送链接到后台即可给我们投稿。

前端需要懂的 APP 容器原理相关推荐

  1. 前端必须懂的计算机网络知识—(XSS、CSRF和HTTPS)

    前端必须懂的计算机网络知识系列文章: DNS服务器和跨域问题 计算机网络的分层模型 IP地址和MAC地址 前端必须懂的计算机网络知识-(跨域.代理.本地存储) 前端必须懂的计算机网络知识-(TCP) ...

  2. iOS:app直播---原理篇

    [如何快速的开发一个完整的iOS直播app](原理篇) 转载自简书@袁峥Seemygo:http://www.jianshu.com/p/7b2f1df74420 一.个人见解(直播难与易) 直播难: ...

  3. (转)【如何快速的开发一个完整的iOS直播app】(原理篇)

    原文链接:https://www.jianshu.com/p/bd42bacbe4cc [如何快速的开发一个完整的iOS直播app](原理篇) [如何快速的开发一个完整的iOS直播app](原理篇) ...

  4. grpc通信原理_容器原理架构详解(全)

    目录 1 容器原理架构 1.1 容器与虚拟化 1.2 容器应用架构 1.3 容器引擎架构 1.4 Namespace与Cgroups 1.5 容器镜像原理 2 K8S原理架构 2.1 K8S主要功能 ...

  5. 前端必读:浏览器内部工作原理

    前端必读:浏览器内部工作原理 作者: Tali Garsiel  发布时间: 2012-02-09 14:32  阅读: 2133 次  原文链接   全屏阅读  [收藏]   http://kb.c ...

  6. 前端必须懂的计算机网络知识—(跨域、代理、本地存储)

    前端必须懂的计算机网络知识系列文章: DNS服务器和跨域问题 计算机网络的分层模型 IP地址和MAC地址 前端必须懂的计算机网络知识-(跨域.代理.本地存储) 前端必须懂的计算机网络知识-(TCP) ...

  7. 前端该懂交互设计吗?

    前端该懂交互设计吗? 接到一个前端动效任务:设计一个点击展开的动效 效果如下: (在产品看来这个交互是没必要存在,根据公司需求这也成了唯一的方法(在移动端页面右边添加一个控件,这种控件会将用户的注意力 ...

  8. serverlet 原理_容器原理架构详解(全)

    目录 1 容器原理架构:容器与虚拟化.容器应用/引擎架构.Namespace与Cgroups.镜像原理 2 K8S原理架构:K8S主要功能.K8S 系统架构.Pod原理与调度 3 K8S存储方案:容器 ...

  9. layer output 激活函数_一文彻底搞懂BP算法:原理推导+数据演示+项目实战(下篇)...

    在"一文彻底搞懂BP算法:原理推导+数据演示+项目实战(上篇)"中我们详细介绍了BP算法的原理和推导过程,并且用实际的数据进行了计算演练.在下篇中,我们将自己实现BP算法(不使用第 ...

最新文章

  1. AI超算“攒机”时代到来:为降低算力成本,这家公司牵头开放硬件标准
  2. CSS3秘笈第三版涵盖HTML5学习笔记13~17章
  3. linux shell sed awk 命令(2)-awk
  4. excel vba 调用webbrowser_Python杀死Excel?我只会用Python来增强Excel!
  5. 一种可提高导师寿命的有效方案
  6. CodeVS 3027 线段覆盖2(DP)
  7. java 预览office_java在线预览office
  8. 上海大学计算机网络实验报告3,上海大学计算机组成原理实验报告(全)
  9. Matlab Tricks(二十四)—— title 置于图像之下(包括 subplots 的情形)
  10. 如何实现微信小程序API的Promise化
  11. foobar2000 常用插件搜集
  12. 篮球计时计分器c语言程序,篮球赛计时计分器程序源代码.doc
  13. 时间换算:UTC是世界协调时,BJT是北京时间,UTC时间相当于BJT减去8
  14. 无主之地计算机中丢失,”无主之地3“游戏存档丢失!解决方法汇总
  15. 阿里云轻量级应用服务器如何使用?
  16. 采用igraph包分析网络数据
  17. 微型计算机的外储存器是指什么,微型计算机的外储存器是指
  18. ConnectBot的使用
  19. MOS管基本驱动电路
  20. 计算机主机漏电,电脑主机箱漏电六大原因和解决方法

热门文章

  1. HEVC视频编码技术概述
  2. vs2015智能提示英文改为中文
  3. 中科大和华师大计算机,今天就是你最后的机会,2019华师软件工程跨考经验
  4. 华为手机比较好用的三款推荐
  5. C# 連接mysql,連接后顯示多個線程池
  6. GPS坐标显示在百度地图上(Qt+百度地图)
  7. 我经历过的失败产品和项目(二):一款无疾而终的棋牌类游戏
  8. 【前端】移动互联动画
  9. SpringBoot: 启动Banner在线生成工具
  10. 高德地图上绘制城市名字和带涟漪的点标记