记一次React Native远程调试,解决只在千里之外可重现的Bug。

前言

这篇文章的主要内容有两个:

  1. React Native如何调试不在局域网内的设备、如何把这些设备变成在局域网内方便地调试(无论设备在4G还是Wifi的情况下)
  2. 深坑提醒:截止到发稿前华为P10最新版本系统 EMUI5.0 (Android 7.0),Style Sheet中的transform属性失效了

故事的起因

一天,老师过来跟我说,咱们App聊天模块里面的列表,在一台华为P10手机上面显示不出来,它能发消息,别人也能看到它发的消息,但是它却连自己发的消息也看不到,是个必现的问题,让我查查这是什么幺蛾子。

嗯,这台手机不在我们这,在分公司同事手里,搞不定就自己去出差吧。😀

分析问题

首先想到:服务端的锅
由于我们聊天列表的数据,客户端是不保存的,这份数据由服务端维护,客户端订阅上后,服务端会给客户端返回指定数据,于是让服务端的哥们帮忙调试,经过一轮紧张忙碌的调试,最后他告诉我:“我不知道,我这里是好的”。(老板,这只是个梗,咱们团队都是负责到底的哈哈哈)

既然如此,为了证明服务端哥们这轮调试的结论是对的,我想我最好能看这台远程手机上面的Log信息,甚至打断点去debug,看看客户端出了什么问题。

提出设想

由于React Native本身就支持Debug JS Remotely,所以首先想到的,肯定是把我的机器配置到外网,然后让分公司的同事连接到我机器上就能进行调试;其本地的npm服务将会加载node_modules,然后提供8081端口给debug包的app进行请求,最终做到了动态把js代码传输到debug包的程序上。
npm服务加载

Just do it.

有了这个想法之后,就去找运维同学,一波操作过后,我的电脑已经可以在公网上进行访问了,二话不说,就是干:

  1. 提取debug apk: 当前项目下,找到 “android/app/build/outputs/apk/app-debug.apk”,如果修改过项目模块名称,自己去找回对应的路径,相信这个不会有难度;在React Native项目中,这个debug包跟release包最大的区别是:release包已经打包好了所有.js文件到assets目录中,而debug包是通过请求加载本项目js代码的npm服务来动态加载js文件;
  2. 安装debug包,运行debug程序后,在 “Dev Settings” 中设置 “Debug server host & port for device” ,地址修改为自己电脑的公网ip,端口8081;
  3. 在调试菜单中,选择 “Debug JS Remotely”。

接下来当然就是最激动人心的时刻啦,同事的机器顺利地连上了我电脑本地的npm服务,我和运维小哥四目相视,会心一笑。然而,事情总是出乎意料,命运也喜欢捉弄人,一下秒,妹子同事说手机红屏了,在我的Chrome调试页面中打印了一行:

1
2
3
Failed to execute 'send' on 'XMLHttpRequest': Failed to load 'http://localhost:8082/create_session'
Realm failed to connect to the embedded debug server inside the app.
If attempting to use Chrome debugging from a device, ensure the device is reachable on the same network as this machine.

惊不惊喜,意不意外?

咋一看,似乎问题不大,再把我机器的8082端口打开,是不是应该就可以了?然而,事情总是出乎意料,命运也喜欢捉弄人。X妹子说,还是一样耶。然后,我在realm-js的项目下找到了这个issues: https://github.com/realm/realm-js/issues/284,这个issues已经是closed状态,但是可能因为我在非局域网调试的原因,升级到最新版本的realm-js,仍然存在。此时,默默说了一句WTF之后,我感觉整个人垮掉了。

WTF之后的思考

注意到错误提示中的 “ensure the device is reachable on the same network as this machine” ,我又有了一个大胆的想法:让该同事的手机,跟我的电脑在同一个网段。

这…似乎还真有这么一种操作,这个世界上,有一种叫 “Virtual Private Network” 的东西(和谐和谐),假设我的电脑和该同事的手机,同时连接上我们自己的代理服务器,通过这个代理的地址,似乎就可以实现 “在同一个网段” 这个需求。

电脑连接上代理后,会再分配一个地址,Mac上使用ifconfig命令查看 (Win ipconfig):

