在 Android 中集成 React Native 的經驗分享
在之前的一篇博客中,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"
}
React Native社區也在努力解決這一問題( React Native for Android is incompatible with 3rd-party 64-bit libraries ),目前看起來只有 android-jsc 這個依賴還沒解決了,而且PR也已經有了,可以期待一下。
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。
具體代碼參見 Make ShadowNodeRegistry thread safe 。
通過這兩個fix,各個應用的crash free rate還是很好的得到的保持
ViewPagerAndroid
在開發過程中發現的另一個問題是,給 ViewPagerAndroid 動態添加頁面后,新的頁面不會顯示。調試后發現這個問題實際上跟 ListView 的性能fix有關,性能優化用力過猛,導致 ViewPagerAndroid 應該進行的layout計算也被省略了。
比較詳細的說明和示例代碼都可以在 React Native v0.43 ViewPagerAndroid work not well when detached then attach 中找到。
為此我們的解決方案是將 ViewPagerAndroid 繞開layout優化,代碼可以參考 Fix ReactViewPager layouting 。
總結
- React Native在Android平臺上的問題確實要比iOS上多一些
- 遇到問題首先可以搜一下官方的issue列表,看看是不是已知問題,可以節省大量時間
- 建議fork一個分支出來把React Native的Android庫發布到私有倉庫中,必要時候可以自行修復一些簡單的問題
- React Native的代碼開發時就應該在Android和iOS上同時進行,避免一方不兼容造成的返工
最后,如果有什么問題歡迎留言來信交流。
來自:http://tech.glowing.com/cn/react_native_practice_on_android/