騰訊跨端框架 Hippy 常用調試方法和問題案例詳解


導讀 | 近日,騰訊開源跨端框架 Hippy,一周即吸引3000+star。在騰訊內部,Hippy 已運行3年之久,跨 BG 共有 18 款線上業務正在使用 Hippy,日均 PV 過億,且已建立一套完整生態。

相較於其他跨端框架,Hippy 對前端開發者更友好:緊貼 W3C 標准,遵從網頁開發各項規則,使用 JavaScript 為開發語言,同時支持 React 和 Vue 兩種前端主流框架。本文將為大家介紹Hippy 常用調試方法和常見問題案例,希望能夠幫助開發者快速上手。

一、常見調試方法

1.調試服務

前端調試在官網 [1] 已經有專門章節進行描述,就不多說,這里具體說一下調試常見問題、案例和一些基本原理。Hippy 已經在 hippy-debug-server [2] 中集成了一套基於 Chrome DevTools Protocol [3] 的調試服務器,啟動后在終端進入本地調試界面,便可以進入遠程調試模式。目前 iOS 和 Android 都已經支持了真機調試,Android 通過 adb reverse 命令直接實現了本地調試端口的轉發。就是指在手機上訪問 localhost:38989 的調試端口時,訪問的實際是開發機上的 38989 端口。但是 iOS 需要終端和前端的雙方面配合修改端口才可以做到真機調試,所以建議先通過 iOS 模擬器進行調試工作。啟動調試服務、進入終端的本地調試環境后,JavaScript 代碼將會通過調試服務加載到真機中運行。如果代碼沒問題應該能正常運行,但有時候會碰到啟動就 Crash 的情況,可以參考常見案例最后一條“iOS 版本低於 9 時模擬器報告 SyntaxError”。同樣的,iOS 上某些特性有的能用 Polyfill 解決,但有的不行(例如 Proxy、正則表達式的 Sticky Flag 等就需要 iOS 10 以上才可以使用,而且無法 Polyfill),所以如果要兼容低版本 iOS,要注意不能使用到太新的 JS 特性。

2.整合到終端內的前端 jsbundle 包調試

有的 App 調試模式下運行很正常,但是打完包集成進去以后發現不行,這時候我們需要用到整合后的 jsbundle 包調試大法了。值得注意的是:該方案暫時只適用於 iOS。其實非常簡單,Hippy 在 iOS 中是通過自帶的 JavaScriptCore 運行的,所以可以通過自帶的 Safar 進行調試,在 Safari 的設置 -> 高級打開開發者菜單后 ,啟動 Hippy 就能看到多出了

一個模擬器設備。

Safari 調試菜單位置

然后就可以用 Safari 開始調試了。唯一要注意的是,斷點需要在啟動后才生效,啟動時是斷不下來的,啟動問題可以在關鍵點加上日志,日志能夠正常輸出。如果是其它啟動后問題,可以直接打斷點,跟 Chrome 調試服務的使用方法基本一致。

整合后包打斷點

3.內存占用情況

前端開發普遍對內存占用缺乏概念,直到終端同學過來說 JS 內存占用太多把 App 搞崩潰了才回過神來。JavaScript 目前主要以標記清除算法 [4] 的方案來進行內存回收,它的核心是定期從全局對象中遍歷所有對象,並且對不可到達的對象進行標記,並進而清除。在絕大多數情況下作為前端開發確實不需要關心內存占用,但是 Hippy 中不太一樣,Hippy 是前端的開發方式去開發終端 App,有幾個類在組件卸載時一定要記得銷毀。包含了 React 中負責事件監聽的 EventEmitter 實例、Animation/AnimationSet 動畫組件,Vue 中的 $app.on() 終端事件監聽等等,不釋放掉它們,它們就會一直占用着內存,隨着界面越來越多,App 最終將會崩潰。其實調試方法也非常簡單,直接在調試器的 Memory 觀察內存占用情況,打快照看一下當時各類對象對內存的占用情況,它是 Hippy 在瀏覽器里運行的容器,可以代表 App 的整體內存占用情況。

內存調試方案當然,這部分內容 Google 官方也有文檔 [5],感興趣的同學也可以去檢索查看。

二、常見案例

1. 數據已更新,但界面內容或樣式不變

