【Android 13源码分析】WindowContainer窗口层级-3-实例分析

news2024/9/26 1:17:33

在安卓源码的设计中,将将屏幕分为了37层,不同的窗口将在不同的层级中显示。
对这一块的概念以及相关源码做了详细分析,整理出以下几篇。

【Android 13源码分析】WindowContainer窗口层级-1-初识窗口层级树

【Android 13源码分析】WindowContainer窗口层级-2-构建流程

【Android 13源码分析】WindowContainer窗口层级-3-实例分析

【Android 13源码分析】WindowContainer窗口层级-4-Surface树

当前为第三篇,以应用窗口和系统窗口2大类型窗口的挂载为例介绍窗口是如何挂载到层级树中的。
这篇看完对AOSP中整个窗口树就有了比较完整的了解。

1. 应用窗口挂载

应用启动流程中会触发ActivityRecord,Task,WindowState的创建与挂载,其中WindowState的处理上在addWindow流程。

ActivityTaskManagerService::startActivity
    ActivityTaskManagerService::startActivityAsUser
        ActivityTaskManagerService::startActivityAsUser
            ActivityStartController::obtainStarter
                ActivityStarter::execute
                    ActivityStarter::executeRequest -- 构建 ActivityRecord --2.2.1 创建ActivityRecord
                        ActivityStarter::startActivityUnchecked
                            ActivityStarter::startActivityInner        -- 2.2.2 关键函数startActivityInner
                                ActivityStarter::getOrCreateRootTask   -- 2.2.2.1 创建或者拿到Task
                                ActivityStarter::setNewTask            -- 2.2.2.2 将task与activityRecord 绑定
                                RootWindowContainer::resumeFocusedTasksTopActivities    --2.2.2.3 显示Activity

addWindow流程调用链:
WindowManagerImpl::addView           --- 创建ViewRootImpl
    WindowManagerGlobal::addView   
        ViewRootImpl::setView        --- 与WMS通信 addView
            Session.addToDisplayAsUser
                WindowManagerService::addWindow
                    WindowState::init           -- WindowState的创建
                    WindowToken::addWindow      -- WindowState的挂载

1.1 ActivityRecord的创建

启动流程开始的时候会执行到ActivityStarter::executeRequest,在这个方法里会创建一个ActivityRecord

# ActivityStarter
  private int executeRequest(Request request) {
        ......
        final ActivityRecord r = new ActivityRecord.Builder(mService)
                .setCaller(callerApp)
                .setLaunchedFromPid(callingPid)
                .setLaunchedFromUid(callingUid)
                .setLaunchedFromPackage(callingPackage)
                .setLaunchedFromFeature(callingFeatureId)
                .setIntent(intent)
                .setResolvedType(resolvedType)
                .setActivityInfo(aInfo)
                .setConfiguration(mService.getGlobalConfiguration())
                .setResultTo(resultRecord)
                .setResultWho(resultWho)
                .setRequestCode(requestCode)
                .setComponentSpecified(request.componentSpecified)
                .setRootVoiceInteraction(voiceSession != null)
                .setActivityOptions(checkedOptions)
                .setSourceRecord(sourceRecord)
                .build();
        ......
        // 继续执行startActivityUnchecked
        mLastStartActivityResult = startActivityUnchecked(r, sourceRecord, voiceSession,
                request.voiceInteractor, startFlags, true /* doResume */, checkedOptions,
                inTask, inTaskFragment, restrictedBgActivity, intentGrants);
        ......
  }

tips: ActivityRecord的构造方法会创建一个Token,这个token就是阅读源码经常看到看到代表activity的那个token。

1.2 Task的创建与挂载

流程开始会执行到ActivityStarter::startActivityInner方法,在这里会执行ActivityStarter::getOrCreateRootTask方法来创建(获取)一个Task

调用链如下:

ActivityStarter::getOrCreateRootTask
    RootWindowContainer::getOrCreateRootTask
        RootWindowContainer::getOrCreateRootTask
            TaskDisplayArea::getOrCreateRootTask
                TaskDisplayArea::getOrCreateRootTask
                    Task::Build     ---创建Task

