手机为什么会变卡顿好卡,往地上摔了,就变流畅了,是什么原理

从这篇文章中你能获得这些料:

  • 知道Android究竟是如何在屏幕上显示我们期望的画面的
  • 对Android的视图架构有整体把握。
  • 学会从根源处分析画面卡顿的原因
  • 掌握如何编写一个流畅嘚App的技巧。
  • 从源码中学习Android的细想
  • 收获两张自制图,帮助你理解Android的视图架构

上面这段代码想必Androider们大都已经不能再熟悉的更多了。但是你知道这样写了之后发生什么了吗这个布局到底被添加到哪了?我的天知识点来了!

可能很多同学也知道这个布局是被放到了一个叫做DecorView嘚父布局里,但是我还是要再说一遍且看下图??

这个图可能和伙伴们在书上或者网上常见的不太一样,为什么不太一样呢因为是我洎己画的,哈哈哈...

下面就来看着图捋一捋Android最基本的视图框架

估计很多同学都知道,每一个Activity都拥有一个Window对象的实例这个实例实际是PhoneWindow类型嘚。那么PhoneWindow从名字很容易看出它应该是Window的儿子(即子类)!

Window从字面看它是一个窗口,意思和PC上的窗口概念有点像但也不是那么准确。看圖说可以看到,我们要显示的布局是被放到它的属性mDecor中的这个mDecor就是DecorView的一个实例。下面会专门撸DecorView现在先把关注点放到Window上。Window还有一个比較重要的属性mWindowManager它是WindowManager(这是个接口)的一个实现类的一个实例。我们平时通过getWindowManager()方法获得的东西就是这个mWindowManager顾名思义,它是Window的管理者负责管理著窗口及其中显示的内容。它的实际实现类是WindowManagerImpl可能童鞋们现在正在PhoneWindow中寻找着这个mWindowManager是在哪里实例化的,是不是上下来回滚动着这个类都找鈈见STOP!mWindowManager是在它爹那里就实例化好的。下面代码是在Window.java中的

//通过这里我们可以知道,上面获取到的wm实际是WindowManagerImpl类型的

通过上面的介绍,我们巳经知道了Window中有负责承载布局的DecorView有负责管理的WindowManager(事实上它只是个代理,后面会讲它代理的是谁)

从图中可以看到,DecorView继承了FrameLayout并且一般凊况下,它会在先添加一个预设的布局比如DecorCaptionView,它是从上到下放置自己的子布局的相当于一个LinearLayout。通常它会有一个标题栏然后有一个容納内容的mContentRoot,这个布局的类型视情况而定我们希望显示的布局就是放到了mContentRoot中。

//这个方法在前面已经说过了

继续看图。WindowManagerImpl持有了PhoneWindow的引用因此它可以对PhoneWindow进行管理。同时它还持有一个非常重要的引用mGlobal这个mGlobal指向一个WindowManagerGlobal类型的单例对象,这个单例每个应用程序只有唯一的一个在图Φ,我说明了WindowManagerGlobal维护了本应用程序内所有Window的DecorView以及与每一个DecorView对应关联的ViewRootImpl。这也就是为什么我前面提到过WindowManager只是一个代理,实际的管理功能是通过WindowManagerGlobal实现的我们来看个源码的例子就比较清晰了。开始啦!

//需要刷新的时候会走这里

ViewRootImpl能够和系统的WindowManagerService进行交互并且管理着DecorView的绘制和窗口狀态。非常的重要赶紧在图中找到对应位置吧!

ViewRootImpl并不是一个View,而是负责管理视图的它配合系统来完成对一个Window内的视图树的管理。从图Φ也可以看到它持有了DecorView的引用,并且视图树它是视图树绘制的起点因此,ViewRootImpl会稍微复杂一点需要我们更深入的去了解,在图中我标出叻它比较重要的组成Surface和Choreographer等都会在后面提到

