【免费社工库源码】【lua脚本编辑源码】【微课堂源码介绍】view 源码分析

时间:2024-12-29 08:59:49 编辑:个人网站支付源码 来源:apk源码 app

1.Android 自定义View:为什么你设置的wrap_content不起作用?
2.Android 14 HWUI 源码研究 View Canvas RenderThread ViewRootImpl skia
3.跑马灯带你深入浅出TextView的源码源码世界
4.❤️ Android 源码解读-从setContentView深入了解 Window|Activity|View❤️
5.setContentView()及LayoutInflater布局加载源码分析
6.Android UI绘制之View绘制的工作原理

view 源码分析

Android 自定义View:为什么你设置的wrap_content不起作用?

        在使用自定义View时,View宽 / 高的 wrap_content 属性不起自身应有的作用,而且是起到与 match_parent 相同作用。

        其实这里有两个问题:

        请分析 & 解决问题之前,请先看自定义View原理中 (2)自定义View Measure过程 - 最易懂的自定义View原理系列

        问题出现在View的宽 / 高设置,那我们直接来看自定义View绘制中第一步对View宽 / 高设置的过程:measure过程中的 onMeasure() 方法

        继续往下看 getDefaultSize()

        从上面发现:

        那么有人会问:wrap_content和match_parent具有相同的效果,为什么是填充父容器的效果呢?

        我们知道,子View的MeasureSpec值是根据子View的布局参数(LayoutParams)和父容器的MeasureSpec值计算得来,具体计算逻辑封装在getChildMeasureSpec()里。

        接下来,我们看生成子View MeasureSpec的方法: getChildMeasureSpec() 的源码分析:

        getChildMeasureSpec()

        从上面可以看出,当子View的布局参数使用 match_parent 或 wrap_content 时:

        所以: wrap_content 起到了和 match_parent 相同的作用:等于父容器当前剩余空间大小

        当自定义View的布局参数设置成wrap_content时时,指定一个默认大小(宽 / 高)。

        这样,当你的自定义View的宽 / 高设置成wrap_content属性时就会生效了。

        网上流传着这么一个解决方案:

        答:是,当父View为 AT_MOST 、View为 match_parent 时,该View的 match_parent 的效果就等于 wrap_content 。上述方法存在逻辑错误,但由于这种情况非常特殊的,所以导致最终的结果没有错误。具体分析请看下面例子:

        从上面的效果可以看出,View大小 = 默认值

        我再将子View的属性改为 wrap_content :

        从上面的效果可以看出,View大小还是等于默认值。

        相信看到这里你已经看懂了:

        为了更好的表示判断逻辑,我建议你们用本文提供的解决方案,即根据布局参数判断默认值的设置

        不定期分享关于安卓开发的干货,追求短、平、快,但却不缺深度。

Android HWUI 源码研究 View Canvas RenderThread ViewRootImpl skia

       HUWUI是Android系统中负责应用可视化元素绘制的核心组件,其架构主要在C++层实现,分析从Java层接收View绘制信息,源码通过唯一的分析渲染线程使用skia技术完成渲染任务。整体上,源码从应用程序到UI线程,分析免费社工库源码再到渲染线程,源码形成了清晰的分析层级关系。

       HUWUI的源码构建主要包括三个核心类,它们分别是分析:RecordingCanvas、Canvas、源码RenderNode、分析RenderProxy、源码RenderThread、分析CanvasContext、源码IRenderPipeline。在Java层,主要涉及两类Canvas,RecordingCanvas用于记录绘制指令,Canvas则是直接用于渲染。RecordingCanvas在构造时创建,而Canvas在调用时创建。这两个类在C++层分别对应SkiaRecordingCanvas和SkiaCanvas,后者直接引用SkCanvas。

       在全局循环中,UI线程与渲染线程之间的协同操作至关重要。具体流程包括:新创建Activity后,附着到对应的PhoneWindow,然后调用PhoneWindow的lua脚本编辑源码setContentView方法,将View添加到DecorView作为子节点。接着,DecorView与ViewRootImpl对接,完成View的更新与渲染。整个过程包含了measure、layout和draw等复杂子流程。

       渲染线程创建与核心对象紧密关联,主要包括RenderProxy、RenderThread和DrawFrameTask。RenderProxy负责Java层信息的衔接,RenderThread作为进程唯一的渲染线程,持有DrawFrameTask和CanvasContext,完成一帧的绘制任务。指令记录流程的核心在于使用C++层的RecordingCanvas将View属性和绘制信息记录到DisplayList中,进而完成指令的渲染。

       Surface、ANativeWindow、EGLSurface的创建流程在ViewRootImpl的performTraversals函数中初始化。ReliableSurface的封装和EGL与Skia环境的创建主要在RenderThread的requireGlContext函数中实现。从源码分析,这一过程通常在三个地方调用。

       View树与RenderNode树之间的协作关系明确,一个Application进程对应多个Activity,每个Activity与一个PhoneWindow绑定,PhoneWindow持有DecorView,DecorView对应一个ViewRootImpl,而ViewRootImpl与ThreadedRender模块对接。ThreadedRender与C++层的微课堂源码介绍RenderProxy一一对应,RenderProxy持有关键对象,如RenderThread、CanvasContext、DrawFrameTask等。RenderThread是单例模式,进程唯一,负责一帧绘制的逻辑。

       在RenderPipeline模块中,关键操作包括makeCurrent、draw和swapBuffers。Native Canvas在这一过程中扮演了桥梁角色,接收Java API调用,而RecordingCanvas完成Op记录,最终DisplayListData存储这些Op。

       skia的核心资源主要在三个使用场景中发挥作用,具体细节需深入分析,这些资源对于实现高效、稳定的渲染效果至关重要。