主流程代码

    # ActivityStarter
        private Task getOrCreateRootTask(ActivityRecord r, int launchFlags, Task task,
                ActivityOptions aOptions) {
            final boolean onTop =
                    (aOptions == null || !aOptions.getAvoidMoveToFront()) && !mLaunchTaskBehind;
            final Task sourceTask = mSourceRecord != null ? mSourceRecord.getTask() : null;
            return mRootWindowContainer.getOrCreateRootTask(r, aOptions, task, sourceTask, onTop,
                    mLaunchParams, launchFlags);
        }
    // onTop 表示是否要移到到当前栈顶,那肯定是要的,新启动的Activity当前要再最上面,这里 aOptions 为null,所以为true
    // sourceTask 表示从哪里启动的,当前launch所在的Task 就是sourceTask

    # RootWindowContainer
        Task getOrCreateRootTask(@Nullable ActivityRecord r, @Nullable ActivityOptions options,
                @Nullable Task candidateTask, boolean onTop) {
            return getOrCreateRootTask(r, options, candidateTask, null /* sourceTask */, onTop,
                    null /* launchParams */, 0 /* launchFlags */);
        }
        Task getOrCreateRootTask(@Nullable ActivityRecord r,
                @Nullable ActivityOptions options, @Nullable Task candidateTask,
                @Nullable Task sourceTask, boolean onTop,
                @Nullable LaunchParamsController.LaunchParams launchParams, int launchFlags) {
                ......
                final int activityType = resolveActivityType(r, options, candidateTask);
                if (taskDisplayArea != null) {
                    if (canLaunchOnDisplay(r, taskDisplayArea.getDisplayId())) {
                        // 重点*1. 传递到TaskDisplayArea
                        return taskDisplayArea.getOrCreateRootTask(r, options, candidateTask,
                                sourceTask, launchParams, launchFlags, activityType, onTop);
                    } else {
                        taskDisplayArea = null;
                    }
                }
                ......
        }
    // 经过同名调用后,逻辑进入到  TaskDisplayArea

    # TaskDisplayArea
        Task getOrCreateRootTask(int windowingMode, int activityType, boolean onTop,
                @Nullable Task candidateTask, @Nullable Task sourceTask,
                @Nullable ActivityOptions options, int launchFlags) {
                if(....) {
                    // 拿到之前创建的Task
                    return candidateTask.getRootTask();
                }
                ......// 第一次显示所以是新建Task
                return new Task.Builder(mAtmService)
                    .setWindowingMode(windowingMode)
                    .setActivityType(activityType)
                    .setOnTop(onTop)
                    .setParent(this)  // 主要这个this被设置为Parent。所以直接挂载到了DefaultTaskDisplayArea下
                    .setSourceTask(sourceTask)
                    .setActivityOptions(options)
                    .setLaunchFlags(launchFlags)
                    .build();
        }
    // 看方法名是获取或创建Task, 这边是新启动的Activity所以需要创建Task。如果是以默认启动方式打开应用内的另一个Activity,就走的是上面的 return candidateTask.getRootTask();
    接下来就是真正触发Task的创建。
    // 另外设置的parent就是层级结构树应用所在的名为“DefaultTaskDisplayArea”的TaskDisplayArea
    # Task
        # Task.Builder
            Task build() {
                if (mParent != null && mParent instanceof TaskDisplayArea) {
                    validateRootTask((TaskDisplayArea) mParent);
                }

                if (mActivityInfo == null) {
                    mActivityInfo = new ActivityInfo();
                    mActivityInfo.applicationInfo = new ApplicationInfo();
                }

                mUserId = UserHandle.getUserId(mActivityInfo.applicationInfo.uid);
                mTaskAffiliation = mTaskId;
                mLastTimeMoved = System.currentTimeMillis();
                mNeverRelinquishIdentity = true;
                mCallingUid = mActivityInfo.applicationInfo.uid;
                mCallingPackage = mActivityInfo.packageName;
                mResizeMode = mActivityInfo.resizeMode;
                mSupportsPictureInPicture = mActivityInfo.supportsPictureInPicture();
                if (mActivityOptions != null) {
                    mRemoveWithTaskOrganizer = mActivityOptions.getRemoveWithTaskOranizer();
                }
                // 重点* 1. 创建task
                final Task task = buildInner();
                task.mHasBeenVisible = mHasBeenVisible;

                // Set activity type before adding the root task to TaskDisplayArea, so home task can
                // be cached, see TaskDisplayArea#addRootTaskReferenceIfNeeded().
                if (mActivityType != ACTIVITY_TYPE_UNDEFINED) {
                    task.setActivityType(mActivityType);
                }
                // 重点* 2. 入栈 这里的 mOnTop为true
                if (mParent != null) {
                    if (mParent instanceof Task) {
                        final Task parentTask = (Task) mParent;
                        parentTask.addChild(task, mOnTop ? POSITION_TOP : POSITION_BOTTOM,
                                (mActivityInfo.flags & FLAG_SHOW_FOR_ALL_USERS) != 0);
                    } else {
                        mParent.addChild(task, mOnTop ? POSITION_TOP : POSITION_BOTTOM);
                    }
                }

                // Set windowing mode after attached to display area or it abort silently.
                if (mWindowingMode != WINDOWING_MODE_UNDEFINED) {
                    task.setWindowingMode(mWindowingMode, true /* creating */);
                }
                // 返回
                return task;
            }

            // 创建
            Task buildInner() {
                return new Task(mAtmService, mTaskId, mIntent, mAffinityIntent, mAffinity,
                        mRootAffinity, mRealActivity, mOrigActivity, mRootWasReset, mAutoRemoveRecents,
                        mAskedCompatMode, mUserId, mEffectiveUid, mLastDescription, mLastTimeMoved,
                        mNeverRelinquishIdentity, mLastTaskDescription, mLastSnapshotData,
                        mTaskAffiliation, mPrevAffiliateTaskId, mNextAffiliateTaskId, mCallingUid,
                        mCallingPackage, mCallingFeatureId, mResizeMode, mSupportsPictureInPicture,
                        mRealActivitySuspended, mUserSetupComplete, mMinWidth, mMinHeight,
                        mActivityInfo, mVoiceSession, mVoiceInteractor, mCreatedByOrganizer,
                        mLaunchCookie, mDeferTaskAppear, mRemoveWithTaskOrganizer);
            }

