前端需要理解的跨平台知识

news2024/11/19 14:40:12

混合开发是指使用多种开发模开发App的一种开发模式,涉及到两大类技术:原生 Native、Web H5。原生 Native 主要指 iOS(Objective C)、Android(Java),原生开发效率较低,开发完成需要重新打包整个App,发布依赖用户的更新,性能较高功能覆盖率更高;Web H5主要由HTML、CSS、JavaScript组成,Web可以更好的实现发布更新,跨平台也更加优秀,但性能较低,特性受限。

跨平台开发是围绕着研发效能用户体验两个主题去打造的,通过在两者之间进行取舍,诞生了多种跨平台解决方案。根据采用的渲染技术不同,跨平台解决方案分为Web 渲染原生渲染自建渲染引擎渲染

1 JSBrigde 和 WebView

1.1 JSBrigde

原生Native与Web端相互通信离不开JSBridge。JSBridge是以 JavaScript 引擎或 Webview 容器作为媒介,通过协定协议进行通信,实现 Native 端和 Web 端双向通信(JS 向 Native 发送消息: 调用相关功能、通知 Native 当前 JS 的相关状态等。Native 向 JS 发送消息: 回溯调用结果、消息推送、通知 JS 当前 Native 的状态等。)的一种机制。

JavaScript 是运行在一个单独的 JS Context 中(例如,WebView 的 Webkit 引擎、JSCore)。由于这些 Context 与原生运行环境的天然隔离,可以将这种情况与 RPC(Remote Procedure Call,远程过程调用)通信进行类比,将 Native 与 JavaScript 的每次互相调用看做一次 RPC 调用。如此可以按照通常的 RPC 方式来进行设计和实现。

目前主流的 JSBridge实现中,都是通过拦截 URL 请求来达到 native 端和 webview 端相互通信的效果。

  1. 首先,需要在 webview 侧和 native 侧分别注册 bridge,其实就是用一个对象messageHandlers把所有handler函数储存起。
  2. 然后,在 webview 里面注入初始化代码:

(1)创建一个名为 WVJBCallbacks 的数组,将传入的 callback 参数放到数组内;

(2)创建一个 iframe,设置不可见,设置 src 为 https://__bridge_loaded__;

(3)设置定时器移除这个 iframe。

  1. 最后,在native 端监听 url 请求:
    1. 拦截了所有的 URL 请求并拿到 url。
    2. 首先判断 isWebViewJavascriptBridgeURL,判断这个 url 是不是 webview 的 iframe 触发的,具体可以通过 host 去判断。
    3. 继续判断,如果是 isBridgeLoadedURL,那么会执行 injectJavascriptFile方法,会向 webview 中再次注入一些逻辑,其中最重要的逻辑就是,在 window 对象上挂载一些全局变量和 WebViewJavascriptBridge属性。
    4. 继续判断,如果是 isQueueMessageURL,那么这就是个处理消息的回调,需要执行一些消息处理的方法。

1.2 WebView

WebView 是移动端提供的运行JavaScript的环境,是系统渲染 Web 网页的一个控件,可与页面JavaScript交互,实现混合开发。

简单来说,WebView 是手机中内置了一款高性能 Webkit 内核浏览器,在 SDK 中封装的一个组件。不过没有提供地址栏和导航栏,只是单纯的展示一个网页界面。

WebView 可以简单理解为页面里的 iframe 。原生 app 与 WebView 的交互可以简单看作是页面与页面内 iframe 页面进行的交互。就如页面与页面内的 iframe 共用一个 Window 一样,原生与 WebView 也共用了一套原生的方法。

其中 Android 和 iOS 又有些不同:

  1. Android目前是 基于 Chromium 内核。
  2. iOS 目前采用的是 WKWebView。

WebView 可以对url请求、页面加载、渲染、页面交互进行强大的处理。webview 去加载 url 并不像是 浏览器加载 url 的过程,webview 存在一个初始化的过程。为了提升init 时间,通常做法是 app 启动时初始化一个隐藏的webview等待使用,当用户点击需要加载URL,直接使用这个webview 来加载,从而减少webview init 初始化时间。弊端就是带来了额外的内存开销。

2 Web渲染

Web 渲染方案的有如下特点:

  1. 开发效率高:采用 Web 技术,对前端友好,一次开发,多端运行;
  2. 动态化好:Web 技术的天然动态特性支持,无需发版;
  3. 表现一致性佳:Web 页面除了个别元素和属性的差异、多屏适配外,其双端表现相对一致;

性能较差:页面采用 WebView 渲染,页面加载耗时长,功能受限于 WebView 沙箱,能力有限,难以承接复杂交互或是需要高性能的任务,整体用户体验差。

Web 渲染本质上是依托原生应用的内嵌浏览器控件 WebView 去渲染 H5 页面,并在原生应用中定义可供 H5 页面访问原生部分能力的接口 JSBridge,从而实现 H5 和 Native 双向通信,也进一步扩展 H5 端侧能力。因此,H5 App 的渲染流水线和 Web 页面渲染相一致。从 WebView 初始化到 H5 页面最终渲染的过程如下:

 

因此,Web 渲染性能上也存在首屏渲染优化问题,而且,还多出一个WebView 初始化的问题(可以通过 APP 启动的时候初始化一个常驻的隐藏WebView来处理)。

针对资源加载所带来的白屏问题,业界提出了离线包的优化方案。大体思路就是将原有从线上加载 H5 应用,提前下发到本地,通过 FileIO 或是内存等方式直接进行页面渲染,达到接近原生的用户体验。

在这基础上业内又提出 h5 容器的技术解决方案,h5 容器提供丰富的内置 JSAPI,增强版的 WebView 控件以及插件机制等能力,对原始版本的方案做了进一步功能高内聚和模块低耦合。

 

Apache Cordova 是一个开源的移动开发框架。它允许您使用标准 Web 技术 - HTML5、CSS3 和 JavaScript 进行跨平台开发。应用程序在针对每个平台的包装器内执行,并依靠符合标准的 API 绑定来访问每个设备的功能,例如传感器、数据、网络状态等。该框架的实现原理就是基于H5 容器Web 渲染技术

 

Cordova 应用程序由几部分组成:

  1. Web App:应用程序代码的实现地方,采用的是 Web 技术,应用运行在原生控件 WebView 中。
  2. HTML Rendering Engine:应用的渲染引擎,即 WebView,该渲染引擎是页面和 Native 实现双向通信的桥梁。
  3. Cordova 插件:提供了 Cordova 和原生组件相互通信的接口并绑定到了标准的设备API上。这使你能够通过JavaScript 调用原生代码,这些核心插件包括的应用程序访问设备功能,比如:电源,相机,联系人等。
  4. Mobile OS:原生系统层,提供系统能力。

小程序是无需下载安装即可使用的轻应用。小程序也是基于Web 渲染方案,即采用 WebView 作为渲染引擎、JSBridge 的封装和离线包机制等,其最大创新之处在于将渲染层和逻辑层进行了分离,提供一个干净纯粹的 JavaScript 运行时,多 WebView 的架构使得用户体验进一步逼近原生体验。

小程序的渲染层和逻辑层分别由两个线程管理,渲染层采用 WebView 进行页面渲染(iOS 使用 UIWebView/WKWebView,Android 使用 WebView),小程序的多页面也由多 WebView 接管。逻辑层从 WebView 分离,使用 JavaScript 引擎(iOS 使用 JavaScriptCore,Android 使用 V8)单独开启一个 Worker 线程去执行 JavaScript 代码。逻辑层和渲染层之间的通信经由 Native 层中转,网络 IO 也通过 Native 层进行转发:

小程序采用的是多 WebView + 双线程模型。由多 WebView 构成的视图层为页面性能赋予更加接近原生的用户体验,单个 WebView 承载更加轻量的页面渲染任务(左侧为原始web渲染,右侧为小程序):

JavaScript 被单独抽离在 Worker 线程,限制了直接操作页面的能力(无法直接操作DOM),也就被约束在微信小程序的规范下。

小程序的组件分为原生组件和非原生组件,原生组件属于原生渲染的一部分,所以小程序算得上是 Web 渲染和原生渲染的融合。

综上来看,Web 渲染的发展经历了从 h5 + JSBridge + WebView,到 h5 容器的抽象提升,再到小程序三个阶段。

3 原生渲染

原生渲染的基本思路是在 UI 层采用前端框架,然后通过 JavaScript 引擎解析 JS 代码,JS 代码通过 Bridge 层调用原生组件和能力,最终,UI 的渲染通过 JSBridge 由原生控件直接接管,从而获得性能和体验的提升。代表的框架是 React Native,其整体架构图如下:

  1. React 层:利用 React 框架进行 UI 的数据描述,开发者使用 Class Component 或 Functional Component 进行页面开发,框架内部将会把页面描述转化为 ReactElement 这一代表的虚拟 DOM 的数据结构,用于运行时的 Diff 对比和消息收发等。
  2. [JS Bundle 中间产物]:RN 通过 metro 打包功能直接将整个 RN 应用打包为一个 JSBundle,通过 Bridge 层在 RN 应用初始化时加载整个 JS 包进来。
  3. Bridge 层:Bridge 是连接 React 和 Native 的中间层,React 层的 UI 需要通过 Bridge 层的 UIManager 接口实现原生控件的创建和更新,通过 NativeModules 接口实现原生能力的调用。
  4. Native 层:在 Native 层中,Native Modules 实现了与上层交互的原生能力接口,Native UI 实现终端实际的控件展示,Yoga 跨平台布局引擎实现了基于 Flexbox 布局系统的 JS 和 Native 的镜像映射关系。

视图层的渲染通过 UIManager 调 createView/updateView 等方法,基于 Yoga 布局引擎创建对应的 shadowView;逻辑层中涉及原生能力调用的部分通过 RCTBridge 对象转发到相应的原生接口。 Native 接收到 Bridge 层的消息,进行视图的更新或是功能处理。

整个 RN 的线程模型:

  1. Main 线程(UI 线程):应用的主线程,进行初始化和处理原生控件的绘制。初始化的内容包括加载 JSBundle、初始化 Native Modules 等原生能力模块、创建 JSCore/Hermes JavaScript 引擎。
  2. JS 线程:React 构成的 JS 代码运行在此线程,解释执行 React 代码,并将生成的布局或逻辑信息序列化后经由 Bridge 发送给 Native。
  3. Shadow 线程:主要用于构建 JS 与原生控件的布局镜像数据。
  4. Native Modules 线程:提供原生能力,这里采用的是多线程模型,iOS 端通过 GCD 实现,Android 端通过 AsyncTask 实现。

原生渲染直接接管渲染层,弥补了 Web 渲染方法在性能和体验上的不足。但是,在原生渲染架构中,页面的更新和事件的响应存在 Native 层和 JS 层的通信的时间成本,同时数据的交互需要频繁进行序列化和反序列化的转换,导致在一些 UI 线程和 JS 线程存在持续频繁交互的场景(动画、滚动等),RN等原生渲染的表现差强人意。

4 自建渲染引擎渲染

自建渲染引擎渲染通过自建渲染引擎方式,直接从底层渲染上实现 UI 的绘制,代表的框架是 Flutter,其架构设计如下:

  1. Dart App 层:以 Widget 为基本视图描述单元,构建起 UI 体系;
  2. Flutter Framework 层:内置基础的 Flutter 组件,并根据不同平台的视觉风格体系,封装 Material 和 Cupertino 两套 UI 库供上层使用;
  3. Flutter Engine 层:Flutter 框架的核心所在,包括 Dart 虚拟机、Skia 跨平台渲染引擎、文字排版、平台通道等,通过 Engine 层,建立起 Dart App 层和原生平台之间联系,从而实现二者的双向通信;
  4. Embedder 层:为 Flutter App 提供宿主环境、线程创建以及基于插件机制的原生能力扩展等。

