Android的视图显示和管理机制:layout view window WindowManager Canvas Surface

在Android系统中,Layout view window WindowManager Canvas Surface SurfaceFlinger这些组件协同工作,以实现图形的绘制和显示。需要搞明白这些组件是什么时候创建的以及他们之间的结构关系。

  • 从上到下的层级关系:用户在View上进行操作,View通过Canvas进行绘制,绘制结果存储在Surface中。每个Window都可以关联一个或多个Surface。
  • 合成和显示:SurfaceFlinger获取所有活跃的Surface,并将它们合成为最终的屏幕显示内容。
  • 性能和异步处理:SurfaceView允许在后台线程中通过其Surface进行绘制,与主UI线程分离,这有助于处理复杂或资源密集型的图形操作,而不阻塞用户界面。

1 LayoutInflater

创建和获取

inflate方法用于将XML布局文件转换为应用程序中的View对象。这个过程是通过LayoutInflater类实现的,它是Android SDK的一部分,专门负责解析XML布局文件,并将其实例化为应用程序界面的层次结构。

setContentView最后会调用mLayoutInflater.inflate来创建了自定义xml中的布局视图,添加到mContentParent中。inflate方法是讲xml文件反射成一个View,但是并不执行View的绘制。

LayoutInflater是一个用于将定义在XML中的布局文件(如activity_main.xml)转化为View对象的类。每个由XML文件定义的布局在运行时都需要被“充气”成一个View树,这样用户才能与之交互。基本功能是读取XML布局文件,并将其转换成为相应的View对象。这个方法通常在Activity、Fragment或视图组件初始化时调用,以生成用户界面。

LayoutInflater提供了几个版本的inflate方法,其中最常用的包括:
View inflate(int resource, ViewGroup root)
View inflate(int resource, ViewGroup root, boolean attachToRoot)

  • resource:这是一个指向布局文件的资源ID,例如R.layout.my_layout。
  • root:这是新View应该附加到的父View。这个参数可以是null。
  • attachToRoot:如果是true,则将加载的View添加到root作为子项;如果是false,则不添加,但仍然会使用root来正确处理布局参数。

inflate过程详解

  1. 解析XML

    • LayoutInflater读取指定的XML布局文件,解析其中定义的各种UI组件(如Button、TextView等)和它们的属性(如android:layout_width)。
  2. 创建View对象

    • 对于XML中的每一个组件,LayoutInflater会创建对应的View对象。例如,一个标签会被实例化为一个Button对象。
  3. 处理属性

    • 每个View对象会根据XML中定义的属性进行配置。这些属性包括尺寸、边距、对齐方式等。
  4. 构建视图树

    • 如果inflate调用中提供了root且attachToRoot为true,解析出的View会被添加到root中,形成一个完整的视图树。如果attachToRoot为false,则不添加,但root仍然用于生成正确的布局参数。

inflate使用场景

  • 在Activity中:通常在onCreate()方法中使用inflate来加载布局。

    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    }

  • 在Fragment中:在onCreateView()方法中使用inflate来为Fragment加载视图。
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
    Bundle savedInstanceState) {
    return inflater.inflate(R.layout.fragment_example, container, false);
    }

通常我们使用inflate有以下三种方式:

LayoutInflater inflater = LayoutInflater.from(this);
View view1 = inflater.inflate(R.layout.view1, null);
View view2 = inflater.inflate(R.layout.view2, null, false);

View.inflate:只有一种形式如下:其实质是通过LayoutInflater来创建View的
public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) {
LayoutInflater factory = LayoutInflater.from(context);
return factory.inflate(resource, root);
}

Context.getSystemService:通过Context.getSystemService来获得LayoutInflater,实质也是通过LayoutInflater来创建View
LayoutInflater inflater = (LayoutInflater) this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view1 = inflater.inflate(R.layout.view1, null);

2 View

创建和获取

创建: View 是 Android 中的基础绘图单元,通常在 XML 布局文件中定义或通过代码直接创建。每个 View 在被创建时会初始化其绘图属性(如大小、颜色等)。
获取: 通常,View 通过其父容器(如 Activity 或 Fragment 的布局)进行管理和访问。
View是Android中UI组件的基础,如按钮、文本框等。负责自己的绘制(通过onDraw方法)和事件处理。View组成ViewGroup(如LinearLayout)进而构建复杂的用户界面。

