最近开新项目,准备尝试一下 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项目源码。
推荐阅读: