原文链接 Android View的渲染过程

对于安卓开发猿来说,每天都会跟布局打交道,那么从咱们写的一个布局文件,到运行后可视化的视图页面,这么长的工夫内到底 产生了啥呢?明天咱们就一起来探询这一旅程。

View tree的创立过程

布局文件的生成过程

个别状况下,一个布局写好了,如果不是特地简单的布局,那么当把布局文件塞给Activity#setContentView或者一个Dialog或者一个Fragment,之后这个View tree就创立好了。那么setContentView,其实是通过LayoutInflater这个对象来具体的把一个布局文件转化为一个内存中的View tree的。这个对象不算太简单,次要的逻辑就是解析XML文件,把每个TAG,用反射的形式来生成一个View对象,当XML文件解析实现后,一颗View tree就生成完了。

然而须要留神,inflate之后尽管View tree是创立好了,然而这仅仅是以单纯对象数据的模式存在,这时去获取View的一些GUI的相干属性,如大小,地位和渲染状态,是不存在的,或者是不对的。

手动创立

除了用布局文件来生成布局,当然也能够间接用代码来撸,这个就比拟直观了,view tree就是你创立的,而后再把根节点塞给某个窗口,如Activity或者Dialog,那么view tree就创立完事了。

渲染前的筹备工作

View tree生成的最初一步就是把根结点送到ViewRootImpl#setView外面,这里会把view增加到wms之中,并着手开始渲染,接下来就次要看ViewRootImpl这个类了,次要入口办法就是ViewRootImpl#requestLayout,而后是scheduleTraversals(),这里会把申请放入到队列之中,最终执行渲染的是doTraversal,它外面调用的是performTraversals(),所以,咱们须要重点查看ViewRootImpl#performTraversals这个办法,view tree渲染的流程全在这外面。这个办法相当之长,靠近1000行,次要就是三个办法performMeasure,performLayout和performDraw,就是常说的三大步:measure,layout和draw。

渲染之measure

就看performMeasure办法,这个办法很简略,就是调用了根view的measure办法,而后传入widthSpec和heightSpec。measure的目标就是测量view tree的大小,就是说view tree在用户可视化角度所占屏幕大小。要想了解透彻measure,须要了解三个事件,MeasureSpec,View#measure办法和View#onMeasure办法:

了解MeasureSpec

从文档中能够理解到,MeasureSpec是从父布局传给子布局,用以代表父布局对子布局在宽度和高度上的束缚,它有两局部一个是mode,一个是对应的size,打包成一个integer。

  • UNSPECIFIED

    父布局对子布局没有要求,子布局能够设置任意大小,这个 基本上 不常见。

  • EXACTLY

    父布局曾经计算好了一个准确的大小,子布局要严格依照 这个来。

  • AT_MOST

    子布局最大能够达到传过来的这个尺寸。

光看这几个mode,还是不太好了解。因为咱们素日里写布局,在大小(或者说宽和高)这块就三种写法:一个是MATCH_PARENT,也就是要跟父布局一样大;要么是WRAP_CONTENT,也就是说子布局想要刚好适合够显示本人就行了;再者就是写死的如100dp等。须要把measure时的mode与LayoutParams联合分割起来,能力更好的了解measure的过程。

还是得从performMeasure这时动手,这个MeasureSpec是由父节点传给子节点,追根溯源,最原始的必定是传给整个view tree根节点的,也就是调用performMeasure时传入的参数值。

根节点的MeasureSpec

