Android Framework(八)WMS-窗口动效概述

news2024/10/5 4:53:59

文章目录

  • 动画简述
    • 本地、远端动画的定义
    • 什么是“leash”图层
    • “leash”图层的命令与创建
  • Winscope流程
    • 小结
  • 动画流程概览分析
  • Activity启动app_transition 动画的主要事件
    • 触发动画执行的套路
    • 动画真正执行
    • 动画的结束回调
    • 触发远端动画的Target

动画简述

  • 1、动画的原理也是利用了视觉停留原理,控制时间点“对象”显示,来组成动画效果。

  • 2、这个系列说的虽然是 framework 层的动画,但是本质上和应用内写动画一样的,要么是加载动画的 xml 文件,要么是通过 Animator 对象。

  • 3、所以后面的分析重点其实不在动画本身,更重要的动画的播放流程和时机。

  • 4、动画的目的,为了提升用户体验,有一个自然的过渡,所以他本身不应该干预业务逻辑,也就是说就算把动画这段代码移掉,业务逻辑也应该正在执行。

本地、远端动画的定义

对于 framework 层来说,动画类型可以分为2类:

  • 1、本地动画 (LocalAnimation)
  • 2、远端动画(RemoteAnimation)

本地和远端值得是播放动画是在哪个进程执行的,这个本地和远端的概念是相对的,不过既然是 framework 层的角度去分析,所以本地动画值得就是在 "system_service " 进程播放的动画。 而远端动画,指的就是在非 "system_service " 进程播放的动画。

这2个点还是挺重要的,比如当前场景视觉明显可见的点击图标后,图标开始放大铺满屏幕的动效,就是在 launcher 进程播放的,所以也是远端动画。

什么是“leash”图层

既然是动画,肯定是对某个“对象”不停的修改其相关属性来达到动画效果的,比如写 APP 的时候写的动画“对象”会是一个 View 。

而当前分析的 framework 层动画的“对象”则是一个 Surface (图层)。

但是前面说了,动画只是为了提升过渡体验不应该干预实际业务。
比如 APP 里有个很复杂的 View 需要做动画,避免对业务的干扰最好的方式就是在外面嵌套一个 View,然后对这个外面的 View 做动画就好了。

AOSP 的设计也确实如此,会创建一个 “leash” 图层,然后把需要做动画的图层挂载到其下面,再对 “leash” 图层做动画。
Leash 丢给百度翻译,解释为:(牵狗的)皮带。(瞬间就有了一个人用皮带牵着一条狗的画面了),其实这个就是 AOSP 单独为动画创建了一个图层,然后把需要做动画的容器图层挂载到这个图层下,那么就也有了动画效果。如果看到 leash 图层这个名词,指的就是做动画的那个图层了。

结合实际 leash 图层创建前后的层级关系看看:
leash图层创建前:

在这里插入图片描述
leash图层创建后:

在这里插入图片描述
对比可以看到要开始做动画的时候,Task 上面出现了一个name为 “animation-leash of app_transition ”的图层,这个就是 “leash”图层。
前面的 “animation-leash of” 是固定的,后面的 “ app_transition ” 是这次动画的类型这些后面都会看到代码的定义,目前有个了解即可。
画个图总结一下动画前后的图层改变:

在这里插入图片描述

这种设计符合六大原则的单一原则,需要动画就单独拿一个图层来做动画,动画结束后再恢复到原来的层级结构。

当前这只是举个例子,不是说每次 leash 图层都是在 Task 上的,这个是需要看具体情况的, AOSP 有个方法会对不同场景计算出这个 leash 图层需要创建在哪里。
比如远端动画就会对创建在 Taks 上面,对整个 Task 做动画,而 window_animation 一般就是在 WindowToken 或者 WindowState 上做动画。

“leash”图层的命令与创建

上面看到的图层命令,是需要通过看前面的Surface Name 才能确定是哪个窗口的图层,也就是出现在前面的 “Surface(name= XXX” 这个图层的名字,这个看窗口三部曲的时候提过,都是在 SurfaceControl::setName 进行设置的。

至于后面的 " - animation-leash of " 固定的,然后就是 “app_transition” ,这个是根据动画类型定义的,映射的方法如下:

# SurfaceAnimator
    static String animationTypeToString(@AnimationType int type) {
        switch (type) {
            case ANIMATION_TYPE_NONE: return "none";
            case ANIMATION_TYPE_APP_TRANSITION: return "app_transition"; // 应用间切换动画
            case ANIMATION_TYPE_SCREEN_ROTATION: return "screen_rotation"; // 屏幕旋转动画
            case ANIMATION_TYPE_DIMMER: return "dimmer"; // 调光动画
            case ANIMATION_TYPE_RECENTS: return "recents_animation"; // 最近任务动画(没发现具体场景,不是从最近任务列表点击A)
            case ANIMATION_TYPE_WINDOW_ANIMATION: return "window_animation"; // 窗口动画,比如窗口移除
            case ANIMATION_TYPE_INSETS_CONTROL: return "insets_animation"; // 插入动画,但是官方注释说这其实不是一个动画
            case ANIMATION_TYPE_TOKEN_TRANSFORM: return "token_transform"; // 动画类型转换
            case ANIMATION_TYPE_STARTING_REVEAL: return "starting_reveal"; // 窗口要显示前的动画
            default: return "unknown type:" + type;
        }
    }

这段代码里看到是根据传入的type值返回一个字符串,看看使用的地方:

# SurfaceAnimator
    static SurfaceControl createAnimationLeash(Animatable animatable, SurfaceControl surface,
            Transaction t, @AnimationType int type, int width, int height, int x, int y,
            boolean hidden, Supplier<Transaction> transactionFactory) {
        // 自己加的堆栈
        android.util.Log.e("biubiubiu", "SurfaceAnimator  createAnimationLeash: "+animationTypeToString(type), new Exception());
        // 日志
        ProtoLog.i(WM_DEBUG_ANIM, "Reparenting to leash for %s", animatable);
        final SurfaceControl.Builder builder = animatable.makeAnimationLeash()
                .setParent(animatable.getAnimationLeashParent()) //设置父节点
                .setName(surface + " - animation-leash of " + animationTypeToString(type)) //命名
                .setHidden(hidden)
                .setEffectLayer()
                .setCallsite("SurfaceAnimator.createAnimationLeash");
        ......
        return leash; // 返回leash图层
    }

这个方法后面还会单独详细分析,当前只看setName这一块,看得出来 Winscope 信息中的信息和这里的格式是匹配上的。前面是“surface”的名字,然后拼上一个 “animation-leash of” ,最后面就是根据type返回一个类型,比如"app_transition"。
这里也有响应的日志来说明创建了哪个窗口的 leash 图层。

Winscope流程

先使用 Winscope 工具观察图层的改变,提取关键点的截图如下:

在这里插入图片描述
这个是默认状态,点击图标后,就会有以下改变:
在这里插入图片描述
可以看到出现了3个动画

  • 1、壁纸的 window_animation
  • 2、launcher 对应 Task 的 app_transition(退出)
  • 3、“电话” 对应 Task 的 app_transition(打开)

这3个动画从 Winscope 看几户是同一帧出现的,稍后从日志上看,也几户是同时执行的。(所以不必纠结这3个的先后顺序)

这3个动画里,最关心的是 “电话” 的打开动画,可以在左边看到下部分已经有一个小矩形出现,结合右边点击的 Visiable 可以确定这快 surface 显示的可见内容其实是
Splash Screen 的 Window。(应用窗口这会还没添加上来。)

根据之前的源码,是先出现Task, 再挂载 ActivityRecord 然后出现 Splash Screen 。

后面一段都是动画执行的过程,主要是这个 Splash Screen 的内容是从小放大到全屏,这个和用户实际看到的视觉效果也是匹配的。

在这里插入图片描述

这里只是截取了其中的一个过程。

这个时候是动画执行的过程截取的图,可以看到 Splash Screen 的 window 已经很大了,即将铺满全屏。

等动画结束后(当前抓到的是3个动画在同一帧结束),就剩下“电话” 对应 Task 相关的图层了,其他的都不可见了。

在这里插入图片描述
在这里插入图片描述
这个时候出现了3个图层–动画的leash图层–窗口容器图层,以及窗口真正的显示图层。

所以可以知道这个 starting_reveal 动画是真正要显示内容前出现的:

从可见性上,starting_reveal 这个动画图层和 Splash Screen 是同级的,但是这个时候 Splash Screen 的窗口从容器顺序上是盖在 starting_reveal 图层上面的

然后就是执行一段时间的 starting_reveal 动画。这个动画结束后说明应用窗口已经要显示了,那么就需要移除 Splash Screen 了,于是开始StartWindow移除动画。

这只是我当前这次抓取的 Winscope 信息,不过不是每次都是这样的。 但是大致流程是一样的,具体的动画出现和结束的时机可能会有点区别,比如某一帧 window_animation 图层已经出现了,但是starting_reveal 图层还在,下一帧才移除。这种1,2帧的差距很正常。

最后的这个状态就是应用已经完全启动展示最后的 Activity 的样子了,其他的窗口都不可见了(最重要的是动画结束后Splash Screen 也移除了)。

在这里插入图片描述

小结

上面截取了各个关键节点的图,发现一共出现了5个动画,关于壁纸和 launcher 的可以先不关注,就启动的这个应用来说分为以下几步:

  • 1、有一个 app 打开的动画,app_transition ,这个时候显示的内容是并不是应用窗口,而是 Splash Screen 的这个 STtartWindow 。

  • 2、app_transition 动画结束后不久,应用窗口绘制后将要显示了,这个时候显示的是 starting_reveal 动画

  • 3、starting_reveal 动画结束后,开始的是移除 Splash Screen 的 window_animation 动画

  • 4、最终显示的是应用的窗口

在这里插入图片描述
不过也可能会有一两帧是几个通话同时存在的,所以也可能抓到的 Winscope 是下面这种图:

在这里插入图片描述

动画流程概览分析

前面的分析提过动画会创建 leash 图层,也就是会执行 SurfaceAnimator::createAnimationLeash 方法,我本地代码加上了堆栈。

然后需要过滤掉 “insets_animation” 类型的动画,因为官方注释也说了这个其实不是动画。然后上一节看 Winscope 也确实没看到 “insets_animation” 相关的图层。 过滤后可以得到下面这些日志:

biubiubiu: SurfaceAnimator  createAnimationLeash: app_transition
biubiubiu: SurfaceAnimator  createAnimationLeash: app_transition
biubiubiu: SurfaceAnimator  createAnimationLeash: window_animation
biubiubiu: SurfaceAnimator  createAnimationLeash: starting_reveal
biubiubiu: SurfaceAnimator  createAnimationLeash: window_animation

看到依次有这5个动画的创建,这个和上面分析的 Winscope 看到的也是对应上了。

这5个动画具体的体现,需要在开发者选项放慢动画时间,才能看的比较清楚,整理了一下对应的动画效果如下:

SurfaceAnimator  createAnimationLeash: app_transition     dialer Task 》dialer的打开动画     从小放大
SurfaceAnimator  createAnimationLeash: app_transition     home   Task 》launcher的关闭动画   略微放大
SurfaceAnimator  createAnimationLeash: window_animation   Wallpaper 的动画, 但没看到具体的效果
SurfaceAnimator  createAnimationLeash: starting_reveal    应用窗口WindowState 要显示时的动画
SurfaceAnimator  createAnimationLeash: window_animation   StartWindow 移除动画

上面的这个5个动画主要分为3大部分:

  • 1、Activity启动最新出现的 app_transition 动画。
    这个阶段会出现3个动画:app_transition(应用),app_transition(桌面),window_animation(壁纸)

  • 2、应用窗口WindowState 要显示时的 starting_reveal 动画

  • 3、移除 StartWindow 的 window_animation 动画

第一部分重点介绍的是应用的 app_transition 动画,这个过程中也会涉及到另外2个。 其中桌面的关闭动画也是 app_transition 类型,所以流程和应用的启动 app_transition 动画流程大致一样的。只有最后 launcher 开始动画的时候做了一下区别。

不过壁纸的 window_animation 在 WallpaperAnimationAdapter 这个专门给壁纸做动画的 Adapter 并没有看到真正的动画执行,然后这边也看到其作为 wallpaperTargets 也传递到 launcher 了,但是最后也没发现有做动画的地方。这点就很奇怪,不过在 Winscope 也只是看到 有 window_animation 的图层,但是也没看到有相关数值的改变,所以个人觉得壁纸的 window_animation 可能并没有什么实际的动画。

在分析应用启动 app_transition 流程的代码前先看看下面的这几个事件,这个只是我个人在撸完整个动画流程,从代码执行顺序上列出来的9个节点。目前不知道是啥没关系,毕竟这只是我个人整理的,不是什么权威的关键节点,不过接下来的代码分析也会一个个的看到。

Activity启动app_transition 动画的主要事件

  • 1、launcher 进程构建 RemoteAnimationAdapter,AppLaunchAnimationRunner

  • 2、prepareAppTransition 流程

  • 3、executeAppTransition,AppTransition.setReady 流程

  • 4、 GOOD TO GO 打印

  • 5、system_service 创建动画leash 图层

  • 6、goodToGo()流程,真正触发远端动画执行

  • 7、launcher 开始远端动画

  • 8、launcher 具体动画的update

  • 9、launcher 动画结束,回调到system_service

后面的2部分相对简单,看对应的具体分析即可。

触发动画执行的套路

不管什么类型的动画都会执行:

WindowContainer::startAnimation
    SurfaceAnimator::startAnimation

会在 SurfaceAnimator::startAnimation 方法中创建动画 leash 图层,并通过 Adapter 来开始动画。然后才是适配器模式各自动画的 Adapter 做自己的处理。

一般:

  • 本地动画就是直接开始执行
  • 远端动画则是会在 goodToGo 触发远端执行

动画真正执行

动画的执行是适配器模式,但是真正干活的也不会是这个 Adapter 。
一般都是 Adapter + Runner 模式,真正干活的是这个 Runner 。
比如本地动画就是 LocalAnimationAdapter + SurfaceAnimationRunner
而分析的应用启动动画是远端动画,它们的组合是是 RemoteAnimationAdapter + LauncherAnimationRunner

动画的结束回调

目前看到的本地动画和远端动画,都会执行到 SurfaceAnimator::startAnimation 。而每个窗口构建的时候都会创建一个 SurfaceAnimator ,并且专递一个动画结束回调(WindowContainer::onAnimationFinished)过去。这个回调被封装成在 mInnerAnimationFinishedCallback 变量了。

所以本地动画和远端动画的结束回调,执行都是 WindowContainer::onAnimationFinished 方法。而这个方法最终又会执行到 WindowManagerService::onAnimationFinished

触发远端动画的Target

远端动画执行前会打印这次操作了哪些图层: 这段日志会打印动画的 Target,输出如下:

// 触发远程动画,打印传递过去的3个类型的leash图层数量
D WindowManager: goodToGo(): onAnimationStart, transit=TRANSIT_OLD_WALLPAPER_CLOSE, apps=2, wallpapers=1, nonApps=0

D WindowManager: startAnimation(): Notify animation start:
I WindowManager: Starting remote animation
// 传递到远端的动画图层打印

I WindowManager: container=Task{cdcc410 #1 type=home ?? U=0 visible=false visibleRequested=false mode=fullscreen translucent=true sz=1}
// 桌面的
I WindowManager: Target:
I WindowManager:   mode=1 taskId=8 isTranslucent=false clipRect=[0,0][0,0] contentInsets=[0,70][0,84] prefixOrderIndex=16 position=[0,0] sourceContainerBounds=[0,0][720,1600] screenSpaceBounds=[0,0][720,1600] localBounds=[0,0][720,1600]
I WindowManager:   windowConfiguration={ mBounds=Rect(0, 0 - 720, 1600) mAppBounds=Rect(0, 70 - 720, 1516) mMaxBounds=Rect(0, 0 - 720, 1600) mDisplayRotation=ROTATION_0 mWindowingMode=fullscreen mDisplayWindowingMode=fullscreen mActivityType=home mAlwaysOnTop=undefined mRotation=ROTATION_0}
I WindowManager:   leash=Surface(name=Surface(name=Task=1)/@0x1842ced - animation-leash of app_transition)/@0x21da6b6
I WindowManager:   taskInfo=TaskInfo{userId=0 taskId=8 displayId=0 isRunning=true baseIntent=Intent { act=android.intent.action.MAIN cat=[android.intent.category.HOME] flg=0x10208000 cmp=com.android.launcher3/.uioverrides.QuickstepLauncher } baseActivity=ComponentInfo{com.android.launcher3/com.android.launcher3.uioverrides.QuickstepLauncher} topActivity=ComponentInfo{com.android.launcher3/com.android.launcher3.uioverrides.QuickstepLauncher} origActivity=null realActivity=ComponentInfo{com.android.launcher3/com.android.launcher3.uioverrides.QuickstepLauncher} numActivities=1 lastActiveTime=500834 supportsSplitScreenMultiWindow=true supportsMultiWindow=true resizeMode=2 isResizeable=true minWidth=-1 minHeight=-1 defaultMinSize=220 token=WCT{RemoteToken{d94d7b1 Task{512e747 #8 type=home I=com.android.launcher3/.uioverrides.QuickstepLauncher U=0 rootTaskId=1 visible=false visibleRequested=false mode=fullscreen translucent=true sz=1}}} topActivityType=2 pictureInPictureParams=null shouldDockBigOverlays=false launchIntoPipHostTaskId=0 displayCutoutSafeInsets=Rect(0, 70 - 0, 0) topActivityInfo=ActivityInfo{5fdf496 com.android.launcher3.uioverrides.QuickstepLauncher} launchCookies=[] positionInParent=Point(0, 0) parentTaskId=-1 isFocused=false isVisible=false isSleeping=false topActivityInSizeCompat=false topActivityEligibleForLetterboxEducation= false locusId=LocusId[17_chars] displayAreaFeatureId=1 cameraCompatControlState=hidden}
I WindowManager:   allowEnterPip=true
I WindowManager:   windowType=-1  hasAnimatingParent=false  backgroundColor=0container=Task{3a0b2b8 #13 type=standard A=10140:com.google.android.dialer U=0 visible=true visibleRequested=true mode=fullscreen translucent=false sz=1}

// “电话”应用的
I WindowManager: Target:
I WindowManager:   mode=0 taskId=13 isTranslucent=false clipRect=[0,0][0,0] contentInsets=[0,70][0,84] prefixOrderIndex=20 position=[0,0] sourceContainerBounds=[0,0][720,1600] screenSpaceBounds=[0,0][720,1600] localBounds=[0,0][720,1600]
I WindowManager:   windowConfiguration={ mBounds=Rect(0, 0 - 720, 1600) mAppBounds=Rect(0, 70 - 720, 1516) mMaxBounds=Rect(0, 0 - 720, 1600) mDisplayRotation=ROTATION_0 mWindowingMode=fullscreen mDisplayWindowingMode=fullscreen mActivityType=standard mAlwaysOnTop=undefined mRotation=ROTATION_0}
I WindowManager:   leash=Surface(name=Surface(name=Task=13)/@0xd0a4264 - animation-leash of app_transition)/@0x62f9fb7
I WindowManager:   taskInfo=TaskInfo{userId=0 taskId=13 displayId=0 isRunning=true baseIntent=Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 pkg=com.google.android.dialer cmp=com.google.android.dialer/.extensions.GoogleDialtactsActivity } baseActivity=ComponentInfo{com.google.android.dialer/com.google.android.dialer.extensions.GoogleDialtactsActivity} topActivity=ComponentInfo{com.google.android.dialer/com.android.dialer.main.impl.MainActivity} origActivity=ComponentInfo{com.google.android.dialer/com.google.android.dialer.extensions.GoogleDialtactsActivity} realActivity=ComponentInfo{com.google.android.dialer/com.android.dialer.main.impl.MainActivity} numActivities=1 lastActiveTime=500849 supportsSplitScreenMultiWindow=true supportsMultiWindow=true resizeMode=2 isResizeable=true minWidth=-1 minHeight=-1 defaultMinSize=220 token=WCT{RemoteToken{37af324 Task{3a0b2b8 #13 type=standard A=10140:com.google.android.dialer U=0 visible=true visibleRequested=true mode=fullscreen translucent=false sz=1}}} topActivityType=1 pictureInPictureParams=null shouldDockBigOverlays=false launchIntoPipHostTaskId=0 displayCutoutSafeInsets=Rect(0, 70 - 0, 0) topActivityInfo=ActivityInfo{346ae8d com.google.android.dialer.extensions.GoogleDialtactsActivity} launchCookies=[android.os.BinderProxy@c557842] positionInParent=Point(0, 0) parentTaskId=-1 isFocused=true isVisible=true isSleeping=false topActivityInSizeCompat=false topActivityEligibleForLetterboxEducation= false locusId=null displayAreaFeatureId=1 cameraCompatControlState=hidden}
I WindowManager:   allowEnterPip=true
I WindowManager:   windowType=-1  hasAnimatingParent=false  backgroundColor=0

这些日志里有很多关键信息,可以在遇到问题的时候看,数据太多就不一一介绍了,继续看日志是在哪里控制的吧。

触发远端动画的流程在 RemoteAnimationController::goodToGo 这些日志的打印也在这,忽略掉无关代码再看一下这个方法:

# RemoteAnimationController
    // 远端动画的Adapter
    private final RemoteAnimationAdapter mRemoteAnimationAdapter;

    void goodToGo(@WindowManager.TransitionOldType int transit) {
        // 打印goodToGo(),表现这才是真正的触发了goodToGo()逻辑
        ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "goodToGo()");
        ......
                // 打印日志,真正开始触发动画
                ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "goodToGo(): onAnimationStart,"
                                + " transit=%s, apps=%d, wallpapers=%d, nonApps=%d",
                        AppTransition.appTransitionOldToString(transit), appTargets.length,
                        wallpaperTargets.length, nonAppTargets.length);
                // 重点* 3. 这里是触发远端动画真正执行的地方
                mRemoteAnimationAdapter.getRunner().onAnimationStart(transit, appTargets,
                        wallpaperTargets, nonAppTargets, mFinishedCallback);
            ......
            // 日志处理
            if (ProtoLogImpl.isEnabled(WM_DEBUG_REMOTE_ANIMATIONS)) {
                ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "startAnimation(): Notify animation start:");
                writeStartDebugStatement();
            }
        ......
    }

可以看到前面2个打印都在这,后续的打印是 RemoteAnimationController::writeStartDebugStatement 方法里触发的。

# RemoteAnimationController

    private void writeStartDebugStatement() {
        ProtoLog.i(WM_DEBUG_REMOTE_ANIMATIONS, "Starting remote animation");
        // 打印内容
        final StringWriter sw = new StringWriter();
        final FastPrintWriter pw = new FastPrintWriter(sw);
        for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
            // 触发每个适配器的dump
            mPendingAnimations.get(i).mAdapter.dump(pw, "");
        }
        pw.close();
        ProtoLog.i(WM_DEBUG_REMOTE_ANIMATIONS, "%s", sw.toString());
    }

可以看到整理的打印是从 mPendingAnimations 下的对象取出对应的 RemoteAnimationRecord 相关的变量然后dump的。
mPendingAnimations 在应用启动动画-app_transition-3 的时候看到,是构建一个 RemoteAnimationRecord 对象就会把其添加进 mPendingAnimations 。
这里也有打印,比如当前场景的日志输出为:

D WindowManager: createAnimationAdapter(): container=Task{8d3f87a #20 type=standard A=10140:com.google.android.dialer U=0 visible=true visibleRequested=true mode=fullscreen translucent=false sz=1}
D WindowManager: createAnimationAdapter(): container=Task{7bfafb5 #1 type=home ?? U=0 visible=false visibleRequested=false mode=fullscreen translucent=true sz=1}

这个和之前的分析是一样的, 一个是新启动应用的Task 一个是 launcher 的。所以只有2个,注意,没有壁纸的,前面的日志也没看壁纸的Target。 所以壁纸动画到底是本地还是远端呢?有点迷了。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vue3 + Ant design vue formItem 无法使用嵌套的form表单校验

文章目录 前言一、背景在这里插入代码片二、操作步骤1.复现前的准备工作&#xff08;1&#xff09;vue版本和ant design vue 版本&#xff08;2&#xff09;任意页面的代码 2.解决问题3.自定义表单校验的代码 总结 前言 提示&#xff1a; 一、背景在这里插入代码片 背景&…

音视频入门基础:FLV专题(13)——FFmpeg源码中,解析任意Type值的SCRIPTDATAVALUE类型的实现

一、SCRIPTDATAVALUE类型 从《音视频入门基础&#xff1a;FLV专题&#xff08;9&#xff09;——Script Tag简介》中可以知道&#xff0c;根据《video_file_format_spec_v10_1.pdf》第80到81页&#xff0c;SCRIPTDATAVALUE类型由一个8位&#xff08;1字节&#xff09;的Type和…

go语言protoc的详细用法与例子

一. 原来的项目结构 二. 选择源proto文件及其目录&目的proto文件及其目录 在E:\code\go_test\simple_demo\api 文件夹下&#xff0c;递归创建\snapshot\helloworld\v1\ad.pb.go E:\code\go_test\simple_demo> protoc --go_outpathssource_relative:./api .\snapshot\h…

数据结构--二叉树的顺序实现(堆实现)

引言 在计算机科学中&#xff0c;二叉树是一种重要的数据结构&#xff0c;广泛应用于各种算法和程序设计中。本文将探讨二叉树的顺序实现&#xff0c;特别是堆的实现方式。 一、树 1.1树的概念与结构 树是⼀种⾮线性的数据结构&#xff0c;它是由 n(n>0) 个有限结点组成…

新款平行进口奔驰GLS450升级原厂AR实景导航人机交互行车记录仪等功能

平行进口的24款奔驰GLS450升级原厂中规导航主机通常具备以下功能&#xff1a; 人机交互系统&#xff1a;该导航主机配备了人机交互系统&#xff0c;可以通过触摸屏、旋钮或语音控制等方式与导航系统进行交互&#xff0c;方便驾驶者进行导航设置和操作。 实景AR导航&#xff1…

使用 classification_report 评估 scikit-learn 中的分类模型

介绍 在机器学习领域&#xff0c;评估分类模型的性能至关重要。scikit-learn 是一个功能强大的 Python 机器学习工具&#xff0c;提供了多种模型评估工具。其中最有用的函数之一是 classification_report&#xff0c;它可以全面概述分类模型的关键指标。在这篇文章中&#xff…

字符串和字符数组(1)

1.字符串和\0 C语言中有字符类型&#xff0c;但没有字符串类型&#xff0c;C语言中字符串就是由双引号引起来的一串字符&#xff0c;比如&#xff1a;"asdf"&#xff1b; 一个字符串中我们能直观的看到一些字符&#xff0c;比如&#xff1a;字符串常量"asdfgh…

三、Java AI 编程助手

AI 对于我们来说是一个高效的编程助手&#xff0c;给我们提供了有效的建议和解决方案&#xff0c;高效利用&#xff0c;无疑是如虎添翼。接下来为大家推荐一个 AI 编程助手。 Fitten Code 1、简介 Fitten Code 免费且支持 80 多种语言&#xff1a;Python、C、Javascript、Type…

2024.9.29 问卷数据分析

最近拿到了一份受众回访的问卷数据&#xff0c;排到的任务是对它进行数据探索。 其实对于问卷数据的处理我只在参加正大杯那次做过&#xff08;正大杯拿了校三&#xff09;&#xff0c;可见这个处理水平还有待提高&#xff08;当然是各种原因促成的结果&#xff09;&#xff0…

python配置环境变量

方法一&#xff1a;首先卸载重新安装&#xff0c;在安装时勾选增加环境变量 方法二&#xff1a;我的电脑-属性-高级系统配置 手动添加环境变量&#xff0c;路径为python的安装路径 检查&#xff1a;查看环境变量是否安装成功 安装第三方lib winr&#xff0c;输入cmd pip ins…

[SAP ABAP] 数据元素添加参数ID(Parameter ID)

学生表(ZDBT_STU_437) 示例&#xff1a;为学生表ZDBT_STU_437中的数据元素ZDE_STUID_437创建Parameter ID 1.使用事务码SM30维护TPARA表 新建参数ID并输入简短描述 点击保存按钮&#xff0c;选择指定的包即可生成参数ID 2.参数ID和数据元素绑定 使用SE11对学生表(ZDBT_STU_…

小程序 uniapp+Android+hbuilderx体育场地预约管理系统的设计与实现

目录 项目介绍支持以下技术栈&#xff1a;具体实现截图HBuilderXuniappmysql数据库与主流编程语言java类核心代码部分展示登录的业务流程的顺序是&#xff1a;数据库设计性能分析操作可行性技术可行性系统安全性数据完整性软件测试详细视频演示源码获取方式 项目介绍 用户 注册…

【ubuntu】ubuntu20.04安装chrome浏览器

1.下载 https://download.csdn.net/download/qq_35975447/89842972 https://www.google.cn/chrome/ 2.安装 sudo dpkg -i google-chrome-stable_current_amd64.deb 3.使用

vscode配置R语言debugger环境:“vscDebugger“的安装

要在 R 中安装 vscDebugger 包&#xff0c;可以按照以下步骤进行&#xff1a; 方法一&#xff1a;使用命令面板自动安装 打开命令面板&#xff1a; 在 Visual Studio Code 中按 CtrlShiftP 打开命令面板。 运行安装命令&#xff1a; 在命令面板中输入并选择 r.debugger.updat…

DES算法的详细描述和C语言实现

访问www.tomcoding.com网站&#xff0c;学习Oracle内部数据结构&#xff0c;详细文档说明&#xff0c;下载Oracle的exp/imp&#xff0c;DUL&#xff0c;logminer&#xff0c;ASM工具的源代码&#xff0c;学习高技术含量的内容。 前言 很久以前用汇编语言实现过DES算法&#x…

UE4 材质学习笔记02(数据类型/扭曲着色器)

一.什么是数据类型 首先为啥理解数据类型是很重要的。一些节点的接口插槽只接受特定类型的数据&#xff0c;如果连接了不匹配的数据就会出现错误&#xff0c;有些接口可以接受任何数据类型&#xff0c;但是实际上只会使用到其中的一些。并且有时可以将多个数据流合并成一个来编…

《python语言程序设计》2018版第8章19题几何Rectangle2D类(上)--原来我可以直接调用

2024.9.29 玩了好几天游戏。 感觉有点灵感了。还想继续玩游戏。 2024.10.4 今天练习阿斯汤加练完从早上10点睡到下午2点.跑到单位玩游戏玩到晚上10点多. 现在回家突然有了灵感 顺便说一句,因为后弯不好,明天加练一次. 然后去丈母娘家. 加油吧 第一章、追求可以外调的函数draw_r…

Spring Boot集成encache快速入门Demo

1.什么是encache EhCache 是一个纯 Java 的进程内缓存框架&#xff0c;具有快速、精干等特点&#xff0c;是 Hibernate 中默认的 CacheProvider。 Ehcache 特性 优点 快速、简单支持多种缓存策略&#xff1a;LRU、LFU、FIFO 淘汰算法缓存数据有两级&#xff1a;内存和磁盘&a…

【Git】vscode链接github拉去镜像

1.拉取别人的项目到自己的仓库 2.回到自己的仓库拉取文件到vscode里面下载 使用vscode进入虚拟机 推送到自己的仓库上面 在 github 页面将修改的内容 PR 到 Tutorial 创建一个个人仓库 代码如下 cd demo git clone https://github.com/3154067760/Tutorial.git cd Tutorial/…

贴吧软件怎么切换ip

在网络使用中&#xff0c;有时我们需要切换IP地址来满足特定的需求&#xff0c;比如需要切换贴吧软件IP以进行不同的操作。本文将介绍几种贴吧切换IP地址的方法&#xff0c;帮助用户更好地管理自己的网络身份和访问权限。 1、更换网络环境‌ 通过连接到不同的Wi-Fi网络或使用移…