一、前言

最近开新项目,准备尝试一下 ReactNative,所以前期做了一些调研工作,ReactNative 的优点非常的明显,可以做到跨平台,除了少部分 UI 效果可能需要对不同的平台进行单独适配,其中的核心逻辑代码,都是可以重用的。所以如果最终用 ReactNative 的话,可以省出某一端的客户端开发人员。而我这里调研的主要方向,就是它对国内第三方 SDK 的支持。

在国内,开发 App,一般都是会集成一些第三方服务的,例如:升级、崩溃分析、数据统计等等。而这些第三方服务,提供的 SDK ,通常只有 Native 层的,例如 Android 就是使用 Java 写的。而 ReactNative 本身 JavaScript 和 Native 层(Java层)的通信,其实已经做的很好了,所以大部分情况下,我们只需要对这些 SDK 做一个简单的封装就可以正常使用它了。

本期就来分享一下,如何在 ReactNative 的基础之上,集成 Bugly。这里主要是看它的崩溃搜集,这也是 Bugly 的主要功能。对于崩溃的收集,我主要关心两个部分:

  1. 是需要统计到正确的崩溃栈。
  2. 统计到的崩溃栈要是易于阅读的。

其实主要工作卡在了后者,接下来让我们具体看看问题。

本文的分析都是基于最新的 ReactNative (v0.49) 版本来分析。

二、ReactNative 的崩溃统计

首先,ReactNative 中 JavaScript 和 Native 层的通信,官方文档已经写的非常清楚了。在官方文档中,举了一个 Toast 模块的例子,写的很清晰,这里就不再赘述了,还不了解的,可以先看看文档。

ReactNative 原生模块(中文文档):

http://reactnative.cn/docs/0.49/native-modules-android.html#content

2.1 ReactNative 的编译模式

而在 ReactNative 的程序中,实际上运行的是 Js 的代码,而它也是分 Debug 和 Release 的。

在 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 模式的话,则会直接崩溃了。

2.2 不同编译模式的 Js 有什么不同

假如 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 这段代码,我在很多地方都用到的话,筛选它也是非常麻烦的。

2.3 Release 缺少了什么?

从前面的内容可以了解到,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 代码,你会发现关于源文件和行号的信息,被剥离了。

这也就是我们无法精准定位出错代码和锁在源文件的根本原因。

2.4 Mapping

既然已经明确的知道,在 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,它就是我们需要的。

完整的说明,你可以在这个网站上找到资料:

https://docs.bugsnag.com/platforms/react-native/showing-full-stacktraces/

我这里把关键信息截图出来看着更清晰。

--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 脚本输出的一样。

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 库来进行解析,可以精准定位到行号和源文件名。

三、集成 Bugly 的坑

Bugly 的集成,非常的简单。如果之前用过 Bugly 的,并且阅读 ReactNative 和 原生通信 这部分文档的话,差不多十分钟就可以集成完毕。

还不了解 ReactNative 和原生通信内容的,建议先阅读一下本文档了解一下。

ReactNative 原生模块(中文文档):

http://reactnative.cn/docs/0.49/native-modules-android.html#content

Bugly 的注册没有什么门槛,这里直接使用个人 QQ 号就可以登录,创建一个专门为 ReactNative 测试的 App,然后根据文档绑定对应的 AppID 即可。

不清楚的可以查阅 Bugly 的文档:

https://bugly.qq.com/docs/user-guide/instruction-manual-android/?v=20171030170001

这部分内容没什么好说的,都是标准话的流程。接下来我们来看看集成它将面临的坑。

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 层。

3.3 解读 Bugly 中,js层的崩溃

Native 层的崩溃,和常规 App 一样,没什么好说的。这里只看 Js 层的崩溃信息。

从这个崩溃栈你可以发现,其实下面 Java 的栈,基本上没有任何信息。这里主要是阅读上面 TypeError 后面的信息。这里描述了 Js 层崩溃的所有信息,包含错误和崩溃栈。

前面的内容如果认真看了,应该不难发现此处就是对 JS 崩溃输出的格式化拉平成一行了,所以如果我们要针对 Bugly 的崩溃栈编写解析脚本,就需要考虑到这些情况。

四、总结

本文说是 ReactNative 集成 Bugly 的一些坑,实际上讲的更多的是在生产环境下,如何分析 ReactNative 的崩溃栈。这些被搜集的原始信息,如何被还原成我们需要的信息。

不过这些,还是期待国内环境下,更多第三方 SDK 能支持到 ReactNative,毕竟官方团队支持的肯定要比我们自己写补丁脚本来的方便实用。

今天在承香墨影公众号的后台,回复『成长』。我会送你一些我整理的学习资料,包含:Android反编译、算法、Linux、虚拟机、设计模式、Web项目源码。

推荐阅读:

在 ReactNative 的 App 中,集成 Bugly 你会遇到的一些坑的更多相关文章

  1. 怎么在我们的App中集成条码扫描功能?

    现在很多App都有条码扫描功能,有的手机比如某米在照相机中集成了条码扫描功能,但是还有一部分手机没有这样的集成,比如韩国某星,需要自己下载一个条码扫描App.今天我们就来看看怎么在自己的App中集成一 ...

  2. 在APP中集成iAd Banner展示广告盈利

    如果你已经做了一款超牛X的APP.你也许还有一件是需要操心.APP够好了,怎么盈利呢?你可以对下载你的APP的用户收费.也可以完全的免费,然后在APP里放广告来实现盈利.现在来说,除非一款APP真的是 ...

  3. 在Android App中集成Google登录

    技术文章 来源:码农网 发布:2016-09-19 浏览:194 摘要:今天,几乎所有的web和移动app都自带谷歌和Facebook登录,这对app开发者和用户来说是一个非常有用的功能,因为几乎每个 ...

  4. Ionic2中集成腾讯Bugly之自定义插件

    Ionic2混合开发,入坑系列:Ionic2中集成腾讯Bugly之自定义插件 1.编写Bugly.js代码 var exec = require('cordova/exec'); module.exp ...

  5. SharePoint2013 中集成AD RMS 与Office Web App 2013集成

    SharePoint2010时Office Web App2010是一个让人又爱又恨的产品,尽管能够在WEB上查看与编辑文档,甚至能够多能协同编辑,但总会遇到两个看似普通的需求却需要给业务人员大费口舌 ...

  6. Android开发之清除缓存功能实现方法,可以集成在自己的app中,增加一个新功能。

    作者:程序员小冰,CSDN博客:http://blog.csdn.net/qq_21376985 Android开发之清除缓存功能实现方法,可以集成在自己的app中,增加一个新功能. 下面是一个效果图 ...

  7. iOS原生项目中集成React Native

    1.本文的前提条件是,电脑上已经安装了CocoaPods,React Native相关环境. 2.使用Xcode新建一个工程.EmbedRNMeituan [图1] 3.使用CocoaPods安装Re ...

  8. [Web前端] WEEX、React-Native开发App心得 (转载)

    转自: https://www.jianshu.com/p/139c5074ae5d 2018 JS状态报告: https://2018.stateofjs.com/mobile-and-deskto ...

  9. react native 之 在现有的iOS工程中集成react native

    在现有的iOS工程中集成react native, 或者说将react native引入到iOS 项目,是RN和iOS混合开发的必经之路 参考官网教程:https://reactnative.cn/d ...

随机推荐

  1. Function Programming - First Class(一等公民function)

    引用外界一等公民的定义:"在JavaScript世界中函数却是一等公民,它不仅拥有一切传统函数的使用方式(声明和调用),而且可以做到像简单值一样赋值.传参.返回,这样的函数也称之为第一级函数 ...

  2. 【ASP.NET Core】运行原理之启动WebHost

    ASP.NET Core运行原理之启动WebHost 本节将分析WebHost.CreateDefaultBuilder(args).UseStartup<Startup>().Build ...

  3. Mysql实现企业级日志管理、备份与恢复实战

    背景 随着业务的发展,公司业务和规模不断扩大,网站积累了大量的用户信息和数据,对于一家互联网公司来说,用户和业务数据是根基.一旦公司的数据错乱或者丢失,对于互联网公司而言就等于说是灭顶之灾,为防止系统 ...

  4. ssm开发关于web.xml配置

    <?xml version="1.0" encoding="UTF-8"?> <web-app version="3.0" ...

  5. calc() ---一个会计算的css属性

    最近这个月一直在赶项目开发,遇到的问题和学到的前端知识没有更新到博客园,现在闲了下来,就整理一下前端知识. 在项目开发中,在样式这方面花费的时间较多,因为针对于数字的变化特别多,本人不爱记数字,在看设 ...

  6. zkw模板

    水平有限,前缀和的前缀和什么的,rbq 两个操作: 1.区间l到r加上一个数x 2.查询区间[l,r]的区间和 #include<iostream> #include<cstdio& ...

  7. JS的隐式转换 从 [] ==false 说起

    前言 最近和大创扯淡时说到了[] == false,从结果上来看我俩都答错了,从气势上来说我俩的歪理都能出书了(恩,程序猿的骄傲),但是这其实背后隐藏了一潭很深的水,对,很深... 隐式类型转换 JS ...

  8. Scala入门系列(十二):隐式转换

    引言 Scala提供的隐式转换和隐式参数功能,是非常有特色的功能.是Java等编程语言所没有的功能.它可以允许你手动指定,将某种类型的对象转换成其他类型的对象.通过这些功能可以实现非常强大而且特殊的功 ...

  9. Sublime Text 3 常用快捷键

    一. 选择类       Ctrl+D 选中光标所占的文本,继续操作则会选中下一个相同的文本.     Alt+F3 选中文本按下快捷键,即可一次性选择全部的相同文本进行同时编辑.举个栗子:快速选中并 ...

  10. object-fit?

    知道有这个属性存在,是一个很偶然的机会.有一天,设计部的一个小伙伴给了我一个网址,说很有个性,让我看一下,当发现一个很有意思的效果时,作为一个前端小兵的我当然是第一时间开始审查元素,然后看到了这个从没 ...