這是經常碰到的,最直接的方式是對 React 和 Vue 進行界面繪畫的模塊 - UIManagerModule 的三個方法 :createNode、updateNode、deleteNode 打斷點。其實不管 MVVM 怎么做,最終都會通過這三個方法把界面通知終端畫上去,這其實也帶來了無限的擴展性,任何框架只要對接了這三個方法就可以進行 Hippy 繪制。如果掌握了 UIManagerModule 的語法,甚至不需要 React 或者 Vue 也可以直接通過它畫界面。但從一定程度上來講,Hippy 畫界面的方式其實跟瀏覽器是不一樣的,它是異步的。MVVM 組件創建完畢,componentDidMount 或者 mounted 后,其實並不意味着界面真的畫上去了(但是這個耗時極少,mounted 后基本可以認為真的畫上去了)。如果要對界面進行操作,需要確定終端確實畫上去了才行,這可以通過 onLayout 事件獲得。其次可以看到畫界面和普通的 Native Module 調用沒有本質區別,最終都要通過 JSBridge 進行通訊。-- 這部分正在通過 C++ 方式重寫。通過觀察它,我們可以了解到最終通過 React、Vue 解析后的組件是什么樣的,可以觀察到為什么界面沒有更新,或者樣式不如預期。Hippy 的前端框架在開發初期就考慮到了調試的便利性,調試模式下會將前端框架與終端之間的通訊都打印到 Console 里,當覺得自己的業務 App 或者框架顯示存在問題時,直接觀察它就能很方便獲得所有信息。以 Hippy-Vue 為例:

Hippy-Vue 的終端通訊日志Hippy-Vue 要關閉該功能只要將入口文件中的 Vue.config.silent 改為 true 即可;Hippy-React 要關閉該功能需要在啟動參數里增加一個 silent: true。不過一般不建議關閉,它在打包后會自動停止輸出。

2. ScrollView或 ListView無法滾動

在 Hippy 中只有這兩種 View 是可以滾動的,剩下的都不可以滾動,但是要讓它們能滾起來也不是那么簡單,需要有樣式進行配合,簡單說就是:

  • ScrollView(Vue 的 div + overflow-x/y:scroll) 以上所有父節點都必須有一個固定的高度,ScrollView 中只能嵌套一個內容子節點,它可以隨意變高。
  • ListView (Vue 的 ul/li)以上所有父節點都必須有一個固定的高度,里面所有的 renderRow 出來的 ListItemView(Vue 中的 li)可以隨意變高。

這里的固定高度可以是直接指定高度,也可以是通過 flex 進行界面動態分割的高度。但是一定要是固定的,因為滾動實際是終端去實現的,它需要能夠區分可以滾動和不可以滾動的區域,如果容器高度和內容高度一樣,那就變成不可以滾動了。另外 Vue 里的 ul 默認已經加上了 flex: 1 樣式會把整個 View 撐滿屏幕,一般情況下不用做特別處理,但是 div + overflow-x/y: scroll 依然需要手工指定高度。當滾動出現異常的時候,可以通過 XCode 調試一下終端代碼,它有個 Debug View Hierarchy 功能,可以非常直觀地看到界面層級和尺寸,對調試樣式問題有很大幫助。

XCode 的界面層級調試

3. ListView性能很差、卡頓、閃爍

這里需要提到前端三點非常需要注意的地方:(1)界面發生異常閃爍首先需要通過第一個小章節里的UIManagerModule 觀察法,看一下那三個方法是否有異常的執行。例如 updateNode 執行過於頻繁,或者 deleteNode/createNode 異常執行,通常是由於數據有變化導致界面重繪,可以通過調用棧看一下是哪里的數據更新導致界面重繪,並針對性地進行前端優化。(2)處理 key 值ListView 決定界面是否重繪,有個很關鍵的參數是 key(React 官文 [6]、Vue 官文 [7]),Hippy-React 也通過 getRowKey() 的方法實現了 key 在 ListView 中的應用。key 其實是數據的唯一標示符,數據不發生改變,key 就不應該發生改變,而 key 一旦發生改變 ListView 就會重繪。目前很多業務在開發時 key 不指定,或者把 index 作為 key,前者會導致 ListView 每次有數據更新都做一次完整的 Array diff,開銷非常大;后者會導致刪除中間一個節點時將后面所有的節點全部刪除再重新插入一次,開銷也非常大。出於性能考慮,key 是必須要加的,一般跟數據的主鍵保持一致即可。但是:如果 ListView 中的數據需要進行排序,那就不要指定 key 了。目前 Hippy 的 moveNode 功能,已經計划但仍未完成,指定 key 后在重新排序時會因為對應索引的 key 值不同,先刪除全部節點內容,再全部重建,可能會造成輕度閃爍。如果此時不指定 key,就只有一個更新節點的請求,兩次請求合並為一次,終端層會對數據進行對比並更新節點內容。(3)終端渲染如果到這一步終端渲染依然很慢、幀率低,我們就要提到另外一個參數 type 了,對應到 Hippy-React 里是 getRowType() 方法,它是用來表示組件樣式的,樣式不變,type 就不變。這里需要先說一下 Hippy ListView 的復用機制,當不指定 type 時,每次有新的ListItemView被渲染(HippyReact 里 renderRow() 將返回 ListItemView、Hippy-Vue 里的 li),終端都會重新構建所有終端組件節點。加了 type 之后,會將將之前渲染過的終端組件節點放到緩存池中,下次碰到相同 type 類型的 ListItemView,就不會重新渲染,而是從緩存池中把緩存的節點拿出來做次拷貝並更新數據,再上屏,即使只有一個樣式的 ListItemView,通過 type 也能做到性能優化。經過上面三步,基本可以解決 90% 的 ListView 性能問題。同樣的,Hippy-Vue 官方范例中也對這三個參數加了注釋。

