原文链接 Android View的烘托进程

关于安卓开发猿来说,每天都会跟布局打交道,那么从咱们写的一个布局文件,到运行后可视化的视图页面,这么长的时刻内究竟 发生了啥呢?今日咱们就一同来探询这一旅程。

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之中,知道相关于父布局的数值就满足在烘托时运用了,没必要用相对屏幕的绝对数值,而且用相对父布局的坐标数值再加上父布局的坐标,就可以得到在屏幕上的绝对数值,假设需求这样做的话。

Android View的渲染过程

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的烘托,没有硬件加速也可以烘托啊,有了硬件加速烘托会愈加的快一些。

Android 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烘托机制
  • 深化Android烘托机制
  • Android进阶——功能优化之布局烘托原理和底层机制机详解及卡顿根源探求(四)
  • View烘托机制
  • Android屏幕改写机制
  • Android 基于 Choreographer 的烘托机制详解
  • Android图形烘托之Choreographer原理

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