小结:
最后描述一下最后创建的2个重点部分:

  1. 看到通过buildInner 创建了一个task,而buildInner 也很简单粗暴,通过各个变量直接new Task 对象。
  2. mParent 不为null, 是 因为在创建的时候 setParent(this),当前的这个this,就是 getDefaultTaskDisplayArea返回的也就是应用Activity存在的"DefaultTaskDisplayArea"。

在 RootWindowContainer::getOrCreateRootTask 体现。

在这里插入图片描述
注意log里的 #17 的这个Task,与前面的层级结构树新增的Task,是对应的上的。而且this= DefaultTaskDisplayArea 说明也确实是往DefaultTaskDisplayArea里添加了。

1.3 ActivityRecord的挂载

调用链
ActivityStarer::setNewTask
ActivityStarer::addOrReparentStartingActivity
主流程代码

    # ActivityStarer
        private void setNewTask(Task taskToAffiliate) {
            // 为true
            final boolean toTop = !mLaunchTaskBehind && !mAvoidMoveToFront;
            // 就是mTargetRootTask,也就是刚刚创建的Task
            final Task task = mTargetRootTask.reuseOrCreateTask(
                    mNewTaskInfo != null ? mNewTaskInfo : mStartActivity.info,
                    mNewTaskIntent != null ? mNewTaskIntent : mIntent, mVoiceSession,
                    mVoiceInteractor, toTop, mStartActivity, mSourceRecord, mOptions);
            task.mTransitionController.collectExistenceChange(task);
            // ActivityRecord的挂载
            addOrReparentStartingActivity(task, "setTaskFromReuseOrCreateNewTask");
            // 需要注意这里的日志打印
            ProtoLog.v(WM_DEBUG_TASKS, "Starting new activity %s in new task %s",
                    mStartActivity, mStartActivity.getTask());

            // mLaunchTaskBehind 为false,所以taskToAffiliate 为null 
            if (taskToAffiliate != null) {
                mStartActivity.setTaskToAffiliateWith(taskToAffiliate);
            }
        }

这里的task 和mTargetRootTask是同一个对象, 进源码跟到流程也是一样。
然后进入 addOrReparentStartingActivity

    # ActivityStarer
        private void addOrReparentStartingActivity(@NonNull Task task, String reason) {
            //  newParent = task 都是刚刚创建的Task
            TaskFragment newParent = task;
            ......
            if (mStartActivity.getTaskFragment() == null
                    || mStartActivity.getTaskFragment() == newParent) {
                // 重点, 将 ActivityRecord挂在到新创建的Task中,并且是顶部
                newParent.addChild(mStartActivity, POSITION_TOP);
            } else {
                mStartActivity.reparent(newParent, newParent.getChildCount() /* top */, reason);
            }
        }