View机制

  • 自定义绘制的基础: Android中的所有可视化组件(如按钮(Button)、文本框(TextView)、输入框(EditText)等)都是基于View类或其子类构建的。View是UI组件的基类,它封装了与用户交互、布局管理和绘制的基本功能。

  • Canvas绘图上下文: 每个需要在屏幕上展示的View,在其生命周期中的某个时刻,都会获得一个Canvas对象。这个Canvas就像是一个画布,提供了丰富的绘图方法,如画线、画圆、填充颜色、绘制文本等。通过Canvas,每个View能够实现自定义的图形绘制逻辑,从而展现出独特的视觉效果和交互行为。

  • 绘制流程: 当UI需要更新或初次展示时,系统会要求相关的View执行其draw()方法。在draw()方法内部,View会使用分配给它的Canvas来执行绘制操作。这个过程包括但不限于背景绘制、内容绘制(比如文本或图像)以及绘制子视图(如果有的话)。通过重写onDraw()方法,开发者可以完全控制View的绘制逻辑,实现自定义的UI组件。

自定义View的意义

  • 个性化UI设计: 开发者可以创造出与系统默认组件外观迥异的UI元素,满足特定应用的设计需求或品牌风格。
  • 动态效果实现: 利用自定义绘制,可以轻松实现复杂的动画效果、图形变换等,增强用户体验。
  • 高效性能控制: 在一些高性能要求的场景(如游戏、复杂动画)中,直接操作Canvas进行绘制能更精细地控制渲染流程,提高应用性能。

3 Window

创建和获取

创建: 在 Android 中,Window 是一个抽象概念,代表屏幕上的一部分区域,可以承载视图内容。每个 Activity 自动关联一个 Window,通常是通过 PhoneWindow 实现的。
获取: 可以通过 Activity 的 getWindow() 方法获取到当前活动的 Window。

机制

Android手机中所有的视图都是通过Window来呈现的,像常用的Activity,Dialog,PopupWindow,Toast,他们的视图都是附加在Window上的。Window是一个更高级的UI概念,代表Android中的一个全屏窗口,比如一个活动(Activity)。它不直接参与内容的绘制,而是为放置视图提供一个容器或框架。

在这里插入图片描述

每一个Activity都包含一个Window对象(dialog,toast 等也是新添加的window对象),而Window是一个抽象类,具体实现是PhoneWindow。在Activity中的setContentView实际上是调用PhoneWindow的setContentView方法。并且PhoneWindow中包含着成员变量DecorView。

在Android中,Window不是一个具体的类,而是通过WindowManager服务管理的一个概念性框架。它代表了一个可以包含视图和布局的容器,这个容器可以完整地填充设备的屏幕或者只是屏幕的一部分(如对话框)。Window作为最顶层的UI容器,主要负责承载应用的视图层次结构。每个Android应用至少有一个Window(通常是主Activity的窗口),复杂应用可能有多个Window(如弹出的对话框,菜单等)。

每个Activity通常都会有一个与之关联的Window,这个Window负责展示Activity的内容。当你创建一个Activity时,系统会为其提供一个Window,这个Window实际上是由Activity的setContentView()方法填充的。

在这里插入图片描述

站在系统的角度上看,系统是不知道有View对象这个说法的!作为系统,不去管你Window如何搬砖、如何砌墙,只给你地皮。而这时,Window为了绘制出用户想要的组件(按钮、文字、输入框等等),于是就定义了View机制,给每个View提供Canvas,让不同的View自己绘制具有自己特色的组件。同时,为了更好的管理View,通过定义ViewGroup等等

在Activity的attach()方法里,系统会创建Activity所属的Window对象并为其设置回调接口,由于Activity实现了Window的Callback接口,因此当Window接收到外界的状态改变时就会回调Activity的方法。Callback接口中的方法很多。Activity实现了Window的Callback接口,因此当Window接收到外界的状态改变时就会回调Activity的方法

public interface Callback {
        public boolean dispatchTouchEvent(MotionEvent event);
        public View onCreatePanelView(int featureId);
        public boolean onMenuItemSelected(int featureId, MenuItem item);
        public void onContentChanged();
        public void onWindowFocusChanged(boolean hasFocus);
        public void onAttachedToWindow();
        public void onDetachedFromWindow();
    }