根节点的MeasureSpec是由getRootMeasureSpec得来的,这个办法传入的是窗口的大小,这是由窗口来给出的,以后的窗口必定 是晓得本人的大小的,以及根节点布局中写的大小。从这个办法就能看出后面说的布局中的三种写法对MeasureSpec的影响了:

  • 如果 根节点布局是MATCH_PARENT的,那么 mode就是EXACTLY,大小就是父布局的尺寸,因为根节点的父亲就是窗口,所以就是窗口的大小
  • 如果 根节点布局是WRAP_CONTENT的,那么 mode是AT_MOST,大小仍然会是父布局的尺寸。这个要这样了解,WRAP_CONTENT是想让子布局本人决定本人多大,然而,你的极限 就是父布局的大小了。
  • 其余,其实就是根节点写死了大小的(写布局时是必须 要指定layout_width和layout_height的,即便某些view能够省略一个,也是因为缺省值,而并非不必指定),那么mode会是EXACTLY,大小用根节点指定的值。
    private static int getRootMeasureSpec(int windowSize, int rootDimension) {        int measureSpec;        switch (rootDimension) {        case ViewGroup.LayoutParams.MATCH_PARENT:            // Window can't resize. Force root view to be windowSize.            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);            break;        case ViewGroup.LayoutParams.WRAP_CONTENT:            // Window can resize. Set max size for root view.            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);            break;        default:            // Window wants to be an exact size. Force root view to be that size.            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);            break;        }        return measureSpec;    }

子View的MeasureSpec

MeasureSpec这个货色是自上而下的,从根节点向子View传递。后面看过了根节点的spec生成形式,还有必要再看一下子View在measure过程中是如何生成spec的,以更好的了解整体过程。次要看ViewGroup#getChildMeasureSpec办法就能够了:

    public static int getChildMeasureSpec(int spec, int padding, int childDimension) {        int specMode = MeasureSpec.getMode(spec);        int specSize = MeasureSpec.getSize(spec);        int size = Math.max(0, specSize - padding);        int resultSize = 0;        int resultMode = 0;        switch (specMode) {        // Parent has imposed an exact size on us        case MeasureSpec.EXACTLY:            if (childDimension >= 0) {                resultSize = childDimension;                resultMode = MeasureSpec.EXACTLY;            } else if (childDimension == LayoutParams.MATCH_PARENT) {                // Child wants to be our size. So be it.                resultSize = size;                resultMode = MeasureSpec.EXACTLY;            } else if (childDimension == LayoutParams.WRAP_CONTENT) {                // Child wants to determine its own size. It can't be                // bigger than us.                resultSize = size;                resultMode = MeasureSpec.AT_MOST;            }            break;        // Parent has imposed a maximum size on us        case MeasureSpec.AT_MOST:            if (childDimension >= 0) {                // Child wants a specific size... so be it                resultSize = childDimension;                resultMode = MeasureSpec.EXACTLY;            } else if (childDimension == LayoutParams.MATCH_PARENT) {                // Child wants to be our size, but our size is not fixed.                // Constrain child to not be bigger than us.                resultSize = size;                resultMode = MeasureSpec.AT_MOST;            } else if (childDimension == LayoutParams.WRAP_CONTENT) {                // Child wants to determine its own size. It can't be                // bigger than us.                resultSize = size;                resultMode = MeasureSpec.AT_MOST;            }            break;        // Parent asked to see how big we want to be        case MeasureSpec.UNSPECIFIED:            if (childDimension >= 0) {                // Child wants a specific size... let them have it                resultSize = childDimension;                resultMode = MeasureSpec.EXACTLY;            } else if (childDimension == LayoutParams.MATCH_PARENT) {                // Child wants to be our size... find out how big it should                // be                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;                resultMode = MeasureSpec.UNSPECIFIED;            } else if (childDimension == LayoutParams.WRAP_CONTENT) {                // Child wants to determine its own size.... find out how                // big it should be                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;                resultMode = MeasureSpec.UNSPECIFIED;            }            break;        }        //noinspection ResourceType        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);    }

单纯从spec角度来了解,与下面的是一样的,基本上WRAP_CONTENT会是AT_MOST,而其余都是EXACTLY。

前面会再具体讨论一下,父布局与子View的相互影响。

View#measure和View#onMeasure

performMeasure比较简单,只是调用根节点的measure办法,而后把计算出来的根节点的MeasureSpec传进去,就完事了,所以 重点要View#measure办法。这里须要留神的是整个View的设计体系外面一些次要的逻辑流程是不容许子类override的,可定制的局部作被动式的办法嵌入在次要逻辑流程中,如measure是不能被override的,它会调用能够被子类override的onMeasure。onMeasure是每个View必须实现的办法,用传入的父布局的束缚来计算出自已的大小。

为了优化measure流程,还有一个cache机制,用从父布局传入的MeasureSpec作为key,从onMeasure得出的后果 作为value,保留在cache中,当前面再次调用measure时,如果MeasureSpec未发生变化,那么就间接从cache中取出后果,如果 有变动 那么再调用onMeasure去计算一次。光看View#measure和onMeasure这两个办法也没啥啊,或者说常见的view或者咱们本人定义的view的onMeasure办法也没啥啊,都不算太简单,有同学就会问,这里为啥这么吃力 非要搞出一个cache呢?这个也好了解,要明确任何一个view不光是你本人,还波及到所有你的子view啊,如果你只是一个未端的view(叶子),那当然 无所谓了,但如果是一个ViewGroup,上面有很多个子view,那么 如果能少调用一次onMeasure,还是能节俭不少CPU资源的。

ViewGroup的onMeasure

每个View的自身的onMeasure并不简单,只须要关注好自身的尺寸就好了。

简单的在于ViewGroup的onMeasure,简略来了解也并不简单,它除了须要测量本人的宽与高之外,还须要一一遍历子view以measure子view。如果ViewGroup本身是EACTLY的,那么onMeasure过程就会简略不少,因为它本身的宽与高是确定的,只须要挨个measure子View就可了,而且子View并不影响它自身。当然,要把padding和margin思考进来。

最为简单的就是AT_MOST,ViewGroup本身的宽与高是由其所有子View决定的,这才是最简单的,也是各个ViewGroup子类布局器须要重点解决的,而且过程各不相同,因为每个布局器的特点不一样,所以过程并不相同,上面来各自讨论一下。

几种常见的ViewGroup的measure逻辑

下来来看一下一些十分常见的ViewGroup是如何measure的:

LinearLayout

它的方向只有两个,能够只剖析一个方向,另外一个方向是差不多的,咱们就看看measureVertical。

第1种状况,也就是height mode是EXACTLY的时候,这个时候LinearLayout布局自身的高度是已知的,挨个遍历子view而后measure一下就能够。

第2种状况,比较复杂的状况,是AT_MOST时,这其实也还好,实践上高度就是所有子view的高度之和。

对于LinearLayout,最为简单的状况是解决weight,这须要很多简单解决,要把残余所有的空间按weight来调配,具体比较复杂,有趣味的能够具体去看源码。这也阐明了,为何在线性布局中应用weight会影响性能,代码中就能够看出当有weight要解决的时候,至多多遍历一遍子view以进行相干的计算。

尽管方向是VERTICAL时,重点只解决垂直方向,然而width也是须要计算的,但width的解决就要简略得多,如果其是EXACTLY的,那么就已知了;如果是AT_MOST的,就要找子view中width的最大值。

FrameLayout

FrameLayout其实是最简略的一个布局管理器,因为它对子view是没有束缚的,无论程度方向还是垂直方向,对子view都是没有束缚,所以它的measure过程最简略。

如果是EXACTLY的,它自身的高度与宽度是确定的,那么就遍历子view,measure一下就能够了,最初再把margin和padding加一下就完事了。

如果是AT_MOST的,那么也不难,遍历子View并measure,而后取子view中最大宽为它的宽度,取最大的高为其高度,再加上margin和padding,基本上就做完了。

因为,FrameLayout的measure过程最为简略,因而零碎里很多中央默认用的就是FrameLayout,比方窗口里的root view。

RelativeLayout

这个是最为简单的,从设计的目标来看,RelativeLayout要解决的问题也是提供了长与宽两个维度来束缚子view。

总体过的过程就是要别离从vertical方向和horizontal方向,来进行两遍的measure,同时还要计算具体的坐标,实际上RelativeLayout的measure过程是把measure和layout一起做了。

自定义View如何实现onMeasure

如果是一个具体的View,那就相当简略了,默认的实现就能够了。

如果是ViewGroup会绝对简单一些,取决于如何从程度和垂直方向上束缚子view,而后进行遍历,并把束缚思考进去。能够参考LinearLayout和RelativeLayout的onMeasure实现。

渲染之layout

measure是确定控件的尺寸,下一步就是layout,也就是对控件进行排列。

首先,须要了解古代GUI窗口的坐标零碎,假如屏幕高为height,宽为width,那么屏幕左上角为坐标原点(0,0),右下角为(width, height),屏幕从上向下为Y轴方向,从左向右则是X轴方向。安卓当中,也是如此。每一个控件都是一个矩形区域,为了能晓得如何渲染每一块矩形(每 一个控件)就须要晓得它的坐标,在前一步measure中,能晓得它的宽与高,如果再能确定它的起始坐标左上角,那么它在整个屏幕中的地位就能够确定了。

对于Android来说,view的渲染的第二步骤就是layout,其目标就是要确定好它的坐标,每一个View都有四个变量mLeft, mTop,mRight和mBottom,(mLeft, mTop)是它的左上角,(mRight, mBottom)是它的右下角,很显著width=mRight-mLeft,而height=mBottom-mTop。这些数值是绝对于父布局来说的,每个View都是存在于view tree之中,晓得绝对于父布局的数值就足够在渲染时应用了,没必要用绝对屏幕的相对数值,而且用绝对父布局的坐标数值再加上父布局的坐标,就能够失去在屏幕上的相对数值,如果须要这样做的话。

layout过程仍然是从根节点开始的,所以仍要从ViewRootImpl#performLayout作为终点来理顺layout的逻辑。performLayout的参数是一个LayoutParam,以及一个windowWidth和desiredWindowHeight,调用performLayout是在performTraversal当中,在做完performMeasure时,传入的参数其实就是窗口window的宽与高(因为毕竟是根节点嘛)。performLayout中会从根节点mView开开对整个view tree进行layout,其实就是调用mView.layout,传入的是0, 0和view的通过measure后宽与高。

单个View的layout办法实现较简略,把传入的参数保留到mLeft,mTop,mRight和mBottom变量,再调用onLayout就完事了,这个很好了解,因为子view是由父布局确定好的地位,只有在measure过程把本人须要的大小通知父布局后,父布局会依据LayoutParam做安顿,传给子view的就是计算过后的后果,每个子view记录一下后果就能够了,不须要做啥额定的事件。

ViewGroup稍简单,因为它要解决其子view,并且要依据其设计的特点对子view进行束缚排列。还是能够看看常见的三个ViewGroup是如何做layout的。

LinearLayout

仍然是两个方向,因为LinearLayout的目标就是在某一个方向上对子view进行束缚。看layoutVertical就能够了,程度方向上逻辑是一样的。

遍历一次子View即可,从父布局的left, top起始,思考子view的height 以及高低的padding和margin,顺次排列就能够了。须要留神的是,对于left的解决,实践上子view的left就应该等于父布局,因为这毕竟是vertical的,程度上是没有束缚的,然而也要思考Gravity,当然也要把padding和margin思考进来。最初通过setChildFrame把排列好的坐标设置给子view。

总体来看,线性布局的layout过程比其measure过程要简略不少。

FrameLayout

FrameLayout对子view的排列其实是没有束缚的,所以layout过程也不简单,遍历子view,子view的left和top初始均为父布局,根据其Gravity来做一下排布即可,比方如果Gravity是right,那么子view就要从父布局的右侧开始计算,childRight=parentRight-margin-padding,childLeft=childRight-childWidth,以次类推,还是比拟好了解的。

RelativeLayout

后面提到过RelativeLayout是在measure的时候就把坐标都计算好了,它的layout就是把坐标设置给子view,其余啥也没有。

自定义View如何实现onLayout

如果是自定义View的话,不须要做什么。

如果是自定义的ViewGroup的话,要看设计的目标,是如何排列子view的。

总之,layout过程相较measure过程还是比拟好了解的,束缚规定越简单的view,其measure过程越简单,但layout过程却不简单。

渲染之draw

draw是整个渲染过程的外围也是最简单的一步,后面的measure和layout只能算作筹备,draw才会真正进行绘制。

draw的整个逻辑流程

与measure和layout的过程十分不一样,尽管在performTraversals中也会调用performDraw,也就是说看似draw流程的终点仍是ViewRootImpl#performDraw,但查看一下这个办法的实现就能够发现,这外面其实并没有调用到View#draw,就是说它其实也是做一些筹备工作,整个View tree的draw触发,并不在这里。

从performDraw中并没有做间接与draw相干的事件,它会调用另外一个办法draw()来做此事件,在draw办法中,它会先计算须要渲染的区域(dirty区域),而后再针对 此区域做渲染,失常状况下会走硬件加速形式去渲染,这部分比较复杂,它间接与一个叫做ThreadedRenderer打交道,稍后再作剖析。

因为各种起因,如果硬件加速未没有胜利,那么会走到软件渲染,这部分逻辑绝对清晰一些,能够先从这里看起,会间接调用到drawSoftware(),这个办法有助于咱们看清楚渲染的流程。这个办法外面会创立一个Canvas对象,是由ViewRootImpl持有的一个Surface对象中创立进去的,并调用view tree根节点的mView.draw(canvas),由此便把流程转移到了view tree下面。

view tree的draw的过程

ViewRootImpl是间接调用根节点的draw办法,那么这里便是整个view tree的入口。可先从View#draw(canvas)办法看起。次要分为四步:1)画背景drawBackground;2)画本人的内容通过onDraw来委派,具体的内容是在onDraw外面做的;3)画子view,通过dispatchDraw办法;4)画其余的货色,如scroll bar或者focus highlight等。能够重点关注一下这些操作的程序,先画背景,而后画本人,而后画子view,最初画scroll bar和focus之类的货色。

重点来看看dispatchDraw办法,因为其余几个都绝对十分好了解,这个办法次要要靠ViewGroup来实现,因为在View外面它是空的,节点本人只须要管本人就能够了,只有父节点才须要关注如何画子View。ViewGroup#dispatchDraw这个办法做一些筹备工作,如把padding思考进来并进行clip,后会遍历子View,针对 每个子view调用drawChild办法,这实际上就 是调用回了View#draw(canvas,parent,drawingTime)办法,留神这个办法是package scope的,也就是说只能供view框架外部调用。这个办法并没有做具体的渲染工作(因为每个View的具体渲染都是在onDraw外面做的),这个办法外面做了大量与动画相干的各种变换。

Canvas对象是从哪里来的

View的渲染过程其实大都是GUI框架外部的逻辑流程管制,真正波及graphics方面的具体的图形如何画进去,其实都是由Canvas对象来做的,比方如何画点,如何画线,如何画文字,如何画图片等等。一个Canvas对象从ViewRootImpl传给View tree,就在view tree中一层一层的传递,每个view都把其想要展现的内容渲染到Canvas对象中去。

那么,这个Canvas对象又是从何而来的呢?从view tree的一些办法中能够看到,都是从里面传进来的,view tree的各个办法(draw, dipsatchDraw和drawChild)都只接管Canvas对象,但并不创立它。

从下面的逻辑能够看到Canvas对象有二个起源:一是在ViewRootImpl中创立的,当走软件渲染时,会用Surface创立出一个Canvas对象,而后传给view tree。从ViewRootImpl的代码来看,它自身就会持有一个Surface对象,大略的逻辑就是每一个Window对象内,都会有一个用来渲染的Surface;

另外一个起源就是走硬件加速时,会由hwui创立出Canvas对象。

draw过程的触发逻辑

从下面的探讨中能够看出draw的触发逻辑有两条路:

一是,没有启用硬件加速时,走的软件draw流程,也是一条比拟好了解的简略流程:performTraversal->performDraw->draw->drawSoftware->View#draw。

二是,启用了硬件加速时,走的是performTraversal->performDraw->draw->ThreadedRenderer#draw,到这里就走进了硬件加速相干的逻辑了。

硬件加速

硬件加速是从Android 4.0开始反对的,在此之前都是走的软件渲染,也就是从ViewRoot(4.0版本以前是叫ViewRoot,起初才是ViewRootImpl)中持有的Surface间接创立Canvas,而后传给view tree去做具体的渲染,与后面提到的drawSoftware过程相似。

硬件加速则要简单得多,多了好多货色,它又搞出了一套渲染架构,但这套货色是间接与GPU分割,有点相似于OpenGL,把view tree的渲染转换成为一系列命令,间接传给GPU,软件渲染则是须要CPU把所有的运算都做了,最终生成graphic buffer送给屏幕(当然也是GPU)。

这一坨货色中最为外围就是RenderNode和RecordingCanvas。其中RenderNode是纯新的货色,它是为了构建 一个render tree(相似于view tree),用以构建简单的渲染逻辑关系。RecordingCanvas是Canvas的一个子类,它是专门用于硬件加速渲染的,但又为了兼容老的Canvas(软件渲染),为啥叫recording呢?因为硬件加速形式渲染,对于view tree的draw过程来说就是记录一系列的操作,这其实就是给GPU的指令,渲染的最初一步就是把整个render tree丢给GPU,就完了。

后面说的两个是数据结构,还不够,还有HardwareRenderer和ThreadedRenderer,这两个用来建设和治理render tree的,也就是说它们外部治理着一组由RenderNode组成的render tree,并且做一些上下文环境的初始化与清理资源的工作。相似于OpenGL中GLSurfaceView的RenderThread做的事件。

硬件加速与原框架的切入点都是RenderNode和RecordingCanvas,View类中多了一个RenderNode成员,当draw的时候,从RenderNode中失去RecordingCanvas,其余操作都与原来统一,都是调用Canvas的办法进行graphics的绘制,这样整体渲染流程就走入到了硬件加速外面。

Choreographer与vsync

尽管在Android 4.0版本退出了硬件加速的反对,但这还是不够,因为它只是相当于具体的渲染工夫可能快了一些,举例来说,可能是一般火车与高铁之间的差别,尽管的确行程所花工夫变短了,然而对于整体的效率来说晋升并不大。对于整体GUI的晦涩度,响应度,特地是动画这一块的流程水平与其余平台(如水果)差距仍是微小的。一个最重要的起因就在于,GUI整体的渲染流程是短少协同的,仍是按需式渲染:应用层布局加载完了要渲染了,或者ViewRootImpl发现dirty了,须要重绘了,或者有用户事件了须要响应了,触发整体渲染流程,更新graphic buffer,屏幕刷新了。

这一过程其实也没有啥大问题,对于惯例的UI显示,没有问题,我没有更新,没有变动 ,当然 不须要重绘了,如果有更新有变动时再按需从新渲染,这显然 没有什么问题。最大的问题在于动画,动画是要求间断不停的重绘,如果仅靠客户这一端(相较于graphic buffer和屏幕这一端来说)来触发,显然FPS(帧率)是不够的,由此造成晦涩度必定不够好。

于是在Android 4.1 (Jelly Bean)中就引入了Choreographer以及vsync机制,来解决此问题,它们两个并不全完是一回事,Choreographer是纯软件的,vsync则是更为简单的更底层的机制,有没有vsync,Choreographer都能很好的工作,只不过有了vsync会更好,就好比硬件加速之于View的渲染,没有硬件加速也能够渲染啊,有了硬件加速渲染会更加的快一些。

Choreographer

它的英文本意是歌舞的编舞者,有点相似于导演,但歌舞个别工夫更短,所以对编舞者要求更高,须要在短时间内把精髓全副展示进去。它的目标就是要协调整个View的渲染过程,对输出事件响应,动画和渲染进行工夫上的把控。文档原文是说:Coordinates the timing of animations, input and drawing.,精髓就在于timing这个词上。

但其实,这个类自身并不是很简单,相较于其余frameworks层的货色来说它算简略的了,它就是负责定时回调,依照肯定的FPS来给你回调,简略来说它就是做了这么一件事件。它公开的接口也特地少,就是postFrameCallback和removeFrameCallback,而FrameCallback也是一个非常简单的接口doFrame(long frameTimeNanos),外面的参数是以后帧开始渲染的工夫序列。

所以,它的工作就是在计时,或者叫把控工夫,到了每一帧该渲染的时候了,它会通知你。有了它,那么GUI的渲染将不再是按需重绘了,而是有节奏的,能够以固定FPS定时刷新。ViewRootImpl那头也须要做调整,每当有被动重绘时(view tree有变动,用户有输出事件等),也并不是说立马就去做draw,而是往Choreographer里post一个FrameCallback,在外面做具体的draw。

vsync(Vertical Synchronization)

垂直同步,是另外一套更为底层的机制,简略来了解就是由屏幕显示零碎间接向软件层派发定时的脉冲信号,用以进步整体的渲染晦涩水平,屏幕刷新,graphic buffer和window GUI(view tree)三者在这个脉冲信号下,做到同步。

vsync是通过对Choreographer来发挥作用的。Choreographer有两套timing机制,一是靠它本人实现的一套,另外就是间接传导vsync的信号。通过DisplayEventReceiver(这个类对于App层是齐全不可见的被hide了)就能够接管到vsync的信号了,调用其sheduleVsync来通知vsync说我想接管下一次同步的信号,而后在重载onVsync办法以接管信号,就可能与vsync零碎连接起来了。

渲染性能优化

这是一个很大的话题

放弃简略

最最重要的准则就是要放弃简略,比方,UI页面尽可能的简洁,view tree的层级要尽可能的少,能用色彩就别用背景图片,能merge就merge。

动画也要尽可能的简略,并且应用规范的ValueAnimator接口,而不要简略粗犷的去批改LayoutParams(如height和width)。

缩小重绘

这个要多用零碎中开发者模式外面的重绘调试工具来做优化,尽可能的缩小重绘。

专项定制

有些时候,对于一些非凡需要的view要进行定制优化。举个例子,比方一个巨简单的页面(如某宝的首页),中有一个用于显示倒计时的view,实现起来并不简单,一个TextView就搞定了,一个Timer来倒计时,一直的刷新数字 就能够了。然而,这通常会导致整个页面都跟着在重绘。因为数字在变动,会导致TextView的大小在变动,进而导致整个View tree都在一直的跟着重绘。

像这种case,如果遇到了,就须要自定义一个专门用于此的View,并针对数字一直刷新做专门的优化,以不让其影响整个view tree。

不要在意这个例子的真实性,要晓得,当某个View演变成了整个页面的瓶颈的时候,就须要专门针对 其进行非凡定制以优化整体页面的渲染性能。

更多的技巧能够参考这篇文章和前面的参考资料。

参考资料

列举一下对于此话题的比拟好的其余资源

  • Android视图绘制流程齐全解析,带你一步步深刻理解View
  • [Android性能优化第(四)篇---Android渲染机制
    ](https://www.jianshu.com/p/9ac245657127)
  • 深刻Android渲染机制
  • Android进阶——性能优化之布局渲染原理和底层机制机详解及卡顿本源探索(四)
  • View渲染机制
  • Android屏幕刷新机制
  • Android 基于 Choreographer 的渲染机制详解
  • Android图形渲染之Choreographer原理

原创不易,打赏点赞在看珍藏分享 总要有一个吧