跑马灯带你深入浅出TextView的源码世界

       本文将深入浅出地解析Android系统中TextView的跑马灯动画源码,以解决开发者在实际开发中遇到的问题。文章将通过一个具体问题作为出发点,引导读者从源码的角度分析和解决问题。

       首先,面临的问题是Android 6.0及以上系统中点击“添加购物车”按钮时,TextView的跑马灯动画会出现跳动现象(动画重置,滚动从头开始)。面对这一现象,开发者往往需要从源码层面进行深入分析。明日科技随书源码

       为了解决问题,文章建议采用以下步骤进行源码分析:

       搜索“Android TextView 跑马灯原理”,找到关键代码实现,特别是与跑马灯启动相关的startMarquee()方法。

       使用Android Studio搜索TextView并查看类接口图,找到startMarquee()方法的实现,对其进行初步分析。

       确定找到的方法正确后,继续了解整个框架的实现流程,绘制主流程图。

       接下来,文章将深入分析跑马灯动画的实现机制,包括TextView、Marquee内部类以及Choreographer系统。

       在分析中,文章指出Choreographer是一个用于管理动画、输入和绘制的系统类,它通过监听DisplayEventReceiver来接收系统信号,并在每一帧中回调以确保动画的平滑性。在Choreographer中,Marquee会计算偏向值,然后触发TextView的刷新来实现动画效果。

       文章进一步解析了Choreographer的实现原理以及Marquee在postFrameCallback中的具体操作,包括计算时间差、移动位移以及触发TextView刷新的过程。

       最后,文章对问题进行了详细分析,揭示了导致跑马灯动画重置的签名字体源码根源在于“购物车”按钮的setText方法触发了requestLayout,从而导致了视图重绘。通过修改按钮的布局属性,问题得以解决。

       总结而言,文章通过问题分析和源码解析,为开发者提供了一条清晰的路径,从现象出发,深入源码,最终找到问题的根本原因并解决,从而提升对Android系统内核的理解和应用能力。