这里的逻辑设计到的Task就是上一步创建的Task,mStartActivity则是“电话”在之前逻辑创建的ActivityRecord.
setNewTask的堆栈信息如下

在这里插入图片描述
另外这段逻辑里有个ProtoLog打印,日志如下:

在这里插入图片描述

1.4 WindowState的创建与挂载

WindowManagerService::addWindow
    WindowState::init           -- WindowState的创建
    WindowToken::addWindow      -- WindowState的挂载
    #  WindowManagerService

        public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility,
                int displayId, int requestUserId, InsetsVisibilities requestedVisibilities,
                InputChannel outInputChannel, InsetsState outInsetsState,
                InsetsSourceControl[] outActiveControls) {
                    ......// token处理
                    // 创建WindowState
                    final WindowState win = new WindowState(this, session, client, token, parentWindow,
                                appOp[0], attrs, viewVisibility, session.mUid, userId,
                                session.mCanAddInternalSystemWindow);
                    ......
                    // 7. 窗口添加进容器
                    win.attach();
                    ......
                    win.mToken.addWindow(win);
                    ......
                }

"win.mToken"窗口的token是ActyivityRecord

    # ActivityRecord
        @Override
        void addWindow(WindowState w) {
            super.addWindow(w);
            ......
        }

直接调用其父类方法,ActivityRecord是WindowToken

    # WindowToken
        void addWindow(final WindowState win) {
            ProtoLog.d(WM_DEBUG_FOCUS,
                    "addWindow: win=%s Callers=%s", win, Debug.getCallers(5));

            if (win.isChildWindow()) {
                // Child windows are added to their parent windows.
                return;
            }
            // This token is created from WindowContext and the client requests to addView now, create a
            // surface for this token.
            // 真正添加进子容器,调用的是WindowContainer的方法
            if (!mChildren.contains(win)) {
                ProtoLog.v(WM_DEBUG_ADD_REMOVE, "Adding %s to %s", win, this);
                // 定义在WindowContainer中,其实就是挂载到父容器下了
                addChild(win, mWindowComparator);
                mWmService.mWindowsChanged = true;
                // TODO: Should we also be setting layout needed here and other places?
            }
        }

2. 系统窗口挂载

2.1 WindowToken,WindowState的创建与挂载

        public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility,
                int displayId, int requestUserId, InsetsVisibilities requestedVisibilities,
                InputChannel outInputChannel, InsetsState outInsetsState,
                InsetsSourceControl[] outActiveControls) {
                ......
                // 系统应用获取不到token
                WindowToken token = displayContent.getWindowToken(
                        hasParent ? parentWindow.mAttrs.token : attrs.token);
                ......
                if (token == null) {
                    ......
                    } else {
                        final IBinder binder = attrs.token != null ? attrs.token : client.asBinder();
                        token = new WindowToken.Builder(this, binder, type)
                                .setDisplayContent(displayContent)
                                .setOwnerCanManageAppTokens(session.mCanAddInternalSystemWindow)
                                .setRoundedCornerOverlay(isRoundedCornerOverlay)
                                .build();
                    }
                }
                // 创建WindowState
                    final WindowState win = new WindowState(this, session, client, token, parentWindow,
                                appOp[0], attrs, viewVisibility, session.mUid, userId,
                                session.mCanAddInternalSystemWindow);
                ......
                // 窗口添加进容器
                win.attach();
                ......
                win.mToken.addWindow(win);
                ......

WindowState的创建和应用窗口一样,区别在与WindowToken,系统窗口执行addWindow方法是没有token的,所以会执行创建逻辑。
在创建的时候会根据窗口类型选择挂载的层级。

2.2 WindowToken的挂载

WMS::addWindow
    WindowToken::init
        DisplayContent::addWindowToken
            mTokenMap::put     -- 存入mTokenMap
            DisplayContent::findAreaForToken  -- 找到对应的层级
                DisplayContent::findAreaForWindowType
                    DisplayAreaPolicyBuilder.Result::findAreaForWindowType
                        RootDisplayArea::findAreaForWindowTypeInLayer  -- 在 mAreaForLayer中根据type查找对应的位置

            DisplayArea.Tokens::addChild  -- 挂载