ipsec0遮挡部分

下面这一波操作就很稳了,我们终于顺利连接到了X同时的手机,可以看到Log,也看到打断点,甚至在4G下都可以调试:

  1. 手机电脑都连上代理
  2. 在 “Dev Settings” 中设置 “Debug server host & port for device”,修改为上面的代理地址(ipsec0遮挡部分)
  3. 在调试菜单中,选择 “Debug JS Remotely”。

解决问题

到了这一步,我已经能够看到Log以及打断点进行调试,最后得出的结论是:“服务端的调试结论的确是正确,数据已经顺利拿到了,而是是正确的,长连接也没有问题”。到这里已经可以确认是客户端的锅,而且是这个页面的ListView组件的锅,因为都是用React Native的ListView,为什么其它页面没有问题?

再次分析
React Native 的 ListView 是基于 ScrollView 组件实现的,显示部分异常,应该是ListView里面的ScrollView出了问题,对比其它模块用的 ListView ,发现了一个十分可疑的地方:ListView 组件提供的 “renderScrollComponent” 属性,关于这个这个属性,官方文档是这样说的:

renderScrollComponent function

(props) => renderable

指定一个函数,在其中返回一个可以滚动的组件。ListView将会在该组件内部进行渲染。默认情况下会返回一个包含指定属性的ScrollView。

简单说,这个属性可以让我们自定义ListView的滚动组件,如果不设置的话,就是一个默认的ScrollView作为ListView的滚动组件。

很不凑巧,我们指定了这个属性,因为对于聊天列表,最新的消息内容需要显示在列表的最下方,于是我们使用 react-native-invertible-scroll-view 这个第三方库,将这个默认的ScrollView翻转了过来,实现了新消息一直在底部,有新消息也直接滚动到最底部这样的效果(类似:react-native-gifted-chat)。

证实猜想
要证明上面的猜想很简单,直接去掉ListView中指定的的 renderScrollComponent 方法,然后刷新界面。X同事说:“显示出来了,不过方向是反的”。

解决问题
我们看看这个库是如何将ScrollView翻转过来的,找到核心文件 ‘InvertibleScrollView.js’ ,看到关键代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let styles = StyleSheet.create({
verticallyInverted: {
flex: 1,
transform: [
{ scaleY: -1 },
],
},
horizontallyInverted: {
flex: 1,
transform: [
{ scaleX: -1 },
],
},
});

到这里已经一切了然,这货使用 transform 属性,在垂直/水平方向上翻转分别使用:

1
2
3
transform: [ { scaleY: -1 } ] //垂直翻转
transform: [ { scaleX: -1 } ] //水平翻转

到这里已经确诊了,transform 在P10上出问题了,这个问题也可以在 React Native 的 issues 上找到:

  1. react-native-invertible-scroll-view not working on Huawei Android 7 devices.
  2. always set camera distance on transforms, to default 1280 if 0

目前已经确认有问题的机型如下:

FRD-AL10(honor 8) EMUI:5.0 android: 7.0
MHA-AL00(Mate9) EMUI:5.0 android:7.0
Huawei P10 VTR-L09, EMUI:5.0 Android 7.0

解决方法有两种:
1)在transform中增加 perspective: 1280 (在我的项目里没有起效,基于 React Native 0.35)

1
transform: [ { scaleY: -1, perspective: 1280 } ]

2)对于Android,使用过时的 {scaleY: -1} 属性,而不是 transform: [ { scaleY: -1 } ],iOS保持不变

1
2
3
4
5
6
7
8
9
10
let styles = StyleSheet.create({
verticallyInverted: Platform.select({
ios: {transform: [{scaleY: -1}]},
android: {scaleY: -1},
}),
horizontallyInverted: Platform.select({
ios: {transform: [{scaleX: -1}]},
android: {scaleX: -1},
}),
});

最后附上修复好的翻转ScrollView: V1sk/react-native-invertible-scroll-view

结语

这一波调试过程十分曲折,希望大家不会遇到这些奇葩问题。另外,评论系统又GG了,继多说停止服务之后,网易云跟帖也停了,评论数据也清了,现在唯有用Disqus了,也是需要 Virtual Private Network 才能看到评论系统哟~