Android View 知识体系

藏宝库编辑 2024-9-6 22:23:40 73 0 来自 中国
工作原理

Android 中通过 Window 作为屏幕的抽象,而 Window 的详细实现类是 PhoneWindow 。通过 WindowManager 和 WindowManagerService 配合工作,来管理屏幕的表现内容。
WindowManager 内部真正调用的是 WindowManagerGobal 方法,添加视图的是 addView 方法。在WindowManagerGobal 中,终极是通过 ViewRootImpl 来将 View 和 结构参数添加到屏幕上。现实上,真正管理 View 树的是 ViewRootImpl 。
ViewRootImpl 通过调用 IWindowSession 接口界说的方法,通过 Binder 通信机制终极调用到 WMS 中的 Session 的 addToDisplay 方法。
在 WMS 中会为 Window 分配一个 Surface,并确定窗口的表现序次;Surface负责表现界面,Window 自己并不具有绘制本事;WMS 会将 Surface 交给 SurfaceFlinger 来处置处罚,SurfaceFlinger 会将这些 Surface混淆,并绘制到屏幕上。
WMS 中实行了 ViewRootImpl 中添加 Window 的哀求,紧张做了四件事故:

  • 对要添加的 Window 举行查抄,如果不符合添加条件,就不会实行下面的操纵
  • WindowToken 相干处置处罚,好比有的窗口范例必要提供 WindowToken,没有提供就会终止添加逻辑;有的窗口必要由 WMS 隐式创建 WindowToken。
  • WindowState 的创建和相干处置处罚,将 WindowToken 和 WindowState 相干联。
  • 创建和设置 DisplayContent,完成窗口添加到体系前的准备工作。
View 构建视图树

在 Activity 的 setContentView 方法中,调用了 PhoneWindow 的 setContentView 方法,此中又颠末一系列调用,把结构文件剖析成 View ,并塞到 DecorView 的一个 id 为 R.id.content 的 mContentView 中。
DecorView 自己是一个 FrameLayout,它还承载了 StatusBar 和 NavigationBar。
随后,调用 handleResumeActivity 来叫醒 Activity 。在这个方法中,会通过 WindowManager 的 addView 方法,通过 WindowManager 来添加 DecorView,此中就用到 ViewRootImpl,ViewRootImpl 构建视图树,并负责 View 树的丈量、结构、绘制,以及通过 Choreographer 来控制 View 的革新。
末了,调用 ViewRoot 的 setView 方法将 DecorView 赋值给 viewRoot 并调用 updateViewLayout 方法。
updateViewLayout

在这个方法中, 会调用一个 scheduleTraversals 方法,scheduleTraversals 方法内部通过一个Choreographer 对象的 postCallback 实行一个 Runnable ,在这个 Runnable 中,调用 doTraversal 方法。
doTraversal 中又调用了 performTraversals, performTraversals 方法使得 ViewTree 开始 View 的工作流程:

  • relayoutWindow,内部调用 IWindowSession 的 relayout 方法来更新 Window ,在这里会把 Java 层的 Surface 与 Native 层的 Surface 关联起来;
  • performMeasure,内部调用 View 的 measure;
  • performLayout,内部调用 View 的 layout;
  • performDraw,内部调用 View 的 draw如许就完成了对 Window 的更新。
ViewRootImpl 与 WMS 交互

从上面的流程中,ViewRootImpl 的 setView 方法现实上通过调用到了 WMS 的 Session 的 addToDisplay 方法来关照 WMS 更新 View 的,WMS 会为每一个 Window 关联一个 WindowState。除此之外,ViewRootImpl 的 setView 还做了一件紧张的事就是注册 InputEventReceiver,这和 View 事故分发有关。
在 WindowState 中,会创建 SurfaceSession,它会在 Native 层构造一个 SurfaceComposerClient 对象,它是应用步调与 SurfaceFlinger 沟通的桥梁。
总结

View 的最底层是一个 Window 容器, Window 承载 View 。
Window 添加过程中,通过 WindowManager 中的 ViewRootImpl 的 setView 关照 WMS 去添加 Window 。
ViewRootImpl 通过 Binder 机制调用 WMS 的 Session 的 addToDisplay 方法,来实现发送哀求的。
在 WMS 吸取到添加 Window 的哀求时,会为 Window 分配一个 Surface ,而且对 Window 举行处置处罚,关联 WindowState。
WMS 将 Surface 交给 SurfaceFlinger 处置处罚,渲染视图。而 WindowState 中同时会创建 SurfaceSession ,与 SurfaceFlinger 举行通信。
View 的交互,同时着实 ViewRootImpl 的 setView 时举行注册的,注册了一个 InputEventReceiver 吸取事故。
绘制流程

ViewRootImpl终极的 performTraversals 方法依次调用了 performMeasure、performLayout、performDraw。
Measure

第一步是获取 DecorView 的宽高的 MeasureSpec 然后实行 performMeasure 流程。
performMeasure 中,由于 View 树结构,通过递归的方式,层层调用子 View 的 measure 方法。
// 伪代码fun measure(args) {        onMeasure(args)}fun onMeasure(args) {        val size = getDefaultSize() // 获取宽高        setMeasureDimension(width, height) // 设置宽高        if (this is ViewGroup) { // 如果是 ViewGroup 遍历子 View                 chidren.foreach { view ->                        view.measure() // 调用子 View 的 measure                 }        }}Layout

performLayout 方法,会先调用 DecorView 的 layout 方法,然后递归调用子 View ,在这个过程会决定 View 的四个极点的坐标和现实的 View 的宽/高。
如果 View 巨细发生厘革,则会回调 onSizeChanged 方法。
如果 View 发生厘革,则会回调 onLayout 方法。

  • onLayout 方法在 View 中是空实现。
  • 在 ViewGroup 中,会递归遍历调用子 View 的 layout 法。
Draw

末了的是 performDraw 方法,内里会调用 drawSoftware 方法,这个方法必要先通过 mSurface#lockCanvas 属性 获取一个 Canvas 对象,作为参数传给 DecorView 的 draw 方法。
这个方法调用的是 View 的 draw 方法,先绘制 View 配景,然后绘制 View 的内容。
如果有子 View 则会调用子 View 的 draw 方法,层层递归调用,终极完成绘制。
完成这三步之后,会在 ActivityThread 的 handleResumeActivity 末了调用 Activity 的 makeVisible 方法,这个方法就是将 DecorView 设置为可见状态。
屏幕革新机制

在一个典型的表现体系中,一样寻常包括CPU、GPU、Display三个部门, CPU负责盘算帧数据,把盘算好的数据交给GPU,GPU会对图形数据举行渲染,渲染好后放到buffer(图像缓冲区)里存起来,然后Display(屏幕或表现器)负责把buffer里的数据出现到屏幕上。如下图:
根本概念

屏幕革新率

一秒内屏幕革新的次数(一秒内表现了多少帧的图像),单元 Hz(赫兹),如常见的 60 Hz。革新频率取决于硬件的固定参数(不会变的)。
逐行扫描

表现器并不是一次性将画面表现到屏幕上,而是从左到右边,从上到下逐行扫描,次序表现整屏的一个个像素点,不过这一过程快到人眼无法察觉到厘革。以 60 Hz 革新率的屏幕为例,这一过程即 1000 / 60 ≈ 16ms。
帧率

表示 GPU 在一秒内绘制操纵的帧数,单元 fps。比方在电影界采取 24 帧的速率富足使画面运行的非常流通。而 Android 体系则采取更加流程的 60 fps,即每秒钟GPU最多绘制 60 帧画面。帧率是动态厘革的,比方当画面静止时,GPU 是没有绘制操纵的,屏幕革新的照旧buffer中的数据,即GPU末了操纵的帧数据。
画面扯破

