ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

面试官:你的App卡顿过吗?你是如何监控的?

面试官:你的App卡顿过吗?你是如何监控的? 一、故事开始面试官平时开发中有遇到卡顿问题吗你一般是如何监控的来面试的小伙额…没有遇到过卡顿问题我平时写的代码质量比较高不会出现卡顿。面试官…这回答似乎没啥问题但是如果你在面试中真这样说他们会认为你在卡顿监控以及优化这一块是0经验。卡顿这个话题相信大部分两年或以上工作经验的同学都应该能说出个大概。一般都能说出卡顿的原因主要是主线程阻塞。在开发过程中遇到的造成主线程阻塞的原因可能是主线程在进行大量I/O操作为了方便代码编写直接在主线程去写入大量数据主线程在进行大量计算代码编写不合理主线程进行复杂计算大量UI绘制界面过于复杂UI绘制需要大量时间主线程在等锁主线程需要获得锁A但是当前某个子线程持有这个锁A导致主线程不得不等待子线程完成任务。…但是如果问得更深一点应用上线后程序频繁出现卡顿如何定位问题当遇见OOM时如何定位到真正导致内存溢出的原因如何在不影响性能的同时实现线上性能监控去过大厂面试的朋友就会知道大厂经常问这样的问题主要是因为一旦发生卡顿就会被用户直观的感受到而其他问题很难被及时的发现比如内存占用高耗费流量等。用户体验不好就很有可能卸载掉我们的 App让公司白白付出高昂的用户成本因此因为性能问题导致用户流失是我们开发人员的失职。二、性能问题如何治理首先搞客户端开发的同学应该都知道解决卡顿的过程往往是曲折的有些并没有我们想的那样简单、浅表。很多时候大部分卡顿是很难及时发现的不可重现的卡顿经常出现在线上用户的真实使用过程中这种卡顿往往跟机器性能手机环境甚至是操作偏好等因素息息相关。我们平时从用户反馈的“好卡呀”这种描述中很难直接洞察到卡顿的根源。甚至有些连卡顿的场景都不知道很难准确重现所以这种卡顿容易让人摸不着头脑。而内存作为计算机程序运行最重要的资源之一需要运行过程中做到合理的资源分配与回收不合理的内存占用轻则使得用户应用程序运行卡顿、ANR、黑屏重则导致用户应用程序发生 OOMout of memory崩溃。我们需要在各种机器资源上保持优秀的流畅性和稳定性内存优化是必须要重视的环节。但是我们即使有接入如Bugly的线上异常采集平台也不能够保证通过异常日志找到OOM的原因。绝大多数的OOM异常日志显示的只是压倒骆驼的最后一根稻草而不是直接的原因。三、如何进行线上性能监控下面总结几种比较流行、有效的卡顿监控方式1基于消息队列1.1替换 Looper 的 PrinterLooper暴露了一个方法public void setMessageLogging(Nullable Printer printer) { mLogging printer; }在Looper 的loop方法有这样一段代码public static void loop() { ... for (;;) { ... // This must be in a local variable, in case a UI event sets the logger final Printer logging me.mLogging; if (logging ! null) { logging.println( Dispatching to msg.target msg.callback : msg.what); }Looper轮循的时候每次从消息队列取出一条消息如果logging不为空就会调用 logging.println我们可以通过设置Printer计算Looper两次获取消息的时间差如果时间太长就说明Handler处理时间过长直接把堆栈信息打印出来就可以定位到耗时代码。不过println 方法参数涉及到字符串拼接考虑性能问题所以这种方式只推荐在Debug模式下使用。基于此原理的开源库代表是BlockCanary看下BlockCanary核心代码类LooperMonitorpublic void println(String x) { if (mStopWhenDebugging Debug.isDebuggerConnected()) { return; } if (!mPrintingStarted) { //1、记录第一次执行时间mStartTimestamp mStartTimestamp System.currentTimeMillis(); mStartThreadTimestamp SystemClock.currentThreadTimeMillis(); mPrintingStarted true; startDump(); //2、开始dump堆栈信息 } else { //3、第二次就进来这里了调用isBlock 判断是否卡顿 final long endTime System.currentTimeMillis(); mPrintingStarted false; if (isBlock(endTime)) { notifyBlockEvent(endTime); } stopDump(); //4、结束dump堆栈信息 } } //判断是否卡顿的代码很简单跟上次处理消息时间比较比如大于3秒就认为卡顿了 private boolean isBlock(long endTime) { return endTime - mStartTimestamp mBlockThresholdMillis; }原理是这样比较Looper两次处理消息的时间差比如大于3秒就认为卡顿了。细节的话大家可以自己去研究源码比如消息队列只有一条消息隔了很久才有消息入队这种情况应该是要处理的BlockCanary是怎么处理的呢这个我在BlockCanary 中测试并没有出现此问题所以BlockCanary 是怎么处理的简单分析一下源码上面这段代码注释1和注释2记录第一次处理的时间同时调用startDump()方法startDump()最终会通过Handler 去执行一个AbstractSampler类的mRunnable代码如下abstract class AbstractSampler { private static final int DEFAULT_SAMPLE_INTERVAL 300; protected AtomicBoolean mShouldSample new AtomicBoolean(false); protected long mSampleInterval; private Runnable mRunnable new Runnable() { Override public void run() { doSample(); //调用startDump 的时候设置true了stop时设置false if (mShouldSample.get()) { HandlerThreadFactory.getTimerThreadHandler() .postDelayed(mRunnable, mSampleInterval); } } };可以看到调用doSample之后又通过Handler执行mRunnable等于是循环调用doSample,直到stopDump被调用。doSample方法有两个类实现StackSampler和CpuSampler分析堆栈就看StackSampler的doSample方法protected void doSample() { StringBuilder stringBuilder new StringBuilder(); // 获取堆栈信息 for (StackTraceElement stackTraceElement : mCurrentThread.getStackTrace()) { stringBuilder .append(stackTraceElement.toString()) .append(BlockInfo.SEPARATOR); } synchronized (sStackMap) { // LinkedHashMap中数据超过100个就remove掉链表最前面的 if (sStackMap.size() mMaxEntryCount mMaxEntryCount 0) { sStackMap.remove(sStackMap.keySet().iterator().next()); } //放入LinkedHashMap时间作为keyvalue是堆栈信息 sStackMap.put(System.currentTimeMillis(), stringBuilder.toString()); } }所以BlockCanary能做到连续调用几个方法也能准确揪出耗时是哪个方法是采用开启循环去获取堆栈信息并保存到LinkedHashMap的方式避免出现误判或者漏判。核心代码就先分析到这里其它细节大家可以自己去看源码。1.2插入空消息到消息队列这种方式可以了解一下。通过一个监控线程每隔1秒向主线程消息队列的头部插入一条空消息。假设1秒后这个消息并没有被主线程消费掉说明阻塞消息运行的时间在01秒之间。换句话说如果我们需要监控3秒卡顿那在第4次轮询中头部消息依然没有被消费的话就可以确定主线程出现了一次3秒以上的卡顿。2.插桩编译过程插桩例如使用AspectJ在方法入口和出口加入耗时监控的代码。 原来的方法public void test(){ doSomething(); }通过编译插桩之后的方法类似这样public void test(){ long startTime System.currentTimeMillis(); doSomething(); long methodTime System.currentTimeMillis() - startTime;//计算方法耗时 }当然原理是这样实际上可能需要封装一下类似这样public void test(){ methodStart(); doSomething(); methodEnd(); }在每个要监控的方法的入口和出口分别加上methodStart和methodEnd两个方法类似插桩埋点。当然这种插桩的方法缺点比较明显无法监控系统方法apk体积会增大每个方法都多了代码需要注意过滤简单的方法只需要监控主线程执行的方法四、性能优化监控到了问题就要开始去优化了针对“性能优化”这个要点献上一份阿里大佬整理的Android性能优化实战手册从各个方面对目标产品进行全方位的“优化”让产品的性能从各个方面得到提升希望大家喜欢。这份《Android360°全方面性能调优》一共有722页4个大点25个小章节不仅仅有详细的底层原理的解析还有大厂的实践案例。有需要的朋友文末有免费领取方式~第一章 设计思想与代码质量优化六大原则单一职责原则里氏替换原则依赖倒转原则接口隔离原则……设计模式结构型模式桥接模式适配器模式装饰器模式代理模式门面外观模式……设计模式创建型模式建造者模式单例模式抽象工厂模式工厂方法模式……数据结构栈队列链表树……算法排序算法查找算法……第二章 程序性能优化启动速度与执行效率优化冷启动和热启动解析APP 启动黑白屏解决办法APP 卡顿问题分析及解决方案启动速度与执行效率优化之 StrictMode……布局检测与优化布局层级优化过度渲染……内存优化内存抖动和内存泄漏内存大户Bitmap 内存优化Profile 内存监测工具Mat 大对象与泄漏检测耗电优化网络传输与数据存储优化网络传输与数据存储优化APK 大小优化屏幕适配……耗电优化DozeStandbyBattery HistorianJobSchedulerWorkManager网络传输与数据存储优化google 序列化工具 protobuf7z 极限压缩……APK 大小优化APK 瘦身微信资源混淆原理……屏幕适配进行适配的原理屏幕分辨率限定符与 smallestWidth 限定符适配原理为什么选择 smallestWidth 限定符适配怎么适配其他 module常见问题处理……OOM 问题原理解析adj 内存管理机制JVM 内存回收机制与 GC 算法解析生命周期相关问题总结Bitmap 压缩方案总结……ANR 问题解析AMS 系统时间调节原理程序等待原理分析ANR 问题解决方案……Crash 监控方案Java 层监控方案Nativie 层监控方案……第三章 开发效率优化分布式版本控制系统 Git企业高效持续集成平台场景介绍GIT 分布式版本控制系统GIT 分支管理……自动化构建系统 GradleGradle 与 Android 插件gradle 与 android gradle 插件的关系Gradle Transform API 的基本使用……Gradle Transform API 的基本使用什么是 TransformTransform 的使用场景Transform API 学习输入的类型……自定义插件开发Gradle 插件简介开始准备实践自定义 Gradle 插件buildSrc 模块方式……插件实战多渠道打包发版自动钉钉……第四章 APP 性能优化实践启动速度应用启动的一般流程冷启动和热启动启动速度的测量启动窗口优化线程优化系统调度优化GC 优化IO 优化资源重排主页布局优化类加载优化选择合适的启动框架减少 Activity 的跳转层次厂商优化后台保活……流畅度性能问题分析的一些工具和套路通过性能数据数据分析Android 平台性能导致的性能案例Android App 自身导致的性能问题低内存的数据特征和行为特征应用宝讯飞输入法无障碍服务导致的整机卡顿分析字节跳动今日头条图文详情页秒开实践……抖音在 APK 包大小资源优化的实践图片压缩webp 无侵入式兼容多 DPI 优化重复资源合并shrinkResource 严格模式资源混淆(兼容 aab 模式)ARSC 瘦身……优酷响应式布局技术全解析优酷APP响应式布局技术概述优酷APP响应式布局Android落地在分发场景的落地在消费场景的落地优酷APP响应式布局之测试方案……网络优化手机淘宝在网络的链路优化百度 APP 在网络深度优化的实践……手机淘宝双十一性能优化项目揭秘一秒法则的实现启动时间和页面帧率提升 20%Android 手机内存节省50%……高德 APP 全链路源码依赖分析高德 APP 平台架构基础实现原理项目架应用场景及实现原理……彻底干掉OOM的实战经验分享排查内存泄漏兜底策略内存峰值太高特大图排查优化……微信 Android终端内存优化实践Activity 泄露检测Bitmap 分配及回收追踪Native 内存泄漏检测线程监控内存监控……如果你也想提升自己移动开发的性能优化技术或者是正在准备移动开发岗的面试我觉得这份笔记你必定不能错过。由于篇幅限制这里只能展示部分内容朋友们如果需要这份完整版的PDF资料合集微信扫描下方CSDN官方二维码【免费获取】。
返回列表