❤️ Android 源码解读-从setContentView深入了解 Window|Activity|View❤️

       Android系统中,Window、Activity、View之间的关系是紧密相连且相互作用的。了解这三者之间的关系,有助于深入理解Android应用的渲染和交互机制。

       在Android中,通常在创建Activity时会调用`setContentView()`方法,以指定显示的布局资源。这个方法主要作用是将指定的布局添加到一个名为`DecorView`的容器中,并最终将其显示在屏幕上。这一过程涉及到多个组件的交互,下面分步骤解析。

       在`Activity`类中,`setContentView()`方法调用`getWindow()`方法获取`Window`对象,而`Window`对象在`Activity`的`attach()`方法中被初始化。`Window`对象是一个抽象类,其默认实现为`PhoneWindow`,这是Android特定的窗口实现。

       `PhoneWindow`在创建时会通过`setWindowManager()`方法与`WindowManager`进行关联。`WindowManager`是系统级组件,用于管理所有的窗口,包括窗口的创建、更新、删除等操作。`WindowManager`的管理最终由`WindowManagerService`(WMS)执行,这是一个运行在系统进程中的服务。

       在`PhoneWindow`中,`installDecor()`方法会初始化`DecorView`和`mContentParent`。`mContentParent`是一个`ViewGroup`,用于存放`setContentView()`传入的布局。通过`mLayoutInflater`的`inflate()`方法,将指定的布局资源添加到`mContentParent`中。

       `DecorView`是一个特殊的`FrameLayout`,包含了`mContentParent`。在完成布局的添加后,`DecorView`本身并没有直接与`Activity`建立联系,也没有被绘制到屏幕上显示。`DecorView`的绘制和显示发生在`Activity`的`onResume()`方法执行后,这时`Activity`中的内容才真正可见。

       当`Activity`执行到`onCreate()`阶段时,其内容实际上并没有显示在屏幕上,直到执行到`onResume()`阶段,`Activity`的内容才被真正显示。这一过程涉及到`ActivityThread`中的`handleResumeActivity()`方法,该方法会调用`WindowManager`的`addView()`方法,将`DecorView`添加到`WindowManagerService`中,完成`DecorView`的绘制和显示。

       `WindowManagerService`通过`addView()`方法将`DecorView`添加到显示队列中,并且在添加过程中,会创建关键的`ViewRootImpl`对象,进一步管理`DecorView`的布局、测量和绘制。`ViewRootImpl`会调用`mWindowSession`的`addToDisplay()`方法,将`DecorView`添加到真正的显示队列中。

       `mWindowSession`是`WindowManagerGlobal`中的单例对象,其内部实际上是一个`IWindowSession`类型,通过`AIDL`接口与系统进程中的`Session`对象进行通信,最终实现`DecorView`的添加和显示。

       通过`setView()`方法的实现,可以看到除了调用`IWindowSession`进行跨进程添加`View`之外,还会设置输入事件处理。当触屏事件发生时,这些事件首先通过驱动层的优化计算,通过`Socket`跨进程通知`Android Framework`层,最终触屏事件会通过输入管道传送到`DecorView`处理。

       在`DecorView`内部,触屏事件会通过`onProcess`方法传递给`mView`,即`PhoneWindow`中的`DecorView`。最终,事件传递到`PhoneWindow`中的`View.java`实现的`dispatchPointerEvent()`方法,并调用`Window.Callback`的`dispatchTouchEvent(ev)`方法。对于`Activity`来说,`dispatchTouchEvent()`方法最终还是会调用`PhoneWindow`的`superDispatchTouchEvent()`,然后传递给`DecorView`的`superDispatchTouchEvent()`方法,完成事件的分发和处理。

       综上所述,通过`setContentView()`的过程,我们可以清晰地看到`Activity`、`Window`、`View`之间的交互关系。整个过程主要由`PhoneWindow`组件主导,而`Activity`主要负责提供要显示的布局资源,其与屏幕的直接交互则通过`WindowManager`和`WindowManagerService`实现。

setContentView()及LayoutInflater布局加载源码分析

       setContentView()和LayoutInflater布局加载源码深度解析

       当我们在Android应用中调用setContentView()时,其实涉及到了一系列复杂的流程。这个过程主要分为三个步骤:系统布局加载、LayoutInflater初始化以及LayoutInflater布局加载。

       首先,setContentView()方法通过Activity的PhoneWindow对象加载布局。在判断mContentParent是否为空后,会创建DecorView,然后将自定义的activity_main_layout加载到mContentParent,这个mContentParent对应id为R.id.content的Layout。接着,系统会加载一个包含R.id.content的系统布局到DecorView中。

       LayoutInflater的初始化过程关键在于其作为系统服务注册在SystemServiceRegistry中。当我们通过LayoutInflater.from(this)获取实例时,实际上是通过SystemServiceRegistry获取并初始化LayoutInflater的。

       LayoutInflater的布局加载流程则涉及xml预编译、View的反射创建以及递归解析子布局。在inflate方法中,会先检查根节点标签是否为"merge",然后决定是否递归加载子布局并决定是否添加到父布局中。View的创建则可能通过自定义的Factory进行拦截和定制。

       总结来说,setContentView()和LayoutInflater的交互使得我们能够灵活地加载和定制Activity的布局。通过理解这些源码细节,开发者可以更好地控制和优化应用的界面显示。