一个屏幕内的数据来自2个差别的帧,画面会出现扯破感,如下图:
双缓存

画面扯破的缘故原由

屏幕革新频是固定的,好比每 16.6ms 从 buffer 取数据表现完一帧,理想环境下帧率和革新频率保持一致,即每绘制完成一帧,表现器表现一帧。但是 CPU/GPU 写数据是不可控的,以是会出现 buffer 里有些数据根本没表现出来就被重写了,即 buffer 里的数据大概是来自差别的帧的, 当屏幕革新时,此时它并不知道 buffer 的状态,因此从 buffer 抓取的帧并不是完备的一帧画面,即出现画面扯破。
简单说就是 Display 在表现的过程中,buffer 内数据被 CPU/GPU 修改,导致画面扯破。
双缓存

由于图像绘制和屏幕读取 利用的是同个buffer,以是屏幕革新时大概读取到的是不完备的一帧画面。
双缓存,让绘制和表现器拥有各自的buffer:GPU 始终将完成的一帧图像数据写入到 Back Buffer,而表现器利用 Frame Buffer,当屏幕革新时,Frame Buffer 并不会发生厘革,当Back buffer准备就绪后,它们才举行互换。如下图:
VSync

那么什么时间举行两个buffer的互换呢?
如果是 Back buffer 准备完成一帧数据以后就举行,那么假云云时屏幕还没有完备表现上一帧内容的话,肯定是会出题目的。看来只能是比及屏幕处置处罚完一帧数据后,才可以实行这一操纵了。
当扫描完一个屏幕后,装备必要重新回到第一行以进入下一次的循环,此时有一段时间安定,称为VerticalBlanking Interval(VBI) 。这个时间点就是我们举行缓冲区互换的最佳时间。由于此时屏幕没有在革新,也就克制了互换过程中出现屏幕扯破的状况。
**VSync **(垂直同步)是 VerticalSynchronization 的简写,它利用 VBI 时期出现的 vertical sync pulse(垂直同步脉冲)来包管双缓冲在最佳时间点才举行互换。别的,互换是指各自的内存地点,可以以为该操纵是刹时完成。
在Android4.1之前,屏幕革新也遵循 上面先容的 双缓存+VSync 机制。
以时间的次序来看下将会发生的过程:

  • Display 表现第 0 帧数据,此时 CPU/GPU 在渲染第 1 帧画面,且需在 Display 表现下一帧前完成。
  • 由于渲染及时,Display 在第 0 帧表现完成后,也就是第 1 个 VSync 后,缓存举行互换,然后正常表现第 1 帧。
  • 接着第 2 帧开始处置处罚,是直到第 2 个 VSync 快来前才开始处置处罚的。
  • 第 2 个 VSync 来时,由于第 2 帧数据还没有准备就绪,缓存没有互换,表现的照旧第 1 帧。这种环境被Android 开辟组定名为 “Jank”,即发生了丢帧
  • 当第 2 帧数据准备完成后,它并不会立即被表现,而是要等待下一个 VSync 举行缓存互换再表现。