WindowToken的构造方法如下:

    # WindowToken
        protected WindowToken(WindowManagerService service, IBinder _token, int type,
                boolean persistOnEmpty, DisplayContent dc, boolean ownerCanManageAppTokens,
                boolean roundedCornerOverlay, boolean fromClientToken, @Nullable Bundle options) {
            super(service);
            token = _token;
            windowType = type;
            ......
            if (dc != null) {
                // 添加token
                dc.addWindowToken(token, this);
            }
        }

创建WindowToken的时候会由DisplayContent执行挂载逻辑,

    # DisplayContent
        DisplayAreaPolicy mDisplayAreaPolicy;

        void addWindowToken(IBinder binder, WindowToken token) {
            ......
            // 放入集合
            mTokenMap.put(binder, token);

            if (token.asActivityRecord() == null) {
                ......
                // 找到对应的位置挂载
                final DisplayArea.Tokens da = findAreaForToken(token).asTokens();
                da.addChild(token);
            }
        }

        DisplayArea findAreaForToken(WindowToken windowToken) {
            // 根据type查找
            return findAreaForWindowType(windowToken.getWindowType(), windowToken.mOptions,
                    windowToken.mOwnerCanManageAppTokens, windowToken.mRoundedCornerOverlay);
        }

        DisplayArea findAreaForWindowType(int windowType, Bundle options,
                boolean ownerCanManageAppToken, boolean roundedCornerOverlay) {
            // 应用类型
            if (windowType >= FIRST_APPLICATION_WINDOW && windowType <= LAST_APPLICATION_WINDOW) {
                return mDisplayAreaPolicy.getTaskDisplayArea(options);
            }
            // 输入法窗口
            if (windowType == TYPE_INPUT_METHOD || windowType == TYPE_INPUT_METHOD_DIALOG) {
                return getImeContainer();
            }
            // 其他类型
            return mDisplayAreaPolicy.findAreaForWindowType(windowType, options,
                    ownerCanManageAppToken, roundedCornerOverlay);
        }

状态栏不属于应用窗口,走后面的逻辑,DisplayAreaPolicy是个接口,真正的实现是DisplayAreaPolicyBuilder的内部类Result

    # DisplayAreaPolicyBuilder

       static class Result extends DisplayAreaPolicy {
            final BiFunction<Integer, Bundle, RootDisplayArea> mSelectRootForWindowFunc;
            ......
            @Override
            public DisplayArea.Tokens findAreaForWindowType(int type, Bundle options,
                    boolean ownerCanManageAppTokens, boolean roundedCornerOverlay) {
                return mSelectRootForWindowFunc.apply(type, options).findAreaForWindowTypeInLayer(type,
                        ownerCanManageAppTokens, roundedCornerOverlay);
            }
            ......
       }

mSelectRootForWindowFunc是一个存放RootDisplayArea的map,所以后续逻辑在RootDisplayArea中


// 根据type 找到在容器树的位置, 如果是应用或者输入法都走不到这

# RootDisplayArea
    // 这个就是层级树的
    private DisplayArea.Tokens[] mAreaForLayer;
    @Nullable
    DisplayArea.Tokens findAreaForWindowTypeInLayer(int windowType, boolean ownerCanManageAppTokens,
            boolean roundedCornerOverlay) {
        // 获取到type
        int windowLayerFromType = mWmService.mPolicy.getWindowLayerFromTypeLw(windowType,
                ownerCanManageAppTokens, roundedCornerOverlay);
        if (windowLayerFromType == APPLICATION_LAYER) {
            throw new IllegalArgumentException(
                    "There shouldn't be WindowToken on APPLICATION_LAYER");
        }
        // 根据type查找对应的位置
        return mAreaForLayer[windowLayerFromType];
    }

getWindowLayerFromTypeLw会根据type找到对应的层级,返回一个int。
然后根据这个值去mAreaForLayer拿到对应的DisplayArea.Tokens,将系统窗口的WindowToken挂载进去
mAreaForLayer其实就是开始构建层级树的那个集合。

2.2.1 mAreaForLayer的赋值

在开机构建窗口层级树的逻辑,最后会执行到RootDisplayArea::onHierarchyBuilt将层级树的集合传递出去。

    # DisplayAreaPolicyBuilder.HierarchyBuilder

            private final RootDisplayArea mRoot;

            private void build(@Nullable List<HierarchyBuilder> displayAreaGroupHierarchyBuilders) {
                ......// 层级树的构建
                // 通知根节点已经完成了所有DisplayArea的添加 (将displayAreaForLayer保存在RootDisplayArea成员变量roomAreaForLayer中,供后面逻辑使用)
                mRoot.onHierarchyBuilt(mFeatures, displayAreaForLayer, featureAreas);
            }