4 WindowManager

创建和获取

创建: WindowManager 是一个系统服务,负责管理应用窗口的创建、销毁、更新等。它是在系统启动时由 SystemService 创建的。
获取: WindowManager 可以通过 Context.getSystemService(Context.WINDOW_SERVICE) 获得。

机制

WindowManager是Android系统中管理窗口的服务,负责管理所有的Window,包括它们的创建、销毁、大小调整和Z顺序(即窗口堆叠的顺序)。WindowManager通过WindowManager.LayoutParams类提供对窗口特性的详细控制,如其尺寸、透明度、位置等。在LayoutParams中,有2个比较重要的参数: flags,type。

PhoneWindow 只是负责处理一些应用窗口通用的逻辑(设置标题栏,导航栏等)。但是真正完成把一个 View,作为窗口添加到 WmS 的过程是由 WindowManager 来完成的。WindowManager 的具体实现是 WindowManagerImpl。这个WindowManagerImpl都是交由WindowManagerGlobal来处理,WindowManagerGlobal以工厂的形式向外提供自己的实例。这种工作模式是桥接模式,将所有的操作全部委托给WindowManagerGlobal来实现。

添加窗口是通过WindowManagerGlobal的addView方法操作的,这里有三个必要参数。view,params,display。
display : 表示要输出的显示设备。
view : 表示要显示的View,一般是对该view的上下文进行操作。(view.getContext())
params : 类型为WindowManager.LayoutParams,即表示该View要展示在窗口上的布局参数。其中有一个重要的参数type,用来表示窗口的类型。

在WindowManagerGlobal的addView()方法里,最后调用ViewRootImpl的setView方法,处理添加过程。

在这里插入图片描述

在ViewRootImpl的setView()方法里,执行requestLayout()方法完成View的绘制流程,并且通过WindowSession将View和InputChannel添加到WmS中,从而将View添加到Window上并且接收触摸事件。
当手动调用 invalidate(),postInvalidate(),requestInvalidate() 也会最终调用performTraversals(),来重新绘制 View。
在setView方法中,首先会调用到 requestLayout(),表示添加 Window 之前先完成第一次 layout 布局过程,以确保在收到任何系统事件后面重新布局。ViewRootImpl 调用到 requestLayout() 来完成 View 的绘制操作,view 的绘制首先会调用 checkThread() 来判断当前线程。通过requestLayout()向主线程发送了一条触发遍历操作的消息,performTraversals方法开始遍历整个View树,执行View的measure,layout,draw流程。ViewRootImpl中接收的各种变化,如来自WmS的窗口属性变化、来自控件树的尺寸变化及重绘请求等都引发performTraversals()的调用,并在其中完成处理。View类及其子类中的onMeasure()、onLayout()、onDraw()等回调也都是在performTraversals()的执行过程中直接或间接的引发。
在这里插入图片描述

performMeasure : 会调用measure方法,在measure方法中又会调用onMeasure方法,在onMeasure方法中则会对所有的子元素进行measure过程,这个时候measure流程就从父容器传到子元素中了,这样就完成了一次measure过程。measure完成以后,可以通过getMeasuredWidth和getMeasureHeight方法来获取到View测量后的宽高。

performLayout : 和performMeasure同理。Layout过程决定了View的四个顶点的坐标和实际View的宽高,完成以后,可以通过getTop/Bottom/Left/Right拿到View的四个顶点位置,并可以通过getWidth和getHeight方法来拿到View的最终宽高。

performDraw : 和performMeasure同理,唯一不同的是,performDraw的传递过程是在draw方法中通过dispatchDraw来实现的。Draw过程则决定了View的显示,只有draw方法完成以后View的内容才能呈现在屏幕上。

在这里插入图片描述

它只提供三个接口方法:addView、updateViewLayout、removeView,这些方法都是针对View的。
在这里插入图片描述

5 Surface

创建和获取

当一个应用程序窗口被创建时,系统会为该窗口创建一个Surface。这个Surface是由窗口管理器(Window Manager)和表面合成器(SurfaceFlinger)共同管理的。
在应用程序的ViewRootImpl类中,有一个名为mAttachInfo的内部类,它持有当前窗口的Surface引用。这个引用是在窗口创建时由系统设置的,并在窗口的生命周期中保持有效。