由于第 2 帧渲染不及时,导致造成了第 1 帧多展示了一个 VSync 周期,丢失了正常应该展示的第 2 帧。
双缓存的buffer 互换是在 Vsync 信号到来时举行。互换后屏幕会取Frame buffer内的新数据,而现实 此时的Back buffer 就可以供 GPU 准备下一帧数据了。 如果 Vsync 到来时 CPU/GPU 就开始操纵的话,是有完备的16.6ms的,如许应该会根本克制 jank 的出现了(除非CPU/GPU盘算凌驾了16.6ms)。那怎样让 CPU/GPU盘算在 Vsync 到来时举行呢?
为了优化表现性能,在 Android 4.1 体系中对 Android Display 体系举行了重构,实现了Project Butter(黄油工程):体系在收到 VSync 脉冲后,将立即开始下一帧的渲染。即一旦收到 VSync 关照(16ms触发一次),CPU/GPU 立即开始盘算然后把数据写入buffer。如下图:
CPU/GPU 根据 VSYNC 信号到来时,同步处置处罚数据,可以让 CPU/GPU 有完备的 16ms 时间来处置处罚数据,淘汰了 jank。
题又来了,如果界面比力复杂,CPU/GPU的处置处罚时间较长 凌驾了16.6ms呢?如下图:
6.png

  • 在第一次 VSync 信号到来时,由于 GPU 还在处置处罚 B 帧,缓存没能互换,导致 A 帧被重复表现。
  • 而 B 完成后,又由于缺乏 VSync pulse 信号,它只能等待下一次到临。于是在这一过程中,有一大段时间是被浪费的。
  • 当下一个 VSync 出现时,CPU/GPU 立即实行操纵(A帧),且缓存互换,相应的表现屏对应的就是B。这时看起来就是正常的。只不过由于实行时间仍然凌驾 16ms ,导致下一次应该实行的缓冲区互换又被推迟了——云云循环反复,便出现了越来越多的 “Jank”。
为什么 CPU 不能在第二个 16ms 处置处罚绘制工作呢?
缘故原由是只有两个 buffer,Back buffer正在被GPU用来处置处罚B帧的数据, Frame buffer的内容用于Display的表现,如许两个buffer都被占用,CPU 则无法准备下一帧的数据。 那么,如果再提供一个buffer,CPU、GPU 和表现装备都能利用各自的buffer工作,互不影响。
三缓存机制

三缓存就是在双缓冲机制根本上增长了一个 Graphic Buffer 缓冲区,如允许以最大限度的利用空闲时间,带来的弊端是多利用的一个 Graphic Buffer 所占用的内存。
7.png

  • 第一个 Jank,是不可克制的。但是在第二个 16ms 时间段,CPU/GPU 利用 第三个 Buffer 完成C帧的盘算,固然照旧会多表现一次 A 帧,但后续表现就比力顺畅了,有效克制 Jank 的进一步加剧。
  • 注意在第 3 段中,A 帧的盘算已完成,但是在第 4 个 VSync 来的时间才表现,如果是双缓冲,那在第三个VSync 到来时就可以表现了。
三缓冲有效利用了等待 VSync 的时间,淘汰了 jank ,但是带来了延长。
Choreographer

在 ViewRootImpl 去调用 updateViewLayout 方法真正实行 绘制流程前,提到了一个 Choreographer 对象。Choreographer 是用来举行渲染的类。
在屏幕革新原理中,我们知道,当 VSync 信号到来时,CPU/GPU 立即开始盘算数据并存入 buffer 中,那么这个逻辑的现实实现,就是 Choreographer 。
它的作用是:

  • 收到VSync信号 才开始绘制,包管绘制拥有完备的16.6ms,克制绘制的随机性。
  • 和谐动画、输入和画图的计时。
  • 应用层不会直接利用 Choreographer,而是利用更高级的API,比方动画和View绘制相干的ValueAnimator.start()、View.invalidate() 等。
  • 业界一样寻常通过 Choreographer 来监控应用的帧率。
