网易资讯客户端 H5 秒开优化
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">H5 因其“天生”的跨平台、实时更新、便于传播等特性,<span style="color: black;">始终</span>是各家 APP 承载内容的重要手段之一。但<span style="color: black;">因为</span> web 技术本身的限制,在功能、性能以及体验方面与 native 仍有<span style="color: black;">必定</span>的差距,<span style="color: black;">例如</span>受限的硬件<span style="color: black;">拜访</span>能力、差强人意的离线功能等。而基于 WebView 的混合模式,借助 native 的<span style="color: black;">加强</span>是比较通用的<span style="color: black;">处理</span><span style="color: black;">方法</span>。本文将围绕这些痛点,分享下网易<span style="color: black;">资讯</span>客户端在资源离线、JsBridge 通信、接口预请求三个方面的优化实践。</span></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><strong style="color: blue;"><span style="color: black;"><span style="color: black;">1、</span>资源离线</span></strong></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><strong style="color: blue;"><span style="color: black;">1、介绍</span></strong></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">Web 页面性能优化的重点之一<span style="color: black;">便是</span>静态资源的加载耗时,而传统的离线<span style="color: black;">方法</span>都难以<span style="color: black;">处理</span>首次加载的问题。在 APP 内,<span style="color: black;">咱们</span><span style="color: black;">能够</span><span style="color: black;">经过</span> native 将资源离线到本地,能够很好的弥补这个缺陷。实现的基本思路是把 web 页面的静态资源生成 Zip 包,客户端在合适的<span style="color: black;">机会</span>拉取 Zip 包到本地解压并持久化存储。当用户<span style="color: black;">拜访</span>时,<span style="color: black;">经过</span>拦截 WebView 发出去的页面请求,直接返回对应的本地文件,<span style="color: black;">这般</span>就<span style="color: black;">能够</span>实现本地加载,缩短页面资源加载时间。</span></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">大体流程如图:</span></span></p>
<div style="color: black; text-align: left; margin-bottom: 10px;"><img src="https://p3-sign.toutiaoimg.com/tos-cn-i-axegupay5k/5721ba67b417463b833c06da12d202b0~noop.image?_iz=58558&from=article.pc_detail&lk3s=953192f4&x-expires=1725650935&x-signature=QkLCy5va0rvmSHyNxhbUFG1pjss%3D" style="width: 50%; margin-bottom: 20px;"></div>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><strong style="color: blue;"><span style="color: black;">2、离线实现</span></strong></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">当<span style="color: black;">咱们</span>确定了采用页面 Zip 离线加载<span style="color: black;">方法</span>,<span style="color: black;">咱们</span>需要制定一套完整流程来完成这个<span style="color: black;">方法</span>。实现<span style="color: black;">全部</span>离线<span style="color: black;">方法</span>分三部分内容:</span></span></p><span style="color: black;"><span style="color: black;">Web 页面 Zip 包生成工具</span></span><span style="color: black;"><span style="color: black;">离线管理系统</span></span><span style="color: black;"><span style="color: black;">客户端离线实现</span></span>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><strong style="color: blue;"><span style="color: black;">2.1、Web 页面 zip 包生成工具</span></strong></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><strong style="color: blue;"><span style="color: black;">2.1.1、工具介绍</span></strong></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">Web 页面 Zip 包生成工具<span style="color: black;">重点</span>是将页面生成 Zip 包,同步配置离线页面信息。</span></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;"><span style="color: black;">思虑</span>到 web 页面多、依赖资源多,手动打<span style="color: black;">包涵</span>易出错且效率低下。<span style="color: black;">因此呢</span><span style="color: black;">咱们</span><span style="color: black;">研发</span>了一款脱离业务、方便灵活、多项目可用的打包工具,方便<span style="color: black;">咱们</span>将本地项目页面<span style="color: black;">或</span>线上页面生成 Zip 包的工具。</span></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;"><span style="color: black;">亦</span><span style="color: black;">便是</span>说配置一个页面的入口文件<span style="color: black;">或</span>线上页面<span style="color: black;">位置</span>:</span></span></p>[{
name: <span style="color: black;">index</span>, <span style="color: black;">// 页面名<span style="color: black;">叫作</span> </span>url: [<span style="color: black;">https</span>:<span style="color: black;">//example.com/index],</span>
src: ./dist/index.html <span style="color: black;">// 本地页面入口文件</span>
}]
<span style="color: black;">// 或</span>
[{
name: <span style="color: black;">index</span>, <span style="color: black;">// 页面名<span style="color: black;">叫作</span> </span>
url: [<span style="color: black;">https</span>:<span style="color: black;">//example.com/index] //页面线上<span style="color: black;">位置</span></span>}]
<span style="color: black;">最后</span>输出客户端可用的离线页面<span style="color: black;">关联</span>信息:
[{
name:<span style="color: black;">index</span>,
url: [<span style="color: black;">//example.com/index],</span>
zipUrl: <span style="color: black;">https</span>:<span style="color: black;">//assets.example.com/static/example.20190525_1020.zip,</span>
md5: <span style="color: black;">md5md5md5md5md5md5md5md5md5md5md5md5</span>
}]<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><strong style="color: blue;"><span style="color: black;">2.1.2、工具实现</span></strong></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">Web 页面生成 Zip 包工具所有环节均为自动化,<span style="color: black;">每一个</span>环节<span style="color: black;">经过</span>中间件方式实现,满足<span style="color: black;">区别</span>场景,方便定制化<span style="color: black;">需要</span>。整体分为通用和定制两部分。</span></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">通用部<span style="color: black;">包含</span>:</span></span></p><span style="color: black;"><span style="color: black;">拷贝页面依赖生成Zip包</span></span><span style="color: black;"><span style="color: black;">判断包的完整性</span></span><span style="color: black;"><span style="color: black;">获取 Zip 包的 MD5 值</span></span><span style="color: black;"><span style="color: black;">生成 Zip 包版本号</span></span>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">定制部分为:</span></span></p><span style="color: black;"><span style="color: black;">确定待更新 Zip 包</span></span><span style="color: black;"><span style="color: black;">上传 Zip 包到 CDN</span></span><span style="color: black;"><span style="color: black;">更新离线数据、Zip 包版本数据</span></span>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">功能流程如下图:</span></span></p>
<div style="color: black; text-align: left; margin-bottom: 10px;"><img src="https://p3-sign.toutiaoimg.com/tos-cn-i-6w9my0ksvp/5fa7b007b05f4eb986d764ea19a83156~noop.image?_iz=58558&from=article.pc_detail&lk3s=953192f4&x-expires=1725650935&x-signature=rjlWcBPfSsr7zXFXLv15gyupCyQ%3D" style="width: 50%; margin-bottom: 20px;"></div>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">实现(build.js):</span></span></p><span style="color: black;">const</span> { del, zip, diff } = <span style="color: black;">require</span>(<span style="color: black;">./index</span>);
<span style="color: black;">const</span>{ upload, version, extend, equal, Compose, copy, check } =<span style="color: black;">require</span>(<span style="color: black;">../utils</span>)
<span style="color: black;">const</span> config = <span style="color: black;">require</span>(<span style="color: black;">../tool.config</span>);
<span style="color: black;">const</span> common = <span style="color: black;"><span style="color: black;">()</span> =></span> {
<span style="color: black;">const</span> compose = <span style="color: black;">new</span> Compose();
compose.use(<span style="color: black;">async</span>(context, next) => {<span style="color: black;">// 通用<span style="color: black;">规律</span>(拷贝打包,包校验,生成版本,获取md5)</span>
next();
});
<span style="color: black;">return</span> compose;
};
<span style="color: black;">const</span> build = <span style="color: black;"><span style="color: black;">list</span> =></span> <span style="color: black;">new</span> <span style="color: black;">Promise</span>(<span style="color: black;">(<span style="color: black;">resolve, reject</span>) =></span> {
<span style="color: black;">if</span> (!<span style="color: black;">Array</span>.isArray(list)) {<span style="color: black;">return</span> reject(<span style="color: black;">The "list" argument must be of type array</span>, list);
}
common().use(<span style="color: black;">async</span> (context, next) => {
<span style="color: black;">// 定制<span style="color: black;">规律</span>(确定需要上传的zip包,上传zip包,更新数据库)</span>
next();
}).exec({
list
}).then(<span style="color: black;"><span style="color: black;">res</span> =></span> resolve(res)).catch(<span style="color: black;"><span style="color: black;">err</span> =></span> reject(err));
});
<span style="color: black;">const</span>compose = common();
build.use = compose.use.bind(compose);
build.exec = compose.exec.bind(compose);<span style="color: black;">module</span>.exports = build;<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><strong style="color: blue;"><span style="color: black;">2.1.3、生成的 Zip 包</span></strong></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">Zip 包里有和客户端<span style="color: black;">朋友</span>约定好了 zip 包的<span style="color: black;">详细</span>内容和目录结构,对应页面的页面入口文件(index.html)和其他<span style="color: black;">包括</span>了页面依赖资源,页面资源目录结构和线上保持一致,<span style="color: black;">这般</span><span style="color: black;">能够</span>方便客户端匹配<span style="color: black;">查询</span>,简化客户端处理<span style="color: black;">规律</span>。</span></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">Zip 包结构如下:</span></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">hot-content_20190808150211.zip</span></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">│ ── 163</span></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">│ └── frontend</span></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">│ └── hot-content</span></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">│ └── js</span></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">│ └── app.10882524.js</span></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">│ └── css</span></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">│ └── app.412b0635.css</span></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">│ ── index.html</span></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><strong style="color: blue;"><span style="color: black;">2.2、离线管理系统</span></strong></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">离线系统<span style="color: black;">重点</span>功能职责:</span></span></p><span style="color: black;"><span style="color: black;">为离线工具<span style="color: black;">供给</span>打包信息及离线包信息存储</span></span><span style="color: black;"><span style="color: black;">为 APP <span style="color: black;">供给</span>离线数据</span></span><span style="color: black;"><span style="color: black;">页面离线数据在线管理</span></span>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">为扩展其他<span style="color: black;">制品</span><span style="color: black;">运用</span>,离线管理系统完<span style="color: black;">成为了</span>多<span style="color: black;">制品</span>、多用户的设计。除工具自动更新数据外,还<span style="color: black;">能够</span>在系统里添加数据,对数据进行改删操作。离线数据<span style="color: black;">保存</span><span style="color: black;">近期</span>5个版本,<span style="color: black;">倘若</span><span style="color: black;">发掘</span>线上 Zip 包有问题,<span style="color: black;">能够</span><span style="color: black;">快速</span>回滚到上个版本。</span></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">核心功能如下图:</span></span></p>
<div style="color: black; text-align: left; margin-bottom: 10px;"><img src="https://p3-sign.toutiaoimg.com/tos-cn-i-6w9my0ksvp/92c4d592d2c6487f950e512af763d7e8~noop.image?_iz=58558&from=article.pc_detail&lk3s=953192f4&x-expires=1725650935&x-signature=ZlKTepANw0NDH43vJw9Ke125igY%3D" style="width: 50%; margin-bottom: 20px;"></div>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><strong style="color: blue;"><span style="color: black;">2.3 、客户端离线实现</span></strong></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">客户端离线实现是<span style="color: black;">全部</span>离线<span style="color: black;">方法</span>最重要的一环,<span style="color: black;">重点</span>分为两大内容:</span></span></p><span style="color: black;"><span style="color: black;">离线资源更新</span></span><span style="color: black;"><span style="color: black;">拦截资源返回</span></span>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;"><span style="color: black;">咱们</span>设计一个离线资源管理器做总调度来处理离线资源的更新和拦截返回<span style="color: black;">规律</span>。离线资源管理器<span style="color: black;">按照</span>配置的离线信息创建一个动态管理器,会<span style="color: black;">安排</span><span style="color: black;">每一个</span> URL 对应的页面入口文件,静态资源(css,js,image)目录,要拦截的静态资源域名。</span></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><strong style="color: blue;"><span style="color: black;">2.3.1、更新实现细节</span></strong></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">APP 在获取离线配置分主动和被动,主动更新是在每次 APP <span style="color: black;">起步</span>后<span style="color: black;">经过</span>接口获取离线配置信息,被动<span style="color: black;">经过</span> push 更新。</span></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">离线配置<span style="color: black;">包括</span>了所有已配置的页面离线信息,在<span style="color: black;">咱们</span>获取到这些离线信息后,读取本地配置缓存进行比对,<span style="color: black;">按照</span>页面名<span style="color: black;">叫作</span>来确定离线文件的更新策略是什么,远端配置无本地配置有则认为当前页面离线包是被删除的,直接删除本地对应的离线页面入口文件;<span style="color: black;">倘若</span><span style="color: black;">发掘</span>两个配置中同名页面 Zip 包的 MD5 值不一致则认为此页面离线包是更新了的;<span style="color: black;">仅有</span>远端有配置则认为是新增;<span style="color: black;">而后</span>交给下载管理器下载并解压 Zip 包,下载解压完成<span style="color: black;">通告</span>离线资源管理器更新本地离线缓存配置。</span></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">更新<span style="color: black;">重点</span>流程:</span></span></p>
<div style="color: black; text-align: left; margin-bottom: 10px;"><img src="https://p3-sign.toutiaoimg.com/tos-cn-i-6w9my0ksvp/db4fd5a9f7c84a6db228ef0ab4f595fd~noop.image?_iz=58558&from=article.pc_detail&lk3s=953192f4&x-expires=1725650935&x-signature=ucOqOF7LAH3JtDxDsFml6Hz5kyA%3D" style="width: 50%; margin-bottom: 20px;"></div>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><strong style="color: blue;"><span style="color: black;">2.3.2、拦截资源返回细节 </span></strong></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;"><span style="color: black;">咱们</span>会统一拦截所有网络请求,<span style="color: black;">经过</span>离线资源管理器来处理<span style="color: black;">拜访</span><span style="color: black;">规律</span>。需要处理的拦截返回分:</span></span></p><span style="color: black;"><span style="color: black;">页面文件(html)</span></span><span style="color: black;"><span style="color: black;">依赖的静态资源(js、css、image)</span></span>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">当 APP 在 Webview 发起页面请求时,<span style="color: black;">咱们</span>会先拦截当前页面请求,获取到页面的 URL <span style="color: black;">位置</span>,<span style="color: black;">按照</span>离线管理器中配置,进行<span style="color: black;">查询</span><span style="color: black;">有没有</span>匹配的本地页面入口文件,有则直接返回入口文件,否则放行请求线上资源。</span></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">页面的加载会<span style="color: black;">伴同</span>着依赖资源的加载,获取请求 URL,<span style="color: black;">倘若</span>在静态资源拦截域名内,则替换域名的 origin 为本地的静态资源目录进行<span style="color: black;">查询</span>。<span style="color: black;">倘若</span>找到,获取文件扩展名,设置返回的文件类型直接返回。</span></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">拦截返回的主流程如下图:</span></span></p>
<div style="color: black; text-align: left; margin-bottom: 10px;"><img src="https://p3-sign.toutiaoimg.com/tos-cn-i-6w9my0ksvp/c23f17316f8d48329f3a48f10d0db7fe~noop.image?_iz=58558&from=article.pc_detail&lk3s=953192f4&x-expires=1725650935&x-signature=pdLTlhkbbpQyVLB%2FQ6aejODO%2FTQ%3D" style="width: 50%; margin-bottom: 20px;"></div>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><strong style="color: blue;"><span style="color: black;">2.3.3、其它</span></strong></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">为了<span style="color: black;">保证</span><span style="color: black;">全部</span> Zip 离线的高可用性,APP 端会对<span style="color: black;">每一个</span>环节<span style="color: black;">显现</span>的错误进行上报,以便快速定位并修复问题。</span></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">和离线<span style="color: black;">关联</span>的错误类型有:</span></span></p><span style="color: black;"><span style="color: black;">获取离线配置接口网络错误</span></span><span style="color: black;"><span style="color: black;">获取离线配置接口数据解析失败</span></span><span style="color: black;"><span style="color: black;">Zip 包请求网络错误</span></span><span style="color: black;"><span style="color: black;">Zip 包解压错误</span></span><span style="color: black;"><span style="color: black;">Zip 包 MD5 值 APP 端和前端不一致</span></span><span style="color: black;"><span style="color: black;">Zip 包解压手机空间不足</span></span>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">如<span style="color: black;">显现</span>上面任何一种错误都不会更新本地离线资源和离线配置。</span></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><strong style="color: blue;"><span style="color: black;">3、小结</span></strong></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">为了加快页面展示速度,<span style="color: black;">咱们</span>做了服务器渲染,合并减少 Request 请求,做 gzip 压缩,<span style="color: black;">安排</span> CDN,做缓存,引入 Service Worker 等优化<span style="color: black;">办法</span>,但还是<span style="color: black;">不可</span>很好的<span style="color: black;">处理</span>首次请求白屏过长的问题。而<span style="color: black;">经过</span> Zip 离线<span style="color: black;">方法</span>,在用户<span style="color: black;">第1</span>次<span style="color: black;">拜访</span>时本地<span style="color: black;">已然</span>有对应的离线资源了,<span style="color: black;">这般</span>大大的缩短了资源加载时间,减少白屏时间。</span></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;"><span style="color: black;">这儿</span><span style="color: black;">经过</span>一个测试页面来看下资源离线的前后数据对比</span>,为了模拟真实的项目状态,<span style="color: black;">咱们</span>在页面中挂载了 jQeury、Bootstrap.js/css、 以及 js-bridge 等静态资源。<span style="color: black;">运用</span> iPhone 6s 机型,分别在<span style="color: black;">区别</span>网络环境下对测试页面进行<span style="color: black;">拜访</span>,记录多组首次<span style="color: black;">拜访</span>总耗时,<span style="color: black;">最后</span>取平均值。</span></p>
<div style="color: black; text-align: left; margin-bottom: 10px;"><img src="https://p3-sign.toutiaoimg.com/tos-cn-i-6w9my0ksvp/43199ac28e464f3e81cab4d0b2521296~noop.image?_iz=58558&from=article.pc_detail&lk3s=953192f4&x-expires=1725650935&x-signature=SblBSLtNlCTXLTpTD2ggJXRg2lc%3D" style="width: 50%; margin-bottom: 20px;"></div>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">从图中数据<span style="color: black;">能够</span>看出,通过<span style="color: black;">运用</span>离线<span style="color: black;">方法</span>,在<span style="color: black;">各样</span>网络环境下加载页面静态资源均不受网络<span style="color: black;">状况</span>影响,相比从远端加载整体有 75% 的<span style="color: black;">提高</span>。需要说明的是,测试页面中<span style="color: black;">无</span>掺杂业务<span style="color: black;">规律</span>,仅纯粹的资源加载,<span style="color: black;">因此</span>效果会比较<span style="color: black;">显著</span>。</span></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><strong style="color: blue;"><span style="color: black;"><span style="color: black;">2、</span>JsBridge</span></strong></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">虽然随着 WebView 的逐步更新,赋予了 web 丰富的功能,但<span style="color: black;">思虑</span>到兼容性以及<span style="color: black;">全部</span> APP 交互体验的统一,大部分的业务场景下<span style="color: black;">咱们</span>仍需要借助 native 的功能。<span style="color: black;">例如</span>:</span></span></p><span style="color: black;"><span style="color: black;">视图层面 - 注册、登录、认证、注销组件、视图路由...</span></span><span style="color: black;"><span style="color: black;">存储层面 - 用户信息、设备信息、业务状态、缓存...</span></span><span style="color: black;"><span style="color: black;">网络层面 - 请求 header、代理转发、预请求...</span></span><span style="color: black;"><span style="color: black;">APP 层面 - 唤起、设置、push、跨 APP 操作...</span></span><span style="color: black;"><span style="color: black;">系统层面 - 底层 API 的调用</span></span><span style="color: black;"><span style="color: black;">其它辅助功能</span></span>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">良好的混合架构能降低设计成本,减少前后端工作量,快速发布迭代,<span style="color: black;">提高</span>稳定性和用户体验。而 JsBridge 正是负责 web 和 native 通信的核心。</span></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><strong style="color: blue;"><span style="color: black;">1、介绍</span></strong></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">JsBridge 的设计和实现有<span style="color: black;">各样</span>各样的版本,<span style="color: black;">这儿</span>简单梳理几个要点:</span></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><strong style="color: blue;"><span style="color: black;">1.1、Web To Native</span></strong></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;"><span style="color: black;">重点</span>采用注入 web 可调用的<span style="color: black;">办法</span>,或进行拦截:</span></span></p><span style="color: black;"><span style="color: black;">iOS UIWebView - JavaScriptCore</span></span><span style="color: black;"><span style="color: black;">iOS WKWebView - WKScriptMessageHandler</span></span><span style="color: black;"><span style="color: black;">Android - addJavascriptInterface(4.2以下有安全漏洞)</span></span><span style="color: black;"><span style="color: black;">URL 拦截(URLScheme)</span></span><span style="color: black;"><span style="color: black;">JS <span style="color: black;">办法</span>拦截(alert、prompt、confirm、console.log)</span></span>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><strong style="color: blue;"><span style="color: black;">1.2、Native To Web</span></strong></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">直接执行 web 暴露的全局<span style="color: black;">办法</span><span style="color: black;">就可</span>:</span></span></p><span style="color: black;"><span style="color: black;">iOS - stringByEvaluatingJavaScriptFromString (兼容但<span style="color: black;">没法</span><span style="color: black;">捕捉</span>错误)</span></span><span style="color: black;"><span style="color: black;">iOS UIWebVIew - JSContext evaluateScript</span></span><span style="color: black;"><span style="color: black;">iOS WKWebView - evaluateJavaScript</span></span><span style="color: black;"><span style="color: black;">Android - loadUrl(<span style="color: black;">没法</span>获取返回结果)</span></span><span style="color: black;"><span style="color: black;">Android 4.4+ - evaluateJavascript(<span style="color: black;">能够</span>获取返回结果)</span></span>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><strong style="color: blue;"><span style="color: black;">2、重构前状态</span></strong></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">重构前,端内 web 调用 native 的方式较为混乱:</span></span></p><span style="color: black;"><span style="color: black;"><span style="color: black;">foo://</span></span><span style="color: black;"> - 无参数调用</span></span><span style="color: black;"><span style="color: black;"><span style="color: black;">bar://encodedParams</span></span><span style="color: black;"> - 所有参数转 JSONString 并 encode</span></span><span style="color: black;"><span style="color: black;"><span style="color: black;">baz://param1/param2</span></span><span style="color: black;"> - 分割单个参数</span></span><span style="color: black;"><span style="color: black;"><span style="color: black;">window.xxx(params)</span></span><span style="color: black;"> - 注入<span style="color: black;">办法</span>直接调用</span></span>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">对应的回调<span style="color: black;">亦</span>是多种多样,web 暴露全局<span style="color: black;">办法</span>由 native 调用,<span style="color: black;">亦</span>有直接<span style="color: black;">经过</span>注入<span style="color: black;">办法</span>返回:</span></span></p><span style="color: black;"><span style="color: black;"><span style="color: black;">window.foo_success(result)</span></span><span style="color: black;"> / </span><span style="color: black;"><span style="color: black;">.foo_fail(error)</span></span><span style="color: black;"> - <span style="color: black;">区别</span>函数<span style="color: black;">表率</span><span style="color: black;">区别</span>状态</span></span><span style="color: black;"><span style="color: black;"><span style="color: black;">window.bar_done(result)</span></span><span style="color: black;"> - 从结果集中区分<span style="color: black;">区别</span>状态</span></span><span style="color: black;"><span style="color: black;"><span style="color: black;">result = window.xxx(params)</span></span><span style="color: black;"> - 直接返回</span></span>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">混乱的实现方式<span style="color: black;">引起</span><span style="color: black;">运用</span>了<span style="color: black;">海量</span>的协议头,<span style="color: black;">同期</span>注入了<span style="color: black;">海量</span>的全局<span style="color: black;">办法</span>,还存在<span style="color: black;">有些</span><span style="color: black;">运用</span>的时序问题。<span style="color: black;">况且</span><span style="color: black;">运用</span>一个功能需要<span style="color: black;">晓得</span>协议头、参数格式、传参方式、回调方式、<span style="color: black;">怎样</span>区分成功/失败,显然过于<span style="color: black;">繁杂</span>了。</span></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><strong style="color: blue;"><span style="color: black;">3、重构思路</span></strong></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">借这次重构,<span style="color: black;">最终</span>有机会对两端通信的 API 做一次全盘整理,而<span style="color: black;">怎样</span>设计统一规范的 JsBridge 是<span style="color: black;">这次</span>重构的<span style="color: black;">重点</span><span style="color: black;">目的</span>。</span></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">从单一职责的<span style="color: black;">方向</span><span style="color: black;">思虑</span>,JsBridge 只应处理一件事:把<span style="color: black;">信息</span>在正确的通道传递给对方,不关心<span style="color: black;">详细</span>业务;而业务<span style="color: black;">研发</span>只聚焦功能<span style="color: black;">自己</span>,无需<span style="color: black;">思虑</span><span style="color: black;">怎样</span>传递。</span></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><strong style="color: blue;"><span style="color: black;">3.1、对<span style="color: black;">叫作</span>设计</span></strong></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;"><span style="color: black;">首要</span>需要<span style="color: black;">知道</span>的是,完整的 JsBridge 功能应该是双向互通的:</span></span></p><span style="color: black;"><span style="color: black;">web 调用 -> native 接收调用并返回结果 -> web 接收结果</span></span><span style="color: black;"><span style="color: black;">native 调用 -> web 接收调用并返回结果 -> native 接收结果</span></span>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">抽象之后应该具备两个通道,<span style="color: black;">每一个</span>通道发送的<span style="color: black;">信息</span>都<span style="color: black;">包括</span>“调用”和“结果”两种内容。而每一端需要具备四项能力:</span></span></p>
<div style="color: black; text-align: left; margin-bottom: 10px;"><img src="https://p3-sign.toutiaoimg.com/tos-cn-i-6w9my0ksvp/27f4fb58a333417c971f722fe7a8d591~noop.image?_iz=58558&from=article.pc_detail&lk3s=953192f4&x-expires=1725650935&x-signature=vdUJtesdNlkNrE3sV8cfaK0D6ec%3D" style="width: 50%; margin-bottom: 20px;"></div><span style="color: black;"><span style="color: black;">调用(invoke)- 调用另一端的<span style="color: black;">办法</span></span></span><span style="color: black;"><span style="color: black;">接收(receive)- 接收执行结果</span></span><span style="color: black;"><span style="color: black;">注册(register)- 注册<span style="color: black;">办法</span>等待另一端的调用</span></span><span style="color: black;"><span style="color: black;">回调(callback)- 回传执行结果</span></span>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;"><span style="color: black;">信息</span>都是<span style="color: black;">经过</span>异步回调的方式进行传输,<span style="color: black;">这般</span>在满足<span style="color: black;">更加多</span>的业务场景时,功能实现方<span style="color: black;">亦</span><span style="color: black;">不消</span>关心功能<span style="color: black;">自己</span>是同步还是异步,只需在得到结果的时候丢给 JsBridge <span style="color: black;">就可</span>。</span></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><strong style="color: blue;"><span style="color: black;">3.2、<span style="color: black;">信息</span>通道</span></strong></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">基于<span style="color: black;">信息</span>传递需具备的能力,<span style="color: black;">咱们</span>来<span style="color: black;">思虑</span>采用哪种方式实现。<span style="color: black;">日前</span>端内的<span style="color: black;">状况</span>,iOS 采用 WKWebview,Android 仍需兼容系统 4.0+,<span style="color: black;">因此</span><span style="color: black;">最后</span>实现:</span></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><strong style="color: blue;"><span style="color: black;">3.2.1、Web To Native</span></strong></span></p><span style="color: black;"><span style="color: black;">iOS - WKScriptMessageHandler</span></span><span style="color: black;"><span style="color: black;">Android - 4.2及以上 采用 addJavascriptInterface,其它<span style="color: black;">运用</span> URLScheme</span></span>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">简单<span style="color: black;">来讲</span><span style="color: black;">便是</span>优先<span style="color: black;">运用</span> native 注入的方式,更通用的 URLScheme 用来兜底。之<span style="color: black;">因此</span><span style="color: black;">无</span>统一<span style="color: black;">运用</span> URLScheme,<span style="color: black;">重点</span>是<span style="color: black;">思虑</span>性能方面,native 注入的<span style="color: black;">办法</span>调用速度更快;<span style="color: black;">况且</span>在并行调用时 URLScheme 方式需要做<span style="color: black;">有些</span> hack 处理,对效率<span style="color: black;">亦</span>有<span style="color: black;">必定</span>影响。</span></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><strong style="color: blue;"><span style="color: black;">3.2.2、Native To Web</span></strong></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">统一由 web 暴露一个全局的接收<span style="color: black;">办法</span>供 native 调用。</span></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">综上,只需要在全局环境注入三个<span style="color: black;">办法</span>(iOS 和 Android 注入的<span style="color: black;">办法</span>限于实现方式<span style="color: black;">区别</span>未强制统一)和一个协议。</span></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><strong style="color: blue;"><span style="color: black;">3.3、数据结构</span></strong></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;"><span style="color: black;">详细</span>到<span style="color: black;">信息</span>本身,<span style="color: black;">咱们</span>需<span style="color: black;">知道</span>应该<span style="color: black;">包括</span><span style="color: black;">那些</span>内容。<span style="color: black;">每一个</span>业务或功能的 API 实现基本都<span style="color: black;">能够</span>简化成:<span style="color: black;">办法</span>名、所需参数、回调和错误信息。而<span style="color: black;">因为</span><span style="color: black;">信息</span>通道的合并,为了区分<span style="color: black;">信息</span>是“调用”还是“结果”,需<span style="color: black;">增多</span>一个 ID 标示,<span style="color: black;">同期</span>利用该 ID 还<span style="color: black;">能够</span>确定是哪一次“调用”,以<span style="color: black;">处理</span>异步的对齐问题。<span style="color: black;">最后</span>的数据结构如下:</span></span></p><span style="color: black;">// Invocation</span>
{
<span style="color: black;">"name"</span>: <span style="color: black;">"foo"</span>,
<span style="color: black;">"params"</span>: {...},
<span style="color: black;">"callbackId"</span>: <span style="color: black;">"cb_1"</span>
}
<span style="color: black;">// Result</span>
{
<span style="color: black;">"responseId"</span>: <span style="color: black;">"cb_1"</span>,
<span style="color: black;">"result"</span>: {
<span style="color: black;">"data"</span>: {...},
<span style="color: black;">"errorMsg"</span>: <span style="color: black;">""</span>,
<span style="color: black;">"errorData"</span>: ...,
<span style="color: black;">"errorDesc"</span>: ...
}
}<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">其中:</span></span></p><span style="color: black;"><span style="color: black;"><span style="color: black;">callbackId</span></span><span style="color: black;"> - <span style="color: black;">显示</span>该<span style="color: black;">信息</span>是一次“调用”,而 </span><span style="color: black;"><span style="color: black;">responseId</span></span><span style="color: black;"> <span style="color: black;">显示</span>是“结果”,<span style="color: black;">况且</span>两者是对应的</span></span><span style="color: black;"><span style="color: black;"><span style="color: black;">name</span></span><span style="color: black;"> - 调用的<span style="color: black;">办法</span>名</span></span><span style="color: black;"><span style="color: black;"><span style="color: black;">params</span></span><span style="color: black;"> - 调用的<span style="color: black;">办法</span>所需的参数,统一<span style="color: black;">运用</span>对象格式,利于扩展</span></span><span style="color: black;"><span style="color: black;"><span style="color: black;">errorMsg</span></span><span style="color: black;"> 用于区分此次处理<span style="color: black;">是不是</span>成功,其它<span style="color: black;">附庸</span> </span><span style="color: black;"><span style="color: black;">error*</span></span><span style="color: black;"> 是<span style="color: black;">有些</span>可选的扩展</span></span><span style="color: black;"><span style="color: black;"><span style="color: black;">data</span></span><span style="color: black;">是处理成功后的返回数据</span></span>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;"><span style="color: black;">思虑</span>到兼容性,<span style="color: black;">信息</span>体采用 JSONString 类型,<span style="color: black;">倘若</span>是<span style="color: black;">经过</span> URLScheme 发送还需要对<span style="color: black;">信息</span>体做一次 encode。</span></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">在确定了<span style="color: black;">信息</span>通道(怎么发)和数据结构(发什么)之后,JsBridge 其实<span style="color: black;">已然</span><span style="color: black;">能够</span>工作了。但在<span style="color: black;">实质</span><span style="color: black;">运用</span>场景下,<span style="color: black;">咱们</span>仍需要<span style="color: black;">思虑</span>以下几个方面。</span></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><strong style="color: blue;"><span style="color: black;">3.4、可用性</span></strong></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><strong style="color: blue;"><span style="color: black;">3.4.1、JsBridge <span style="color: black;">是不是</span>可用</span></strong></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">Native <span style="color: black;">办法</span>注入<span style="color: black;">是由于</span>系统底层实现的,当 WebView 构建 Window 环境时会相应的挂载要注入的<span style="color: black;">办法</span>,当 web 页面运行时,注入<span style="color: black;">办法</span><span style="color: black;">已然</span>可用;而对 URLScheme 的监听<span style="color: black;">机会</span>更早,且跟 Window 无直接<span style="color: black;">相关</span>。<span style="color: black;">因此</span><span style="color: black;">咱们</span><span style="color: black;">能够</span>在页面的任何<span style="color: black;">地区</span>直接发起调用,而无需进行双方“握手”,perfect!</span></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><strong style="color: blue;"><span style="color: black;">3.4.2、API <span style="color: black;">是不是</span>可用</span></strong></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">随着业务迭代,native 支持的<span style="color: black;">办法</span>在<span style="color: black;">区别</span>的版本下会产生差异,比起<span style="color: black;">运用</span>版本号来判断,统一暴露出一个检测 API <span style="color: black;">是不是</span>可用的<span style="color: black;">办法</span>则更为方便。但 JsBridge 的调用均为异步,两个异步嵌套会让<span style="color: black;">运用</span>变的繁琐。<span style="color: black;">咱们</span>期望能像浏览器自带 API <span style="color: black;">同样</span><span style="color: black;">能够</span>直接<span style="color: black;">运用</span>,<span style="color: black;">因此</span><span style="color: black;">最后</span>采用由 native 向 Window 环境中注入当前所支持的 APIList <span style="color: black;">方法</span>。但两端的实现有些差异:</span></span></p><span style="color: black;"><span style="color: black;">iOS <span style="color: black;">经过</span> WKScriptMessageHandler 注入的<span style="color: black;">办法</span><span style="color: black;">没法</span>直接拿到返回值,改由 WKUserScript 注入 js</span></span><span style="color: black;"><span style="color: black;">Android 4.2 及以上仍利用 addJavascriptInterface 注入<span style="color: black;">办法</span>,<span style="color: black;">能够</span>直接拿到返回值</span></span><span style="color: black;"><span style="color: black;">Andoird 4.2 以下<span style="color: black;">运用</span> loadUrl 在页面加载的多个<span style="color: black;">周期</span>尝试注入 js</span></span>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><strong style="color: blue;"><span style="color: black;">3.5、命名空间</span></strong></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;"><span style="color: black;">一般</span> native <span style="color: black;">供给</span>的功能<span style="color: black;">大都是</span>偏向通用的,但某些业务场景会需要比较定制的混合功能(<span style="color: black;">例如</span>业务数据同步、UI 交互联动),显然与通用功能放在<span style="color: black;">一块</span>不太合适,<span style="color: black;">况且</span>会<span style="color: black;">增多</span>命名<span style="color: black;">包袱</span>。<span style="color: black;">咱们</span><span style="color: black;">能够</span>把通用功能放在一个池子里,任何业务都<span style="color: black;">能够</span>继承自己所需的,<span style="color: black;">而后</span>再实现<span style="color: black;">自己</span>定制的部分,组合成一套新的 APIList,并利用命名空间加以区分,<span style="color: black;">这般</span>定制业务<span style="color: black;">能够</span>更加灵活而<span style="color: black;">不消</span>担心对其它业务做成影响。Web 端直接调用指定命名空间下注入的<span style="color: black;">办法</span>,或在 URLScheme 中<span style="color: black;">增多</span>一级 namespace 便<span style="color: black;">能够</span><span style="color: black;">拜访</span>到指定业务下的对应<span style="color: black;">办法</span>。</span></span></p>
<div style="color: black; text-align: left; margin-bottom: 10px;"><img src="https://p3-sign.toutiaoimg.com/tos-cn-i-6w9my0ksvp/9d87d93afabb4347a9189b2008c70493~noop.image?_iz=58558&from=article.pc_detail&lk3s=953192f4&x-expires=1725650935&x-signature=5A7N6L%2BrziNK0XK%2FhtMDR53GpAU%3D" style="width: 50%; margin-bottom: 20px;"></div>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><strong style="color: blue;"><span style="color: black;">3.6、<span style="color: black;">详细</span>实现</span></strong></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">在 web 端的<span style="color: black;">详细</span>实现中,对<span style="color: black;">信息</span>传递部分进行了封装,抹平了 iOS、Android 中的差异,并<span style="color: black;">运用</span> </span><span style="color: black;"><span style="color: black;">Promise</span></span><span style="color: black;"> 来承接设计中的双向异步、状态区分(成功、失败)等特性。</span></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><strong style="color: blue;"><span style="color: black;">Invoke</span></strong></span></p>
<div style="color: black; text-align: left; margin-bottom: 10px;"><img src="https://p3-sign.toutiaoimg.com/tos-cn-i-6w9my0ksvp/9e646ad30bb144edb96e3454d24b8dfc~noop.image?_iz=58558&from=article.pc_detail&lk3s=953192f4&x-expires=1725650935&x-signature=4Jzw4TTB6CPEFbFCK1PuU3A6Awg%3D" style="width: 50%; margin-bottom: 20px;"></div><span style="color: black;">jsBridge</span><span style="color: black;">.config</span>({
<span style="color: black;">namespace</span>: <span style="color: black;">common</span>, <span style="color: black;">// 设置命名空间</span>
...
})
<span style="color: black;">if</span> (jsBridge.isAvailable(<span style="color: black;">foo</span>)) { <span style="color: black;">// 判断<span style="color: black;">是不是</span>可用</span>
<span style="color: black;">jsBridge</span><span style="color: black;">.invoke</span>(<span style="color: black;">foo</span>, params) <span style="color: black;">// 只需关心<span style="color: black;">办法</span>名和参数<span style="color: black;">就可</span></span>
<span style="color: black;">.then</span>(data => {...})<span style="color: black;">// 成功数据</span>
<span style="color: black;">.catch</span>(error => {...}) <span style="color: black;">// 失败处理</span>
}<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><strong style="color: blue;"><span style="color: black;">Register</span></strong></span></p>
<div style="color: black; text-align: left; margin-bottom: 10px;"><img src="https://p3-sign.toutiaoimg.com/tos-cn-i-6w9my0ksvp/3c79299e8645478e948b6b0cc1240934~noop.image?_iz=58558&from=article.pc_detail&lk3s=953192f4&x-expires=1725650935&x-signature=obZpAiNrXJNLw4flrCMXDWMvPO4%3D" style="width: 50%; margin-bottom: 20px;"></div>jsBridge.register(<span style="color: black;">bar</span>, <span style="color: black;"><span style="color: black;">params</span> =></span> {
...
<span style="color: black;">// 直接返回结果</span>
<span style="color: black;">return</span> result
<span style="color: black;">// Or 返回一个异步操作</span>
<span style="color: black;">return</span> <span style="color: black;">new</span> <span style="color: black;">Promise</span>(...)
})<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><strong style="color: blue;"><span style="color: black;">4、小结</span></strong></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">总体来讲,JsBridge 的设计并不<span style="color: black;">繁杂</span>,只需要确定职责范围,针对性技术选型,再处理一部分兼容问题<span style="color: black;">就可</span>,剩下的<span style="color: black;">便是</span><span style="color: black;">详细</span>业务功能的实现和联调了。</span></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;"><span style="color: black;">另一</span>针对 URLScheme 有两个问题需要<span style="color: black;">弥补</span>下:</span></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><strong style="color: blue;"><span style="color: black;">4.1、URLScheme 特殊处理</span></strong></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">在<span style="color: black;">运用</span> URLScheme 时,会遇到“并发”问题,即<span style="color: black;">同期</span>发送多次调用,仅最后一个能正确传递到 native 端,前边的都被忽略了。针对这个问题<span style="color: black;">咱们</span><span style="color: black;">能够</span><span style="color: black;">运用</span>纯 web 的<span style="color: black;">处理</span><span style="color: black;">方法</span>,每次调用都创建一个新的 iframe,但<span style="color: black;">思虑</span>到频繁地创建销毁 DOM 元素,对<span style="color: black;">全部</span>页面会<span style="color: black;">导致</span><span style="color: black;">必定</span>的影响,<span style="color: black;">最后</span>采用队列 + 确认的<span style="color: black;">方法</span>。</span></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">Native 每收到一次协议<span style="color: black;">信息</span>(不管里边<span style="color: black;">包括</span>多少调用),立即发送接收成功的 confirm 到 web 端,<span style="color: black;">而后</span> web 端<span style="color: black;">检测</span>等待 confirm 这<span style="color: black;">时期</span><span style="color: black;">是不是</span>有累积新的调用,<span style="color: black;">倘若</span>有就一次性发走。</span></span></p>
<div style="color: black; text-align: left; margin-bottom: 10px;"><img src="https://p3-sign.toutiaoimg.com/tos-cn-i-6w9my0ksvp/c6f29d1d035f4af18c6aa20c188c18ee~noop.image?_iz=58558&from=article.pc_detail&lk3s=953192f4&x-expires=1725650935&x-signature=jzHLbDVsT6dKgSK%2B0ou562lDp%2FU%3D" style="width: 50%; margin-bottom: 20px;"></div>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;"><span style="color: black;">另一</span>,URL 是有长度限制的,<span style="color: black;">因此</span><span style="color: black;">针对</span><span style="color: black;">尤其</span><span style="color: black;">繁杂</span>的调用是有可能丢失数据的,这<span style="color: black;">亦</span>是<span style="color: black;">非常多</span> JsBridge 实现采用 web 发送<span style="color: black;">通告</span>(有新<span style="color: black;">信息</span>),native 自己来取的策略。但这就<span style="color: black;">增多</span>了一个取数据的环节,<span style="color: black;">思虑</span>到这种<span style="color: black;">状况</span>极少,<span style="color: black;">况且</span> URLScheme 只是<span style="color: black;">咱们</span>的 fallback <span style="color: black;">方法</span>,<span style="color: black;">咱们</span><span style="color: black;">最后</span><span style="color: black;">选取</span>忽略这个问题。</span></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><strong style="color: blue;"><span style="color: black;"><span style="color: black;">3、</span><span style="color: black;">实质</span>应用</span></strong></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">基于资源离线和 JsBridge 的设计,WebView 容器的<span style="color: black;">基本</span>功能<span style="color: black;">已然</span>实现,之后<span style="color: black;">按照</span>规范对 native API 进行完善,<span style="color: black;">全部</span>重构就算完<span style="color: black;">成为了</span>。除了常规的 API 外,<span style="color: black;">咱们</span>还实现了 web 端请求代理功能,以<span style="color: black;">处理</span>在<span style="color: black;">实质</span>的业务场景中数据请求存在的种种问题。</span></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><strong style="color: blue;"><span style="color: black;">1、请求代理</span></strong></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">利用 request 协议<span style="color: black;">办法</span>,将请求参数传递给 native 端,native 端封装好请求并发送,<span style="color: black;">最后</span>将返回数据挂载到 request 的回调中返回给 web 端。利用该<span style="color: black;">办法</span>能带来以下几个方面的<span style="color: black;">提高</span>:</span></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><strong style="color: blue;"><span style="color: black;">1.1、预请求</span></strong></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">在加载页面的<span style="color: black;">同期</span>,native 端<span style="color: black;">按照</span>配置参数(合并在离线配置中)提前发送请求并暂存,web 端运行时发送<span style="color: black;">一样</span>请求时,native 端<span style="color: black;">经过</span>比对将<span style="color: black;">已然</span>暂存的请求数据返回(<span style="color: black;">倘若</span>请求还未完成则等待完成后返回)。<span style="color: black;">经过</span>简单的并行,缩短了用户的感知时间,<span style="color: black;">尤其</span>针对首屏有数据依赖的页面,性能<span style="color: black;">提高</span>较为<span style="color: black;">显著</span>。</span></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><strong style="color: blue;"><span style="color: black;">1.2、统一业务 header</span></strong></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">为了方便和统一,在 native 端封装了一套通用的请求 header,<span style="color: black;">包括</span><span style="color: black;">有些</span>常用的设备信息、用户登录信息等,用于后端接口的数据<span style="color: black;">查找</span>和校验。而 web 端想保持一致的话,<span style="color: black;">有些</span> header 信息需要先从 native 端取,<span style="color: black;">而后</span><span style="color: black;">自动</span>封装发送,存在<span style="color: black;">必定</span>耗时且较为繁琐。利用 request 协议<span style="color: black;">办法</span>,web 端<span style="color: black;">能够</span>省略通用 header,统一由 native 端注入,既方便又能保持数据的一致性。</span></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><strong style="color: blue;"><span style="color: black;">1.3、统一日志管理</span></strong></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">Native 端在处理 request 协议<span style="color: black;">办法</span>时,将请求和返回数据一并<span style="color: black;">插进</span>日志队列,集中存储,能更清晰的记录用户的<span style="color: black;">行径</span>和请求状态,改善了 web 端难以落日志的痛点,<span style="color: black;">针对</span>反馈问题的排查有很大<span style="color: black;">帮忙</span>。</span></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><strong style="color: blue;"><span style="color: black;">1.4、跨域</span></strong></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">省去了跨域带来的种种麻烦。</span></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><strong style="color: blue;"><span style="color: black;">2、WebView 预创建</span></strong></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">除了资源加载,容器(WebView)的创建和初始化<span style="color: black;">亦</span>存在<span style="color: black;">必定</span>的耗时,对此,<span style="color: black;">咱们</span>创建了一个 WebView 的“池子”,当 APP <span style="color: black;">起步</span>时,在主序列任务完成后,会主动创建一个 WebView 放进“池子”中,以供后续 web 页面的<span style="color: black;">运用</span>。<span style="color: black;">同期</span>,每当“池子”中的一个 WebView 被<span style="color: black;">运用</span>了便会再创建一个留作备用,依次循环。这就<span style="color: black;">保准</span>了,每次 web 页面加载时,总有一个 ready 状态的 WebView <span style="color: black;">能够</span>立即<span style="color: black;">运用</span>。</span></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><strong style="color: blue;"><span style="color: black;">3、性能对比</span></strong></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;"><span style="color: black;">协同</span>以上种种特性,<span style="color: black;">咱们</span>最后来看下,<span style="color: black;">经过</span><span style="color: black;">安排</span>离线和预请求来<span style="color: black;">提高</span>性能的前后对比数据。基于章节二中的测试页面,<span style="color: black;">咱们</span>在静态资源加载完毕后追加了一次数据请求,这<span style="color: black;">亦</span>更接近真实的业务场景,<span style="color: black;">一样</span>在多种网络环境<span style="color: black;">拜访</span>多次取均值:</span></span></p>
<div style="color: black; text-align: left; margin-bottom: 10px;"><img src="https://p3-sign.toutiaoimg.com/tos-cn-i-6w9my0ksvp/ae75a1da90ef4aa1bad958ef05abe63d~noop.image?_iz=58558&from=article.pc_detail&lk3s=953192f4&x-expires=1725650935&x-signature=HaASnSBwrfBJekh3WLoFLrcxcVc%3D" style="width: 50%; margin-bottom: 20px;"></div>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">从图中<span style="color: black;">能够</span>看出,受到业务接口的影响,平均整体耗时由 942.9ms 优化到了 391.1ms,优化幅度在 50% <span style="color: black;">上下</span>,接口的平均耗时要高于静态资源的记载耗时。但随着业务<span style="color: black;">规律</span><span style="color: black;">繁杂</span>度的<span style="color: black;">提高</span>,资源加载和接口预请求的并行时间会进一步<span style="color: black;">增多</span>,收益<span style="color: black;">亦</span>会相应的<span style="color: black;">提高</span><span style="color: black;">更加多</span>。</span></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><strong style="color: blue;"><span style="color: black;">4、线上实例</span></strong></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;"><span style="color: black;">经过</span>线上实例来对比,<span style="color: black;">能够</span>更直观的感受到用户体验的变化。视频中在<span style="color: black;">一样</span>环境下<span style="color: black;">拜访</span>同一个 H5 页面(首次打开和二次打开),其中左侧为普通加载,右侧开启离线和预请求,以 WebView 所在原生视图推进的瞬间对齐时间线。</span></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;"><span style="color: black;">能够</span>看出,经过离线和预请求的优化,<span style="color: black;">显著</span>的缩短了首次打开的页面白屏时间。</span></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><strong style="color: blue;"><span style="color: black;"><span style="color: black;">4、</span>结语</span></strong></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">在如今<span style="color: black;">各样</span>混合框架/语言激烈竞争的大前端环境下,基于 WebView 的 H5 Hybrid 凭着简单、通用、轻量、稳定等特性,仍占据着重要的地位。而借助资源离线、数据预请求、native <span style="color: black;">办法</span>注入等优化<span style="color: black;">方法</span>,对其在性能和硬件能力上的短板进行加强,使其能够承接<span style="color: black;">更加多</span>的业务场景。</span></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;">作者:<span style="color: black;">毛仪桓 李贺柯</span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">源自</span>-<span style="color: black;">微X</span>公众号:<span style="color: black;"><span style="color: black;">网易传媒技术团队</span></span></p>
<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;">出处</p>:https://mp.weixin.qq.com/s/AV2SwFfwwJH7xyrIBJemgw
谷歌网站排名优化 http://www.fok120.com/ 认真阅读了楼主的帖子,非常有益。 i免费外链发布平台 http://www.fok120.com/ 回顾历史,我们感慨万千;放眼未来,我们信心百倍。
页:
[1]