Surface 表示一个具体的可绘制区域,通常是由 SurfaceView 或者窗口系统(如 Window)来创建和管理。Surface 与底层的图形缓冲区关联,用于存储实际的像素数据。
可以通过 SurfaceView.getHolder().getSurface() 获得,或者从 Window 的某些方法中间接获取。

Surface 的作用:

在draw阶段,每个视图通过调用其draw方法将自身绘制到一个Canvas对象上。这个Canvas实际上是通过Surface的lockCanvas方法获取的,它代表了Surface的一块画布。
视图的绘制操作(如画线、画圆、贴图等)都是在这个Canvas上执行的。完成绘制后,通过Surface的unlockCanvasAndPost方法将结果显示到屏幕上。

表示一个具体的绘图表面,这个表面可以存储绘制好的图像数据。在更底层,与一个被称为SurfaceHolder的缓冲区相关联,这使得SurfaceView一个特殊的View可以在一个独立的线程中更新其内容,而不影响主UI线程的性能。

在Android中,Window与Surface一一对应。 如果说Window关心的是层次和布局,是从设计者角度定义的类,Surface则从实现角度出发,是工程师关心和考虑的类。Window的内容是变化 的,Surface需要有空间来记录每个时刻Window的内容。在Android的SurfaceFlinger实现里,通常一个Surface有两块 Buffer, 一块用于绘画,一块用于显示,两个Buffer按照固定的频率进行交换,从而实现Window的动态刷新。

每个Window在Android中都关联着一个Surface,这个Surface是应用程序用来绘制其用户界面(UI)的地方。当应用程序需要更新其显示内容时,它会在对应的Surface上绘制新的UI元素,如按钮、文本或者图像。这一过程可以发生在主线程中,也可以在专门的渲染线程中,特别是对于需要复杂动画或视频播放的场景。

Surface的工作流程通常包括以下几个关键步骤:

  1. 创建:Surface在窗口创建时被创建,为应用程序提供一块可供绘制的“空画布”。

  2. 锁定和绘制:应用通过锁定Surface,获得一个Canvas对象,然后在这个Canvas上进行绘制操作。

  3. 提交:绘制完成后,Surface会被解锁并提交,这意味着绘制的内容已经准备好用于显示。

  4. 合成与显示:SurfaceFlinger系统服务负责从不同的Surface中读取已提交的缓冲区数据,进行合成(如果存在多个叠加的窗口),然后输出到显示屏上。

SurfaceView是一种特殊的View,它有自己的Surface,允许在单独的线程中进行高效的后台绘制,这对于视频播放或游戏等高性能图形应用特别有利,因为它减少了主线程的负担,并且支持在不阻塞UI更新的情况下进行快速连续的图像更新。

6 Canvas

创建和获取

Canvas是一个提供绘图功能的类,它定义了绘制文本、线条、图形、图片等的方法。在View的onDraw方法中,Canvas对象被传递进来,开发者通过这个Canvas来绘制自定义的UI内容。

performTraversals方法中ViewRootImpl就会去创建Surface,而此后的渲染则可以通过Surface的lockCanvas方法获取Surface的Canvas来进行,然后遍历ViewHierachy把需要绘制的View通过Canvas(View.onDraw(Canvas canvas))绘制到Surface上,绘制完成后解锁(Surface.unlockCanvasAndPost)让SurfaceFlinger将Surface绘制到屏幕上。我们onDraw(Canvas canvas)方法中传入的Canvas对象大致就是这么来的……Surface对应了一块屏幕缓冲区,每个window对应一个Surface,任何View都是画在Surface上的,传统的view共享一块屏幕缓冲区,所有的绘制必须在UI线程中进行.但是viewgroup在依次绘制子view的时候,都会先对canvas进行save操作,绘制完之后restore,所以其实虽然绘制都共用同一个canvas,但是子view之间的绘制是互不影响的

机制

一个Canvas对象有四大基本要素:
1、一个用来保存像素的Bitmap
2、一个Canvas在Bitmap上进行绘制操作
3、绘制的东西
4、绘制的画笔Paint

Canvas类提供了一系列的draw…方法
填充 public void drawARGB(int a, int r, int g, int b)
绘制几何图像 canvas.drawArc (扇形)
绘制图片 canvas.drawBitmap (位图)

