最近開新項目,準備嘗試一下 ReactNative,因此前期作了一些調研工做,ReactNative 的優勢很是的明顯,能夠作到跨平臺,除了少部分 UI 效果可能須要對不一樣的平臺進行單獨適配,其中的核心邏輯代碼,都是能夠重用的。因此若是最終用 ReactNative 的話,能夠省出某一端的客戶端開發人員。而我這裏調研的主要方向,就是它對國內第三方 SDK 的支持。javascript
在國內,開發 App,通常都是會集成一些第三方服務的,例如:升級、崩潰分析、數據統計等等。而這些第三方服務,提供的 SDK ,一般只有 Native 層的,例如 Android 就是使用 Java 寫的。而 ReactNative 自己 JavaScript 和 Native 層(Java層)的通訊,其實已經作的很好了,因此大部分狀況下,咱們只須要對這些 SDK 作一個簡單的封裝就能夠正常使用它了。 html
本期就來分享一下,如何在 ReactNative 的基礎之上,集成 Bugly。這裏主要是看它的崩潰蒐集,這也是 Bugly 的主要功能。對於崩潰的收集,我主要關心兩個部分:java
其實主要工做卡在了後者,接下來讓咱們具體看看問題。node
本文的分析都是基於最新的 ReactNative (v0.49) 版原本分析。react
首先,ReactNative 中 JavaScript 和 Native 層的通訊,官方文檔已經寫的很是清楚了。在官方文檔中,舉了一個 Toast 模塊的例子,寫的很清晰,這裏就再也不贅述了,還不瞭解的,能夠先看看文檔。android
ReactNative 原生模塊(中文文檔):程序員
而在 ReactNative 的程序中,實際上運行的是 Js 的代碼,而它也是分 Debug 和 Release 的。shell
在 Debug 模式下,會從本地開啓一個 Packager 服務,而後 App 運行起來以後,直接從服務里拉取最新的編譯後的 JS 代碼,這樣能夠在開發階段,作到代碼實時更新的效果,只須要在設備上,從新 Load 一下便可。編程
而在 Release 模式下,ReactNative 會將 JS 代碼,總體打包,而後放到 assets 目錄下,而後從這裏去加載 JS 代碼。
這樣的邏輯被封裝在 ReactInstanceManager 類的 recreateReactContextInBackgroundInner()
方法中,有興趣能夠自行看看。
能夠很清晰的看到,在 Debug 和 Release ,分別使用的不一樣的方式,加載 JS 文件的。這裏爲何要說到 ReactNative App 的編譯模式呢?其實和後面的邏輯有關係。
ReactNative 在 Debug 的狀況下,其實仍是很貼心的,若是出現崩潰的 Bug,會直接出紅屏,提示你崩潰的棧的具體信息,這些內容能夠幫助你快速的定位問題。
這裏給的例子,是一個 Js 層的崩潰,能夠看到它崩潰棧中,很清晰的看到 App.js 文件的第 48 行 21列,會有一個 ReferenceError 的錯誤。
最方便的是,你直接點擊崩潰棧的代碼,會自動打開對應的 Js 文件。固然,若是是一個 Native 層的崩潰,雖然也會出紅屏,可是點擊並不能跳轉。
而假如如今一樣的代碼,使用 Release 模式的話,則會直接崩潰了。
假如 Release 和 Debug 同樣,能夠有如此清晰的崩潰棧,其實問題就已經獲得解決。可是當你使用 Release 包來觸發一個崩潰的時候,你就會發現,它並非同樣的。
使用命令,能夠直接安裝一個 Release 版本到設備上。
cd android && ./gradlew installRelease複製代碼
這裏實際上是兩行命令,先進入到 android 項目的目錄,而後運行 ./gradlew installRelease
這個沒什麼好說的,若是運行失敗,注意一下當前 shell 環境的目錄路徑。
此時,咱們再運行它就會直接致使崩潰,來看看崩潰的 Log 輸出。
很尷尬的是,雖然崩潰棧也被輸出出來了,和前面紅屏的截圖對比一下,也能發現它們實際上是一個內容。可是,這些代碼被混淆過了,若是 Native App 同樣,混淆過的代碼,反編譯來看會變成 a.b.c ,這裏的效果也是相似的。
這樣的崩潰棧,其實拿出來,可讀性很是的差,可是並非不可讀的。
那麼接下來來看看如何定位到這個崩潰的真實代碼,value@304:1133
這裏,就是線索。咱們把 Apk 解壓,拿到其內 assets/index.android.bundle
文件,它其內就是咱們 ReactNative 編譯好的 Js 文件,能夠看到它的第 304 行 1133 列,就是咱們須要定位的出了問題的代碼。
這樣的編譯後的代碼,查 Bug 查起來就很是的費時了,你首先須要根據當前版本發佈出去的 Apk,而後根據其中的 index.android.bundle 文件,定位到具體的代碼,以後再結合上下文全文搜索你的源代碼,才能找到對應出錯的代碼。
注意我這裏自己項目就是一個 Demo 項目,代碼量比較少,還能準確的定位到問題,若是是一個實際的項目,在打 Release 包的時候,會將全部的 JS 文件所有打包到 index.android.bundle 文件中去。在這個例子中,若是 props.username.name
這段代碼,我在不少地方都用到的話,篩選它也是很是麻煩的。
從前面的內容能夠了解到,Release 包一樣也是能夠定位到出錯的代碼的。可是,你依然須要全文的搜索這段代碼,沒法精準定位到具體出錯代碼所在的源文件,這是爲何?
Release 包的 Js 必定是通過混淆的,會剝離掉一些必要的信息,這些被剝離的信息,致使咱們沒法精準定位到代碼的源文件上。
在 Debug 模式下,運行咱們的 Packager Server ,而後在瀏覽器中訪問:
http://localhost:8081/index.android.bundle?platform=android&dev=true
請確保你的 Packager Server 保持運行的狀況下訪問。
就能夠看到當前 Debug 模式,App 所運行的 JS 代碼。咱們直接根據出錯代碼,精準定位一下。
在這裏,就能夠很清晰的看到,它有一個 fileName 和 lineNumber 兩個屬性,分別用來記錄當前源碼的文件和這段代碼所在的行數。而回憶一下以前 Release 版本的 JS 代碼,你會發現關於源文件和行號的信息,被剝離了。
這也就是咱們沒法精準定位出錯代碼和鎖在源文件的根本緣由。
既然已經明確的知道,在 Release 下,會過濾掉一些關於源文件和行號的信息,就如同 Android 的混淆同樣,那它是否包含相似對照關係的 Mapping 文件,能夠幫助咱們還原回去?
那麼咱們就須要找到 index.android.bundle 這個文件,是如何產生的。
ReactNative App 的打包,徹底藉助了 react.gradle 這個文件,你能夠在 Android 工程的 build.gradle 文件中找到它。
繼續最終 node/modules 下的 react.gradle 文件。
能夠看到它其實是經過 react-native bundle
命令,經過增長參數的形式,輸出 index.android.bundle 文件的。
而若是你查閱文檔,你會發現 react-native
命令,還有一個可配置的參數 —sourcemap-output
,它就是咱們須要的。
完整的說明,你能夠在這個網站上找到資料:
我這裏把關鍵信息截圖出來看着更清晰。
--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複製代碼
運行效果以下:
注意這段命令,須要在 ReactNative 目錄的根目錄下執行,否者會提示你找不到 node_module 。執行完成,就能夠在 ReactNative 項目目錄下,看到輸出的 android-release.bundle.map 文件了。
點開看看,徹底看不懂,隨便截個圖讓你們感覺一下。
其實到這裏,已經離咱們的答案,更近一步了,Android 混淆的 Mapping 文件,也不是咱們肉眼能清晰看懂的,咱們接下來只須要找到它的解析規則就能夠了。
解析這個 source-map ,NodeJs 爲咱們提供了一個專門的庫來解析,這裏很少解釋,直接上代碼。
/** * 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
腳本輸出的同樣。
到此,咱們算是完成了 ReactNative App,崩潰分析的一個完整的鏈路邏輯,咱們只須要本身寫個腳本工具,就能夠幫咱們精準定位了。
前面有點長,這裏總結一下本小結的內容。
--sourcemap-output
參數,指定輸出須要的混淆 Mapping 文件,它其內包含了混淆的信息。Bugly 的集成,很是的簡單。若是以前用過 Bugly 的,而且閱讀 ReactNative 和 原生通訊 這部分文檔的話,差很少十分鐘就能夠集成完畢。
還不瞭解 ReactNative 和原生通訊內容的,建議先閱讀一下本文檔瞭解一下。
ReactNative 原生模塊(中文文檔):
Bugly 的註冊沒有什麼門檻,這裏直接使用我的 QQ 號就能夠登陸,建立一個專門爲 ReactNative 測試的 App,而後根據文檔綁定對應的 AppID 便可。
不清楚的能夠查閱 Bugly 的文檔:
這部份內容沒什麼好說的,都是標準話的流程。接下來咱們來看看集成它將面臨的坑。
以前也提到,Debug 模式下,若是觸發了崩潰,會直接進入紅屏狀態,顯示當前崩潰棧的信息。這個功能,在咱們開發階段,很是的好用,能快速定位問題。
可是正是由於 ReactNative 會在 Debug 模式下,Hook 住咱們的崩潰棧,從而會致使 Bugly SDK 沒法蒐集到對應的崩潰也就沒法進行上報。
因此,若是你在 ReactNative 項目內,集成了 Bugly 以後。造的崩潰沒有獲得上報,檢查一下本身編譯模式,必定要切換到 Release 模式下。
Bugly 爲了方便開發者查看,會將相似崩潰棧的崩潰,整合成一個,而後進行計數統計,只顯示當前崩潰了多少次和影響的人數。
而在 ReactNative 項目中,若是是 Native 層出現的崩潰,那其實沒有什麼差異,崩潰信息和咱們平時開發常規 App 同樣。
可是,若是這個崩潰是發生在 Js 層的話,它最終會把崩潰拋到 Native 層,一樣也是能夠統計的的。可是這些崩潰會被封裝成一個 JavascriptException 拋出來,從而致使它們被簡單的歸爲了 JavascriptException 。可能它們描述的是不一樣的 Bug,可是卻被歸位一類,這樣以後查閱起來,就須要人工進行篩選。
這裏看兩個崩潰,第一個發生在 Js 層,第二個發生在 Native 層。
Native 層的崩潰,和常規 App 同樣,沒什麼好說的。這裏只看 Js 層的崩潰信息。
從這個崩潰棧你能夠發現,其實下面 Java 的棧,基本上沒有任何信息。這裏主要是閱讀上面 TypeError 後面的信息。這裏描述了 Js 層崩潰的全部信息,包含錯誤和崩潰棧。
前面的內容若是認真看了,應該不難發現此處就是對 JS 崩潰輸出的格式化拉平成一行了,因此若是咱們要針對 Bugly 的崩潰棧編寫解析腳本,就須要考慮到這些狀況。
本文說是 ReactNative 集成 Bugly 的一些坑,實際上講的更多的是在生產環境下,如何分析 ReactNative 的崩潰棧。這些被蒐集的原始信息,如何被還原成咱們須要的信息。
不過這些,仍是期待國內環境下,更多第三方 SDK 能支持到 ReactNative,畢竟官方團隊支持的確定要比咱們本身寫補丁腳原本的方便實用。
今天在承香墨影公衆號的後臺,回覆『成長』。我會送你一些我整理的學習資料,包含:Android反編譯、算法、Linux、虛擬機、設計模式、Web項目源碼。
推薦閱讀: