在 ReactNative 的 App 中,集成 Bugly 你會遇到的一些坑

1、前言

最近開新項目,準備嘗試一下 ReactNative,因此前期作了一些調研工做,ReactNative 的優勢很是的明顯,能夠作到跨平臺,除了少部分 UI 效果可能須要對不一樣的平臺進行單獨適配,其中的核心邏輯代碼,都是能夠重用的。因此若是最終用 ReactNative 的話,能夠省出某一端的客戶端開發人員。而我這裏調研的主要方向,就是它對國內第三方 SDK 的支持。javascript

在國內,開發 App,通常都是會集成一些第三方服務的,例如:升級、崩潰分析、數據統計等等。而這些第三方服務,提供的 SDK ,一般只有 Native 層的,例如 Android 就是使用 Java 寫的。而 ReactNative 自己 JavaScript 和 Native 層(Java層)的通訊,其實已經作的很好了,因此大部分狀況下,咱們只須要對這些 SDK 作一個簡單的封裝就能夠正常使用它了。 html

本期就來分享一下,如何在 ReactNative 的基礎之上,集成 Bugly。這裏主要是看它的崩潰蒐集,這也是 Bugly 的主要功能。對於崩潰的收集,我主要關心兩個部分:java

  1. 是須要統計到正確的崩潰棧。
  2. 統計到的崩潰棧要是易於閱讀的。

其實主要工做卡在了後者,接下來讓咱們具體看看問題。node

本文的分析都是基於最新的 ReactNative (v0.49) 版原本分析。react

2、ReactNative 的崩潰統計

首先,ReactNative 中 JavaScript 和 Native 層的通訊,官方文檔已經寫的很是清楚了。在官方文檔中,舉了一個 Toast 模塊的例子,寫的很清晰,這裏就再也不贅述了,還不瞭解的,能夠先看看文檔。android

ReactNative 原生模塊(中文文檔):程序員

reactnative.cn/docs/0.49/n…算法

2.1 ReactNative 的編譯模式

而在 ReactNative 的程序中,實際上運行的是 Js 的代碼,而它也是分 Debug 和 Release 的。shell

在 Debug 模式下,會從本地開啓一個 Packager 服務,而後 App 運行起來以後,直接從服務里拉取最新的編譯後的 JS 代碼,這樣能夠在開發階段,作到代碼實時更新的效果,只須要在設備上,從新 Load 一下便可。編程

而在 Release 模式下,ReactNative 會將 JS 代碼,總體打包,而後放到 assets 目錄下,而後從這裏去加載 JS 代碼。

這樣的邏輯被封裝在 ReactInstanceManager 類的 recreateReactContextInBackgroundInner() 方法中,有興趣能夠自行看看。

react-server
react-server

能夠很清晰的看到,在 Debug 和 Release ,分別使用的不一樣的方式,加載 JS 文件的。這裏爲何要說到 ReactNative App 的編譯模式呢?其實和後面的邏輯有關係。

ReactNative 在 Debug 的狀況下,其實仍是很貼心的,若是出現崩潰的 Bug,會直接出紅屏,提示你崩潰的棧的具體信息,這些內容能夠幫助你快速的定位問題。

js-crash
js-crash

這裏給的例子,是一個 Js 層的崩潰,能夠看到它崩潰棧中,很清晰的看到 App.js 文件的第 48 行 21列,會有一個 ReferenceError 的錯誤。

最方便的是,你直接點擊崩潰棧的代碼,會自動打開對應的 Js 文件。固然,若是是一個 Native 層的崩潰,雖然也會出紅屏,可是點擊並不能跳轉。

而假如如今一樣的代碼,使用 Release 模式的話,則會直接崩潰了。

2.2 不一樣編譯模式的 Js 有什麼不一樣

假如 Release 和 Debug 同樣,能夠有如此清晰的崩潰棧,其實問題就已經獲得解決。可是當你使用 Release 包來觸發一個崩潰的時候,你就會發現,它並非同樣的。

使用命令,能夠直接安裝一個 Release 版本到設備上。

cd android && ./gradlew installRelease複製代碼

這裏實際上是兩行命令,先進入到 android 項目的目錄,而後運行 ./gradlew installRelease 這個沒什麼好說的,若是運行失敗,注意一下當前 shell 環境的目錄路徑。

此時,咱們再運行它就會直接致使崩潰,來看看崩潰的 Log 輸出。

js-crash-stack
js-crash-stack

很尷尬的是,雖然崩潰棧也被輸出出來了,和前面紅屏的截圖對比一下,也能發現它們實際上是一個內容。可是,這些代碼被混淆過了,若是 Native App 同樣,混淆過的代碼,反編譯來看會變成 a.b.c ,這裏的效果也是相似的。

這樣的崩潰棧,其實拿出來,可讀性很是的差,可是並非不可讀的。

那麼接下來來看看如何定位到這個崩潰的真實代碼,value@304:1133 這裏,就是線索。咱們把 Apk 解壓,拿到其內 assets/index.android.bundle 文件,它其內就是咱們 ReactNative 編譯好的 Js 文件,能夠看到它的第 304 行 1133 列,就是咱們須要定位的出了問題的代碼。