7 SurfaceFlinger

创建和获取

创建: SurfaceFlinger 是系统级的服务,由系统在启动时初始化。它负责管理所有应用和系统的 Surface,并进行屏幕内容的合成。
获取: 作为系统服务,开发者通常不直接与 SurfaceFlinger 交互;它在后台运行,通过系统调用间接与之交互。

机制

SurfaceFlinger是系统级别的服务,用于管理所有的Surface。它负责将来自不同应用和系统界面的多个Surface合成到一个帧缓冲区中,并输出到设备的显示屏上。通过硬件加速和优化算法来有效地进行这种图像合成,以实现高性能的图形显示。

在这里插入图片描述

SurfaceFlinger是一个独立的Service,它接收所有Window的Surface作为输入,根据ZOrder, 透明度,大小,位置等参数,计算出每个Surface在最终合成图像中的位置,然后交由HWComposer或OpenGL生成最终的显示Buffer, 然后显示到特定的显示设备上。

Layer——Layer是SurfaceFlinger 进行合成的基本操作单元。Layer在应用请求创建Surface的时候在SurfaceFlinger内部创建,因此一个Surface对应一个 Layer, 但注意,Surface不一定对应于Window,Android中有些Surface并不跟某个Window相关,而是有程序直接创建,比如说 SurfaceView, 用于显示有硬件输出的视频内容等。当多个Layer进行合成的时候,并不是整个Layer的空间都会被完全显示,根据这个Layer最终的显示效果,一个Layer可以被划分成很多的Region

在这里插入图片描述

应用程序的每个Surface都对应一个SurfaceFlinger端的Layer,每个Layer都有两个buffer可以用,一个前端buffer,一个后端buffer,前端buffer用于显示,后端buffer用于绘制,SurfaceFlinger所要做的事情就是把后端buffer绘制完成后需要显示的buffer进行合成,合成到framebuffer上,然后丢给Display去显示。

双缓冲的主要目的是减少画面撕裂和避免渲染过程中的视觉闪烁。通过在后台缓冲区进行绘制,然后一次性将完成的画面切换到前台,可以提供更平滑和更稳定的视觉效果。

  • 当SurfaceFlinger作为消费者时,它会取用Surface的frontBuffer中的图像,将其与系统中其他的Surface图像合成,然后输出到屏幕。
  • 当MediaCodec作为消费者时,frontBuffer中的内容被用于视频编码过程,编码后的数据通常用于存储或网络传输。

工作流程

  • Lock:通过lockCanvas()方法获取对backBuffer的访问,这允许在后台缓冲区上进行绘制。这个过程中,应用程序可以在这个画布上绘制图像、文本、动画等内容。
  • Draw:在backBuffer上完成所有绘制操作。由于这些操作是在后台执行的,它们不会影响当前用户看到的屏幕内容。
  • Unlock and Post:完成绘制后,调用unlockCanvasAndPost(Canvas canvas),这个操作不仅释放画布资源,同时触发缓冲区的交换。此时,backBuffer(新绘制的内容)和frontBuffer(当前显示的内容)的身份互换,新的内容变为可显示的,而旧的前台缓冲区则转为后台,准备下一轮绘制。

Surface 和 SurfaceFlinger 的基本关系

  1. Surface: 在Android中,Surface代表一块可显示内容的区域。它是一个虚拟的显示层,可以接收绘制命令(通过Canvas),并保存绘制的内容。

  2. SurfaceFlinger: SurfaceFlinger是Android系统的底层服务,负责接收各个应用和系统服务的图像数据(即Surface数据),并将这些数据合成到屏幕上。SurfaceFlinger工作在系统级别,管理着所有的Surface,并负责最终的屏幕渲染。

Surface 到 SurfaceFlinger 的数据流动

  • 应用程序通过Canvas对象在Surface上进行绘制。这些操作实质上是在修改与Surface关联的硬件缓冲区的内容。

  • 绘制完成后,通过Surface的方法(如unlockCanvasAndPost(Canvas canvas))将修改后的缓冲区标记为待显

  • Surface内部实现了与SurfaceFlinger的通信机制。每个Surface都是通过Binder IPC机制注册到SurfaceFlinger服务的。

  • Surface内容更新时,Surface会通知SurfaceFlinger其内容已更新。这通常通过发送一个同步信号(例如,一个时间戳或帧标识)来实现。

  • SurfaceFlinger接收到更新通知后,会将新的Surface内容拉取到其合成流程中。SurfaceFlinger根据所有可用的Surface图层和它们的Z顺序进行图像合成,然后输出到显示硬件。

  • SurfaceFlinger合成的结果会发送到显示硬件(如LCD屏幕)。这一步通常涉及到VSYNC信号,确保屏幕刷新与内容更新同步,从而减少图像撕裂或闪烁现象。