RootDisplayArea下的mAreaForLayer变量赋值

    # RootDisplayArea
        private DisplayArea.Tokens[] mAreaForLayer;

        void onHierarchyBuilt(ArrayList<Feature> features, DisplayArea.Tokens[] areaForLayer,
                Map<Feature, List<DisplayArea<WindowContainer>>> featureToDisplayAreas) {
            if (mHasBuiltHierarchy) {
                throw new IllegalStateException("Root should only build the hierarchy once");
            }
            mHasBuiltHierarchy = true;
            mFeatures = Collections.unmodifiableList(features);
            // 赋值
            mAreaForLayer = areaForLayer;
            mFeatureToDisplayAreas = featureToDisplayAreas;
        }

所以mAreaForLayer保存了层级树各个层级的对象因此根据index可以获取到对应的DisplayArea.Tokens,并执行系统窗口WindowToken的挂载。

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

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

相关文章

Redis的AOF持久化、重写机制、RDB持久化、混合持久化

1、AOF持久化 1.1.AOF持久化大致过程 概括&#xff1a;命令追加&#xff08;append&#xff09;、文件写入、文件同步&#xff08;sync&#xff09; Redis 每执行一条写操作命令&#xff0c;就把该命令以追加的方式写入到一个文件里&#xff0c;然后重启 Redis 的时候&#…

Pytest配置文件pytest.ini如何编写生成日志文件?

