相对于其他类型的性能指标,卡顿是能直接让用户产生视觉反馈的现象,比如App反应滞后于用户的操作,在严重的情况下会出现ANR。关乎用户体验的大事,是很容易遭到用户吐槽的。因此,开发人员平时写代码时必须要时刻提醒自己不要落入卡顿的陷阱之中。

一. 卡顿原因

在罗列卡顿可能会发生的几个点之前,先简单介绍一下发生卡顿的原因。

在之前《handler系列》聊过,UI线程是基于queue中的message事件驱动的,事件 -> 执行 -> 下一个事件...,另一方面由于Android的帧率是60fps,也就是每16ms就会触发一次UI刷新,如果某个message的处理时间 > 16ms,就会导致接收到VSYNC信号的时候无法完成本次刷新操作,产生掉帧现象。

因此,从本质上来讲,我们必须让UI线程的任何事件在16ms之内解决战斗。

基于此,可能会导致卡顿的原因有三大类:1)事件本身太耗时。2)事件本身并不耗时,但需要等待别的地方返回耗时。3)UI线程本身已经拿不到CPU资源来执行事件。

下面根据这三大类来分别具体细聊。

二. 耗时事件

这个很容易理解,就是把一些耗时业务逻辑直接写在了UI线程中,比如计算密集型的复杂计算,庞大的MD5计算,非对称RSA解密等。一般情况下,开发人员都不会犯这种错误,因为能够直接意识到计算量很大,本身就有警醒的作用。

三.耗时等待

1)网络I/O 同步请求这种如果是在用以前比较老的网络库,比如URLConnection这种就需要开发人员自己来开启新的线程。开发者可能忘记开启子线程,又同时做了同步请求等待,导致卡顿的发生。但是现代网络库比如Okhttp,Retrofit已经帮我们准备好了线程池,一般不会再遇到。 

2)磁盘I/O 文件,数据库一般的文件和数据库操作,大家可能都会自觉的在子线程中操作。但是值得一提的是SharedPreference的存储和读取,根据sp的设计,创建的时候会开启子线程把整个文件全部加载进内存,加载完毕再通知主线程,如果读取尚未结束,此时想获取某个key的值,主线程就必须等待加载完毕为止。

因此,如果你的sp文件比较大,那么会带来几个严重问题:a)第一次从sp中获取值的时候,有可能阻塞主线程,使界面卡顿、掉帧。b)解析sp的时候会产生大量的临时对象,导致频繁GC,引起界面卡顿。c)这些key和value会永远存在于内存之中,不会被释放,占用大量内存。所以千万不要把庞大的key/value存在sp中,比如把复杂的json当value。

另外对于sp的存储,commit是同步操作,要在子线程中使用。而apply虽然是在子线程执行的,但是无节制地apply也会造成卡顿,原因是每次有系统消息发生的时候(handleStopActivity,handlePauseActivity)都会去检查已经提交的apply写操作是否完成,如果没有完成则阻塞主线程。

3)跨进程Binder同步等待返回数据

四.CPU时间片

1)其他应用发生抢占CPU资源的情况,导致本应用无法获得CPU执行时间片。2)线程间发生死锁,UI线程无法获取锁,导致无法继续执行。3)频繁GC,内存抖动。GC的次数越多,消耗在GC上的时间就越长,CPU花在界面绘制上的时间相应就越短。

五. 分析

对于卡顿的分析手段,有很多工具可以使用,下面介绍几种。

1)TraceView相比之下,TraceView是分析卡顿的神兵利器,它不仅能看出每个方法消耗的时间、发生次数,并且可以进行排序,直接从最耗时的方法开始处优化。

2)ANR-WatchDog其原理简单来说就是开启一个子线程,设置tick = interval,然后每隔一个interval(可设置)就往UI线程queue中扔一个runnable,若UI线程没卡顿,则interval时间内会取出此runnable执行,即重置tick,那么下一个interval循环时根据检测此tick是否被重置来判断是否有卡顿发生。如果有,则打印此时的各个线程运行时的stack trace(可设置只打印主线程),以帮助定位。