surfaceflinger的工作流程

在这里插入图片描述

当Surface绘制完成后会发出一个Invalidate的消息给Surfaceflinger的等待线程,当waitForEvent接收到消息后就会交给onMessageReceivered去处理,处理过程中会依次调用handleMessageTransaction、handleMessageInvalidate、handleMessageRefresh接口。

handleMessageTransaction——主要处理之前对屏幕和应用程序窗口的改动。窗口状态的改变只能在一个Transaction中进行。因为窗口状态的改变可能造成本窗口和其他窗口的可见区域变化,所以就必须重新来计算窗口的可见区域。在这个处理子过程中Android会根据标志位来对所有layer进行遍历,一旦发现哪个窗口的状态发生了变化就设置标志位以在将来重新计算这个窗口的可见区域。

handleMessageInvalidate——主要调用handlePageFlip()函数,该函数主要是从各Layer对应的BufferQueue中拿图形缓冲区数据,并根据内容更新脏区域。

handleMessageRefresh——就是合并和渲染输出了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/617104.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数据结构复习指导之树、森林

文章目录 树、森林 考纲内容 复习提示 1.树的存储结构 1.1双亲表示法 1.2孩子表示法 1.3孩子兄弟表示法 2.树、森林、与二叉树的转换 2.1树转换为二叉树 2.2森林转换为二叉树 2.3二叉树转换为森林 3.树和森林的遍历 3.1树的遍历 3.2森林的遍历 树、森林 考纲内容…

手机电脑通用便签推荐 好用便签下载

便签软件作为一种日常记录和管理工具,其实用性和便捷性深受用户喜爱。一款优秀的便签软件不仅能帮助我们随时随地记录重要信息,还能有效提高工作效率。然而,市场上很多便签应用仅限于单一平台使用,对于需要在手机和电脑间频繁切换…

FPGA第1篇,FPGA现场可编程门阵列,从0开始掌握可编程硬件开发(FPGA入门指南)

简介:FPGA全称Field-Programmable Gate Array,是一种可编程逻辑器件,它通过可编程的逻辑单元和可编程的连接网络实现了灵活的硬件实现。与固定功能的集成电路(ASIC)相比,FPGA具有更高的灵活性和可重新配置性…

python随机显示四级词汇

python实现一个浮动窗口随机显示四级单词在桌面跑来跑去 实现一个浮动窗体随机显示四级单词在windows桌面置顶移动 tkinter库来创建窗口和显示单词,以及random库来随机选择单词。 使用after方法来定时更新窗口的位置,实现单词窗口的慢慢移动效果 使用…

day10-Map集合

