在之前的一篇部落格中,Allen已經為大家介紹了React Native在Glow的應用以及大體架構。由於React Native庫本身的一些原因,其在Android的成熟度遠不及iOS,因此也給在Android的應用帶來了更多的挑戰。

在本文中,給大家分享一下在Android平臺上整合React Native的過程中碰到的一些問題和解決辦法。

64位支援

目前React Native的二進位制庫還不支援64位,而Android並不支援32位和64位二進位制庫的混合載入(詳見Mixing 32- and 64-bit Dependencies in Android

)。 因此如果應用中已經包含了64位的二進位制庫,必須用abiFilters去掉64位二進位制庫。

ndk {  
    abiFilters "armeabi", "mips", "armeabi-v7a", "x86"
}

APK大小

React Native帶來的另一個問題就是apk檔案變大,僅32位的支援大概就會使APK增大8MB。對此比較敏感的話可以考慮使用多apk技術來解決,但因為會造成版本管理變得複雜,我們並未採用。

因為x86機型市場上比較少,我們曾經嘗試過去掉對x86的支援,這樣大概可以節省4MB的空間。但效果並不好,原因是Play Store似乎無法100%正確識別x86裝置,造成在某些裝置上下載了無法使用。也許這跟我們的應用是中途去掉了x86的支援有關,但無法驗證,最後也只能放棄。

另外建議仔細閱讀Google減小APK大小的建議Reduce APK Size,對減小APK尺寸也有不小的幫助。

JS堆疊記錄

對於JS內部的錯誤捕獲,我們使用的是Sentry + Raven-js的解決方案。但使用中發現Android系統上無法正確捕獲JS堆疊記錄,原因是Raven-js cannot correctly parse android stacktrace這個bug。最後的解決方案是在打包指令碼中給Raven-js打一個補丁。

另外發現的一個問題是Android上的堆疊記錄在某些情況下會產生偏移,而且只在minify過的JS程式碼上出現。這個問題目前還沒有得到解決,幸好大多數情況下都可以根據異常本身的資訊來找出正確的錯誤程式碼。如果有讀者瞭解這個問題,還望不吝賜教。

ListView效能問題

在最初使用的React Native 0.42.3中,發現ListView在Android上有一個很嚴重的問題:ListView滾動結束後往往有一秒以上的時間內整個ListView停止響應,期間不能響應任何的點選事件。

花費大量時間除錯後發現,ListView滾動過程中React Native的某個計算layout的函式佔據了相當多的CPU時間。最後發現這也是React Native在0.40.0之後引入的一個bug,React Native做了過多的沒有必要的layout計算,詳見Extreme lag after upgrade to 0.39.2 and 0.40.0

通過升級React Native到0.44.0,問題得到了解決,但相關fix也導致了另外一個bug,這個在後面ViewPagerAndroid一節會講到。

圖片和記憶體

整合React Native後的第一個版本出現了不少crash,其中很大一部分的原因是記憶體不足。使用Android Studio自帶的記憶體分析工具可以發現,在某些場景下有些圖片佔用了太多的記憶體(有的圖片甚至可以達到20M)。進一步分析確定了原因:圖片沒有經過尺寸調整。我們的應用允許使用者自己上傳圖片,而一旦某些圖片尺寸比較大(注意不是檔案大小,而是圖片的長和寬),經過解碼後就會佔用很大的記憶體。

解決方案有兩點:

  • 在服務端就返回調整過尺寸的圖片,這樣同時減少了網路開銷。
  • 強制Image必須指定resizeMethod,對於尺寸和顯示大小差不多的圖片(例如圖示),使用scale,對於可能超出顯示尺寸很多的圖片則一定要用resize來減少記憶體開銷(具體說明可以參考React Native的官方文件)。

為了防止以後的開發過程中遺漏resizeMethod,我們定義瞭如下兩個元件來代替原生的Image元件。

export function ResizeImage({...props}: Object): ReactNative.ReactElement {  
    return <ReactNative.Image resizeMethod={'resize'} {...props} />;
}
export function ScaleImage({...props}: Object): ReactNative.ReactElement {  
    return <ReactNative.Image resizeMethod={'scale'} {...props} />;
}

減少應用Crash

在剛才提到的crash中,另一部分是React Native自身引起的,而且用Monkey Test工具可以部分重現。通過在React Native加log除錯可以發現,ShadowNodeRegistry會被多個執行緒訪問,但沒有做保護。簡單的加入synchronized關鍵字可以修復很大一部分的crash。

通過這兩個fix,各個應用的crash free rate還是很好的得到的保持

ViewPagerAndroid

在開發過程中發現的另一個問題是,給ViewPagerAndroid動態新增頁面後,新的頁面不會顯示。除錯後發現這個問題實際上跟ListView的效能fix有關,效能優化用力過猛,導致ViewPagerAndroid應該進行的layout計算也被省略了。

為此我們的解決方案是將ViewPagerAndroid繞開layout優化,程式碼可以參考Fix ReactViewPager layouting

總結

  • React Native在Android平臺上的問題確實要比iOS上多一些
  • 遇到問題首先可以搜一下官方的issue列表,看看是不是已知問題,可以節省大量時間
  • 建議fork一個分支出來把React Native的Android庫釋出到私有倉庫中,必要時候可以自行修復一些簡單的問題
  • React Native的程式碼開發時就應該在Android和iOS上同時進行,避免一方不相容造成的返工

最後,如果有什麼問題歡迎留言來信交流。