Flutter 在打包的时候,将 Dart 业务代码和 Flutter Engine 代码基于 iOS/Android 不同平台分别进行打包。 Native 在启动时会通过调用 C++ 的各自实现(Java 通过 JNI,OC 天然支持)初始化 Flutter Engine 层提供的接口,创建 UI/GPU/IO 三个线程和实例化 Dart VM。Dart 业务代码在 Release 模式下采用 AOT编译(Ahead-of-Time Complier提前编译) 的方式进行编译,并运行在 Dart VM 中。

Flutter App 的线程模型:

  1. Platform 线程:主线程,由 Native 创建。负责平台 vsync 信号的回调注册,即当接收到从显示设备的 vsync 信号后,Platform 线程驱动 UI 线程的执行。
  2. UI 线程:负责响应 vsync 信号,执行 Dart 层代码,驱动渲染管线的运行,将 Widget Tree 生成 Layer Tree 并提交给 GPU 线程做进一步处理。
  3. GPU 线程:将 Layer Tree 转化为具体的绘制指令,并调用 skia 跨平台渲染引擎进行光栅化上屏。
  4. IO 线程:主要负责请求图片资源并完成解码,然后将解码的图片生成纹理并传递给 GPU 线程。

显示器在一帧 vblank 后,会向 GPU 发送 vsync 信号,Native 的 Plaform 线程接收到 vsync 信号后,执行绘制帧回调方法,即驱动 UI 线程进行 UI 绘制。

UI 线程中,Native 通过调用 C++ 的各自实现,将绘制指令通过 window 对象发送给 Dart 层,Dart 层会重构代表 UI 的数据树(Widget Tree、Element Tree 和 RenderObject Tree)并生成布局信息:

  1. Widget Tree 是直接面向开发者的 UI 元素的配置信息,Widget 是 Immutable 的,如果 Widget 的状态发生更新,会发生重建。实际业务场景中,Widget 会频繁触发重建。
  2. Element Tree 是 Widget Tree 和 RenderObject Tree 的桥梁,当Widget 发生变化后,会将其 Element 标记为 Dirty Element,在下一次 vsync 信号到来时进行渲染。当 Widget 挂载到 Widget Tree 时,会调用 widget.createElement 方法,创建其对应的 Element,Flutter 再讲这个 Element 挂载到 Element Tree 并持有有创建它的 Widget 的引用
  3. RenderObject Tree 是真正执行组件布局渲染的工作,通过 RenderObjectToWidgetAdapter 这个 RenderObjectWidget 建立起 Widget 、Element 和 RenderObject 三者之间的联系

根据布局信息生成一系列绘制指令的 Layer Tree,并通过 window 对象传递给 GPU 线程。

GPU 线程根据绘制指令和通过 Skia 这一跨平台渲染引擎进行光栅化,绘制成帧数据,将帧数据放在帧缓冲区,然后等待显示器上屏。

因此,以 Flutter 为代表的的自建渲染引擎的优势在于:

  1. UI 控件是直接采用 Skia 这一跨平台渲染引擎进行绘制:顶层使用 Dart 的语法进行 UI 的配置信息描述,并通过 Diff 算法优化渲染流程,生成 Layer Tree 后,再调用 C++ 的代码将布局信息发送给 Flutter Engine,Flutter Engine 直接通过 Skia 将 UI 控件绘制上屏。这里与原生渲染方案最大的不同点在于,Native 应用仅作为宿主环境,UI 控件不需要转化为原生控件,直接采用渲染引擎进行绘制,从而保证了双端的一致性和良好的性能与体验。
  2. Dart 在 Release下采用 AOT 的 编译模式:Dart 代码在 Release 采用 AOT 的编译模式转化为二进制代码,从而在 Dart 运行时环境中执行效率更高,性能也更为卓越。对比 React Native 来说,由于打包的是 JSBundle,所以在运行时仍是基于 JavaScript 运行时进行解释执行 JS 代码,因而产生较大的性能瓶颈。
  3. UI 层与原生层的数据交换性能更高。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/927898.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Metasploit提权

