移动端H5调试技巧

chrome://inspect/#devices

July 10, 2019 · 1 min · jiezi

移动端真机调试实战经验安卓篇

android手机+pc,pc可以是windows也可以是mac安卓手机只需要下载chrom浏览器,就可以再电脑上用chrom调试了,是不是很赞(づ ̄3 ̄)づ╭ 首先需要装chrom浏览器 打开手机的开发者模式,一般是:设置->关于手机->版本号连按5次,之后设置菜单中会多出一个开发人员选项,进入将其中的“usb调试”打开 将手机与电脑通过usb连接,弹出对话框“是否允许usb调试”,选择确定 在手机chrom上打开要调试的页面 在电脑上打开chrom,新开一地址栏为chrome://inspect/的页面,然后就可以调试了 点击inspect弹出chrom调试工具

July 9, 2019 · 1 min · jiezi

移动端硬解关键流程梳理

介绍移动端Android/iOS硬解用法的文章有很多,本文将以笔者在实际开发工作中的经验为基础,抽出几个比较关键的部分来跟大家分享,旨在解决实际工作中可能遇到的花屏、(半边)绿屏、播放不完整等问题。本文将以目前广泛应用的H.264编码的视频为例来说明,主要包含:H.264码流数据结构说明、解码器的初始化、seek、前后台切换、无缝分辨率切换、播放结束时的处理以及iOS如何避免下半部分绿屏的问题。 一、H.264码流数据结构说明1. 理解码流数据结构的重要性我们讲支持硬解,提高硬解兼容性,实际上就是对码流数据的结构进行处理以符合平台硬解要求,因此对码流数据结构的理解是必不可少的。2. SPS/PPS与IDR帧SPS(Sequence Parameter Set)序列参数集、PPS(Picture Parameter Set)图像参数集,包含了图像编码的各种参数信息,是作为解码器初始化所必须的参数信息。IDR(Instantaneous Decoding Refresh)帧,也就是即时解码刷新帧,直观意思就是解码器在接收到IDR帧后会刷新参考帧缓存。IDR帧前后的视频帧不会有任何参考关系,解码器可以从任何一个IDR帧开始解码。3. H.264的NAL单元NALU结构图示: H.264标准中,视频流是由NAL(Network Abstraction Layer)单元组成的(简称NALU),每个NALU中可能是IDR图像、SPS、PPS、non-IDR图像等。上图中示意的NALU单元是以startcode方式分割的,关于NALU的分割方式将在后面说明。 另外,NALU内容中添加了防竞争字节,也就是说在一个NALU中,我们不可能再找到匹配的startcode.H.264流的NALU组成图示 从上图可以看到,一个视频帧中可能可能包含多个NALU, 此时可以称该视频帧为多slice视频帧(一个NALU中包含该视频帧的一个slice)。NAL Header的结构说明 其中nal_unit_type是我们关心的字段,该字段标识了当前NALU的类型,我们可以通过将NALU中第一个字节&0x1F的方式来得到NALU类型。NALU类型的具体定义如下图所示:NALU类型定义 其中5代表上面提到的IDR帧数据,7、8分别代表SPS/PPS数据。 AVCC与Annex-BH.264码流分为AVCC与Annex-B两种组织格式。• AVCC格式 也叫AVC1格式,MPEG-4格式,字节对齐,因此也叫Byte-Stream Format。用于mp4/flv/mkv等封装中。 • Annex-B格式 也叫MPEG-2 transport stream format格式(ts格式), ElementaryStream格式。用于TS流中(以及使用TS作为切片的hls格式中)。这两种格式的区别有两点: (1)NALU的分割方式不同; (2)SPS/PPS的数据结构不同。• AVCC格式使用NALU长度(固定字节,字节数由extradata中的信息给定)进行分割,在封装文件或者直播流的头部包含extradata信息(非NALU),extradata中包含NALU长度的字节数以及SPS/PPS信息。• Annex-B格式使用start code进行分割,start code为0x000001或0x00000001,SPS/PPS作为一般NALU单元以start code作为分隔符的方式放在文件或者直播流的头部。AVCC格式的extradata格式定义在“ISO_IEC_14496-15"文档中,Annex-B格式的SPS/PPS定义可以在"ISO_IEC_14496-10"文档中找到。MediaCodec与VideoToolBox使用的数据格式Android的硬解码接口MediaCodec只能接收Annex-B格式的H.264数据,而iOS平台的VideoToolBox则相反,只支持AVCC格式。这就导致:• 在Android平台硬解播放flv/mp4/mkv等封装的视频时,需要将AVCC格式的extradata以及NALU数据转为Annex-B格式;• 在iOS平台播放ts或ts切片的hls视频时,需要将Annex-B格式的SPS/PPS NALU转为AVCC格式的extradata,以及将其他以size方式分割的NALU转为start code方式。 二、解码器的初始化及数据输入初始化解码器,除了配置输入视频流的的编码格式、宽高以及输出格式之外,还需要配置一些额外的信息。 对于H.264视频,需要填充的就是我们前面提到的SPS/PPS信息。1. Android平台MediaCodec的初始化我们需要将Annex-B格式的两个SPS/PPS NALU单元通过setByteBuffer方法,以"csd-0"为名称(或SPS设为"csd-0", PPS设为"csd-1")设置到MediaFormat对象中,并调用configure接口配置到MediaCodec中去。MediaCodec设置SPS/PPS信息的示例代码MediaCodec mediaCodec = MediaCodec.createDecoderByType("video/avc");MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", width, height);// extradata中是Annex-B格式的SPS、PPS NALU数据mediaFormat.setByteBuffer("csd-0", extradata);// ...mediaCodec.configure(mediaFormat, surface, 0, 0);// ...如上节所述,对于mp4/flv/mkv等封装,我们得到的是AVCC格式的extradata,需要先将该extradata转换为Annex-B格式的两个NALU, 然后用startcode进行分割。 Android平台在配置解码方式时,最好使用MediaCodec直接渲染到Surface的方式,一是可以避免不同硬件平台繁杂的YUV格式兼容,二是在解码渲染高分辨率的视频时可以有非常明显的效率提升。2. iOS平台VideoToolBox接口的初始化VideoToolBox针对AVCC格式和Annex-B格式的SPS/PPS信息设置,分别提供了两个方法:• CMVideoFormatDescriptionCreate: 可以设置AVCC格式的extradata信• CMVideoFormatDescriptionCreateFromH264ParameterSets: 用来设置Annex-B格式的SPS/PPS NALU信息(需要去掉startcode)需要注意,iOS平台不支持隔行H.264视频的解码,需要在创建videoToolBox前从SPS中判断当前视频是否隔行编码。3. 数据格式的转换如前所述,Android平台只接受Annex-B格式以startcode分割的H.264 NALU;iOS平台则相反,只接受AVCC格式以size分割的NALU. 在原视频流格式不匹配时需要进行相应的转换。iOS还有以下的一些限制需要留意:(1)如果源视频流本身已经是AVCC格式,但NALU size的大小是3个字节,而非4字节时,需要转为4字节格式。具体的话,需要先更改extradata中标识NALU size的字段,然后每个视频帧中的NALU size都要改成4个字节。(2)如果一个视频帧内有多个NALU(多slice),那必须将这些NALU打包到一个CMSampleBuffer中,一次性送给解码器。 ...