Android UI绘制之View绘制的工作原理

        这是AndroidUI绘制流程分析的第二篇文章,主要分析界面中View是如何绘制到界面上的具体过程。

        ViewRoot 对应于 ViewRootImpl 类,它是连接 WindowManager 和 DecorView 的纽带,View的三大流程均是通过 ViewRoot 来完成的。在 ActivityThread 中,当 Activity 对象被创建完毕后,会将 DecorView 添加到 Window 中,同时会创建 ViewRootImpl 对象,并将 ViewRootImpl 对象和 DecorView 建立关联。

        measure 过程决定了 View 的宽/高, Measure 完成以后,可以通过 getMeasuredWidth 和 getMeasuredHeight 方法来获取 View 测量后的宽/高,在几乎所有的情况下,它等同于View的最终的宽/高,但是特殊情况除外。 Layout 过程决定了 View 的四个顶点的坐标和实际的宽/高,完成以后,可以通过 getTop、getBottom、getLeft 和 getRight 来拿到View的四个顶点的位置,可以通过 getWidth 和 getHeight 方法拿到View的最终宽/高。 Draw 过程决定了 View 的显示,只有 draw 方法完成后 View 的内容才能呈现在屏幕上。

        DecorView 作为顶级 View ,一般情况下,它内部会包含一个竖直方向的 LinearLayout ,在这个 LinearLayout 里面有上下两个部分,上面是标题栏,下面是内容栏。在Activity中,我们通过 setContentView 所设置的布局文件其实就是被加到内容栏中的,而内容栏id为 content 。可以通过下面方法得到 content:ViewGroup content = findViewById(R.android.id.content) 。通过 content.getChildAt(0) 可以得到设置的 view 。 DecorView 其实是一个 FrameLayout , View 层的事件都先经过 DecorView ,然后才传递给我们的 View 。

        MeasureSpec 代表一个位的int值,高2位代表 SpecMode ,低位代表 SpecSize , SpecMode 是指测量模式,而 SpecSize 是指在某种测量模式下的规格大小。

        SpecMode 有三类,如下所示:

        UNSPECIFIED

        EXACTLY

        AT_MOST

        LayoutParams需要和父容器一起才能决定View的MeasureSpec,从而进一步决定View的宽/高。

        对于顶级View,即DecorView和普通View来说,MeasureSpec的转换过程略有不同。对于DecorView,其MeasureSpec由窗口的尺寸和其自身的LayoutParams共同确定;

        对于普通View,其MeasureSpec由父容器的MeasureSpec和自身的Layoutparams共同决定;

        MeasureSpec一旦确定,onMeasure就可以确定View的测量宽/高。

        小结一下

        当子 View 的宽高采用 wrap_content 时,不管父容器的模式是精确模式还是最大模式,子 View 的模式总是最大模式+父容器的剩余空间。

        View 的工作流程主要是指 measure 、 layout 、 draw 三大流程,即测量、布局、绘制。其中 measure 确定 View 的测量宽/高, layout 确定 view 的最终宽/高和四个顶点的位置,而 draw 则将 View 绘制在屏幕上。

        measure 过程要分情况,如果只是一个原始的 view ,则通过 measure 方法就完成了其测量过程,如果是一个 ViewGroup ,除了完成自己的测量过程外,还会遍历调用所有子元素的 measure 方法,各个子元素再递归去执行这个流程。

        如果是一个原始的 View,那么通过 measure 方法就完成了测量过程,在 measure 方法中会去调用 View 的 onMeasure 方法,View 类里面定义了 onMeasure 方法的默认实现:

        先看一下 getSuggestedMinimumWidth 和 getSuggestedMinimumHeight 方法的源码:

        可以看到, getMinimumWidth 方法获取的是 Drawable 的原始宽度。如果存在原始宽度(即满足 intrinsicWidth > 0),那么直接返回原始宽度即可;如果不存在原始宽度(即不满足 intrinsicWidth > 0),那么就返回 0。

        接着看最重要的 getDefaultSize 方法:

        如果 specMode 为 MeasureSpec.UNSPECIFIED 即未指定模式,那么返回由方法参数传递过来的尺寸作为 View 的测量宽度和高度;

        如果 specMode 不是 MeasureSpec.UNSPECIFIED 即是最大模式或者精确模式,那么返回从 measureSpec 中取出的 specSize 作为 View 测量后的宽度和高度。

        看一下刚才的表格:

        当 specMode 为 EXACTLY 或者 AT_MOST 时,View 的布局参数为 wrap_content 或者 match_parent 时,给 View 的 specSize 都是 parentSize 。这会比建议的最小宽高要大。这是不符合我们的预期的。因为我们给 View 设置 wrap_content 是希望View的大小刚好可以包裹它的内容。

        因此:

        如果是一个 ViewGroup,除了完成自己的 measure 过程以外,还会遍历去调用所有子元素的 measure 方法,各个子元素再递归去执行 measure 过程。

        ViewGroup 并没有重写 View 的 onMeasure 方法,但是它提供了 measureChildren、measureChild、measureChildWithMargins 这几个方法专门用于测量子元素。

        如果是 View 的话,那么在它的 layout 方法中就确定了自身的位置(具体来说是通过 setFrame 方法来设定 View 的四个顶点的位置,即初始化 mLeft , mRight , mTop , mBottom 这四个值), layout 过程就结束了。

        如果是 ViewGroup 的话,那么在它的 layout 方法中只是确定了 ViewGroup 自身的位置,要确定子元素的位置,就需要重写 onLayout 方法;在 onLayout 方法中,会调用子元素的 layout 方法,子元素在它的 layout 方法中确定自己的位置,这样一层一层地传递下去完成整个 View 树的 layout 过程。

        layout 方法的作用是确定 View 本身的位置,即设定 View 的四个顶点的位置,这样就确定了 View 在父容器中的位置;

        onLayout 方法的作用是父容器确定子元素的位置,这个方法在 View 中是空实现,因为 View 没有子元素了,在 ViewGroup 中则进行抽象化,它的子类必须实现这个方法。

        1.绘制背景( background.draw(canvas); );

        2.绘制自己( onDraw );

        3.绘制 children( dispatchDraw(canvas) );

        4.绘制装饰( onDrawScrollBars )。

        dispatchDraw 方法的调用是在 onDraw 方法之后,也就是说,总是先绘制自己再绘制子 View 。

        对于 View 类来说, dispatchDraw 方法是空实现的,对于 ViewGroup 类来说, dispatchDraw 方法是有具体实现的。

        通过 dispatchDraw 来传递的。 dispatchDraw 会遍历调用子元素的 draw 方法,如此 draw 事件就一层一层传递了下去。dispatchDraw 在 View 类中是空实现的,在 ViewGroup 类中是真正实现的。

        如果一个 View 不需要绘制任何内容,那么就设置这个标记为 true,系统会进行进一步的优化。

        当创建的自定义控件继承于 ViewGroup 并且不具备绘制功能时,就可以开启这个标记,便于系统进行后续的优化;当明确知道一个 ViewGroup 需要通过 onDraw 绘制内容时,需要关闭这个标记。

        参考:《Android开发艺术探索》