index-bundle
index-bundle

這樣的編譯後的代碼,查 Bug 查起來就很是的費時了,你首先須要根據當前版本發佈出去的 Apk,而後根據其中的 index.android.bundle 文件,定位到具體的代碼,以後再結合上下文全文搜索你的源代碼,才能找到對應出錯的代碼。

注意我這裏自己項目就是一個 Demo 項目,代碼量比較少,還能準確的定位到問題,若是是一個實際的項目,在打 Release 包的時候,會將全部的 JS 文件所有打包到 index.android.bundle 文件中去。在這個例子中,若是 props.username.name 這段代碼,我在不少地方都用到的話,篩選它也是很是麻煩的。

2.3 Release 缺乏了什麼?

從前面的內容能夠了解到,Release 包一樣也是能夠定位到出錯的代碼的。可是,你依然須要全文的搜索這段代碼,沒法精準定位到具體出錯代碼所在的源文件,這是爲何?

Release 包的 Js 必定是通過混淆的,會剝離掉一些必要的信息,這些被剝離的信息,致使咱們沒法精準定位到代碼的源文件上。

在 Debug 模式下,運行咱們的 Packager Server ,而後在瀏覽器中訪問:

http://localhost:8081/index.android.bundle?platform=android&dev=true

請確保你的 Packager Server 保持運行的狀況下訪問。

就能夠看到當前 Debug 模式,App 所運行的 JS 代碼。咱們直接根據出錯代碼,精準定位一下。

debug-server
debug-server

在這裏,就能夠很清晰的看到,它有一個 fileName 和 lineNumber 兩個屬性,分別用來記錄當前源碼的文件和這段代碼所在的行數。而回憶一下以前 Release 版本的 JS 代碼,你會發現關於源文件和行號的信息,被剝離了。

這也就是咱們沒法精準定位出錯代碼和鎖在源文件的根本緣由。

2.4 Mapping

既然已經明確的知道,在 Release 下,會過濾掉一些關於源文件和行號的信息,就如同 Android 的混淆同樣,那它是否包含相似對照關係的 Mapping 文件,能夠幫助咱們還原回去?

那麼咱們就須要找到 index.android.bundle 這個文件,是如何產生的。

ReactNative App 的打包,徹底藉助了 react.gradle 這個文件,你能夠在 Android 工程的 build.gradle 文件中找到它。

app-gradle
app-gradle

繼續最終 node/modules 下的 react.gradle 文件。

react-gradle
react-gradle

能夠看到它其實是經過 react-native bundle 命令,經過增長參數的形式,輸出 index.android.bundle 文件的。

而若是你查閱文檔,你會發現 react-native 命令,還有一個可配置的參數 —sourcemap-output,它就是咱們須要的。

完整的說明,你能夠在這個網站上找到資料:

docs.bugsnag.com/platforms/r…

我這裏把關鍵信息截圖出來看着更清晰。

source-map
source-map

--sourcemap-output 命令很是的簡單,只須要配置一個輸出的文件名就能夠了。

這裏咱們直接在命令行裏運行以下代碼,就能夠自動從新生成一個 index.android.bundle 文件,而且同時也會生產一個對應關係的 map 文件。

react-native bundle 
--platform android 
--dev false 
--entry-file index.js 
--bundle-output android/app/src/main/assets/index.android.bundle 
--assets-dest android/app/src/main/res/ 
--sourcemap-output android-release.bundle.map複製代碼

運行效果以下:

build-map
build-map

注意這段命令,須要在 ReactNative 目錄的根目錄下執行,否者會提示你找不到 node_module 。執行完成,就能夠在 ReactNative 項目目錄下,看到輸出的 android-release.bundle.map 文件了。

點開看看,徹底看不懂,隨便截個圖讓你們感覺一下。

map
map

其實到這裏,已經離咱們的答案,更近一步了,Android 混淆的 Mapping 文件,也不是咱們肉眼能清晰看懂的,咱們接下來只須要找到它的解析規則就能夠了。

解析這個 source-map ,NodeJs 爲咱們提供了一個專門的庫來解析,這裏很少解釋,直接上代碼。

map-js
map-js

/** * Created by cxmyDev on 2017/10/31. */
var sourceMap = require('source-map');
var fs = require('fs');

fs.readFile('../android-release.bundle.map', 'utf8', function (err, data) {
    var smc = new sourceMap.SourceMapConsumer(data);

    console.log(smc.originalPositionFor({
        line: 304,
        column: 1133
    }));
});複製代碼

注意看這裏指定的 304 行 1133 列,咱們運行一下,看看輸出。

map-js-output
map-js-output

這段代碼,會很清晰的輸出對應的源文件名和行號,以及錯的字段,仍是很清晰的。

再來對照咱們的源代碼驗證一下。

error-line
error-line

確實也如 map.js 腳本輸出的同樣。