June 28, 2019 · 1 min · jiezi

腾讯视频国际版(Android)电量测试方法研究与总结

本文由云+社区发表作者:腾讯移动品质中心TMQ1、研究背景:在2017年Google I/O大会上,Google发布了Google Play管理中心的新功能:Android vitals。当app在大量设备上运行时,Android vitals会收集与应用性能相关的各种匿名数据,比如:与app稳定性相关的数据、app启动时间、电量使用情况、渲染时间以及权限遭拒等等,这些数据会被分析整理后展示在Google Play管理中心的Android vitals dashboard中。Android vitals 中需要开发者重点关注的核心指标有:crash率、ANR率、excessive wakeups(过渡唤醒)、stuck wake locks(唤醒锁定卡住)。其他指标,需根据应用类型选择性关注(Android vitals中的指标总览见图1-1)。若app某些指标表现很差,会影响用户体验,并且会导致应用在Google Play商店中的等级很低、排名靠后(APP指标异常示例图见图1-2)。开发者可以通过分析Android vitals中提供的一些参照指标,采取相应的措施来优化app。图1-1 Android vitals平台检测指标总览图1-2 某APP指标异常示例图2、核心指标详细信息:要对APP的指标进行监控,首先要明确该指标在Android vitals中是如何进行统计的,这一节主要介绍电量相关核心指标的基本概念和计算方式。2.1 Stuck partial wake locks(部分唤醒锁定卡住)A.WakeLock(唤醒锁)基本概念:Android系统本身为了优化电量的使用,会在没有操作时进入休眠状态, 来节省电量。为了便于开发(很多应用不可避免的希望在灭屏后还能运行一些事儿,或是要保持屏幕一直亮着–比如播放视频),Android提供了一个PowerManager.WakeLock的东西。我们可以用WakeLock来保持CPU运行,或是防止屏幕变暗/关闭,让手机可以在用户不操作时依然可以做一些事儿。然而,获取WakeLock很容易,释放不好就会成为难题,消耗电量。例如我们获取了一个WakeLock来保持CPU运转,做一个复杂运算并将数据上传到后台服务器, 然后释放该WakeLock。然而这个过程可能并不像我们想象的那么快,可能因为比如服务器挂掉,计算出了异常等等WakeLock没有释放。问题就来了,CPU会一直得不到休眠,而大大增加耗电。唤醒锁可划分为并识别四种用户唤醒锁:自 API 等级 17 开始,FULL_WAKE_LOCK 将被弃用。应用应使用FLAG_KEEP_SCREEN_ON。相关链接:https://developer.android.com…B.Partial wake locks(部分唤醒锁):部分唤醒锁可确保CPU正常运行,但屏幕和键盘背光可以关闭。如果运行在后台的APP长时间持有某个部分唤醒锁,就导致部分唤醒锁卡住。这种情况十分消耗设备电量,因为它会阻止设备进入低电量状态。Android vitals重点关注了stuck partial wake locks这项指标,当你的APP存在唤醒锁定卡住的现象时,它会通过Play管理中心给出告警(APP出现部分唤醒锁定卡住示例图见图2-1),并从各个维度给出相关的详细统计图(如图2-2中给出每个工作时段后台wake lock最长持续时间分布图)。当出现以下情况时,Android vitals会报告唤醒锁定卡住:至少70%以上的battery sessions发生过至少一次、长达一小时以上的部分唤醒锁定。当只在后台运行时,至少10%以上的battery sessions发生过至少一次、长达一小时以上的部分唤醒锁定。(ps:battery session指两次电池充满电之间的时间间隔,Android vitals展示的battery sessions是所有app测试用户的battery session合计。)相关链接:https://developer.android.goo…图2-1 某APP出现部分唤醒锁定卡住(后台)示例图图2-2 每个工作时段后台wake lock最长持续时间的分布图2.2 Excessive wakeups(过渡唤醒)A.Wakeups 基本概念Wakeups 是AlarmManager API中的一种机制,开发者可以设置一个alarm在特定的时间来唤醒设备。当某个唤醒alarm触发,设备会走出低电量模式,在执行alarm的onRecieve()或onAlarm()方法的时候,Alarm Manager会持有一个部分唤醒锁。如果wake alarms频繁触发,会耗尽设备电量。Android vitals中展示了app的过渡唤醒次数。Alarm有以下四种类型:1)RTC_WAKEUP 在指定的时刻(设置Alarm的时候),唤醒设备来触发Intent。2)RTC 在一个显式的时间触发Intent,但不唤醒设备。 3)ELAPSED_REALTIME 从设备启动后,如果流逝的时间达到总时间,那么触发Intent,但不唤醒设备。流逝的时间包括设备睡眠的任何时间。注意一点的是,时间流逝的计算点是自从它最后一次启动算起。 4)ELAPSED_REALTIME_WAKEUP 从设备启动后,达到流逝的总时间后,如果需要将唤醒设备并触发Intent。在Android vitals中只列出了RTC_WAKEUP和ELAPSED_REALTIME_WAKEUP两种类型的唤醒数据,Google会统计每小时发生10次以上wakeup的电池工作时段百分比(APP发生过渡唤醒示例见图2-3)。分别从应用版本、wakeup标记、设备、Android版本等几个维度统计每小时的Alarm Manager wakeup次数(每个工作时段中每小时的wackup分布图见图2-4)。图2-3 某APP发生过渡唤醒示例图图2-4 每个工作时段每小时wakeup次数分布图3、测试方法研究3.1 传统电量测试方法回顾我们之前也对腾讯视频主线版本进行过电量测试,之前关注的重点在于APP在各场景中耗电量是否正常,是从比较宏观的角度去进行测试的,采取的测试方法主要是物理仪器测试法和GT测试法。A.物理仪器测试法(电流表等)在保持电压恒定的情况下,获取各场景平均电流值来统计系统耗电情况,通过此方法可以从大体上看出APP电量消耗是否正常,若仪器精度大,此方法测出的电量值是最准确的。缺陷:此方法只能测试整个手机的电流,不能区分APP,受影响的因素多,如屏幕亮度大小、音量大小等等,要保证每次测试的环境完全一致是不可能的。图3-1 物理仪器测电量B.GT测试法GT(随身调)是由MIG专项测试组自主研发的APP随身调测平台,它是直接运行在手机上的“集成调测环境”(IDTE, Integrated Debug Environment)。利用GT,仅凭一部手机,无需连接电脑,您即可对APP进行快速的性能测试(CPU、内存、流量、电量、帧率/流畅度等等)、开发日志的查看、Crash日志查看、网络数据包的抓取、APP内部参数的调试、真机代码耗时统计等。通过GT,可以采集手机耗电量相关数据:电流、电压、电量、温度等,通过分析这些数据,可以对整个手机的电量使用情况进行分析。缺点:和物理仪器测试方法一样,采用GT测试也只能获取到整个手机的电量数据,无法只关注单独APP,且受各种因素影响较大。3.2 国际版电量测试方法预研由于国际版APP在Google Play上发布,我们做电量测试不仅仅需要关注整个APP的电量使用情况是否正常,还需要关注APP持有 wack lock和使用alarm的情况。因此,传统的电量测试方法已经无法满足我们的需求,我们需要在此基础上增加额外的测试方法。A.Batterystats/ bugreportAndroid5.0后,电量数据可通过dumpsys batterystats获取。Android系统统计耗电量的基本公式是W=UIt。在手机中,U一般恒定不变,因此可以单独通过Q(电容量)=I*t来表示电量。核心类BatterStatsImpl提供App各部件运行时间、PowerProfile提供部件电流数值。Android部件电流信息存于:power_profile.xml文件中,每个OEM厂商都有私有的power_profile.xml文件,PowerProfile通过读取该文件获取访问部件电流数值(图3-3是samsung某型号的power_profile.xml)。Android系统以uid为单位,依次统计每个apk的使用cpu使用耗电量、wake lock耗电量、移动数据耗电量、wifi数据耗电量、wifi维持耗电量、wifi扫描耗电量、各传感器耗电量。其中wake lock消耗的电量只统计了持有Partial wake lock的耗电量,正好是我们需要关注的唤醒类型,因此我们可以通过分析batterystats获得的电量数据来测试app持有Partial wake lock情况。Android为了方便开发人员分析整个系统平台和某个app在运行一段时间之内的所有信息,专门开发了bugreport工具。bugreport文件中记录了系统允许过程中的各种log信息,其中也包括了耗电量信息。通过分析bugreport中的电量相关数据也能获取APP持有Partial wake lock的信息。ps:Uid与App关系:2个App签名和sharedUserId相同,则在运行时,他们拥有相同Uid。就是说processAppUsage统计的可能是多个App的耗电量数据,对于普通App,出现这种情况的几率较少,而对于Android系统应用则较为常见。图3-2 wack lock耗电量计算源代码图3-3 sumsung某型号power_profile.xml数据准备:先断开adb服务,然后开启adb服务。(1)adb kill-server(2)adb start-server由于开发时做电量记录时会打开很多可能造成冲突的东西,为了保险起见,重启adb命令。重置电池数据、收集数据(3) adb shell dumpsys batterystats –enable full-wake-history(4) adb shell dumpsys batterystats –reset(5) adb shell logcat -c通过以上命令来打开电池数据的获取以及重置,清除干扰的数据,清除历史日志。获取电量报告把数据线拔掉,防止数据线造成充放电数据干扰。然后做一些测试的case,经过一段时间后,重新连接手机确认adb连上了,运行以下命令来将bugreport的信息保存到txt文件中。(6) adb bugreport >D:/bugreport.txt或者用下面的命令也可以,官网上记述的内容,经实践,无法被读取…(7) adb shell dumpsys batterystats > batterystats.txt(8) adb shell dumpsys batterystats > com.example.app(包名) >batterystats.txtps:在此注意一定要等到该条命令执行完(稍微会有些慢)后,再打开bugreport.txt文件,之前遇到过没有导出完,就点开,信息缺失的情况,导致无法成功生成图表。B.battery historian生成的bugreport文件有的时候异常庞大,能够达到15M+,想一想对于一个txt文本格式的文件内容长度达到了15M+是一个什么概念,如果使用文本工具打开查看将是一个噩梦。因此google针对android 5.0(api 21)以上的系统开发了一个叫做battery historian的分析工具,这个工具就是用来解析这个txt文本文件,然后使用web图形的形式展现出来,这样出来的效果更加人性化,更加可读。我们可以使用该工具对bugreport文件进行解析,更轻松的获取电量相关数据。battery historian的安装可以参考以下链接:https://github.com/google/bat…https://developer.android.com…也可以直接使用在线版本:https://bathist.ef.lc/数据分析:(1)选择腾讯视频app(2)Wacklocks表格中展示app持有的wacklock,持有时间及数量,通过这个表格我们可以看到我们APP是否有持有一小时以上的wack_lock。(3)Wakeup alarm info表格中展示了APP运行过程中触发的wakeup alarm名字和个数,通过该分析工具也可以统计app的闹钟唤醒次数。C.QAPMQAPM是SNG开发的致力于解放专项测试人员的工具平台,该平台带有电量监控功能,在电量个例菜单中会统计前台30分钟、后台5分钟两个场景下的wacklock持有信息。该平台上的数据可以作为我们电量测试的参考对象,具体的统计方法还需后续深入了解。D.dumpsys命令Android提供的dumpsys工具能够用于查看感兴趣的系统服务信息与状态,手机连接电脑后能够直接命令行运行adb shell dumpsys 查看电池、电量相关信息。adb shell dumpsys power通过该条命令可以看到手机中所有的wack_lock持有信息adb shell dumpsys alarm此命令会提供设备上的alarm系统服务相关信息。其中Alarm Stats列出了应用设置alarm的情况,其中有系统被该应用所有alarm消耗的时间以及被闹钟唤醒的次数。可以通过获取一小时内的电量数据来分析用户在每小时的唤醒次数。相关链接:https://blog.csdn.net/memoryj…该方法与通过burgreport文件统计电量信息类似,都是通过Android系统中提供的工具来输出电量的消耗情况,且该种方式输出的报告也比较复杂,可读性查,可在测试过程中作为参考。4、国际版电量测试方法总结与实践4.1 测试方法总结根据上一节的测试方法研究,我们打算首先用GT测试各个场景中APP电量消耗是否有异常。接下来采用battery historian分析工具对手机里获取的bugreport文件进行分析,统计app中持有超过一小时的wack_lock和一小时内发生的wackup数。QAPM中采集到的数据作为我们的辅助分析数据,我们可以比较两份数据,看我们通过battery historian统计的wack_lock数据是否准确。我们也可以通过使用dumpsys命令,查看app电量相关信息作为测试辅助方法。4.2 测试方法实践腾讯视频国际版1.0.0已经发布,我们已经使用该方法对其进行了一次电量测试,具体测试过程如下:A.GT测试:测试场景:启动-播放-前台静置测试机器:nexus测试结果分析:从以下电流趋势变化图中可以看出,播放过程和前台静置过程,电流曲线平稳,无较大波动,无明显异常。从播放到退出播放前台静置,使用电流明显变小,符合预期。B.Battery Historian测试:测试场景:app前台静置2小时app后台静置2小时全屏播放2小时测试型号:Y7 Pro 2018 (LDN-LX2)OPPO F7 (CPH1819)测试结果分析:三个场景中,仅播放场景下会持有WindowManager这个wakelock超过1小时以上。而Android Vitals中关注的是app运行在后台时,长时间持有部分唤醒锁的情况,播放这个场景可以排除在外,因此得出结论,国际版APP持有唤醒锁情况正常。场景机型持有1小时以上的wack lockapp前台华为Y7 Pro 2018 (LDN-LX2)无OPPO F7 (CPH1819)无 app后台华为Y7 Pro 2018 (LDN-LX2)无OPPO F7 (CPH1819)无 全屏播放华为Y7 Pro 2018 (LDN-LX2)WindowManagerOPPO F7 (CPH1819)WindowManager 2. 测试过程中没有统计到alarm数据,说明国际版APP暂时没有使用到AlarmManager定时任务。C.测试结论:GT电流测试显示国际版APP各应用场景电量使用情况正常。场景启动APP播放退出播放,前台静置结论启动过程需加载图片等资源,电流较大,正常播放过程电流平稳无异常退出播放电流变小,静置过程平稳无异常2. Battery Historian分析电量数据得出,前台静置、后台静置、播放三个场景中仅播放场景会持有wack lock1小时以上,不属于Android Vitals统计范畴,不会影响到国际版APP在Google Play商店的排名。场景机型stuck wake locksexcessive wakeups结论前台静置华为Y7 Pro无唤醒锁定卡住无过渡唤醒正常OPPO F7无唤醒锁定卡住无过渡唤醒正常 后台静置华为Y7 Pro无唤醒锁定卡住无过渡唤醒正常OPPO F7无唤醒锁定卡住无过渡唤醒正常 播放华为Y7 Pro持有唤醒锁1小时以上无过渡唤醒正常OPPO F7持有唤醒锁1小时以上无过渡唤醒正常 5、总结与展望由于腾讯视频国际版目前功能比较少,用到wack_lock和alarm的情况比较少,我们只测试了前台静置、后台静置、播放三个场景,电量测试的结果也显示APP电量使用情况正常,无部分唤醒锁定卡住和过渡唤醒的情况出现,后续国际版功能会日渐丰富,可能需要补充push、下载等测试场景,持有wack_lock和alarm的情况也会更加复杂,因此我们会根据实际情况不断改进和完善我们的电量测试方法。此文已由腾讯云+社区在各渠道发布获取更多新鲜技术干货,可以关注我们腾讯云技术社区-云加社区官方号及知乎机构号 ...