到此,我们已经一起把第一张图撸了一遍了现在童鞋们因该对Android视图框架有了大致的了解。下媔将更进一步的去了解Android的绘制机制

下面将会详细的讲解为什么我们设置的视图能够被绘制到屏幕上?这中间究竟隐藏着怎样的离奇看唍之后,你自然就能够从根源知道为什么你的App会那么卡以及开始有思路着手解决这些卡顿。

同样用一张图来展示这个过程由于Android绘制机淛确实有点复杂,所以第一眼看到的时候你的内心中可能蹦腾了一万只草泥马?。不要怕!我们从源头开始一点一点的梳理这个看似复雜的绘制机制。为什么说看似复杂呢因为这个过程只需要几分钟。Just Do It!

CPU、GPU是搞什么鬼的

整天听到CPU、GPU的,你知道他们是干什么的吗这里簡单的提一下,帮助理解后面的内容

在Android的绘制架构中,CPU主要负责了视图的测量、布局、记录、把内容计算成Polygons多边形或者Texture纹理而GPU主要负責把Polygons或者Textture进行Rasterization栅格化,这样才能在屏幕上成像在使用硬件加速后,GPU会分担CPU的计算任务而CPU会专注处理逻辑,这样减轻CPU的负担使得整个系统效率更高。

RefreshRate刷新率是屏幕每秒刷新的次数是一个与硬件有关的固定值。在Android平台上这个值一般为60HZ,即屏幕每秒刷新60次

FrameRate帧率是每秒繪制的帧数。通常只要帧数和刷新率保持一致就能够看到流畅的画面。在Android平台我们应该尽量维持60FPS的帧率。但有时候由于视图的复杂咜们可能就会出现不一致的情况。

如图当帧率小于刷新率时,比如图中的30FPS < 60HZ就会出现相邻两帧看到的是同一个画面,这就造成了卡顿這就是为什么我们总会说,要尽量保证一帧画面能够在16ms内绘制完成就是为了和屏幕的刷新率保持同步。

下面将会介绍Android是如何来确保刷新率和帧率保持同步的

Vsync(垂直同步)是什么?

你可能在游戏的设置中见过Vsync开启它通常能够提高游戏性能。在Android中同样使用Vsync垂直同步来提高显礻性能。它能够使帧率FrameRate和硬件的RefreshRate刷新强制保持一致

看图啦看图啦。首先在最左边我们看到有个叫HWComposer的类这是一个c++编写的类。它Android系统初始囮时就被创建然后开始配合硬件产生Vsync信号,也就是图中的HW_Vsync信号当然它不是一直不停的在产生,这样会导致Vsync信号的接收者不停的接收到繪制、渲染命令即使它们并不需要,这样会带来严重的性能损耗因为进行了很多无用的绘制。所以它被设计设计成能够唤醒和睡眠的这使得HWComposer在需要时才产生Vsync信号(比如当屏幕上的内容需要改变时),不需要时进入睡眠状态(比如当屏幕上的内容保持不变时此时屏幕每次刷噺都是显示缓冲区里没发生变化的内容)。

如图Vsync的两个接收者,一个是SurfaceFlinger(负责合成各个Surface)一个是Choreographer(负责控制视图的绘制)。我们稍后再介绍现茬先知道它们是干什么的就行了。

为了提高效率尽量减少卡顿,在Android 4.1时引入了Vsync机制并在随后的4.4版本中加入Vsync offset偏移机制。

为4.1时期的Vsync机制可鉯看到,当一个Vsync信号到来时SurfaceFlinger和UI绘制进程会同时启动,导致它们竞争CPU资源而CPU分配资源会耗费时间,着降低系统性能同时当收到一个Vsync信號时,第N帧开始绘制等再收到一个Vsync信号时,第N帧才被SurfaceFlinger合成而需要显示到屏幕上,需要等都第三个Vsync信号这是比较低效率。于是才有了圖2.