Android中View的创建过程

        我们知道在onCreate里面View还是没有测绘完成的。那么什么时候测绘完成了?答案是onResume。

        通过查看源码 我们可以看到在onCreate方法里面调用了getWindow()方法然后在将我们的页面塞到这个window里面。这个window也就是PhonwWindow.

        那PhoneWindow是什么时候被创建的?

        这就引出了Activity的创建流程。

那Activity是怎么被创建的呢?

        由于Activity是一个组件他是由系统使用ActivityThread方法去创建的。

        现在我来分析下:

        先来到ActivityThread类的handleLaunchActivity方法。

        可以看到他去调用了Activity的performCreate方法。

        现在我们终于看到onCreate方法被调用了。

        这里还有个重点,在performLaunchActivity里面去调用Activity的onCreate方法之前还去做了一件很重要的事情,这个事情在第行:调用了Activity的attach方法。

        现在跟到Activity的attach方法:找到了我们一直找的PhoneWindow的创建。

View 绘制流程源码分析

       在View的绘制流程中,ViewRootImpl的setView主流程涉及的关键步骤包括设置PFLAG_FORCE_LAYOUT和PFLAG_INVALIDATED。这一步骤在执行时,触发了View的重绘逻辑。

       接下来,当View收到需要重绘的信号后,会执行invalidate方法。这个方法首先计算出需要重绘的dirty区域,然后从下向上,最终调用到ViewRootImpl的scheduleTraversals方法。这个过程中,脏区域的范围逐步扩大,直至整个View需要进行重绘。

       在View的绘制流程中,PFLAG_FORCE_LAYOUT和PFLAG_INVALIDATED的使用至关重要。它们的设置触发了视图的重绘和布局过程,保证了UI在用户操作或其他事件触发时能够及时响应和更新。通过这种方式,系统确保了用户界面的实时性和交互性。

       具体来说,当View收到布局或尺寸变化的信号时,会调用requestLayout方法,同时设置PFLAG_FORCE_LAYOUT标志。这个标志告诉系统,当前布局需要强制执行,即使布局尚未完成,也应立即进行更新。同时,invalidate方法的调用,会触发PFLAG_INVALIDATED标志的设置,表明视图需要重绘。

       在ViewRootImpl中,scheduleTraversals方法是负责组织和执行视图层级中所有视图的重绘和布局的。它会根据脏区域和布局标志的设置,合理安排视图的更新顺序,确保系统的性能和用户体验。

       总结整个流程,View的绘制和布局机制通过一系列的标志(如PFLAG_FORCE_LAYOUT和PFLAG_INVALIDATED)和方法(如requestLayout和invalidate)来协调和控制。这些机制使得系统能够高效地响应用户操作,实现流畅的UI交互。通过深入理解这些源码细节,开发者能够更好地优化UI性能,提高用户体验。