• 浅谈混合应用的演进


    前端想要写 APP

    开篇想以这样的方式开头,从 APP 开始火到现在,前端同学就一直想要写 APP,各方技术也是为了让前端同学写上 APP 操碎了心。
    为什么要前端同学来写 APP,站在整个技术链上来看,都是在做页面呈现,页面交互,对于技术而言只是将产品在不同的端上进行呈现,所以很早之前就有提倡说大前端的概念。既然功能都差不多,就没有分家的理由。
    再者,开发客户端现在的常规平台有 iOS 和 android 两个大的平台,在前端领域越来越细分的大环境下,传统意义上的客户端开发至少需要两拨开发者来做,成本就会有增加。并且伴随着成本增加还带来了需要两套体系化的语言开发,开发效率也会大打折扣。相较于两大客户端平台的开发人员,前端的开发人员是要更多的,并且随着移动端的兴起前端开发者会有资源剩余的情况,前端来写 APP 也可以平衡资源。
    所以不仅仅是前端同学想要来写 APP,更多的是未来发展需要这样的技术来提高生产力,所以才会有各种各样的方案出现,各种方案的诞生也发现了前端写 APP 的不足,所以又有了不断的演进来适应未来发展。送给每一个前端人,我们的征途是星辰大海

    web APP

    就是我们常说的 h5 应用,这个阶段属于满足展示功能阶段,首先是解决手机端能展示页面的问题。
    实现方式就是客户端提供 webView 组件,就是一个基于 webkit 引擎、展现 web 页面的控件。web APP 实际上就是客户端提供了一个浏览器的宿主环境,能在手机上显示对应的网页内容,但是各大手机厂商对于内核都会有所谓的“优化”,这些所谓的优化或者内核版本升级所带来的兼容性问题,也是让前端同学咬牙切齿。
    客户端提供了类似浏览器的宿主环境,前端将自己的 HTML/CSS/JS 运行起来,就可以看到页面了。但是在 PC 端原来我们看到的页面放在手机上,展示和交互都不太合适。之所以我们要叫 h5 应用,是说我们在移动端上需要用到 HTML5/CSS3 的新功能,通过这些功能能在客户端的浏览器上做适配展示页面。
    响应式的布局方式,新式的 meta 标签,媒体查询等功能让前端以前显示在 PC 端上的页面能展示在手机端。随着 PWA 的出现,现在手机端逐渐对 Service Worker 的支持(Safari 还在踌躇犹豫),web APP 又重新焕发了青春。
     
    优点:
    • 基于 webView 的跨平台,调试方便(控制台调试)
    • 开发速度快,只要有浏览器就能打开,适合小版本的试错
    • 无需安装,不占内存,随时更新,维护成本低
    缺点:
    • 打开白屏时间长,用户体验较差,交互功能受限。
    • 产品也受限于浏览器,并且留存低,适合拉新
    • 不采用 PWA 的方案就无法离线,没有网络就失去了活力,现在不用网络的 APP 情况已经很少了。重复打开重复加载
    代表产品:
    • 所有可以使用手机浏览器打开的网页(不做兼容展示会有问题)
    • 微信公众号里纯文章
    hybrid 应用
     
    跨过了 web APP 的满足展示功能阶段,这个时候开始想有更多的功能满足交互需求,比如调起原生的摄像头(虽然 input 也可以实现),这个时候催生了 hybrid APP,也进入了丰富功能阶段。
    前端 webView 的交互实现无法直接与摄像头的 API 进行交互,类似这样的功能是客户端的能力,webView 想要完成这样的功能,如果不能直接完成是不是可以通过调用客户端来完成这样的功能, 但是前端和客户端是两种不同的语言及实现方式,如何进行通信?
    想要通信完成通信,其实就是需要再前端与和客户端搭建一个沟通桥梁,就是现在常说的 js bridge。介绍击中常见的 js bridge 通信方式。
    js bridge
    js 调用 native:
    • 请求拦截
    • 弹窗拦截
    • 注入 js 方法
    native 调用 js:
    • 直接执行 js 代码
    请求拦截
    webView 发送请求都会经过客户端的请求发送模块,客户端可以在请求发送出去之前做拦截,大家约定好一种 URI 的实现协议,如果符合实现协议的请求,就拦截下来进行约定协议解析,其他的就当做真正的请求发送出去。
    首先约定一个简单的协议
    eros://bmImage/camera?params={"imageNum":2,"allowCrop":true,"callbackId":"bmImage.camera1527235458519"}
    eros // 作为协议存在,用于做拦截的方式
    bmImage/camera // 用于约定是调用客户端的方法名,可以是想要调用相机上传两种图片的接口
    params // query 表示参数,其中有一个参数名叫 params 的参数是一个 json,是调用客户端的方法所需要的三个参数复制代码
    不管 js 如何封装,就是需要发送一个请求出去,最好前端能做统一封装通过 iframe 或者直接通过 ajax 发送请求。
    客户端如何拦截
    android 的拦截方式 shouldOverrideUrlLoading
    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
      // 根据协议判断是否需要拦截
      if (true){
        // 解析协议路径得知调用方法
        // 解析 query 得到参数
        // 通过反射的方式去调用对应的原生方法
        return true;
      }
      return super.shouldOverrideUrlLoading(view, url);
    }
    iOS 的拦截方式(UIWebView) webView:shouldStartLoadWithRequest:navigationType:
    - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{
      // 根据协议判断是否需要拦截
      if (true){
        // 解析协议路径得知调用方法
        // 解析 query 得到参数
        // 通过 AOP 的方式去调用去调用对应的原生方法
        return NO;
      }
      return YES;
    }
    上述过程就完成了 js 向 native 的通信的一种方式,客户端完成了操作之后如何告知 js 后面会讲到。这种方式不好的地方在于只能支持异步的调用方式,网络 I/O 就决定了这种方式不支持同步。既然是 URI 的协议就会有长度限制,超长了请求就会被截断
    更为严重的是无序性和有丢消息的可能,这也导致了这个方式非常不稳定,也是早期没有其他选择使用的方式。
    弹窗拦截
    js 调用弹窗时一般有 alert/confirm/prompt 三种弹窗,这三种弹窗对应客户端都会有方法实现,可以直接做拦截。
     
    这种方式比请求拦截好的地方是可以支持同步调用了。但是 iOS 中有两种 UIWebView 和 WKWebView,前者在 webView 中屏蔽的弹窗的功能。后者虽然内存要比前者控制的好但是有很多兼容性的问题。
    js 注入上下文
    上面的使用方式还是比较曲折,发展到现在有了 JavaScriptCore,也是现在大部分混合应用解决方案核心,JavaScriptCore 没有 BOM 对象也没有 DOM 对象,甚至还缺失一些浏览器的方法,但是这块是 js 和 native 都能访问到的公共区域。在打开 webView 之后,往 js 的上下文中注入方法,供 js 进行调用。
    iOS JavaScriptCore 注入(UIWebView)
     
    这种方式就更接近写代码的方式了,不管是调用方式、传递参数的方式还是同步异步回调都更好了。但是这种方式也有需要注意的地方,首先需要注意客户端注入的时机,在 loadUrl 之前注入是无效的,但是在 FinishLoad 之后注入可能 webView 已经调用了方法,此时会出现调用不到该方法,所以还需要其他的机制来保证这个问题。比如微信公众号的 wx.config 接口。
    native 调用 js
    前面已经讲了 js 调用 客户端,js 在向客户端发出调用指令之后,如果是同步方法,比如 获取当前客户端版本能立即得到结果,如果是异步调用,之前每次调用客户端的时候都传递了一个 callbackId,这个就是为客户端回调 js 做准备。
    首先 js 会提供一个方法等待客户端调用
     
    上述 js bridge 的实现大致就是现在大部分的客户端与 js 的通信方式,现在 webView 也丰富了自己的功能,有能力调用客户端提供的方法,完成所有客户端能做的功能,js bridge 设计完之后剩下的就是需求推动,根据所需的功能提供不同的方法供前端调用。
    如果需要与原生页面间互相通信,应该约定一种 scheme,也可以理解成一种 URI 的实现,通过 js bridge 的接口调用客户端(此时还可以传一个回调到客户端,当客户端完成某个操作之后返回该页面执行回调,比如日期选择),客户端解析协议,打开对应的原生页面,完成对应的操作。scheme 还是手机浏览器会识别的一种协议,如果有对应的 APP 可以直接呼起 APP,但是当你打开客户端页面的参数太多时,你的 scheme 会变得超长,浏览器会将 URL 截断,这个时候还需要简单的短链服务转化
     
    优点:
    • 首先具有所有 webView 所有的优点
    • 解决了单纯 webView 的交互功能受限,能调用原生接口完成需求
    缺点
    • 也无法避免 webView 所带来的性能问题,打开白屏时间长,用户体验较差,等问题,也存在受限与浏览器,及重复打开需要重复加载等问题
    • 所有新需要客户端提供的功能都需要客户端发版
    • 随着业务发展客户端定义的接口必须向下兼容,这会带来很多冗余代码
    代表产品:
    • 公众号使用到微信 sdk 的功能
    • AppCan、PhoneGap、cordova 等 hybrid 框架
    小程序
     
    在 hybrid 混合应用稳定之后,大家发现功能上已经能满足开发需求了,所以下个阶段应该追求体验和性能上的提升,就是 RN 的方案了,ReactNative 的方案已经逐渐被大家所熟知并逐步落到项目中,混合应用方案也是本文介绍的重点,后面会详述。
    这里想先讲一讲小程序,因为在技术的演进上小程序才应该是下一小步的提升,并且小程序严格意义上来说并非 APP 的一种方案,更像是微信分发生态中的体验进化,在使用公众号这类 hybrid 的方案时,发现体验和交互上的一些问题,比如加载白屏时间,页面之间跳转的交互,页面内容的缓存等等。
    由于小程序现在实现方案面向开发者也是一个黑盒,我也是一边了解一边猜小程序如何实现的,小程序的框架包含两部分:View 视图层和 APP service。前者用来渲染页面结构,后者用来做逻辑处理,接口调用。他们在两个进程里运行,有点类似在当前的页面里使用 web worker,关系如下图:
     
    APP View
    我们写的视图层和逻辑代码是分离的,WXML 和 WXSS 构图了视图层的代码,WXML 通过 wcc 工具转换成 VDOM,WXSS 通过 wxsc 转化 style 标签。底层通过 WAWebview.js 来提供底层的封装,每一个视图就会有一个 webView 来渲染,这也就提高了页面渲染性能的问题,多个视图页面时就会有多个 webView 进程,所以小程序对页面层级是有限制的,内存受限。视图层主要包括以下内容:
    • WeixinJSBridge 封装和上述的 hybrid 的 jsBridge 一样
    • 小程序提供的组件注册,一些能操作 DOM 的 API
    • 渲染的实现:VDOM、diff、render UI(在高人知道下了解到渲染出来的本质还是 webview,在原生端只支持 flex 布局,但是小程序里还支持 web 的布局,由此可以看出,这里一定是 webView 的渲染,并非原生)
    • 页面生命周期管理
    APP Service
    逻辑处理的代码全部加载到一个进程 APP service 中,和视图 webView 不同,所有的代码逻辑都会一次性全部加载到这个进程中,因为主要的瓶颈还是来自于渲染性能,并且所有的逻辑代码都加载到进程中保证视图切换的流程性,并且加上小程序有 2MB 大小的限制,在现在的网络环境下一次加载体验会更好。APP Service 包含以下内容:
    • WeixinJSBridge 封装和上述的 hybrid 的 jsBridge 一样
    • 所有小程序提供的 API 方法注入,全局方法注入
    • AMD 模块化实现
    小程序的开发环境
    小程序运行在开发环境中和线上环境是不同的,线上环境 iOS 和 android 都有真正的 webView 环境提供,在开发过程中小程序提供了一个 IDE,IDE 是基于 nwjs 实现的,钉钉客户端也是基于这个实现的。其实就是在 PC 端的客户端环境下尽可能提供原生能力,如果是移动端才会有的差异化能力就会 mock 掉。两边环境不同还体现在 APP Service 中,APP Service 主要是调用客户端底层,所以底层的不同也影响着这层的封装。
    APP View 与 APP service 通信
    当我们理解了前面的 hybrid 的通信原理,这里的通信就比较好理解了。小程序的 bridge 实现原理就和 hybrid 一样了。iOS 和 android 就不多说了。基于 nwjs 的客户端是通过 window.postMessage 实现的,使用 chrome 扩展的接口注入一个 contentScript.js,封装了 postMessage 方法。
    // 发送消息通过
    window.postMessage(data, ‘*’);// 接受消息通过
    window.addEventListener(‘message’, messageHandler);复制代码
    小程序的推动主要来自微信平台的大流量,这就是所谓的微信流量红利,公众号已经承载了传播和拉新的低成本方式,但是体验一直被诟病,限制于 hybrid 的体验问题让追求体验的开发者逐渐无法忍受,此时小程序原生的组件,良好多页面切换,几乎没有白屏时间的等待,让大家又有了探索无限的可能。
    优点:
    • 首先具有所有 hybrid 所有的优点
    • 无需安装,极速打开
    • 原生的组件有了体验上质的提升
    • 性能上也有了新的飞跃,白屏时间,页面切换也有了更好的体验(为了减少白屏时间,小程序采用预加载的方案,而分不清哪个会是逻辑里的下一个页面,所以小程序将所有 webView 全局预加载,这也是页面层级和包大小限制的主要原因)
    缺点
    • 只能用于微信平台,如果有多端的用户需求,开发成本增加了
    • 组件还是较少,很多操作 DOM 的方式也会有所限制,完成需求受限
    • 为了性能的体验,包的大小受限,打开页面的层级受限
    代表产品:
    • 各种客户端的小程序
    快应用
     
    快应用是对标微信小程序的一场阻截,几大手机厂商联合发布这个方案,一瞬间吸引了大家的眼球,越来越多端让前端追都追不过来(这么多技术确实让人很绝望,甚至前段时间大家去恶意灌水了很多大项目的 issue,但这也是前端蓬勃发展的表现,当这些前沿的技术落地在项目中甚至你只是完成一个 demo 的时候,你还是会有抑制不住的兴奋和成就感)
    快应用对自我的介绍是基于手机硬件平台的新型应用形态,用的是新型应用形态,并非新技术,类似这样的实现有 PWA,只是 PWA 缺少了手机硬件平台的支持。
     
    快应用标准是由手机厂商组成的快应用联盟联合制定,快应用标准的诞生将在研发接口、能力接入、开发者服务等层面建设标准平台。底层的硬件平台提供底层 API 让开发者调用,原生渲染组件,hybrid bridge 通信,原理大致也与小程序实现类似,这里就不详细说明,下面是几张快应用发布会的 PPT 截图,如果理解了上面的原理,这个应该就是比较好理解了。
     
     
    之所以说是小程序的一场阻截,小程序曾放话说,未来两年内,小程序将取代 80% 的 APP 市场,而手机厂商并不希望大量的 APP 被取代,所以催生了快应用的落地。但是建立在几大国内手机厂商硬件平台上的方案,就目前来看支持性,通用性,社交性上都是比不上小程序的。两者的前景不敢妄加揣测,但是小程序凭借微信平台的用户量和用户粘性,比几大手机厂商实现的标准化还是一目了然的。
    优缺点就不详述了,一目了然
    Flutter
     
    ReactNative 的开疆扩土进一步奠定了,React 的霸主地位,React 也由一个框架演进成了,以 React 为基础的前端多端解决方案,技术栈的生态方案。google 怎么可能放弃自己在科技领域的地位,即使是前端相关的技术,所以出了 Flutter 技术方案完成 APP 的一环。
    借由本身的 android 和 chrome 的 平台优势,准备重磅推出 Fuchsia OS,打造自己不基于 Linux 的底层系统,而 Fuchsia OS 钦定 UI Toolkit 就是 Flutter。Flutter 最大的改进就是自己重做了渲染引擎去渲染页面,将混合应用的渲染性能推向了另一个高度。
    Flutter 之于 RN 来讲最大的区别在于渲染,脱离了 JSFramework 的 Component 递归传递和逐个计算绘制,更像是 canvas 绘制,将 UI 直接绘制出来,Flutter 都是在状态变更时重新构建 Widget Tree,Flutter 的渲染引擎在将 Widget Tree 转化成渲染的 Render Tree,最后交给操作系统调用 GPU 去渲染,Flutter 由 Dart 程序写的,Dart 和 native 之间仍然存在一个接口,可以进行数据编码和解码,这可能比 JavaScript bridge 快好几个数量级。
     
    使用 Dart 是整个框架不太被前端所接受的问题,但是代码都是相通的,上手还是很快,尝试 demo 期间还是很快就能模仿出一个页面,语言上的争论就不说了,毕竟都不如 PHP。
    由于自己使用 Flutter 还在 demo 阶段,并没有真正围绕 Flutter 做过相关的解决方案,具体的细节实现也没有清楚的认知,也不适合展开详述,很早之前以矢量图起家的 Adobe 也尝试过类似的方案,但是最终没有把这条路走下去。
    如果有兴趣可以移步:
    weex 和 ReactNative
     
    在讲这两种混合应用之前,再啰嗦几句,上面大致了解了各种混合应用的方案,这些方案了解其原理和使用场景,在合适的场景去使用对应的方案,不要为了落地某项方案而把业务生搬硬套,没有一种方案是绝对的好,在不同的业务需求的驱动下权衡利弊再做决策。
    这两者一直有人在试图去比较分析得出利弊,看哪种更好,甚至有人偏激的认为 Weex 就不配合 RN 相提并论,但是我还是想说理性的看待这个问题,客观的面对前端技术,曾经 React 和 Vue 也是这样的差距,直到现在依旧有人认为使用 Vue 低俗,使用 React 高雅,但雅俗之分真的如此吗?还是应该理解其原理,辨别自己的业务场景,团队学习成本,开发体验等等一系列的原因,然后再做评判。
    就目前的各方面情况讲,RN 确实更优,横空出世,背靠当时火的不行 React,一路都是风光无限。这种解决方案出来之后,如果没有竞争者的挑战,没有对比,没有选择,也不一定是好事儿,虽然现在两者还有很大的差距。
    我猜 Weex 始于 KPI,毕竟大厂造轮子来升级是非常好的途径之一,但是往后发展,却成了两大阵营完善生态的方案,现在基于 React 的一整套的前端解决方案覆盖PC、Web、Native 可谓是成了大公司前端方案的标准套路,后起之秀 Vue 用良好的开发体验和低门槛的入门方式开始抢占用户,各种前端培训学校让一些前端不懂 js 基础,但是 Vue 溜的飞起。但Vue 并没有形成对 Native 的覆盖,这个可能也是 Weex 和 Vue 一拍即合的原因,双方都有需求,就开始了合作。
    扯会正题,在大前端逐渐融合的背景下,作为以 Vue 建立技术栈的团队开始寻找客户端的方案,做技术选型开始考虑业务达成、团队学习成本、开发效率,性能效率等问题,我们一开始也是将 Weex 和 RN 作为比较,在上一家公司使用的是 RN,但是现团队建立技术栈基于 Vue,考虑到第二个因素偏向 Weex,实际调研过程中发现 Weex 的坑确实很多,但是好在有很多问题已经有解决方案了,当然不管想要使用上述两种方案,都需要有一定的原生能力。实际开发过程中发现随着对 Weex 的了解越来越多,开发效率也越来越快,入坑之后才发现了第三个和第四个甚至第五第六个优势。
    至于 Weex 最大的卖点,兼容三端,Web 端这个优势每个人都有自己的看法,我们尝试了去兼容 Web 但是发现反而牺牲了开发效率,有兼容的时间,单独开发一套也出来了,并且更重要的是由于用户习惯的不一致,Web 端和 Native 针对的用户群体也不一样,在这个流量就是金子的环境下牺牲用户体验和开发效率去兼容 Web 并非是我们的初衷。
    当然也遇到了一些问题,一开始 Weex 没有托管 Apache 的时候还有 issue,很多问题确实不能及时得到解决,所以决定脱离 Weex 的所有 module,自行开发所有 module,重新设计,这也让我们对项目有了一些控制。
    决定了自行开发 module 和组件之后,减少了对 Weex 本身的依赖,项目本身受到的限制也越来越小,业务很快也完成了。当然现在类似 demo 跑不起来,毁灭式的升级,市场组件无法接入,bug 横飞,组件不足以完成业务功能,兼容性不足,热更新,公共文件导致包过大,社区经营不好等等这些问题,也都是一个技术产品成长的必经之路。
    两种方案在选型的时候,一定有可取的地方才会选择,工程师的精神就是趟坑,找准正确的方向然后一往无前,两种方案都有已经上线的优秀产品,说明是这条路至少是能走通的,当然我们也在过程中总结了我们的解决方案,希望能帮助在 Weex 路上举步维艰的同行人,不管选择哪种方案,选择了就把他做好。Eros项目地址请戳(不要吝啬你的 star)Eros文档地址请戳。我们公司已有三个项目通过 Eros 上线,也有几十个应用通过 Eros 发布上线。
    当然还有很多优秀的方案没有一一枚举,比如:kotlin、WebAssembly,每个方案都沉淀了很多前端工程师的积累,提供了各种能落地可实行的方案 phonegap -> cordova、mui、appcan、apicloud 等等
  • 相关阅读:
    Button
    启动活动最佳写法
    随时随地退出程序
    知晓当前是在哪个活动
    Failed to resolve:com.android.support:appcompat-v7:报错处理
    未能从程序集“System.ServiceModel”中加载类型“System.ServiceModel.Activation.HttpModule”
    office文档图标不正常显示
    feign和ribbon的异常捕捉
    rocketMQ为什么会重复消费
    springboot时区问题
  • 原文地址:https://www.cnblogs.com/zhenjianyu/p/13404064.html
Copyright © 2020-2023  润新知