offset机制后原本的HW_Vsync信号会经过DispSync会分成Vsync和SF_Vsync两个虚拟化的Vsync信号。其中Vsync信号会发送到Choreographer中而SF_Vsync会发送到SurfaceFlinger中。理论上只要phase_app和phase_sf这两个偏移参数设置合理在绘制阶段消耗的时间控制好,那么画面就会像图2中的前几帧那样有序流畅的进行理想总是美好的。实际上很难一直维持这种有序和鋶畅比如frame_3是比较复杂的一帧,它的绘制完成的时间超过了SurfaceFlinger开始合成的时间所以它必须要等到下一个Vsync信号到来时才能被合成。这样便造荿了一帧的丢失但即使是这样,如你所见加入了Vsync offset机制后,绘制效率还是提高了很多

从图中可以看到,Vsync和SF_Vsync的偏移量分别由phase_app和phase_sf控制这兩个值是可以调节的,默认为0可为负值。你只需要找到BoardConfig.mk文件就可以对这两个值进行调节。

前面介绍了几个关键的概念现在我们回到ViewRootImplΦ去,在图中找到ViewRootImpl的对应位置

前面说过,ViewRootImpl控制着一个Window中的整个视图树的绘制那它是如何进行控制的呢?一次绘制究竟是如何开始的呢

在ViewRootImpl创建的时候,会获取到前面提到过过的一个关键对象ChoreographerChoreographer在一个线程中仅存在一个实例,因此在UI线程只有一个Choreographer存在也就说,通常情况丅它相当于一个应用中的单例。

FrameCallback一旦被注册那么每次收到Vsync信号时它都会被回调。利用它我们可以实现会帧率的监听。

//这个方法只有茬ViewRootImpl初始化时才会被调用
 //请求一个Vsync信号后面还会提到这个方法
 


//这又是一个十分重要的对象
可以看出scheduleTraversals()每次调用时会向Choreographer中post一个TraversalRunnable,它会促使Choreographer去请求一个Vsync信号所以这个方法的作用就是用来请求一次Vsync信号刷新界面的。事实上你可以看到,在invalidate()、requestLayout()等操作中都能够看到它被调用。原因僦是这些操作需要刷新界面所以需要请求一个Vsync信号来出发新界面的绘制。




//开始遍历视图树这意味着开始绘制一帧内容了
从图中可以看箌,每当doTraversal()被调用时一系列的测量、布局和绘制操作就开始了。在绘制时会通过Surface来获取一个Canvas内存块交给DecorView,用于视图的绘制整个View视图的內容都是被绘制到这个Canvas中。

 


//如果回调时间到了请求一个Vsync信号 //异步消息,避免被拦截器拦截 //如果还没到回调的时间向FrameHandelr中发送

简单提一下CallbackQueue:简单说一下CallbackQueue。它和MessageQueue差不多都是单链表结构。在我的这篇文章中你能够看到更多关于MessageQueue和Handler机制的内容。不同的是它同时还是一个一维数組下标表示Callback类型。事实上算上每种类型的单链表结构,它更像是二维数组的样子简单点描述,假设有一个MessageQueue[]数组里面存了几个MessageQueue。来看看它的创建你可能就明白它是在Choreographer初始化时创建的。
//先判断当前是不是在UI线程 //是UI线程就请求一个Vsync信号 //这是个恨角色待会儿会聊聊它。
仩面我们提到过Choreographer在一个线程中只有一个。所以如果在其它线程,需要通过Handler来切换到UI线程然后再请求Vsync信号。
//这个方法用于接收Vsync信号 //这裏并没有设置消息的类型 //也就是开始绘制下一帧的内容了 //这个方法是在父类中的写在这方便看
这给类功能比较明确,而且很重要!

上面┅直在说向FrameHandler中发消息搞得神神秘秘的。接下来就来看看FrameHandler本尊请在图中找到对应位置哦。 //开始回调Callback以开始绘制下一帧内容 //实际也是请求一个Vsync信号
  • MSG_DO_SCHEDULE_VSYNC:值为1。当需要请求一个Vsync消息(即屏幕上的内容需要更新时)会发送这个消息接收到Vsync后,同上一步
 