2.5 小結

到此,咱們算是完成了 ReactNative App,崩潰分析的一個完整的鏈路邏輯,咱們只須要本身寫個腳本工具,就能夠幫咱們精準定位了。

前面有點長,這裏總結一下本小結的內容。

  1. ReactNative 不一樣的編譯模式,使用的 JS 來源不一樣。Debug 模式來自 Packager Server,而 Release 模式,來自 Apk 的 assets 目錄。
  2. Debug 模式下的崩潰,會觸發紅屏,而 Release 模式下的崩潰,會直接致使 App 崩潰。
  3. Debug 模式,之因此能夠顯示崩潰棧的基本信息,是由於編譯的 JS 文件中,包含了對應的源文件和代碼行號。而這些在 Release 模式下的 JS 是沒有的。
  4. Release 模式的崩潰棧是被混淆後的,能夠經過崩潰棧顯示的行號和列號,來定位代碼,可是沒法定位具體源文件。
  5. 經過 react-native 命名,增長 --sourcemap-output參數,指定輸出須要的混淆 Mapping 文件,它其內包含了混淆的信息。
  6. 解讀 ReactNative Mapping 文件,可使用 source-map 這個 NodeJs 庫來進行解析,能夠精準定位到行號和源文件名。

3、集成 Bugly 的坑

Bugly 的集成,很是的簡單。若是以前用過 Bugly 的,而且閱讀 ReactNative 和 原生通訊 這部分文檔的話,差很少十分鐘就能夠集成完畢。

還不瞭解 ReactNative 和原生通訊內容的,建議先閱讀一下本文檔瞭解一下。

ReactNative 原生模塊(中文文檔):

reactnative.cn/docs/0.49/n…

Bugly 的註冊沒有什麼門檻,這裏直接使用我的 QQ 號就能夠登陸,建立一個專門爲 ReactNative 測試的 App,而後根據文檔綁定對應的 AppID 便可。

不清楚的能夠查閱 Bugly 的文檔:

bugly.qq.com/docs/user-g…

這部份內容沒什麼好說的,都是標準話的流程。接下來咱們來看看集成它將面臨的坑。

3.1 Debug 模式下不會上報崩潰

以前也提到,Debug 模式下,若是觸發了崩潰,會直接進入紅屏狀態,顯示當前崩潰棧的信息。這個功能,在咱們開發階段,很是的好用,能快速定位問題。

可是正是由於 ReactNative 會在 Debug 模式下,Hook 住咱們的崩潰棧,從而會致使 Bugly SDK 沒法蒐集到對應的崩潰也就沒法進行上報。

因此,若是你在 ReactNative 項目內,集成了 Bugly 以後。造的崩潰沒有獲得上報,檢查一下本身編譯模式,必定要切換到 Release 模式下。

3.2 崩潰信息整合

Bugly 爲了方便開發者查看,會將相似崩潰棧的崩潰,整合成一個,而後進行計數統計,只顯示當前崩潰了多少次和影響的人數。

而在 ReactNative 項目中,若是是 Native 層出現的崩潰,那其實沒有什麼差異,崩潰信息和咱們平時開發常規 App 同樣。

可是,若是這個崩潰是發生在 Js 層的話,它最終會把崩潰拋到 Native 層,一樣也是能夠統計的的。可是這些崩潰會被封裝成一個 JavascriptException 拋出來,從而致使它們被簡單的歸爲了 JavascriptException 。可能它們描述的是不一樣的 Bug,可是卻被歸位一類,這樣以後查閱起來,就須要人工進行篩選。

這裏看兩個崩潰,第一個發生在 Js 層,第二個發生在 Native 層。

bugly-crash
bugly-crash

3.3 解讀 Bugly 中,js層的崩潰

Native 層的崩潰,和常規 App 同樣,沒什麼好說的。這裏只看 Js 層的崩潰信息。

js-stack
js-stack

從這個崩潰棧你能夠發現,其實下面 Java 的棧,基本上沒有任何信息。這裏主要是閱讀上面 TypeError 後面的信息。這裏描述了 Js 層崩潰的全部信息,包含錯誤和崩潰棧。

前面的內容若是認真看了,應該不難發現此處就是對 JS 崩潰輸出的格式化拉平成一行了,因此若是咱們要針對 Bugly 的崩潰棧編寫解析腳本,就須要考慮到這些狀況。

4、總結

本文說是 ReactNative 集成 Bugly 的一些坑,實際上講的更多的是在生產環境下,如何分析 ReactNative 的崩潰棧。這些被蒐集的原始信息,如何被還原成咱們須要的信息。

不過這些,仍是期待國內環境下,更多第三方 SDK 能支持到 ReactNative,畢竟官方團隊支持的確定要比咱們本身寫補丁腳原本的方便實用。

今天在承香墨影公衆號的後臺,回覆『成長』。我會送你一些我整理的學習資料,包含:Android反編譯、算法、Linux、虛擬機、設計模式、Web項目源碼。

推薦閱讀:

相關文章
相關標籤/搜索