Map 1.Map 1.1 Map简介 1.为什么使用Map集合 购物车提供的四个商品和购买的数量在后台需要容器存储。 每个商品对象都一一对应一个购买数量。 把商品对象看成是Map集合的键,购买数量看成Map集合的值。 例如: {商品12 , 商品23 , 商品3 2 , 商品4…

GitHub操作

远程库-GitHub GitHub网址 GitHub是全球最大的远程库 1. 创建远程库 2. 远程仓库操作 2.1 创建远程仓库别名 git remote -v 查看当前所有远程库地址别名 git remote add 别名 远程地址 设置远程库地址别名 案例操作 起一个别名会出现两个别名,是因为既可以拉取…

第二步->手撕spring源码之bean操作

本步骤目标 本步骤继续完善 Spring Bean 容器框架的功能开发,在这个开发过程中会用到较多的接口、类、抽象类,它们之间会有类的实现、类的继承。 这一次我们把 Bean 的创建交给容器,而不是我们在调用时候传递一个实例化好的 Bean 对象&#x…

vue3使用setup模式的store报错

** setup store模式 $reset方法报错 ** 顾名思义就是 使用store 使用的是setup 语法模式 不能执行$reset 方法 解决方式: // main.ts import { createPinia } from pinia const pinia createPinia() pinia.use(({ store }) > {const initialState JSON.pars…

JupyterLab OpenCV展示图片

JupyterLab OpenCV展示图片 方式一 注意:此种方式如果在远程服务器上的JupyterLab上运行,可能会出现错误。 import cv2# 读取图片 image cv2.imread(photo/blg.png)# 显示图片 cv2.imshow(image, image)# 等待按键,之后关闭所有窗口 cv2.w…

c语言题库之多个数组从两边移动向中间汇聚

文章目录 题目分析代码实现代码分析 题目 c语言题库之多个数组从两边移动向中间汇聚 呈现效果:输入想要输入的字符数组呈现数组从两边向中间逐渐打开的样子 分析 首先我们需要一组我们想要输入的字符数组用来展示打开的字符其次我们需要进行对数组的替换&#x…

基于STM32单片机的环境监测系统设计与实现

基于STM32单片机的环境监测系统设计与实现 摘要 随着环境污染和室内空气质量问题的日益严重,环境监测系统的应用变得尤为重要。本文设计并实现了一种基于STM32单片机的环境监测系统,该系统能够实时监测并显示室内环境的温湿度、甲醛浓度以及二氧化碳浓…

怎么把学浪课程视频下载到相册

在这个快节奏的学习时代,每一刻的知识获取都显得至关重要。想象一下,在浩瀚如海的学浪app中,你已经找到了那些能够点亮智慧的课程视频,它们不仅充满了启发,还是你求学旅途中的宝贵资源。但是,在网络不稳定或…

Unity2D 模拟手柄实现玩家移动

1,创建控制器UI 2,挂载脚本 3,脚本编写 基本要素 [Tooltip("玩家游戏体")]public Rigidbody2D player;[Tooltip("玩家速度")]public float speed 1f;[Tooltip("玩家动画")]public Animator animator;public …

Docker in Docker(DinD)原理与实战

🐇明明跟你说过:个人主页 🏅个人专栏:《Docker幻想曲:从零开始,征服容器宇宙》 🏅 🔖行路有良友,便是天堂🔖 目录 一、引言 1、Docker简介 2、Docker …

MES系统与WMS集成方法(满分100学习资料)

导语 大家好,我是智能仓储物流技术研习社的社长,老K。专注分享智能仓储物流技术、智能制造等内容。 新书《智能物流系统构成与技术实践》 完整版文件和更多学习资料,请球友到知识星球【智能仓储物流技术研习社】自行下载 这份文件是关于MES系…

什么是XXE漏洞,日常如何做好web安全,避免漏洞威胁

随着网络技术的不断发展,网站安全问题日益受到人们的关注。当前随着技术发展,网站存在一些常见的可能被攻击者利用的漏洞,而在众多网站安全漏洞中,XXE(XML External Entity)漏洞是一个不容忽视的问题。今天…

多线程·线程状态

目录 1.等待一个线程 join 2.休眠当前线程 3.线程的所有状态 4.线程的状态转换 1.等待一个线程 join 有些场景,我们需要控制线程的执行顺序,这时候就需要用到 join 了 比如:把大象装进冰箱要几步? 第一步:打开冰…

QT ERROR: Unknown module(s) in QT: xlsx怎么办

现象描述 QT编译c代码的时候,报这种QT ERROR: Unknown module(s) in QT: xlsx,应该如何解决? 这里,我简单记录一下自己的解决问题过程。有可能,对遇到同样的问题的你,也有所帮助 第一步 检查perl是否安装…

软考144-下午题-【试题三】:UML图-类图、用例图

一、分值与目标 题型: 问题一~问题三(扩展/UML——>设计模式) 二、UML基础知识回顾 2-1、关系 UML中有四种关系:依赖、关联、泛化、实现。 1、关联 关联是一种结构关系,它描述了一组链,链是对象之间的…

【计算机网络篇】数据链路层(10)在物理层扩展以太网

文章目录 🍔扩展站点与集线器之间的距离🛸扩展共享式以太网的覆盖范围和站点数量 🍔扩展站点与集线器之间的距离 🛸扩展共享式以太网的覆盖范围和站点数量 以太网集线器一般具有8~32个接口,如果要连接的站点数量超过了…