3)AndroidPerformanceMonitorAndroidPerformanceMonitor 是国人开发的一个检测卡顿的开源库,原名是BlockCanary,可以设置卡顿检测时间,debug模式下检测到的卡顿可以通知展示(基本和LeakCanary一样),这个在开发自测时很有用。

其基本原理稍有不同,它并没有采用新开线程自己往UI线程里扔runnable的这种普通思想。而是利用系统在loop()方法里取出message前后进行了log打印这一特点,来重写Printer的println(String)方法,根据message处理前后的时间差,来判断是否发生了卡顿。

public static void loop() {...for (;;) {...// This must be in a local variable, in case a UI event sets the loggerPrinter logging = me.mLogging;if (logging != null) {logging.println(">>>>> Dispatching to " + msg.target + " " +msg.callback + ": " + msg.what);}msg.target.dispatchMessage(msg);if (logging != null) {logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);}...}
}

而且这个工具在卡顿发生时,收集的信息还比较丰富,包括基本信息,耗时信息,CPU信息,堆栈信息等。

4)ANR trace.txt而对于ANR,每当测试跑monkey一晚下来,ANR必是log的重点关注对象,若存在ANR,测试肯定会开jira贴上log给开发解决。对于trace.txt的分析,有几个基本的点是需要重点关注的:a)具体的call stack指向的具体代码,是否是卡顿发生的原因。b)是否有lock相关的关键字,代表可能发生死锁。c)是否有iowait字样,是否在UI线程发生了网络或者磁盘I/O。d)CPU使用率是否很高,很高表示要么自身有计算密集型任务发生,要么在其他地方有抢占CPU资源的任务。很低说明非耗时计算导致,可怀疑死锁和I/O耗时等待。

六. 解决

只要通过log分析能够找到发生卡顿的代码,基本上可以宣告问题很容易解决了,因为无论是对于耗时事件还是耗时等待,都可以采取异步的方式搞定。

而对于被抢占时间片的场景:1)如果是死锁,则需要fix发生死锁的漏洞;2)如果排除了以上所有可能后,就可以怀疑卡顿是由于被其他应用抢占CPU或者GC抖动导致,这需要通过log中的CPU使用率,和memory相关的回收信息,或者通过在debug模式下场景复现,综合profiler来观察和确定。

对于无法找到定位但是能够复现的场景,还可以根据业务场景来log打印时间,逐步缩小可疑代码的范围,从而排查和定位原因。

七. 总结

总之,关于卡顿的分析,并不是所有卡顿发生了都能找到原因,相反,大量ANR发生后通过log分析来解决是非常棘手的,甚至根本无从下手。所以我的观点是,对于卡顿一定要在开发写代码时做好警惕,养成良好习惯才是正道,防范为主,解决问题为辅。

一切从android的handler说起(一)之message

一切从android的handler说起(二)之threadLocal

一切从android的handler说起(三)之UI线程不卡顿

一切从android的handler说起(四)之postDelay原理

一切从android的handler说起(五)之触摸事件模型

一切从android的handler说起(六)之生命周期来源

一切从android的handler说起(七)之Handler内存泄露


进入公众号,回复“程序员“可以领取一份计算机技术电子书福利合集

欢迎转发,关注公众号 肖晖

每天几分钟,掌握一个硬核面试知识点