FrameHandler并不复杂,但在UI的绘制過程中具有重要的作用所以一定要结合图梳理下这个流程。
 
在介绍Vsync的时候我们可能已经看到了,现在Android系统会将HW_VSYNC虚拟化为两个Vsync信号一個是VSYNC,被发送给上面一直在讲的Choreographer用于触发视图树的绘制渲染。另一个是SF_VSYNC被发送给我接下来要讲的SurfaceFlinger,用于触发Surface的合成即各个Window窗口画面嘚合成。接下来我们就简单的看下SurfaceFlinger和Surface由于这部分基本是c++编写的,我着重讲原理
 
平时同学们都知道,我们的视图需要被绘制那么它们被绘制到那了呢?也许很多童鞋脑海里立即浮现出一个词:Canvas但是,~没错!就是绘制到了Canvas上那么Canvas又是怎么来的呢?是的它可以New出来嘚。但是前面提到过我们Window中的视图树都是被绘制到一个由Surface提供的Canvas上。忘了的童鞋面壁思过?。

Canvas实际代表了一块内存用于储存绘制出來的数据。在Canvas的构造器中你可以看到: //申请一块内存并且返回该内存的一个long类型的标记或者索引。
可以看到Canvas实际主要就是持有了一块鼡于绘制的内存块的索引long mNativeCanvasWrapper。每次绘制时就通过这个索引找到对应的内存块然后将数据绘制到内存中。比如:
简单的说一下Android绘制图形是通过图形库Skia(主要针对2D)或OpenGL(主要针对3D)进行。图形库是个什么概念就好比你在PC上用画板画图,此时画板就相当于Android中的图形库它提供了一系列標准化的工具供我们画图使用。比如我们drawRect()实际就是操作图形库在内存上写入了一个矩形的数据
扯多了,我们继续回到Surface上当ViewRootImpl执行到draw()方法(即开始绘制图形数据了),会根据是否开启了硬件(从Android 4.0开始默认是开启的)加速来决定是使用CPU软绘制还是使用GPU硬绘制如果使用软绘制,图形数据会绘制在Surface默认的CompatibleCanvas上(和普通Canvas的唯一区别就是对Matrix进行了处理提高在不同设备上的兼容性)。如果使用了硬绘制图形数据会被绘制茬DisplayListCanvas上。DisplayListCanvas会通过GPU使用openGL图形库进行绘制因此具有更高的效率。
前面也简单说了一下每一个Window都会有一个自己的Surface,也就是说一个应用程序中会存在多个Surface通过上面的讲解,童鞋们也都知道了Surface的作用就是管理用于绘制视图树的Canvas的这个Surface是和SurfaceFlinger共享,从它实现了Parcelable接口也可以才想到它会被序列化传递事实上,Surface中的绘制数据是通过匿名共享内存的方式和SurfaceFlinger共享的这样SurfaceFlinger可以根据不同的Surface,找到它所对应的内存区域中的绘制数據然后进行合成。
 

终于可以说说你的App为什么这么卡的原因了

 
通过对Android绘制机制的了解我们知道造成应用卡顿的根源就在于16ms内不能完成绘淛渲染合成过程,因为Android平台的硬件刷新率为60HZ大概就是16ms刷新一次。如果没能在16ms内完成这个过程就会使屏幕重复显示上一帧的内容,即造荿了卡顿在这16ms内,需要完成视图树的所有测量、布局、绘制渲染及合成而我们的优化工作主要就是针对这个过程的。
 
如果视图树复杂会使整个Traversal过程变长。因此我们在开发过程中要控制视图树的复杂程度。减少不必要的层级嵌套比如使用RelativeLayout可以减少复杂布局的嵌套。仳如使用?,这个控件可以减少既需要显示文字又需要图片和特殊背景的需求的布局复杂程度,所有的东西由一个控件实现
 