1、新建pytest.ini文件 [pytest] log_clitrue log_leveLNOTSET log_format %(asctime)s %(levelname)s %(message)s %(filename)s %(funcName)s %(lineno)d log_date_format %Y-%m-%d %H:%M:%Slog_file ./logdata/log.log log_file_level info log_file_format %(asctime…

实时(按帧)处理的低通滤波C语言实现

写在前面&#xff1a; 低通滤波采用一般的FIR滤波器&#xff0c;因为本次任务&#xff0c;允许的延迟较多&#xff0c;或者说前面损失的信号可以较多&#xff0c;因此&#xff0c;涉及一个很高阶的FIR滤波器&#xff0c;信号起始段的信号点可以不处理&#xff0c;以及&#xf…

召回01 基于物品是协同过滤 ItemCF

相似度&#xff0c;类似机器学习里面常用的cosine相似度

python AssertionError: Torch not compiled with CUDA enabled

查看&#xff1a;torch import torch# 输出带CPU&#xff0c;表示torch是CPU版本的 print(ftorch的版本是&#xff1a;{torch.__version__}) # print(ftorch是否能使用cuda&#xff1a;{torch.cuda.is_available()}) 修改一下代码&#xff0c;将cuda改成cpu 最后运行正常&…

【React源码解析】深入理解react时间切片和fiber架构

时间切片 假如React一个更新需要耗时200ms&#xff0c;我们可以将其拆分为40个5ms的更新&#xff08;后续会讲到如何拆分&#xff09;&#xff0c;然后每一帧里只花5ms来执行更新。那么&#xff0c;每一帧里不就剩余16.7 - 5 11.7ms的时间可以进行用户事件&#xff0c;渲染等…

13 Midjourney从零到商用·进阶篇:灯光、角度与风格等精细控制方法

在前面我们了解了提示词的书写&#xff0c;那么如何利用提示词来精确控制生成画面的灯光、角度与风格 呢&#xff1f;在本篇文章中我么一起来探讨一下。 一、灯光 在摄影中&#xff0c;对灯光的要求都是非常高的。灯光能对人物、动物、物体、场景等进行修饰。每一种微小的的灯光…

链接升级:Element UI <el-link> 的应用

链接升级&#xff1a;Element UI 的应用 一 . 创建文字链接1.1 注册路由1.2 创建文字链接 二 . 文字链接的属性2.1 文字链接的颜色2.2 是否显示下划线2.3 是否禁用状态2.4 填写跳转地址2.5 加入图标 在本篇文章中&#xff0c;我们将深入探索Element UI中的<el-link>组件—…

本地不能訪問linux的kafka服務

1.本地使用kafka客戶端工具連接kafka服務&#xff0c;提示連接失敗 2. 本地使用telnet ip port命令也失敗 3.查看zookeeper和kafka服務是否正常 ps -ef | grep zookeeper ps -ef | grep kafka 3.關閉操作系統的防火墻(僅限于測試使用) 3.1.禁用防火墙 systemctl stop firew…

先有正态分布,还是先有高斯函数?

正态分布&#xff08;也称为高斯分布&#xff09;是由德国数学家卡尔弗里德里希高斯在研究天文学中的误差分布时提出的。而高斯函数通常指的是正态分布的概率密度函数&#xff0c;它是描述正态分布特性的一个数学表达式。因此&#xff0c;可以明确地说&#xff0c;是先有正态分…

eureka.client.service-url.defaultZone的坑

错误的配置 eureka: client: service-url: default-zone: http://192.168.100.10:8080/eureka正确的配置 eureka: client: service-url: defaultZone: http://192.168.100.10:8080/eureka根据错误日志堆栈打断电调试 出现两个key&#xff0c;也就是defaultZone不支持snake-c…

Vue: watch5种监听情况

目录 一.watch的性质与作用 1.watch 的性质包括&#xff1a; 2.watch 常用于以下场景&#xff1a; 二.监视ref定义的基本类型数据 三.监视ref定义的对象类型数据 四.监视reactive定义的对象类型数据 五.监视ref或reactive定义的对象类型数据中的某个属性 六.监视上述的…

c++题目_【模板】最小生成树Prim

题目描述 这是一道最小生成树Prim的模板题&#xff0c;本题与【模板】最小生成树Kruskal&#xff0c;仅仅只有nn和mm的大小不同 给出一个无向图&#xff0c;求出最小生成树&#xff0c;如果该图不连通&#xff0c;则输出orz 输入 第一行输入2个正整数n,mn,m&#xff0c;代表…

【自然语言处理】实验三:新冠病毒的FAQ问答系统

目录 前言 1.新建data_process.py 1.1导入包并定义功能模块1用来读取问题和答案FAQ的文件 1.2功能模块2&#xff1a;进行问题/问题列表处理&#xff08;正则化&#xff0c;分词&#xff09; 1.3功能模块3&#xff1a;处理输入的问题 1.4功能模块4&#xff1a;计算输入问题与问题…

Java | Leetcode Java题解之第403题青蛙过河

题目&#xff1a; 题解&#xff1a; class Solution {public boolean canCross(int[] stones) {int n stones.length;boolean[][] dp new boolean[n][n];dp[0][0] true;for (int i 1; i < n; i) {if (stones[i] - stones[i - 1] > i) {return false;}}for (int i 1…

redis windows安装包下载路径

https://github.com/tporadowski/redis/releases 通过网盘分享的文件&#xff1a;Redis-x64-5.0.14.1.zip 链接: https://pan.baidu.com/s/12XQOfKB75yajJ0fJLzl4rQ?pwd1234 提取码: 1234

C++数据结构-树的概念及分类介绍(基础篇)

1.什么是树 树是数据结构中的一种&#xff0c;其属于非线性数据结构结构的一种&#xff0c;我们前文所提到的数据结构多数都是线性的&#xff0c;这也是较为简单的数据结构&#xff0c;而接下来的树与图均属于非线性数据结构&#xff0c;也是概念极多的一类。 树是由结点或顶…

软件设计师容易考吗?

一、软考软件设计师难吗 软考软件设计师考试对于不同的人来说&#xff0c;难度可能有所差异。然而&#xff0c;总体来说&#xff0c;软考软件设计师考试是相对较难的考试&#xff0c;需要考生具备扎实的软件设计理论知识和实践经验。 从各地2024年上半年软考合格人数的公布情…

Python | Leetcode Python题解之第405题数字转换为十六进制数

题目&#xff1a; 题解&#xff1a; CONV "0123456789abcdef" class Solution:def toHex(self, num: int) -> str:ans []# 32位2进制数&#xff0c;转换成16进制 -> 4个一组&#xff0c;一共八组for _ in range(8):ans.append(num%16)num // 16if not num:b…

c++ 红黑树(带头结点)

想必在看到这篇文章的时候&#xff0c;你一定是带着问题去搜索的&#xff0c;一定是对红黑树已经有了初步大致的认识&#xff0c;已经知道了红黑树的性质与普通红黑树的功能与如何代码实现&#xff0c;但是莫一天突然看到了带头结点的红黑树&#xff0c;肯定是对此有一些疑惑的…