March 18, 2019 · 1 min · jiezi

Android调试神器stetho使用详解和改造

本文由云+社区发表作者:NaOH概述stetho是Facebook开源的一个Android调试工具,项目地址:facebook/stetho 通过Stetho,开发者可以使用chrome的inspect功能,对Android应用进行调试和查看。 功能概述stetho提供的功能主要有:Network Inspection:网络抓包,如果你使用的是当前流行的OkHttp或者Android自带的 HttpURLConnection,你可以轻松地在chrome inspect窗口的network一栏抓到所有的网络请求和回包,还用啥Postman,还用啥Fiddler哦(开个玩笑,一些场合还是需要用的,毕竟Stetho Network Inspection 只是用来查看回报和发送数据是否有误,在开发初期,调试API还是用Postman快一点)Database Inspection:数据库查看,可以直接看到当前应用的sqlite数据库,而且是可视化的,不需要再下什么奇怪的工具或者用命令行看了。这个确实非常棒!View Hierarchy:布局层级查看,免去使用查看布局边界的花花绿绿带来的痛苦和卡顿,而且能看到每个view和layout的各类属性。Dump App:命令行拓展,构造了一个命令行与Android App的交互通道,在命令行输入一行命令,App可以收到并且在命令行上进行反馈输出。Javascript Console:Javascript控制台,在inspect的console窗口,输入Javascript可以直接进行Java调用。使用这个功能,得先引入facebook/stethostetho-js-rhino和mozilla/rhino。在这里,笔者先承认这个文章有点标题党了——在我实际使用体验过后,第一感觉是:这个所谓神器也没有特别神的感觉…造成首次使用感觉不太好的原因在于:使用教程不太全,尤其是Dump App的使用,不管是在README还是wiki中都没有太多的叙述。Network Inspection 抓包只封装了OkHttp和HttpURLConnection的,然而大多数情况下,各个应用开发者可能都会有自己的一套网络请求库,它提供的接口这时候就不太友好了,得自己包装一下。View Hierarchy 用起来有一丝丝的不方便,因为调试视图还包括了Android系统自带的状态栏布局之类的,导致Activity的布局天然处于一个比较深的节点,每次还要手动一层一层展开(其实这里有一个技巧,后面会提到)。Javascript Console 感觉是最鸡肋的功能,因为自带的console只能关联到application的context,能进行的操作非常有限,且在控制台写js调用Java层的函数是没有自动补全的,容易写错不说,要换成Js的语法也是相当费劲。就算解决这几个问题,也还是想不到什么合适的使用场景。后面将会对Dump App和Network Inspection进行详细介绍(其他的几个功能都比较简单)。初始化Stetho首先引入在安卓项目中引用必要的依赖包,可以使用gradle,也可以直接下载jar包。dependencies { compile ‘com.facebook.stetho:stetho:1.5.0’ } 需要注意的是如果使用Javascript Console需要额外引入facebook/stethostetho-js-rhino和mozilla/rhino。 然后在应用的Application初始化时,进行Stetho初始化。这些都在官网有详细的说明,不再赘述了。开始使用由于大部分功能依赖于Chrome DevTools 所以第一步你需要先打开Chrome,然后在浏览器地址栏输入:chrome://inspect 接触过前端开发或者Webview开发的捧油应该是很熟悉这个套路了。你会看到一个如下界面: inspect界面你会发现这里有两项,是因为我的这个示例应用有两个进程。由于App的每个进程都会单独创建一个Application,所以在应用包含多个进程时,Stetho也会为每个进程都初始化一次。那么这里我要调试的是主进程,就点击第一项inspect就行了。 接下来我们就开始搞事情了:View Hierarchy查看布局层级没啥好说的,但是之前提到,由于系统的view层级也包括进来了,所以我们Activity的Layout层级都很深,每次一层一层点开很难找,这里提供一个简便方法,在Elements面板,按Ctrl + F,搜索 @android:id/content 即可快速定位到我们当前界面根布局,例如这里的Constraintlayout:Database Inspection点击Resource-Web SQL即可查看App的数据库:Javascript Console在Console面板,输入context可以看到目前的ApplicationContext:输入如下代码弹出Toast:importPackage(android.widget);importPackage(android.os);var handler = new Handler(Looper.getMainLooper());handler.post(function() { Toast.makeText(context, “Hello from JavaScript”, Toast.LENGTH_LONG).show() });应用场景比较有限,但是mozilla/rhino这个Javascript引擎倒是挺有意思的,可以用来做一些有趣的事情,以后有机会再分享一下。Dump App官方对dump app的使用说明实在太少了,感觉非常捉急。研究了一番,大概知道了使用流程,即首先需要在App内,通过enableDumpapp方法注册自己的插件: Stetho.initialize(Stetho.newInitializerBuilder(context).enableDumpapp(new DumperPluginsProvider() { @Override public Iterable<DumperPlugin> get() { return new Stetho.DefaultDumperPluginsBuilder(context) .provide(new MyDumperPlugin()) .finish(); }}).enableWebKitInspector(Stetho.defaultInspectorModulesProvider(context)).build())也可以使用默认的插件: Stetho.initialize(Stetho.newInitializerBuilder(this) .enableDumpapp(new DumperPluginsProvider() { public Iterable<DumperPlugin> get() { return (new Stetho.DefaultDumperPluginsBuilder(StethoNetworkApplication.this)).finish(); } }).enableWebKitInspector(Stetho.defaultInspectorModulesProvider(context)).build())然后,stetho的github项目地址下有一个script文件夹:facebook/stetho-script 把这个文件夹下到本地,发现里面有几个文件: .gitignore dumpapp hprof_dump.sh stetho_open.py 说实话第一眼看上去根本不知道这东西干啥用的,dumpapp这文件看起来就跟可执行文件似的,但事实上它又不是exe,用记事本打开一看,是Python3的文件,我也是醉了…所以使用Python3.x来运行这个文件即可。(由于他还引用了stetho_open.py,为了看起来不那么别扭,我把几个文件都整合在一齐,搞了一个dump.py) 这里我并没有注册任何插件,但是由于Stetho自带了几个插件,我们可以看看他们的实现:例如files插件,来试用一下:即用户发送命令时,Plugin的dump方法会被调用,Plugin通过dumpContext.getStdout()来获取输出流,将反馈输出到命令行:public void dump(DumperContext dumpContext) throws DumpException { Iterator<String> args = dumpContext.getArgsAsList().iterator(); String command = ArgsHelper.nextOptionalArg(args, “”); if(“ls”.equals(command)) { this.doLs(dumpContext.getStdout()); } else if(“tree”.equals(command)) { this.doTree(dumpContext.getStdout()); } else if(“download”.equals(command)) { this.doDownload(dumpContext.getStdout(), args); } else { this.doUsage(dumpContext.getStdout()); if(!"".equals(command)) { throw new DumpUsageException(“Unknown command: " + command); } } }Network Inspection其实这也是重点之一了。我在这里添加了一个OkHttp的Inspector。 注意:此处有坑,因为你会发现用gradle添加的stetho依赖中没有StethoInterceptor这个类,你可以到stetho的github页面下载一下,同事需要跟你的OkHttp版本对应,因为2.x跟3.x对应的StethoInterceptor还有差异): 下载地址: facebook/stetho-okhttp3 facebook/stetho-okhttp 代码示例如下: public void testOkHttp(){Thread thread = new Thread(new Runnable() { @Override public void run() { String url = “http://www.zhihu.com/"; OkHttpClient.Builder builder = new OkHttpClient.Builder().addNetworkInterceptor(new StethoInterceptor()); OkHttpClient client = builder.build(); Request request = new Request.Builder() .url(url) .get() .build(); try { Response response = client.newCall(request).execute(); } catch (IOException e) { e.printStackTrace(); } }});thread.start();}运行这个函数,可以看到Network一栏的请求,每项网络请求发出时,Status处于Pending状态,收到回包后,Status等栏目都会变化,展示httpcode,请求耗时、回包数据类型等信息。当然这不是重点。重点是我们要对这个东西改造一下,他是如何抓下包来发送给Chrome的呢? 看一下StethoInterceptor的intercept函数,写了些注释:private final NetworkEventReporter mEventReporter = NetworkEventReporterImpl.get();public Response intercept(Chain chain) throws IOException { // 构造一个独特的eventID,一对网络事件(请求和回包)对应一个eventID String requestId = mEventReporter.nextRequestId(); Request request = chain.request(); // 准备发送请求 RequestBodyHelper requestBodyHelper = null; if (mEventReporter.isEnabled()) { requestBodyHelper = new RequestBodyHelper(mEventReporter, requestId); OkHttpInspectorRequest inspectorRequest = new OkHttpInspectorRequest(requestId, request, requestBodyHelper); // 请求即将发送,构造一个OkHttpInspectorRequest,报告给Chrome,此时Network会显示一条请求,处于Pending状态 mEventReporter.requestWillBeSent(inspectorRequest); } Response response; try { // 发送请求,获得回包 response = chain.proceed(request); } catch (IOException e) { // 如果发生了IO Exception,则通知Chrome网络请求失败了,显示对应的错误信息 if (mEventReporter.isEnabled()) { mEventReporter.httpExchangeFailed(requestId, e.toString()); } throw e; } if (mEventReporter.isEnabled()) { if (requestBodyHelper != null && requestBodyHelper.hasBody()) { requestBodyHelper.reportDataSent(); } Connection connection = chain.connection(); // 回包的header已收到,构造一个OkHttpInspectorResponse,发送给Chrome用于展示 mEventReporter.responseHeadersReceived( new OkHttpInspectorResponse( requestId, request, response, connection)); // 展示回包信息 ResponseBody body = response.body(); MediaType contentType = null; InputStream responseStream = null; if (body != null) { contentType = body.contentType(); responseStream = body.byteStream(); } responseStream = mEventReporter.interpretResponseStream( requestId, contentType != null ? contentType.toString() : null, response.header(“Content-Encoding”), responseStream, new DefaultResponseHandler(mEventReporter, requestId)); if (responseStream != null) { response = response.newBuilder() .body(new ForwardingResponseBody(body, responseStream)) .build(); } } return response; }所以整个流程我们可以简化为:发送请求时,给Chrome发了条消息,收到请求时,再给Chrome发条消息(具体怎么发的可以看NetworkEventReporterImpl的实现) 两条消息通过EventID联系起来,它们的类型分别是OkHttpInspectorRequest 和 OkHttpInspectorResponse,两者分别继承自NetworkEventReporter.InspectorRequest和NetworkEventReporter.InspectorResponse。我们只要也继承自这两个类,在自己的网络库发送和收到请求时,构造一个Request和Response并发送给Chrome即可。 发送部分示例:PulseInspectorRequest 继承自NetworkEventReporter.InspectorRequest public void reportRequestSend(PulseInspectorRequest request){ String requestId = request.id(); // request will be sent RequestBodyHelper requestBodyHelper = null; if (mEventReporter.isEnabled()) { requestBodyHelper = new RequestBodyHelper(mEventReporter, requestId); mEventReporter.requestWillBeSent(request); // report request send if (requestBodyHelper.hasBody()) { requestBodyHelper.reportDataSent(); } } }回包获取成功:public void reportRequestSuccess(PulseInspectorResponse response){ mEventReporter.responseHeadersReceived(response); mEventReporter.responseReadFinished(response.requestId()); String requestId = response.requestId(); String contentType = “application/json”; String encoding = null; InputStream responseStream = new ByteArrayInputStream(response.getResponseBody().getBytes()); InputStream responseHandlingInputStream = mEventReporter.interpretResponseStream( requestId, contentType, encoding, responseStream, new DefaultResponseHandler(mEventReporter, requestId)); try { if (responseHandlingInputStream == null) return; // 重点在这,这两行代码一定要加上,StethoInterceptor之所以不需要加, // 是因为OkHttp本身对请求采取了职责链式的处理, // 虽然在StethoInterceptor的intercept函数里没有进行read和close // 但是后续的Interceptor会进行这个操作,实际上这里,才把回包数据发送给了Chrome responseHandlingInputStream.read(response.getResponseBody().getBytes()); responseHandlingInputStream.close(); } catch (IOException e) { e.printStackTrace(); }}回包获取失败public void reportRequestFail(String eventId,String errMsg){ mEventReporter.httpExchangeFailed(eventId, errMsg);}至于PulseInspectorResponse 和PulseInspectorRequest如何实现,就依赖实际使用场景了。总结stetho 为开发者提供了一个很好的调试手段,但是自带的基础功能还比较弱,开发者可以根据自己的需求去改造。(不过官网文档是有点太少了……) 如果说这个工具有啥亮点,想来想去,大概App跟Chrome的通信,火狐的rhino引擎更可以被称之为亮点= .=|||3此文已由腾讯云+社区在各渠道发布获取更多新鲜技术干货,可以关注我们腾讯云技术社区-云加社区官方号及知乎机构号 ...

February 15, 2019 · 3 min · jiezi