在 Android 源码中,关于 Choreographer ,是从 ViewRootImpl 的 scheduleTraversals 方法中开始的。当我们利用 ValueAnimator.start()、View.invalidate() 时,末了也是走到 ViewRootImpl 的 scheduleTraversals 方法。即全部 UI 的厘革都是走到 ViewRootImpl 的 scheduleTraversals() 方法。
    void scheduleTraversals() {        if (!mTraversalScheduled) { // 包管同时间多次更改只会革新一次            mTraversalScheduled = true;            // 添加消息屏蔽(异步消息插队并优先实行),屏蔽同步消息,包管VSync到来立即实行绘制            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();            mChoreographer.postCallback(                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);            notifyRendererOfFramePending();            pokeDrawLockIfNeeded();        }    }在这个方法中,终极调用 mChoreographer.postCallback() 方法,实行一个 Runnable,这个 Runnable 会在下一个 VSync 到来时,实行 doTraversal()
doTraversal() 内部会调用performTraversals 方法,举行渲染流程。
而这个 mChoreographer 对象,是在 ViewRootImpl 对象构造方法中举行初始化的:
public ViewRootImpl(Context context, Display display) {    // ...    mChoreographer = Choreographer.getInstance();    // ...}看一下 Choreographer 类:
public final class Choreographer {        private static final ThreadLocal<Choreographer> sThreadInstance =            new ThreadLocal<Choreographer>() {        @Override        protected Choreographer initialValue() {            Looper looper = Looper.myLooper();            if (looper == null) {                throw new IllegalStateException("The current thread must have a looper!");            }            Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP);            if (looper == Looper.getMainLooper()) {                mMainInstance = choreographer;            }            return choreographer;        }    };    public static Choreographer getInstance() {        return sThreadInstance.get();    }}Choreographer 和 Looper 一样,是线程单例的。
它的 postCallaback 方法,第一个参数是 int 摆列,包括:
      //输入事故,起首实行    public static final int CALLBACK_INPUT = 0;    //动画,第二实行    public static final int CALLBACK_ANIMATION = 1;    //插入更新的动画,第三实行    public static final int CALLBACK_INSETS_ANIMATION = 2;    //绘制,第四实行    public static final int CALLBACK_TRAVERSAL = 3;    //提交,末了实行,    public static final int CALLBACK_COMMIT = 4;内部调用了 postCallbackDelayedInternal :
    private void postCallbackDelayedInternal(int callbackType,            Object action, Object token, long delayMillis) {                // ...        synchronized (mLock) {            // 当前时间            final long now = SystemClock.uptimeMillis();            // 加上延长时间            final long dueTime = now + delayMillis;            //取对应范例的CallbackQueue添加使命            mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);            if (dueTime <= now) {                //立即实行                scheduleFrameLocked(now);            } else {                //延长运行,终极也会走到scheduleFrameLocked()                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);                msg.arg1 = callbackType;                msg.setAsynchronous(true);                mHandler.sendMessageAtTime(msg, dueTime);            }        }    }内部末了实行的是 scheduleFrameLocked 方法。
通过 postCallback 方法名称和与 Looper 一样的结构我们就能推测到,这里用到了 Handler 机制,来处置处罚 VSync 。
Choreographer 内部有自己的消息队列 mCallbackQueues ,有自己的 Handler 。
    private final class FrameHandler extends Handler {        public FrameHandler(Looper looper) {            super(looper);        }        @Override        public void handleMessage(Message msg) {            switch (msg.what) {                case MSG_DO_FRAME:                    // 实行doFrame,即绘制过程                    doFrame(System.nanoTime(), 0);                    break;                case MSG_DO_SCHEDULE_VSYNC:                    //申请VSYNC信号,比方当前必要绘制使命时                    doScheduleVsync();                    break;                case MSG_DO_SCHEDULE_CALLBACK:                    //必要延长的使命,终极照旧实行上述两个事故                    doScheduleCallback(msg.arg1);                    break;            }        }    }doScheduleCallback 方法内部实行的也是 scheduleFrameLocked 。
在 scheduleFrameLocked 中,逻辑大概是:

  • 如果体系未开启 VSYNC 机制,此时直接发送 MSG_DO_FRAME 消息到 FrameHandler。此时直接实行 doFrame 方法。
  • Android 4.1 之后体系默认开启 VSYNC,在 Choreographer 的构造方法会创建一个 FrameDisplayEventReceiver,scheduleVsyncLocked 方法将会通过它申请 VSYNC 信号。
  • 调用 isRunningOnLooperThreadLocked 方法,其内部根据 Looper 判断是否在原线程,否则发送消息到 FrameHandler。终极照旧会调用 scheduleVsyncLocked 方法申请 VSYNC 信号。
FrameHandler的作用:发送异步消息,这些异步消息会被插队优先处置处罚。有延长的使命发延长消息、不在原线程的发到原线程、没开启VSYNC的直接走 doFrame 方法取实行绘制。
继承跟进, scheduleVsyncLocked 方法是怎样申请 VSYNC 信号的呢?
    private void scheduleVsyncLocked() {        mDisplayEventReceiver.scheduleVsync();    }    public DisplayEventReceiver(Looper looper, int vsyncSource) {        if (looper == null) {            throw new IllegalArgumentException("looper must not be null");        }        mMessageQueue = looper.getQueue();        // 注册VSYNC信号监听者        mReceiverPtr = nativeInit(new WeakReference<DisplayEventReceiver>(this), mMessageQueue, vsyncSource);        mCloseGuard.open("dispose");    }在 DisplayEventReceiver 的构造方法会通过 JNI 创建一个 IDisplayEventConnection 的 VSYNC 的监听者。
FrameDisplayEventReceiver的scheduleVsync()就是在 DisplayEventReceiver中:
    public void scheduleVsync() {        if (mReceiverPtr == 0) {            Log.w(TAG, "Attempted to schedule a vertical sync pulse but the display event "                    + "receiver has already been disposed.");        } else {            // 申请VSYNC停止信号,会回调onVsync方法            nativeScheduleVsync(mReceiverPtr);        }    }scheduleVsync 调用到了 native 方法 nativeScheduleVsync 去申请 VSYNC 信号。VSYNC 信号的担当回调是onVsync() 。在 FrameDisplayEventReceiver 中有详细实现:
    private final class FrameDisplayEventReceiver extends DisplayEventReceiver            implements Runnable {        private boolean mHavePendingVsync;        private long mTimestampNanos;        private int mFrame;        // ...        @Override        public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {            long now = System.nanoTime();            if (timestampNanos > now) {                Log.w(TAG, "Frame time is " + ((timestampNanos - now) * 0.000001f)                        + " ms in the future!  Check that graphics HAL is generating vsync "                        + "timestamps using the correct timebase.");                timestampNanos = now;            }            if (mHavePendingVsync) {                Log.w(TAG, "Already have a pending vsync event.  There should only be "                        + "one at a time.");            } else {                mHavePendingVsync = true;            }            mTimestampNanos = timestampNanos;            mFrame = frame;            //将自己作为runnable传入msg, 发消息后 会走run(),即doFrame(),也是异步消息            Message msg = Message.obtain(mHandler, this);            msg.setAsynchronous(true);            mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);        }        @Override        public void run() {            mHavePendingVsync = false;            doFrame(mTimestampNanos, mFrame);        }    }onVsync() 中,将吸取器自己作为 Runnable 传入异步消息 msg,并利用 mHandler 发送 msg,终极实行doFrame 方法。
末了是 doFrame 方法:
    void doFrame(long frameTimeNanos, int frame) {        final long startNanos;        synchronized (mLock) {            // ...                        // 预期实行时间            long intendedFrameTimeNanos = frameTimeNanos;            startNanos = System.nanoTime();            // 超时时间是否凌驾一帧的时间            final long jitterNanos = startNanos - frameTimeNanos;            if (jitterNanos >= mFrameIntervalNanos) {                    // 盘算掉帧数                final long skippedFrames = jitterNanos / mFrameIntervalNanos;                if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {                        // 掉帧凌驾30帧打印Log提示                    Log.i(TAG, "Skipped " + skippedFrames + " frames!  ");                }                final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;                // ...                frameTimeNanos = startNanos - lastFrameOffset;            }            // ...            mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);            // Frame标志位规复            mFrameScheduled = false;            // 纪录末了一帧时间            mLastFrameTimeNanos = frameTimeNanos;        }        try {                        // 按范例次序 实行使命            AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);            // input 开始            mFrameInfo.markInputHandlingStart();            doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);            // animation            mFrameInfo.markAnimationsStart();            doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);            doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos);                        // CALLBACK_TRAVERSAL            mFrameInfo.markPerformTraversalsStart();            doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);                        // commit 末了            doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);        } finally {            AnimationUtils.unlockAnimationClock();            Trace.traceEnd(Trace.TRACE_TAG_VIEW);        }    }重绘相干的方法


  • invalidate
    调用 View.invalidate() 方法后会逐级往上调用父 View 的相干方法,终极在 Choreographer 的控制下调用 ViewRootImpl.performTraversals() 方法。只有满意条件才会实行 measure 和 layout 流程,否则只实行 draw 流程。
    draw 流程的实行过程与是否开启硬件加快有关:

    • 关闭硬件加快则从 DecorView 开始往下的全部子 View 都会被重新绘制。
    • 开启硬件加快则只有调用 invalidate 方法的 View 才会重新绘制。

  • requestLayout
    调用 View.requestLayout 方法后会依次调用 performMeasure, performLayout 和 performDraw 方法。
    调用者 View 及其父 View 会重新从上往下举行 measure , layout 流程,一样寻常环境下不会实行 draw 流程。
    因此,当只必要举行重绘时可以利用 invalidate 方法,如果必要重新丈量和结构则可以利用 requestLayout 方法,而 requestLayout 方法不肯定会重绘,因此如果要举行重绘可以再手动调用 invalidate 方法。
事故分发

MotionEvent

事故分发就是把MotionEvent分发给View的过程。当一个MotionEvent产生以后,体系必要把这个事故通报给一个详细的View,这个通报过程就是分发过程。
点击事故的分发方法

涉及事故分发有三个方法:

  • dispatchTouchEvent
    如果事故可以或许通报给当前的 View ,那么此方法肯定会被调用,返回效果受当前 View 的 onTouchEvent 和下一级 View 的 dispatchTouchEvent 方法的影响,表示是否斲丧当前事故。
  • onInterceptTouchEvent
    在 dispatchTouchEvent 内部调用,用来判断是否拦截某个事故,如果当前 View 拦截了某事故,那么在同一个事故序列中,此方法不会再次调用,返回效果表示是否拦截当前事故。
  • onTouchEvent
    在 dispatchTouchEvent 方法中调用,用来处置处罚点击事故,返回效果表示是否斲丧当前事故,如果不斲丧,则在同一个事故序列中,当前 View 无法再次吸取到事故。
事故分发分为两种环境,View 和 ViewGroup:
View

public boolean dispatchTouchEvent(MotionEvent event) {    // ...     if (onFilterTouchEventForSecurity(event)) {        // ...         // 优先实行 onTouchListener 的 onTouch 方法        ListenerInfo li = mListenerInfo;        if (li != null && li.mOnTouchListener != null                && (mViewFlags & ENABLED_MASK) == ENABLED                && li.mOnTouchListener.onTouch(this, event)) {            result = true;        }        // 其次实行自己的 onTouchEvent        if (!result && onTouchEvent(event)) {            result = true;        }    }    // ...    return result;}View 的事故分发,紧张是起首实行 onTouchListener ,然后调用 onTouchEvent 返回是否斲丧事故。
ViewGroup

// 伪代码public boolean dispatchTouchEvent(MotionEvent event) {        // ...        // 1\. 优先处置处罚拦截        boolean intercepted = onInterceptTouchEvent(ev);        if (!canceled && !intercepted) {                // 去查找可以或许斲丧事故的子View        final float x = isMouseEvent ? ev.getXCursorPosition() : ev.getX(actionIndex);                final float y = isMouseEvent ? ev.getYCursorPosition() : ev.getY(actionIndex);                final ArrayList<View> preorderedList = buildTouchDispatchChildList(); // 子 View 列表                for (int i = childrenCount - 1; i >= 0; i--) {                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {                                break;                        }                       }        }        if (mFirstTouchTarget == null) {                handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS);    }    // ...}ViewGroup 中,会优先实行 onInterceptTouchEvent 判断是否必要拦截,如果不必要会继承遍历子 View ,通过
dispatchTransformedTouchEvent 方法,继承分发事故:
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) {        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {        event.setAction(MotionEvent.ACTION_CANCEL);        if (child == null) {            handled = super.dispatchTouchEvent(event);  // 【2】自身 dispatchTouchEvent        } else {            handled = child.dispatchTouchEvent(event);  // 【1】        }        event.setAction(oldAction);        return handled;    }    if (newPointerIdBits == oldPointerIdBits) {        if (child == null || child.hasIdentityMatrix()) {            if (child == null) {                 handled = super.dispatchTouchEvent(event); // 【2】自身 dispatchTouchEvent            } else {                    // 带有偏移的事故相应                final float offsetX = mScrollX - child.mLeft;                final float offsetY = mScrollY - child.mTop;                event.offsetLocation(offsetX, offsetY);                handled = child.dispatchTouchEvent(event); //【1】                event.offsetLocation(-offsetX, -offsetY);            }            return handled;        }        transformedEvent = MotionEvent.obtain(event);    } else {        transformedEvent = event.split(newPointerIdBits);    }    // Perform any necessary transformations and dispatch.    if (child == null) {        handled = super.dispatchTouchEvent(transformedEvent);    } else {        final float offsetX = mScrollX - child.mLeft;        final float offsetY = mScrollY - child.mTop;        transformedEvent.offsetLocation(offsetX, offsetY);        if (! child.hasIdentityMatrix()) {            transformedEvent.transform(child.getInverseMatrix());         }        handled = child.dispatchTouchEvent(transformedEvent); // 【1】    }    // Done.    transformedEvent.recycle();    return handled;}dispatchTransformedTouchEvent 中会根据传进来的子 View 去继承调用 dispatchTouchEvent ,向下分发。
当调用 super.dispatchTouchEvent(event)时,会调用 ViewGroup 的父类 View 的 dispatchTouchEvent 方法。也就是会先查抄 onTouchListener,然后实行 onTouchEvent 。
真实的分发流程

ViewGroup 中包罗 View 的环境:

  • ViewGroup 先调用 dispatchTouchEvent 举行事故分发
  • ViewGroup 调用 onInterceptTouchEvent 判断是否要拦截

    • return true, 拦截事故举行处置处罚,事故不会继承向子 View 分发,调用自身的 onTouchEvent。
    • return false,继承实行步调 3。

  • 调用子 View 的 dispatchTouchEvent
  • 子 View 调用 onTouchEvent 判断是否斲丧事故

    • return true, 举行拦截,事故不会继承向子 View 分发,调用自身的 onTouchEvent。
    • return false,不相应事故。

卡顿分析

卡顿常见的缘故原由


  • 频繁的 requestLayout
    如果频繁调用rquestLayout()方法,会导致在一帧的周期内频繁的发生存算,导致整个 Traversal 过程变长,以是,应当只管克制频繁的 requestLayout 的环境,常见的环境有,对 View 举行多次重复执办法画,比方在列表中,对一个 item 添加动画。
  • UI 线程被壅闭
    经典的不要在 UI 现场做耗时操纵。
作者:自动化BUG制造器
链接:https://juejin.cn/post/7083157254035210276
您需要登录后才可以回帖 登录 | 立即注册

Powered by CangBaoKu v1.0 小黑屋藏宝库It社区( 冀ICP备14008649号 )

GMT+8, 2024-10-18 19:24, Processed in 0.165287 second(s), 35 queries.© 2003-2025 cbk Team.

快速回复 返回顶部 返回列表