android db加载后无法读取任何内容_android性能优化(二)之卡顿优化相关推荐

  1. 深入探索Android卡顿优化

    由于卡顿优化这一主题包含的内容太多,为了更详细地进行讲解,因此,笔者将它分为了上.下两篇.本篇,即为<深入探索Android卡顿优化>的上篇. 本篇包含的主要内容如下所示: 卡顿优化分析方 ...

  2. android 获取位置数据库,尝试从webview获取位置时,Android“SQLite数据库无法从/CachedGeoposition.db加载”错误...

    我正在创建一个使用webkit和chrome客户端的android应用程序.我希望能够在网页请求时获取当前位置.我设置的网页适用于普通浏览器就好了.然而,当我尝试访问该网页WebKit中,我不断收到此 ...

  3. android动态图片适配,Android适配利用webview加载后图片显示过大的问题解决

    前言 最近在开发过程中,需要用webview控件来展示文章的详情页面,流程是通过请求后台数据,然后用控件加载,而后台返回的文章详情页面是直接网页端使用的,并没有对移动端进行适配,导致webview加载 ...

  4. android webView加载网络视频

    之前,我写过webView加载本地网页的博客,今天,就写写webView加载网络视频的内容. 一.加载网页 1.WebView用来显示网页,使用必须时刻注意我们需要添加网络权限 <uses-pe ...

  5. android 动态 dex,Android 动态加载dex

    首先如果仅仅是因为64K method的问题可以直接看这里DexGuard.Proguard.Multi-dex给出的解决方案. 本文主要讨论从编译层面,dex动态加载器选择层面以及安全层面讨论dex ...

  6. android 双 webview,Android webview加载页面

    释放双眼,带上耳机,听听看~! Android webview加载页面 private WebView webView; public void init() { webView = (WebView ...

  7. android glide的历史,Android 图片加载的那些事:为什么你的Glide 缓存没有起作用?...

    前言Glide,该功能非常强大 Android  图片加载开源框架 相信大家并不陌生 正由于他的功能强大,所以它的源码非常复杂,这导致很多人望而却步 本人尝试将 Glide 的功能进行分解,并单独针对 ...

  8. Android异步加载全解析之引入二级缓存

    Android异步加载全解析之引入二级缓存 为啥要二级缓存 前面我们有了一级缓存,为啥还要二级缓存呢?说白了,这就和电脑是一样的,我们电脑有内存和硬盘,内存读取速度快,所以CPU直接读取内存中的数据, ...

  9. 提升网页加载速度—预加载VS预读取

    预加载器(Pre-loader)可以说是提高浏览器性能最重要的举措.Mozilla 官方发布数据,通过预加载器技术网页的加载性能提升了19%,Chrome测试了 Alexa 排名前2000名网站,性能 ...

最新文章

  1. 支付宝变身新宠,钱包族再也“不差钱”
  2. HDU 3278 Puzzle
  3. tableview的reloadData 产生的问题
  4. 未找到导入的项目,请确认 Import 声明中的路径正确
  5. Dapr + .NET 实战(六)绑定
  6. 飞鸽传书:浅谈 Scrum
  7. 双卡项目如何在状态栏显示或隐藏G,3G以及卡1和卡2的信号标识
  8. 数据科学入门与实战:玩转pandas之一
  9. 验证码识别库 python_python 验证码识别库pytesseract的使用
  10. Matlab遗传算法工具箱(gaot)下载及安装
  11. access建立er图_Visio绘制ER图教程
  12. 中国首档「程序员真人秀」综艺登上热搜,燃炸了!
  13. Google浏览器拖拽安装扩展程序报错
  14. 人脸识别接口_智慧小区人脸识别门禁系统室外人脸识别门禁终端厂家
  15. 【Linux】kibana安装与使用教程
  16. TiDB2.1 报错statement count 5001 exceeds the transaction limitation, autocommit = false
  17. 基于stm32单片机智能温控风扇控制系统Proteus仿真
  18. react-create-app 配置alias别名
  19. JavaWeb:HTML
  20. 阿里云函数计算快速入门

热门文章

  1. nyoj138 哈希的简单应用(查找)
  2. 内网信息安全厂商对客户的误导
  3. RHEL5上Oracle9i的安装
  4. 大同语网站页面资料汇总编书(PDF)
  5. 创建了一个.NET 技术的 Wiki 和论坛
  6. docker运行dubbo-admin
  7. 编译原理0909的作业
  8. js基础知识温习:构造函数与原型
  9. train problem I (栈水题)
  10. oracle odbc配置