1.背景 H5 頁面做秒開優化是業務的常規操作,一般正常通過網路請求的 H5 頁面,我們都是圍繞資源載入速度優化展開。優化手段主要分兩個方向,一個是提升網路速度,一個是減少資源大小。 提升網路速度,一般的手段有 DNS 預解析、多功能變數名稱、升級 HTTP2、使用 CDN、SSR。而即使有靜態資源的網路緩 ...
1.背景
H5 頁面做秒開優化是業務的常規操作,一般正常通過網路請求的 H5 頁面,我們都是圍繞資源載入速度優化展開。優化手段主要分兩個方向,一個是提升網路速度,一個是減少資源大小。
提升網路速度,一般的手段有 DNS 預解析、多功能變數名稱、升級 HTTP2、使用 CDN、SSR。而即使有靜態資源的網路緩存,HTML 也只能用協商緩存,需要消耗一次網路請求。這也註定了無法避免因網路問題導致的頁面白屏時間較長的問題,在我們真實的數據中也能得到印證,無論怎麼優化,頁面的 1.5 秒開穩定在 90% 以上非常困難。
因此,如果想實現 95% 以上甚至 99% 以上秒開,離線化 H5 是必然的選擇。同時根據歷史經驗,隨著 iOS 和 Android 手機的性能不斷提升,Webview 的渲染性能也不斷提升,目前大部分手機的 H5 離線化渲染都可以實現無白屏體驗,無限接近原生的交互體驗。
2.收益
在實踐過程中,我們分兩個場景構建離線化 H5 基座,一個是由 H5 開發的新 APP,一個是汽車之家 APP。
主機廠內部有一個應用,第一版是 Native 和 H5 的混合開發,會通過網路請求資源,雖然有網路緩存,但是第一次打開也很慢,非常影響用戶體驗,內部性能監控平臺顯示首屏平均耗時 1s。後面全面改造成內置離線 H5 應用,Native 只提供橋功能,首屏平均耗時減少到 237ms,載入速度提升 4 倍,大部分情況下實現無白屏體驗。業內曾經做過人眼識別白屏的最小時間測試,當降到 200ms 左右時,人眼幾乎無法識別白屏。
新 APP 我們主要使用 H5 開發,而不是用 Flutter 或者 RN 等技術,最主要的原因是人才儲備不足,在業務場景並沒有特別複雜的原生體驗情況下,我們發現業務迭代,對客戶端的依賴大大減少,溝通成本降低,迭代效率有明顯提高,團隊不需要面對複雜的 Flutter 和 RN 引擎,也不需要熟悉客戶端的開發模式。
汽車之家 APP 中,由於歷史原因,不能預置離線包 H5,所以只能有選擇性地進行動態預載入,而這會導致在開屏等特殊場景下,訪問速度仍然不高。整體上首屏時間從 1240.4ms 縮短為 505.4ms,載入速度提升一倍。
3.技術架構
離線化 H5 Hybrid 架構上,主要分成四大模塊:H5 離線包管理工具、APP 開發工具、Native 運行時和 Webview 運行時。
4.離線包管理工具
H5 離線包管理工具包含:離線包管理平臺、之家雲打包腳本。離線包管理平臺包含:APP 應用管理、H5 應用管理、發佈回滾、開關控制四大功能。通過和之家雲發佈流水線聯動,可以實現網路版和離線版 H5 的版本同步,發佈操作也實現同步,由於之家雲功能限制,回滾目前還不支持聯動。
管理的物料包括:H5 離線包(H5 代碼、離線包配置文件)、APP 離線包配置文件。APP 配置文件,在沒有預置離線包的情況下,下載資源是阻塞性的。為了提升下載速度和可用性,APP 配置文件也被維護成了一個 CDN 上的 JSON 文件。
4.1
離線包發佈流程
在之家雲發佈平臺,使用離線包管理 CLI 工具,執行上傳離線包命令,並配置一個離線包發佈的 Webhook,即可實現自動化離線包發佈。離線管理平臺會同步之家雲平臺的離線包版本號。
4.2
離線包設計
和普通的 H5 打包文件相比,離線包新增了一個專屬的離線包配置文件 config.json,同時會把資源打包成 gzip 壓縮包,從而提升整體資源的下載速度。
資源目錄
h5id
├── js/
├── css/
├── img/
├── pages
│ ├── index.html
│ └── list.html
└── config.json(配置文件)
配置文件
interface HybridConfig {
// 離線 H5 APP ID
h5id: string;
// 匹配頁面和靜態資源的規則
mapping: {
[env: string]: {
pages: Resource[];
resources: Resource[];
}
};
package: {
// 離線包資源目錄路徑,根目錄相對路徑
file: string;
// 包含的文件,和 excludes 互斥,只能同時有一個
includes: {
ext: string[];
file: string[];
};
// 不包含的文件
excludes: {
ext: string[];
file: string[];
};
// Native APP 版本適配
appRules: {
// 離線包管理平臺的應用 ID
[appid: string]: {
// [最小版本,最大版本]
ios: [string, string];
android: [string, string];
}
};
}
}
interface Resource {
// 攔截到的請求 url 的規則,不提供 http 或者 https,支持單個文件和文件目錄。
// 例如:example.com/page,example.com/static/img.png
remoteUrl: string;
// 和 downloadUrl 必須有一個存在,相對離線包所在目錄的文件路徑。
// 「path」值會替換掉請求 url 中的「remoteUrl」字元串。
path?: string;
// 和 path 必須有一個存在,指定下載資源的 url,
// 下載後存放在離線包的 vendor 目錄下。
// 並把存儲 path 同步到配置文件的 path 欄位。
downloadUrl?: string;
// 可選的 mime type,如果不提供,通過文件名尾碼自動補償
contentType?: string;
}
4.3
打包命令行工具
-
打包發佈腳本發佈到之家私有源,以腳手架命令形式調用,提供打包、上傳命令;
-
業務方結合自身編譯上線流程進行調用,上傳完成則自動進行發佈;
-
前端靜態資源按照頁面/工程緯度打包成zip;
-
zip包含js/css/img/pages/config.json配置文件;
// 腳本安裝
npm i @auto/dt-fe-cli
// 編譯上傳
// 指定腳本的配置文件,打包並上傳至伺服器,預設配置文件為 config.json,可以使用 --config 指定配置文件
dt-fe-cli offline --config hybrid-config.json
4.4
管理平臺
為了更好管理離線包,我們提供了一個簡潔的管理後臺,用來管理 H5 應用和 APP 應用的關係,記錄之家雲編譯好的離線包,同時提供 APP 配置給客戶端查詢。為了提高 APP 配置下載速度和可靠性,我們用 CDN 上的 JSON 文件來存儲 APP 配置。
►4.4.1 APP 配置
interface APPConfig {
appid: string;
version: string;
updateTime: string;
isIosEnable: boolean;
isAndroidEnable: boolean;
H5Apps: H5App[];
}
interface H5App {
// H5 應用的 APP ID
h5Id: string;
versions: H5Config[];
lastVerison: {};
latestUrl: string;
}
interface H5Config {
version: string;
// true,開啟離線化
isEnable: boolean;
// true,開啟 iOS APP 離線化
isIosEnable: boolean;
// true,開啟 Android APP 離線化
isAndroidEnable: boolean;
// true,需要預先載入
isPreLoad: boolean;
pages: Resource[];
appRules: AppRules;
downloadUrl: string;
}
interface Resource {
// 攔截到的請求 url 的規則,支持單個文件和文件目錄。例如:/page,/static/img.png
remoteUrl: string;
// 和 downloadUrl 必須有一個存在,相對離線包所在目錄的文件路徑。
// path 會替換掉請求 URL 中的「remoteUrl」字元串
path?: string;
// 和 path 必須有一個存在,指定下載下載資源的 url,下載後存放在離線包的 vendor 目錄下。
// 並把存儲 path 同步到配置文件的 path 欄位。
// 下載失敗,則該匹配規則失效自動訪問網路資源
downloadUrl?: string;
// 可選的 mime type,如果不提供,通過文件名尾碼自動補償
contentType?: string;
}
interface AppRules {
// ios APP 的開始和結束版本,最大版本可設置 infinite
ios: [string,string];
android: [string,string];
}
►4.4.2 管理平臺截圖
5.客戶端設計
作為整體 Hybrid 離線包應用架構中的重要一環,端內 Hybrid 離線包 SDK 包括 Webview 管理、離線包管理、Bridge 三個模塊。
5.1
Webview管理
-
定製 Hybrid 瀏覽器,設置可通過特定 Scheme 協議打開;
-
如果沒有離線資源,可以降級 HTTP 請求,也可以選擇阻塞下載;
-
通過 H5 應用映射表匹配當前頁面 Url 和緩存資源,存在緩存資源時,Hybrid 瀏覽器攔截 H5 所有資源請求,執行本地緩存邏輯:命中緩存時直接返回本地資源;未命中緩存則交還給WebView進行預設處理;
-
關閉 Hybrid 瀏覽器時觸發離線包管理邏輯,進行資源更新。
5.2
離線包管理
-
APP 預置:APP 打包時可以使用命令行工具批量下載需要預置的離線包,並集成到 APP 中;
-
預載入:有時候出於 APP 體積的考慮,我們不能預置所有離線包,為了提高離線包的載入體驗,可以開啟預載入,在 APP 啟動後的空閑時間主動進行離線包下載;
-
更新:根據唯一性原則,同一個 H5 應用同時只保留一份離線資源;
-
磁碟空間管理:及時刪除下載失敗或已解壓完成的 ZIP 包;清理舊版本離線資源;結合 LRU 演算法進行離線包緩存上限管理。
-
環境隔離
5.3
環境區分及降級處理
-
H5 應用區分測試、生產環境,不同環境匹配不同離線資源;
-
通過預支的欄位開關可以控制是否啟用離線包邏輯,開關關閉時直接使用線上資源。
5.4
Hybrid 離線包方案的下一步規劃
Hybrid 方案在預載入模式下取得了較好的效果,有效的提升了 H5 頁面的秒開率,後續將在以下幾個方面繼續提升 Hybrid 方案的能力,更好的為主機廠相關業務助力。
-
增加並完善預置離線包能力:在 APP 大小可控的前提下,在 APP 內預置關鍵頁面的離線包,彌補預載入邏輯在第一次打開時命中率低的不足。APP接入預置離線包後,頁面第一次打開時預計資源命中率提高到100%。完成相關方案如下:
-
增加 One Shot 能力(類小程式):支持在 Hybrid 瀏覽器首次訪問 H5 應用時,實時下載離線資源包並匹配離線資源。APP接入One Shot 能力後,頁面第二次打開時預計資源命中率提高到100%。相關方案如下:
6.踩過的坑
6.1
Post 請求丟失 Body
iOS 系統,瀏覽器攔截協議有 NSURLProtocol 和 WKURLSchemeHandler 兩種,並且都存在 Post 請求丟失 Body 的問題,針對 Post 請求都需專門處理。
開始時使用的是 NSURLProtocol 協議,優點是可挑選處理請求,不需要處理的可拋回瀏覽器,但是在特殊場景下出現問題:NSURLProtocol 是全局攔截,打開後所有瀏覽器都會進行攔截,所以在多個瀏覽器同時存在,並且非Hybrid瀏覽器還有發送 Post 請求時,Post 請求 Body 會丟失,導致請求失敗。NSURLProtocol 的全局攔截問題無法解決,於是又將目光移向 WKURLSchemeHandler,開始了完全自己實現Http請求。
6.2
無侵入式攔截
WKURLSchemeHandler 需要解決的問題有很多,包括Cookies,重定向,Post請求支持等。最開始沿用了NSURLProtocol Post 請求處理方法,業務方將Post請求通過橋的方式,扔給Native進行請求,但緊接著就遇到另一個問題:需要業務方配合改造。作為一個通用平臺,接入成本過高是一個致命問題,直接影響業務方接入的意願。
實現無侵入式攔截,是我們必須要解決的問題,最終通過多次實驗,採用了JS註入攔截的方式,具體流程主要為以下幾點:
-
若命中離線,載入網頁時開啟Handler攔截,註入 Fetch / XMLHttpRequest 攔截請求腳本;
-
發送請求時,Post請求通過Bridge發送給Native,Get請求又傳遞給原生應用進行存儲處理WKURLSchemeHandler;
-
Handler攔截的請求若命中離線,走本地資源匹配,匹配到後模擬請求返回H5,未命中時Native發送請求;
-
Post請求,Native通過橋拿到url和Body信息,通過原生請求發送,結果透傳給H5。
7.總結
隨著移動設備整體性能的持續提升,離線化 H5 Hybrid技術架構在載入和交互體驗方面取得了顯著改善。在許多標準應用場景中,它能夠提供接近原生應用的用戶體驗。此外,它的跨平臺、低成本、充足的人才資源、豐富的生態系統和動態更新等優勢,使其在與其他跨平臺解決方案的比較中脫穎而出。
作者|
本文來自博客園,作者:古道輕風,轉載請註明原文鏈接:https://www.cnblogs.com/88223100/p/Practice-of-Offline-H5-Hybrid-at-Autohome-Main-Engine-Factory.html