在知乎回答了一个“为什么微博的app在iPhone比Android上流畅”的问题。后面部分是一个典型的动画卡顿的性能分析过程,因此帖在这里。有编程问题可以在这里交流。知乎链接

=========================================================

我来说下我所知道的事情。我不知道iOS为什么流畅,但我知道一些Android为什么不流畅的原因。

首先,就题主所说的问题,我用iPad和小米Pad对比了一下微博滑动滚屏这件事情(2014年8月10日目前微博app最新版本)。正如题主所说,直观感受上明显感觉iOS要流畅、舒服。

在这件事情上我认为主要是这三个原因:

  1. 速度曲线。
    当你滑动界面然后松手,这时界面会继续滑动,然后速度减小,直到速度为0时停止。iOS下速度减小的这个过程比较慢,尤其是快要停的时候是慢慢停的,视觉上有种很顺滑的感觉;Android下则从松手到停要快很多,相比之下有种戛然而止的感觉。
    从数据/技术角度来看这个事情,我们滑动界面的最终目的不是为了“动”,而是为了“停”,因此只要平滑的到达目的地,似乎越快完成这个过程越好,所以Android的选择是理所当然的。但事实是,大家普遍更喜欢iOS的方式,这样做显得更顺滑、更优雅。
  2. 帧率。
    绝大部分时间两者都能保持60FPS左右的满帧率。但都会有偶尔的掉帧。并且Android上要比iOS上严重很多。(好吧,比起前两年,已经好太多了。)我前前后后滑动了几十次,iOS在前面遇到1次掉帧,后面就很稳定了。而Android几乎每滑动一次都会伴随一次掉帧。这完全就是真真实实的卡顿,用户必然会感觉到那一刻的不流畅。Android掉帧的原因我后面再详细分析。
  3. 触摸响应速度。
    从手指碰到触摸屏,到屏幕上显示处理这次触摸产生的画面,是需要时间的。时间越短感觉越跟手。据说iOS的触摸屏的处理时间要比一般的Android手机快,这不是我的专长,不知道怎么验证。但在软件系统层面,Android的显示机制是app-->SurfaceFlinger-->Display,这比传统的app-->Display多了一步,主要基于这个原因,画面最终输出到屏幕要比传统的方式慢一帧(16.7ms)。

我觉得第1点造成的影响最大,恰恰却是最技术/设备无关的。如果微博app或者Android系统要改变,很容易就可以调得跟iOS一模一样。但正是由于这是产品形态上的差别而不是纯粹技术上的优劣,反倒成了Android最不太可能改变的。

第2点的影响其次,当然是指在目前这个大部分时候都能满帧的情况下。这方面是Android从早期到现在进步最明显的方面,使用了很多方法来优化帧率。但就算现在Android进化了很多,硬件性能也进化了很多,却仍旧不可能彻底消灭掉帧的情况。

第3点通俗的讲就是跟手性,跟手性的重要性不言而喻,但现在的差别比较细微,且具体数据我也不清楚。
我想过一个办法让桌面、微博这种内容和手一起动的应用绘制到屏幕的速度快一帧(16.7ms),其实就是抵消之前提到的慢的那一帧,需要framework层和app层一起配合改动,目前已申请了专利但代码还没进,将来有时间了应该会进到MIUI。感兴趣的可以看看专利:滑动操作响应方法、装置及终端设备

最后我来用专业技术分析一下微博app在Android里掉帧的原因。非编程人员可以不看下去了。(这个过程我使用的是小米3高通版+最新版微博app。)

最初,我认为这种现象很像GC(垃圾回收)导致的,于是打开logcat观察每次卡顿的时候有没有GC发生。结果发现并没有。停下来的时候才会有GC,这说明微博app在滑动过程中控制得不错,在停下来的一刻才去分配内存,使GC不影响帧率。

然后我打开“开发者选项”->“GPU呈现模式分析”->“在屏幕上显示为条形图”(好像是4.4才有这个选项,之前的只能在dumpsys里看),这会在屏幕上直观的显示每帧绘制花费的时间。屏幕上有条基准线大概是16ms,如果超过这条线则很有可能掉帧了。如果下面的蓝色部分很长则说明是软件draw的部分太费时,那么可以通过traceview来继续分析draw的java代码。(我假定了现在的app都是硬件加速的方式。)如果中间红色部分很长则说明是OpenGL ES绘制过程太费时。可以用gltrace来分析OpenGL ES的调用过程。
打开选项后使用微博app,发现虽然偶尔有超基准线,但并不严重,并且卡顿的时候并没有明显异常。

于是我开始使用systrace,这下就很明显了:

可以发现每隔几帧,就会有一次大间隙,图中我圈了红圈圈的两个地方都明显超过正常的耗时时间。现在问题是,这些地方的代码在干什么呢?是什么花费了这么长时间?

放大后发现都是这两个:obtainView和setupListItem。它们都是在draw之前调用的,这也解释了通过上一步的方法(“GPU呈现模式分析”)为什么没有显示出来,因为那个方法只展示draw里面的时间消耗。
通过在源码里搜索obtainView和setupListItem,可以发现是AbsListView的obtainView方法和ListView的setupChild方法打下的这个log。

接下来,我们用traceview来看看这两个方法里面究竟调用了什么代码导致耗时过长。
跟踪obtainView最终到了这里:

代码混淆了,看不太出来了,也懒得再跟下去了。但随便点了点然后看到:

TextView.setText用了挺多的时间。感觉Android团队应该优化优化这个方法。
然后再来跟踪setupChild方法:

都是measure占用的时间,并且调用次数比较多。这应该说明了View的数量不少。用hierarchyviewer看了下,的确不少,一条微博有50多个View:

结论:我们最终大致找到了耗时的代码及部分原因。这两个耗时方法应该都是有可能减下来的,但应该不那么容易。
在Android上实现功能比较容易,但如果默认实现有了性能问题,要想解决就不好说了,有时候要绕很远。

最后,列一些相关链接:
Android图形架构(绘图慢一帧的事情这里有说):http://source.android.com/devices/graphics/architecture.html
Android Performance Case Study:http://www.curious-creature.org/docs/android-performance-case-study-1.html

Android中app卡顿原因分析示例的更多相关文章

  1. android中app卡顿优化问题

     所谓app卡顿原因就是在运行时出现了丢帧,还可能是UI线程被阻塞.首先来一下丢帧现象,android每16ms会对界面进行一次渲染,如果app的绘制.计算等超过了16ms那么只能等下一个16ms才能 ...

  2. android TextView SetText卡顿原因

    [android TextView SetText卡顿原因] 不要用wrap_content即可. 参考:http://blog.csdn.net/self_study/article/details ...

  3. H5:加载原理,慢加载和卡顿原因分析,

    前端H5工作原理: 请求和显示原理 H5页面卡顿原因分析: 1.动画太多:渲染重绘占用GPU 2.页面操作导致重绘频繁 3.页面元素复杂:资源类标签太多(图像/视频/dom树太长) 4.内置webvi ...

  4. android中fragment卡顿的原因

    首页的ViewPager有十几个Fragment,在快速切换的时候,容易产生卡顿现象. 二.分析当ViewPager切换到当前的Fragment时,Fragment会加载布局并显示内容,如果用户这时快 ...

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

    一.故事开始 面试官:平时开发中有遇到卡顿问题吗?你一般是如何监控的? 来面试的小伙:额...没有遇到过卡顿问题,我平时写的代码质量比较高,不会出现卡顿. 面试官:... 这回答似乎没啥问题,但是如果 ...

  6. Android 界面滑动卡顿分析与解决方案(入门)

    Android 界面滑动卡顿分析与解决方案(入门) 导致Android界面滑动卡顿主要有两个原因: 1.UI线程(main)有耗时操作 2.视图渲染时间过长,导致卡顿 目前只讲第1点,第二点相对比较复 ...

  7. Android中Java反射技术的使用示例

    import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Metho ...

  8. android中倒计时控件CountDownTimer分析

    android中倒计时控件CountDownTimer分析 1 示例代码 new CountDownTimer(10000, 1000) { public void onTick(long milli ...

  9. Android 教你如何发现 APP 卡顿

    最近部门打算优化下 APP 在低端机上的卡顿情况,既然想优化,就必须获取卡顿情况,那么如何获取卡顿情况就是本文目的. 一般主线程过多的 UI 绘制.大量的 IO 操作或是大量的计算操作占用 CPU,导 ...

随机推荐

  1. TFS错误-TF249053

    前几天规划了下代码结构,改了很多东西后,台式机依然正常访问,但是笔记本一连接或者更改TFS相关资源就报错TF249053.报错点击后不影响正常使用,但是很郁闷.于是查了下资料如下. 错误原因: htt ...

  2. Java之 Hashtable

    Hashtable是Java中键值对数据结构的实现.您可以使用“键”存储和检索“值”,它是存储值的标识符.显然“关键”应该是独一无二的. java.util.Hashtable扩展Dictionary ...

  3. Mysql 锁技术要点【转载】

    MyISAM和InnoDB的区别 MySQL默认采用的是MyISAM. MyISAM不支持事务,而InnoDB支持.InnoDB的AUTOCOMMIT默认是打开的,即每条SQL语句会默认被封装成一个事 ...

  4. unbound域名解析

    安装unbound服务 # yum install unbound -y 开启服务 linux系统如何查看命令属于哪一个安装包 # yum provides */netstat 安装netstat命令 ...

  5. application/xml 和 text/xml的区别

    application/xml and text/xml的区别 经常看到有关xml时提到"application/xml" 和 "text/xml"两种类型, ...

  6. Linux下安装Nginx依赖包和Nginx的命令

    1.安装依赖包pcrecd /usr/local/srcwget ftp://ftp.csx.cam.ac.uk/pub/software/programming/pcre/pcre-8.39.tar ...

  7. Sublime Text 2 - 性感无比的代码编辑器!程序员必备神器!

    Sublime Text 2 - 性感无比的代码编辑器!程序员必备神器! http://www.iplaysoft.com/sublimetext.html 代码编辑器或者文本编辑器,对于程序员来说, ...

  8. Process Pool实现Python的并行执行

    参考:Python3.6.2文档 Source code: Lib/concurrent/futures/thread.py and Lib/concurrent/futures/process.py ...

  9. django从请求到响应的过程深入讲解

    django启动 我们在启动一个django项目的时候,无论你是在命令行执行还是在pycharm直接点击运行,其实都是执行'runserver'的操作,而ruserver是使用django自带的的we ...

  10. AVL树Python实现(使用递推实现添加与删除)

    # coding=utf-8 # AVL树的Python实现(树的节点中包含了指向父节点的指针) def get_height(node): return node.height if node el ...