转载

如何打造一个高性能的Hybrid App

引言

在多元化的今天,一个热门的移动app,或多或少都会有内在H5在其中。而对于一个有很多运营场景的app来说,这种情况更常见了。试想一下,如果在一个公司,存在很多native和H5同时需要开发的页面,为了节省开发成本,此时如果只开发H5,就需要考虑native的体验了,而这就是本文的目的,如何让native端拥有像加载本地页面一样的速度去加载H5。

在app内加载H5速度慢一直是客户端开发的痛点,抛开H5的体验本身与native就有差距不说,如果加载速度还很慢,这将会对用户体验造成巨大影响。那么像做到像native页面一样瞬间加载完H5,思路就会变得比较清晰了: 提前在本地存储远程资源包

方案选择

从这个点出发,我们需要考虑,以怎样的形式来提前拿到资源包(css,js,html,通用的图片等),减少这些静态资源的网络请求,增加加载速度。有以下两种方案:

1.将资源包在app打包阶段直接植入

2.在运行时动态下载资源包

单纯从业务层来说,如果你的业务够简单,其实第一种方式已经完全满足,每次需要新增页面就重新发版嘛,虽然显得有点愚笨,但是还是能满足的。

但是从长远的角度来说,我们要做到尽可能的动态化,动态化是客户端的热点,我们要做到尽量不依赖于版本更新来实现动态化。对于iOS来说,更新机制本身就非常缓慢,要通过app store的审核有时候还需要靠人品,更何况用户也不一定买账,他们不一定会更新我们的app。在这样的情况下,第二种方案就会显得更加友好和方便快捷。

设计加载流程

那么,该怎么设计一套完整的解决方案来满足运行时动态下载资源包呢?

抽出细节,大体上可以归结为下图所示的结构图:

如何打造一个高性能的Hybrid App

我来解释下这个图,我是建立在客户端已经实现 socket 层协议,所以能与 server 保持长连接以至于 server 能主动push数据的情况,实现这种协议蛮复杂的。实际上如果没有这个协议,那就需要 client 找时机主动去 server 请求(app启动时请求一次?或者是每隔一段时间请求一次,取决于你),本文以后者为例。

下面我来演示下一个完整的下载新资源包的过程:

1.运营小妹觉得某节日要到了,需要发布一个新的页面,然后在运营后台生成资源包,运营后台会自动更新 config ,其中包括资源包的 version ,是否强制关闭加载本地资源包(降级策略,防止这个组件本身有BUG),还有一些 hotpatch 脚本。并且将资源包根据里面的内容部署到 remote database

2.在合适的时机, client 发起http请求向 server 查询是否有新版本的资源包,并带上本地的 config

3. server 根据 config 里的选项,比对从 client 拿到的 config ,发现客户端是旧版本的 config ,OK,则下发新的 configclient ,并且发送从 database 里拿到的资源包(为了加快速度,可以部署到 CDN )。

4. client 拿到最新的资源包后,在本地进行解密,解压等操作,并映射成对应 URL 相对于本地的 local file url 。比如: http://www.baidu.com 这个网址下的静态资源文件在本地的 file://dsalkfjsldfjalsd/ 目录下。

至此,已经完成资源包的下载。

拦截并加载本地资源包

那么有了资源包后,怎么能让app像native页面的速度去加载H5呢。

其实原理就是对H5请求进行拦截,如果本地已经有对应的静态资源文件,则直接加载,这样就能达到“秒开”webview的效果。

对于iOS而言,这就需要用到 NSURLProtocol 这个神器了。接下来,分析下它到底是什么东西,我们怎么利用它达到上述效果。

NSURLProtocol:它能够让你去重新定义苹果的URL加载系统(URL Loading System)的行为,URL Loading System里有许多类用于处理URL请求,比如NSURL,NSURLRequest,NSURLConnection和NSURLSession等。当URL Loading System使用NSURLRequest去获取资源的时候,它会创建一个NSURLProtocol子类的实例,你不应该直接实例化一个NSURLProtocol,NSURLProtocol看起来像是一个协议,但其实这是一个 类,而且必须使用该类的子类,并且需要被注册。

换句话说, NSURLProtocol 能拦截所有当前app下的网络请求,并且能自定义地进行处理。

代码如下

如何打造一个高性能的Hybrid App

这里只介绍与我们需求相关的 NSURLProtocol 方法。

搞了这么多,其实最核心的就是前四个方法:

+ (BOOL)canInitWithRequest:(NSURLRequest *)request

这个方法的作用是判断当前 protocol 是否要对这个 request 进行处理(所有的网络请求都会走到这里,所以我们只需要对我们产生的 request 进行处理即可)。

+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request

这个方法其实很强大,它可以对 request 进行预处理,比如对 header 加一些东西什么的,我们这里没什么要改的,所以直接返回 request 就好了。

- (void)startLoading

重点是这个方法,我们这里需要做一件事,就是自己拼装 httpResponse ,并且返回给url load system,然后到了webview那一层,会收到 response ,对于webview而言,加载本地和走网络拿到的 response 是完全一样的。所以上述代码展示了如何拼装一个 httpResponse ,当组装完成后,需要调用 self.client 将数据传出去。

何为 self.client ,这个东西其实就是 protocol 与url load system交互的一个对象,系统提供给我们的,这样理解就够了。

需要注意的是,细心的读者会看到else里会有一段代码:

[NSURLProtocol setProperty:@YES forKey:WDHybridResourceProtocolHandledKey inRequest:newRequest];

这个是干什么用的?else的作用是当本地不存在这个文件时,则主动重新发请求,此时又会调用 canInitWithRequest ,如果不设置flag,则会无限递归了。所以你懂得。

当然,重新发请求自然要实现 NSURLConnectionDelegate

总结

至此,如何快速加载H5已经全部介绍完毕。

附上前后加载速度对比:

如何打造一个高性能的Hybrid App

原文  https://segmentfault.com/a/1190000005732602
正文到此结束
Loading...