一、bypassuac 用户账户控制(User Account Control,简写作UAC)是微软公司在其Windows Vista及更高版本操作系统中采用的一种控制机制。其原理是通知用户是否对应用程序使用硬盘驱动器和系统文件授权,以达到帮助阻止恶意程序(有时也…

编写c语言程序调用openssl编译出的动态链接库

文章目录 一、编译生成链接库二、示例一:调用RAND_bytes函数三、示例二:调用SHA256 一、编译生成链接库 下载安装openssl并编译生成链接库的过程在我的另一篇文章中已经详细说明了:Ubuntu中安装OpenSSL 此外,我们还需要提前了解…

Android studio之GridView使用

目录 效果图:![在这里插入图片描述](https://img-blog.csdnimg.cn/86e4a48a71164dec82613d58b1fbaa1c.jpeg)代码: 效果图: 代码: UserGridviewAdapter package com.example.gridviewpro.Adapter;import android.content.Contex…

Python练习 函数取列表最小数

练习2:构造一个功能函数,可以解决如下问题: 要求如下: 1,任意输入一个列表,函数可以打印出列表中最小的那个数, 例:输入: 23,56,67,4,17,9 最小数是 :4 方法一: #内置函…

AURIX TriCore内核架构学习笔记

名词缩写 ISA - Instruction Set Architecture,指令集架构PC - Program Counter, holds the address of the instruction that is currently runningGPRs - 32 General Purpose RegistersPSW - Program Status WordPCXI - Previous Context InformationCSA - Conte…

C语言练习2(巩固提升)

C语言练习2 选择题 前言 “志之所趋,无远弗届,穷山距海,不能限也。”对想做爱做的事要敢试敢为,努力从无到有、从小到大,把理想变为现实。要敢于做先锋,而不做过客、当看客,让创新成为青春远航的…

word电子报刊制作过程

随之网络的迅猛发展,利用计算机排版技术编辑制作电子报刊也很普及了。这里教大家如何将WODR转换成翻页的电子报刊 我们可以使用FLBOOK制作电子报刊,操作很简单 1.搜索FLBOOK制作电子杂志平台 2.点击登录与注册,可支持QQ、微信登录 3.现在点击…

Unity中的数学基础——贝塞尔曲线

一:前言 一条贝塞尔曲线是由一组定义的控制点P0到 Pn,n1为线性,n2为二次......第一个和最后一个控制点称为起点和终点,中间的控制点一般不会位于曲线上 获取两个点之间的点就是通过线性插值( Mathf.Lerp&#xff09…

C++信息学奥赛1127:图像旋转

这段代码的功能是输入一个二维数组 arr&#xff0c;然后按列逆序输出该数组的元素。 #include<iostream> #include<cmath> #include <iomanip> using namespace std; int main() {int n,m; // 定义变量n和m&#xff0c;表示数组的行数和列数cin>>n>…

W5500-EVB-PICO进行UDP组播数据回环测试(九)

前言 上一章我们用我们的开发板作为UDP客户端连接服务器进行数据回环测试&#xff0c;那么本章我们进行UDP组播数据回环测试。 什么是UDP组播&#xff1f; 组播是主机间一对多的通讯模式&#xff0c; 组播是一种允许一个或多个组播源发送同一报文到多个接收者的技术。组播源将…

<C++> 类和对象-类的默认成员函数

1.类的默认成员函数 默认成员函数&#xff1a;用户没有显式实现&#xff0c;编译器会生成的成员函数称为默认成员函数。 如果一个类中什么成员都没有&#xff0c;简称为空类。 空类中真的什么都没有吗&#xff1f;并不是&#xff0c;任何类在什么都不写时&#xff0c;编译器会…

kali安装使用dirsearch扫描文件

apt-get install dirsearch使用 irsearch -u [协议://域名 | ip端口]可以在这里面看到扫出来的结果 利用 发现了 通过githack将源码下来 知乎&#xff1a;AJEST安全实验室

[技术杂谈]MobaXterm中文乱码编码问题一种解决方法

今日使用mobaxterm连接树莓派发现安装出现乱码&#xff0c;看不清文字是什么。最最简单方式是ssh设置终端字体&#xff0c;具体步骤为&#xff1a; 1. 右键会话&#xff0c;点击编辑会话 2.在以下画面点击终端字体设置 3.选择编码&#xff1a;GBK或者ISO-8859-1

Git 版本控制系统

git相关代码 0、清屏幕&#xff1a;clear 1、查看版本号 git -v2、暂存、更改、提交 3、当前项目下暂存区中有哪些文件 git ls-files4、查看文件状态 git status -s5、暂时存储&#xff0c;可以临时恢复代码内容 git restore 目标文件 //&#xff08;注意&#xff1a;完全…

mysql处理json格式的字段,一文搞懂mysql解析json数据

文章目录 一、概述1、什么是JSON2、MySQL的JSON3、varchar、text、json类型字段的区别 二、JSON类型的创建1、建表指定2、修改字段 三、JSON类型的插入1、字符串直接插入2、JSON_ARRAY()函数插入数组3、JSON_OBJECT()函数插入对象4、JSON_ARRAYAGG()和JSON_OBJECTAGG()将查询结…

南卡再次发布新款OE-CC开放式耳机,被誉为年度开放式耳机百元标杆!

​国内著名声学品牌南卡&#xff0c;在近日又推出一款全新开放式耳机OE-CC&#xff0c;致力打造舒适、安全、健康听歌新体验&#xff0c;该耳机以其卓越的音质和令人惊叹的性价比而备受瞩目。再次证明了南卡在开放式音频领域的领先地位&#xff0c;被誉为年度开放式耳机百元标杆…

暄桐展览| 我们桐学有自己的习作展(1)

林曦老师《从书法之美到生活之美》的第五阶课程《静定的滋养2021》已告一段落。570天的用功&#xff0c;桐学们的技艺都有了水涨船高的进益。      无论书法课&#xff08;全阶和五阶&#xff09;还是国画课&#xff0c;暄桐都有一套完整系统的教学体系&#xff0c;也会在桐…

攻防世界-reverse-666

1. 题目描述 下载附件&#xff0c;发现是一个可执行文件 2. 思路分析 先逆向分析下源码 整个程序的逻辑还是比较简单的&#xff0c;输入key&#xff0c;对key进行encode&#xff0c;如果加密后的字符串和指定字符串相同&#xff0c;那么key就是我们需要的flag&#xff0c;…

AntDB-M的审计功能

数据库的审计功能是指对数据库访问行为进行监管&#xff0c;记录数据库里面发生了什么操作&#xff0c;是数据库系统安全功能的组成部分。 AntDB-M的审计功能关注客户端的连接信息&#xff0c;比如&#xff1a;用户名和主机地址、客户端发送的SQL语句、SQL执行访问的对象、修改…

W6100-EVB-PICO进行UDP组播数据回环测试(九)

前言 上一章我们用我们的开发板作为UDP客户端连接服务器进行数据回环测试&#xff0c;那么本章我们进行UDP组播数据回环测试。 什么是UDP组播&#xff1f; 组播是主机间一对多的通讯模式&#xff0c; 组播是一种允许一个或多个组播源发送同一报文到多个接收者的技术。组播源将…