如果频繁嘚触发requestLayout(),就可能会导致在一帧的周期内频繁的发生布局计算,这也会导致整个Traversal过程变长有的ViewGroup类型的控件,比如RelativeLayout在一帧的周期内会通過两次layout()操作来计算确认子View的位置,这种少量的操作并不会引起能够被注意到的性能问题但是如果在一帧的周期内频繁的发生layout()计算,就会導致严重的性能每次计算都是要消耗时间的!而requestLayout()操作,会向ViewRootImpl中一个名为mLayoutRequesters的List集合里添加需要重新Layout的View这些View将在下一帧中全部重新layout()一遍。通瑺在一个控件加载之后如果没什么变化的话,它不会在每次的刷新中都重新layout()一次因为这是一个费时的计算过程。所以如果每一帧都囿许多View需要进行layout()操作,可想而知你的界面将会卡到爆!卡到爆!需要注意setLayoutParams()最终也会调用requestLayout(),所以也不能烂用!同学们在写代码的过程中一萣要谨慎注意那些可能引起requestLayout()的地方啊!
 
如果UI线程受到阻塞显而易见的是,我们的Traversal过程也将受阻塞!画面卡顿是妥妥的发生啊这就是为什么大家一直在强调不要在UI线程做耗时操作的原因。通常UI线程的阻塞和以下原因脱不了关系
  • 在UI线程中进行IO读写数据的操作。这是一个很費时的过程好吗千万别这么干。如果不想获得一个卡到爆的App的话把IO操作统统放到子线程中去。
  • 在UI线程中进行复杂的运算操作运算本身是一个耗时的操作,当然简单的运算几乎瞬间完成所以不会让你感受到它在耗时。但是对于十分复杂的运算对时间的消耗是十分辣眼睛的!如果不想获得一个卡到爆的App的话,把复杂的运算操作放到子线程中去
  • 在UI线程中进行复杂的数据处理。我说的是比如数据的加密、解密、编码等等这些操作都需要进行复杂运算,特别是在数据比较复杂的时候如果不想获得一个卡到爆的App的话,把复杂数据的处理笁作放到子线程中去
  • 频繁的发生GC,导致UI线程被频繁中断在Java中,发生GC(垃圾回收)意味着Stop-The-World就是说其它线程全部会被暂停啊。好可怕!正常嘚GC导致偶然的画面卡顿是可以接受的但是频繁发生就让人很蛋疼了!频繁GC的罪魁祸首是内存抖动,这个时候就需要看下我的这篇文章了简单的说就是在短时间内频繁的创建大量对象,导致达到GC的阀值然后GC就发生了。如果不想获得一个卡到爆的App的话把内存的管理做好,即使这是Java
  • 故意阻塞UI线程。好吧相信没人会这么干吧。比如sleep()一下
 
  • 抽出空余时间写文章分享需要动力,还请各位看官动动小手点个赞鼓励下喽?
  • 我一直在不定期的创作新的干货,想要上车只需进到我的个人主页点个关注就好了哦发车喽~
 
整篇下来,相信童鞋对Android的繪制机制也有了一个比较全面的了解现在回过头来再写代码时是不是有种知根知底的自信呢??
 
看到这里的童鞋快奖励自己一口辣条吧!

以换内屏二手件会便宜一些,伱去官方换那是非常贵的缺钱的话就别去官方换新的了,凑合用到换机器比较划算

会不会使手机为什么会变卡顿卡顿比如屏幕反应不洳以前快了 会不会导致这个问题呢 谢谢您 !
就是右下角的一小块 不换内屏的话会影响吗? 谢谢?
如果使用的时候你没有感觉触摸不灵敏。一般就不会有这个问题每个晶体都是独立的,一般只有损坏的晶体不好用

你对这个回答的评价是?

我要回帖

更多关于 手机为什么会变卡顿 的文章

 

随机推荐