4. iOS 上 ListView 不渲染,但 Android 沒問題

首先需要檢查 numberOfRows 參數是否真的是 ListView 中 ListItemView 的數量,這個除了在業務代碼中打斷點查看數據數量是否和 numberOfRows 一致以外,也可以通過第一個 UIManagerModule 的調試方法查出來。這個問題牽扯到 iOS 上一個 ListView 的上屏性能優化,iOS 上並不是發一個 ListItemView 就上屏一個的。而是需要先改變 ListView 的 numberOfRows 再去創建節點,當節點數量與 numberOfRows 一致時再上屏。目前碰到的所有不渲染的問題都是因為這個原因造成的。另外在 Hippy-Vue 中,對於靜態的 li(就是終端的 ListItemView),可以不需要手工指定 numberOfRows,Hippy-Vue 會在 DOM 層計算子節點數量。但是對於動態獲取的數據,也必須要加上該參數,因為 Hippy-Vue 位於 Vue 的渲染層,跟業務還隔了一個 Vue,無法知道業務到底有多少數據准備要渲染。

5. iPhone 中紅屏報告 ModuleNotRegist

這里需要提到 Hippy App 的啟動方式:當終端 JS 引擎加載完 JavaScript 后,會從 GLOBAL.appRegister對象里去尋找終端指定的 moduleName,而 __GLOBAL__.appRegister 是在 Hippy 啟動時通過 HippyRegister.regist() 方法注冊上的。在 Hippy-React 入口文件或者 Hippy-Vue 入口文件定義的 appName 最終都會執行到 regist() 方法上進行  __GLOBAL__.appRegister 的注冊,所以,首先我們要檢查終端的 moduleName 是否和 appName 一致。如果一致依然出錯的話,很大幾率是之前 JS 執行失敗,也不排除 SDK 更新后存在 bug,也有可能其它問題,會導致 __GLOBAL__.appRegister 未注冊成功。但我們可以在該錯誤拋出時二次確認一下終端所尋找到 moduleName 是否和前端定義的 appName 一致,只要在那一行打上日志,然后使用上文的 Release 包調試方案檢查終端過來查的到底是什么 appName,問題就可以得到解決。

6. iOS 版本低於 9 時模擬器報告 SyntaxError

這是因為 Hippy 自帶的 Webpack 默認調試模式配置文件,最低僅開啟了 iOS 9 的輸出,因為輸出到 iOS 8 會多出很多 polyfill,語法上也會轉換,導致體積大很多。Hippy 本身最低支持的 iOS 8,我們建議在高版本的 iOS 上進行調試,然后打包后在低版本 iOS 走一遍測試流程,沒什么問題即可。如果非要在低版本的 iOS 上進行調試,修改一下 webpack 配置文件 iOS 將 preset-env 中的 ios 版本改成更低即可,但目前經過測試 core-js 對 iOS 8 那樣對低版本可能存在問題,這就需要你自己手工調整了。

參考資料:

[1] Hippy 前端調試詳解:https://tencent.github.io/Hippy/#/guide/debug

[2] 關於 hippy-debug-server :https://www.npmjs.com/package/hippy-debug-server

[3] Chrome DevTools Protocol 簡介:https://developer.chrome.com/devtools/docs/integrating

[4] 標記清除算法:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Memory_Management#Mark-and-sweep_algorithm

[5] 解決內存問題的Google官方文檔:https://developers.google.com/web/tools/chrome-devtools/memory-problems/

[6] React 官方文檔:https://reactjs.org/docs/lists-and-keys.html#keys

[7] Vue 官方文檔:https://vuejs.org/v2/guide/list.html#Maintaining-State

歡迎關注「雲加社區」,Hippy 的實戰和原理解析系列文章將會陸續上線。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM