【Android 14源码分析】WMS-窗口显示-第一步:addWindow

忽然有一天,我想要做一件事:去代码中去验证那些曾经被“灌输”的理论。
                  
                  
                                           – 服装学院的IT男

本篇已收录于Activity短暂的一生系列
欢迎一起学习讨论Android应用开发或者WMS
V:WJB6995
Q:707409815

正文

窗口显示流程一共分为以下5篇:

窗口显示-流程概览与应用端流程分析

窗口显示-第一步:addWindow

窗口显示-第二步:relayoutWindow -1

窗口显示-第二步:relayoutWindow -2

窗口显示-第三步:finishDrawingWindow

1. 流程概述

本篇开始真正看 addWindow 流程,首先从结果上对比下应用启动后窗口的区别来确认本篇的目的:

在这里插入图片描述
红色部分就是启动应用后多出来的部分,在 DefaultTaskDisplayArea 节点下多出来这么一个层级:

Task
    ActivityRecord
        WindowState

在这里插入图片描述
其中 Task 和 ActivityRecord 是如何挂载上去的在【Activity启动流程】已经介绍了,当前要分析的 addWindow 流程最重要的目标就是分析窗口对应的 WindowState 是如何创建并且挂载到窗口树中的。

也就是这一变化:在这里插入图片描述
这个流程逻辑相对简单,整个流程框图如下:

在这里插入图片描述

    1. 应用端 Activity 执行到 onResume 说明 Activity 已经可见,下面就需要处理可见的内容
    1. 应用端 Session 调用到 WindowManagerService::addWindow 方法
    1. WMS 处理 addWindow 流程也就做了2件事:
    • 创建出对应的 WindowState
    • 挂载到层级树中(挂载到对应的 WindowToken 下)

2. 流程分析

上一篇知道流程已经执行到 ViewRootImpl::setView 来触发 addWindow 流程,回忆一下应用端的调用:

# ViewRootImpl

    final W mWindow;

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
                int userId) {
                    ......
                        res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
                                getHostVisibility(), mDisplay.getDisplayId(), userId,
                                mInsetsController.getRequestedVisibleTypes(), inputChannel, mTempInsets,
                                mTempControls, attachedFrame, compatScale);
                    ......
                }

这里有几个参数比较重要:
mWindow : 用于 WMS 与应用端通信
mWindowAttributes : DecorView 的参数
getHostVisibility() :可见性
inputChannel:Input 通路

看到你这些参数有个疑问:

明明是 addWindoW 流程,但是到了 WindowManagerImpl 就变成了 addView 传递的也是 DecoreView ,再到和 WMS 通信的时候,参数里连 DecoreView 都不剩了,这怎么能叫 addWindow 流程呢?

本篇将介绍WindowManagerService是如何处理剩下逻辑的文末也会回答这个问题。

2.1 WindowManagerService::addWindow方法概览

接上篇知道 Session::addToDisplayAsUser 方法调用的是 WindowManagerService::addWindow ,先看一下这个方法。

#  WindowManagerService
    // 保存应用端 ViewRootImpl 和 WindowState 的映射关系
    /** Mapping from an IWindow IBinder to the server's Window object. */
    final HashMap<IBinder, WindowState> mWindowMap = new HashMap<>();

    public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility,
            int displayId, int requestUserId, InsetsVisibilities requestedVisibilities,
            InputChannel outInputChannel, InsetsState outInsetsState,
            InsetsSourceControl[] outActiveControls) {
            ......
            // 1.1 权限检查
            int res = mPolicy.checkAddPermission(attrs.type, isRoundedCornerOverlay, attrs.packageName,
                    appOp);
            if (res != ADD_OKAY) {
                return res;
            }
            // 父窗口,应用 Activity 窗口逻辑是没有父窗口的
            WindowState parentWindow = null;
            ......
            // 拿到当前窗口类型
            final int type = attrs.type;
            ......
            synchronized (mGlobalLock) {
                    ......
                    // 1.2 如果窗口已经添加,直接return
                    if (mWindowMap.containsKey(client.asBinder())) {
                        // 日志
                        ProtoLog.w(WM_ERROR, "Window %s is already added", client);
                        return WindowManagerGlobal.ADD_DUPLICATE_ADD;
                    }
                    ......
                    
                    ActivityRecord activity = null;
                    // 是否为 hasParent
                    final boolean hasParent = parentWindow != null;
                    // 2.1 拿到token
                    WindowToken token = displayContent.getWindowToken(
                            hasParent ? parentWindow.mAttrs.token : attrs.token);
                    // Activity 没有父窗口,这里也为null
                    final int rootType = hasParent ? parentWindow.mAttrs.type : type;
                    ......
                    if (token == null) {
                        ......
                        if (hasParent) {
                            // Use existing parent window token for child windows.
                            // 2.2子窗口用父窗口的 token
                            token = parentWindow.mToken;
                        } else if (...) {
                            ......
                        } else {
                            // 2.3 系统窗口会创建 token
                            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();
                        }
                    } else if (rootType >= FIRST_APPLICATION_WINDOW
                        && rootType <= LAST_APPLICATION_WINDOW) {
                        ......
                    } else if......// 忽略其他各种创建对 token的处理

                    // 3.1 创建 WindowState
                    final WindowState win = new WindowState(this, session, client, token, parentWindow,
                            appOp[0], attrs, viewVisibility, session.mUid, userId,
                            session.mCanAddInternalSystemWindow);
                    ......
                    final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
                    // 调整window的参数
                    displayPolicy.adjustWindowParamsLw(win, win.mAttrs);
                    ......
                    // 1.3 验证Window是否可以添加,主要是验证权限
                    res = displayPolicy.validateAddingWindowLw(attrs, callingPid, callingUid);
                    if (res != ADD_OKAY) {
                        // 如果不满足则直接return
                        return res;
                    }   
                    final boolean openInputChannels = (outInputChannel != null
                            && (attrs.inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) == 0);
                    if  (openInputChannels) {
                        // 4.1 Input 事件输入通道
                        win.openInputChannel(outInputChannel);
                    }
                    ......
                    
                    // 3.2 创建SurfaceSession
                    win.attach();
                    // 3.3 窗口存入mWindowMap
                    mWindowMap.put(client.asBinder(), win);
                    ......
                    // 3.4 窗口挂载
                    win.mToken.addWindow(win);
                    displayPolicy.addWindowLw(win, attrs);
                    ......
                    // 4.2 处理窗口焦点切换
                    boolean focusChanged = false;
                    if (win.canReceiveKeys()) {
                        focusChanged = updateFocusedWindowLocked(UPDATE_FOCUS_WILL_ASSIGN_LAYERS,
                                false /*updateInputWindows*/);
                        if (focusChanged) {
                            imMayMove = false;
                        }
                    }
                    ......
                    // 调整父容器下的元素层级
                    win.getParent().assignChildLayers();
                    // 4.3 更新inut焦点
                    if (focusChanged) {
                        displayContent.getInputMonitor().setInputFocusLw(displayContent.mCurrentFocus,
                                false /*updateInputWindows*/);
                    }
                    // 4.4 更新input窗口
                    displayContent.getInputMonitor().updateInputWindowsLw(false /*force*/);
                    // 窗口添加log
                    ProtoLog.v(WM_DEBUG_ADD_REMOVE, "addWindow: New client %s"
                            + ": window=%s Callers=%s", client.asBinder(), win, Debug.getCallers(5));
                    ......
                }
                Binder.restoreCallingIdentity(origId);
                return res;
        }

这个方法就是 addWindow 流程的核心方法了,代码很多,保留了下面4个主要逻辑:

    1. 校验处理
    • 1.1 1.3 为操作权限校验
    • 1.2 为限制应用端的一个 RootView 只能执行一次 addWindow
    1. Token 处理
    • 这个 token 其实就是 WindowToken(ActivityRecord 是其子类)
    • 获取 token,如果是子窗口就从父窗口拿,没有就从参数里拿
    • 如果是系统窗口就会在2.3出根据窗口类型创建出一个 WindowToken
    1. WindowState 处理
    • 3.1 创建 WindowState
    • 2.2 执行 WindowState::attach 会创建 SurfaceSession
    • 3.3 将新建的 WindowState 和 W 映射,存入 mWindowMap
    • 3.4 窗口挂载
    1. Input 和焦点处理

当然这个方法实际上做的事肯定不止这些,只是根据我的个人理解整理出了几个比较重要的处理点。

当前分析的 addWindow 主流程,所以分析2,3两点,也就是 Token 和 WindowState 的处理逻辑。

3 Token相关

首先给一个结论,当前分析的场景,这个 Token 就是 Activity 启动流程中创建 ActivityRecord 时创建的 Token ,而 ActivityRecord 是 WindowToken 的子类。
在 【WindowContainer窗口树】介绍过,WindowState 的父节点大部分情况是 WindowToken ,而且在上一篇看到 dump 启动应前后的窗口树区别,明确知道 WindowState 是挂载到 ActivityRecord 下的,
在分析 WindowState 的创建和挂载前,需要先给它找到它的父节点: WindowToken 。这也是 WindowManagerService::addWindow 方法中比较靠前执行的逻辑。

根据上一小节的分析,当前场景的 Token 来自参数“attrs.token” 。这个参数是应用端传递过来的,上一篇在分析 WindowManagerGlobal::addView 方法的时候提到在Window::adjustLayoutParamsForSubWindow 方法对赋值 token 给参数,现在看一下这个方法。

# Window
    
    // 应用Token
    private IBinder mAppToken;

    void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) {
        ......
        if (wp.token == null) {
            wp.token = mContainer == null ? mAppToken : mContainer.mAppToken; // activity的window在这里设置token
        }
        ......
    }

mContainer 唯一赋值的地方在 Window::setContainer 方法,当前没调用,所以 wp.token 最终的值是为 mAppToken ,而mAppToken 的赋值在给 Window 设置 WindowManager 的时候赋值,也就是setWindowManager 方法,这里的 token 就是 ActivityRecord 的 token 。

下面这张图可以更直观的看到 Token 的传递:

在这里插入图片描述

    1. WindowToken 内部有个成本变量 token ,ActivityRecord 是其子类
    1. Activity 启动过程中会先创建 ActivityRecord ,在创建 ActivityRecord 的时候会创建一个匿名 Token 对象,并保存在变量 token 中
    1. 随着启动流程的执行,会在 ActivityTaskSupervisor::realStartActivityLocked 方法里构建事务,这个时候 token 就被保存在 ClientTransaction 的成员变量 mActivityToken
    1. ClientTransaction 提供了一个 getActivityToken 方法返回 mActivityToken 。这个方法在具体的事务执行时,比如 LaunchActivityItem::execute 方法执行,会作为参数传递过去
    1. LaunchActivityItem::execute 方法会构建一个 ActivityClientRecord ,构建方法需要 Token 参数,这个时候 Token 就被保存在 ActivityClientRecord 的成员变量 token 中
    1. 接下里就到了应用进程,应用进程执行 ActivityThread::performLaunchActivity 方法开始处理 Activity 启动流程,ActivityClientRecord 作为参数被传递了过来
    1. ActivityThread::performLaunchActivity 方法内部会执行 Activity::attach 方法,这个方法需要一个 Token 作为参数,传递的就是从 ActivityClientRecord 里取出的 token
    1. Activity::attach 方放会将 Token 赋值给成员变量 mToken
    1. Window 创建后会执行 Window::setWindowManager ,这个时候会将 mToken 作为参数传递进去,保存在 Window 的成员变量 mAppToken 中
    1. 在执行 WindowManagerGlobal::addView 时会执行 Window::adjustLayoutParamsForSubWindow 调整参数,这个时候 Token 就被复制到 WindowManager.LayoutParams 下的 token 变量中
    1. 执行 addWindow 流程时,WindowManager.LayoutParams 会被传递到 WMS ,这样 Token 也就被传递了过去

3.1 补充 生命周期事务流程图

这里补充下 system_service 是如何通过 ClientTransaction 来完成应用端生命周期相关执行的流程,具体代码不是当前重点,就不具体分析了。

在这里插入图片描述

4. WindowState的创建与挂载

addWindow 流程中 WindowState 的创建与挂载是重点,回顾一下这一流程层级树的变化:
在这里插入图片描述

4.1 WindowState的创建

在 WindowManagerService::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) {
            ......// WindowToken相关处理
            // 创建WindowState
            final WindowState win = new WindowState(this, session, client, token, parentWindow,
                    appOp[0], attrs, viewVisibility, session.mUid, userId,
                    session.mCanAddInternalSystemWindow);
            ......
        }

这里注意几个参数,然后直接看WindowState的构造方法

# WindowState

    final IWindow mClient;
    @NonNull WindowToken mToken;
    
    // The same object as mToken if this is an app window and null for non-app windows.
    // 与 mToken 相同的对象(如果这是应用程序窗口),而对于非应用程序窗口为null
    // 说人话就是应用窗口才有ActivityRecord
    ActivityRecord mActivityRecord;
    // 层级
    final int mBaseLayer;

    WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
            WindowState parentWindow, int appOp, WindowManager.LayoutParams a, int viewVisibility,
            int ownerId, int showUserId, boolean ownerCanAddInternalSystemWindow,
            PowerManagerWrapper powerManagerWrapper) {
            ......
            mClient = c;
            ......
            // 保存token
            mToken = token;
            // 只有 ActivityRecord 重写了 asActivityRecord 其他默认返回努力了
            mActivityRecord = mToken.asActivityRecord();
            ......
            //子窗口处理
            if (mAttrs.type >= FIRST_SUB_WINDOW && mAttrs.type <= LAST_SUB_WINDOW){
                ......
            }else {
                // Activity的窗口指为  2 * 10000 + 1000  = 21000
                mBaseLayer = mPolicy.getWindowLayerLw(parentWindow)
                        * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;
                ......
            }
        }

创建 WindowState 有2个重要的参数 :client,和 token 。这个 client 代表着客户端也就是 ViewRootImpl 的内部类 W ,另一个参数就是上节的 Token 。

WindowState 以后会经常看到,不过当前只要知道在 WindowManagerService::addWindow 会创建出一个 WindowState 对象即可。

4.2 WindowState的挂载

WindowState 创建好后自然是需要挂载到窗口树的,操作也很简单,直接添加到对应的 (ActivityRecord)WindowToken 下就好。

# WindowManagerService
    // ViewRootImpl和WindowState的map
    final HashMap<IBinder, WindowState> mWindowMap = new HashMap<>();

    public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility,
            int displayId, int requestUserId, InsetsVisibilities requestedVisibilities,
            InputChannel outInputChannel, InsetsState outInsetsState,
            InsetsSourceControl[] outActiveControls) {
            ......
            // 窗口已经添加,直接return
            if (mWindowMap.containsKey(client.asBinder())) {
                // 打印log
                ProtoLog.w(WM_ERROR, "Window %s is already added", client);
                return WindowManagerGlobal.ADD_DUPLICATE_ADD;
            }
            ......// WindowToken相关处理
            ......// WindowState的创建
            // WindowState的挂载
            win.attach();
            // 1. 存进map
            mWindowMap.put(client.asBinder(), win);
            ......
            // 2. 挂载
            win.mToken.addWindow(win);
            ......
        }
    1. 在看挂载前先看一下 mWindowMap 这个数据结构,key 是一个 IBinder,value 是 WindowState ,这边将新创建的 WindowState 作为 value 添加到了 map 中,前面说过 client是应用端 ViewRootImpl 下的 “W”这个类,也就是说在 WMS 中应用端的这个 ViewRootImpl 和为其创建的 WindowState 已经被记录在 mWindowMap 中了。

在执行WMS::addWindow方法开始的时候就会尝试通过 clent 从 mWindowMap 获取值,如果获取到了说明已经执行过 addWindow 则进行 return 不执行后面逻辑。

    1. 这里是窗口的挂载,“win.mToken” 这里的 mToken 刚刚看到是创建 WindowState 的时候传递的 token 也就是 ActivityRecord (WindowToken)。

也就是说调用的是 ActivityRecord::addWindow 方法进行挂载的。

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

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

# WindowToken
    void addWindow(final WindowState win) {
        // WindowState 挂载日志
        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.
            // 子窗口的父类应该是WindowState所以不执行后续
            return;
        }
        // This token is created from WindowContext and the client requests to addView now, create a
        // surface for this token.
        // 真正添加进子容器
        if (!mChildren.contains(win)) {
            // 日志
            ProtoLog.v(WM_DEBUG_ADD_REMOVE, "Adding %s to %s", win, this);
            // 挂载(添加进孩子容器),有一个比较方法
            addChild(win, mWindowComparator);
            // 记录有窗口边框
            mWmService.mWindowsChanged = true;
            // TODO: Should we also be setting layout needed here and other places?
        }
    }

执行完 WindowContainer::addChild 方法后 WindowState 已经被添加到层级树中了,挂在到对应的 ActivityRecord 下。

当然这里需要注意 WindowToken::addWindow 最终也是调用父类 WindowContainer::addChild 将 WindowState 添加到自己的孩子中,这里传递了一个mWindowComparator。

4.3 挂载的位置

WindowContainer::addChild 方法被定义在基类,也就是容器添加孩子时都会按一定规则添加,当然默认其实还是按顺序,但是有的时候也有特殊情况,所以这个方法提供了一个参数,使得可以在具体场景控制具体的添加逻辑。

# WindowContainer
    protected void addChild(E child, Comparator<E> comparator) {
        // 检查子元素是否已经被其他容器拥有,如果是,则抛出异常
        if (!child.mReparenting && child.getParent() != null) {
            throw new IllegalArgumentException("addChild: container=" + child.getName()
                    + " is already a child of container=" + child.getParent().getName()
                    + " can't add to container=" + getName());
        }
        // 初始化插入位置为-1,表示尚未找到合适的插入位置
        int positionToAdd = -1;
        // 如果有比较器则进行比较
        
        // 遍历当前容器中的所有子元素
        if (comparator != null) {
            final int count = mChildren.size();
            // 使用比较器比较待插入的子元素和当前容器中的子元素
            for (int i = 0; i < count; i++) {

                // 如果比较结果小于0,表示待插入元素应该位于当前元素之前
                if (comparator.compare(child, mChildren.get(i)) < 0) {
                    positionToAdd = i;
                    break;
                }
            }
        }
        // 没有比较器或者比较的结果还是-1 ,则添加到最后(大部分场景)
        if (positionToAdd == -1) {
            mChildren.add(child);
        } else {
            // 如果比较器计算出了准确位置,则按要求添加
            mChildren.add(positionToAdd, child);
        }

        // Set the parent after we've actually added a child in case a subclass depends on this.
        // 调用孩子容器设置当前容器为其父节点
        child.setParent(this);
    }
    1. 方法目的就是添加子元素到父容器中,但是可以根据 comparator 比较规则添加到正确的位置
    1. 比较方式很简单,拿当前需要添加的元素和容器内其他元素逐个比较,如果比较 comparator 返回值小于0,则添加到“被比较”的元素前面
    1. 有2种情况,是按顺序添加到容器末尾
    • 3.1 没有比较器。positionToAdd 为默认值 -1
    • 3.2 和每个元素比较的返回值都大于0,说明要添加其后面,这个时候 positionToAdd 还是为默认值 -1
    1. setParent 调用孩子容器设置当前容器为其父节点,另外还会将 mSyncState 变量设置为 SYNC_STATE_WAITING_FOR_DRAW

当前场景,父容器 ActivityRecord 还是是空的,所以没什么意义。

不过既然看到这里,就继续分析,根据分析,当前逻辑调用的比较器是 WindowToken下的 mWindowComparator 。

4.3.1 addWindow是顺序-- WindowToken下的 mWindowComparator

# WindowToken

    private final Comparator<WindowState> mWindowComparator =
            (WindowState newWindow, WindowState existingWindow) -> {
        ......
        return isFirstChildWindowGreaterThanSecond(newWindow, existingWindow) ? 1 : -1;
    };
    protected boolean isFirstChildWindowGreaterThanSecond(WindowState newWindow,
            WindowState existingWindow) {
        // 就是比较两个窗口的mBaseLayer
        return newWindow.mBaseLayer >= existingWindow.mBaseLayer;
    }

这里的 newWindow 和 existingWindow 当然是一个当前需要添加进容器的 WindowState 和上一个存在的 WindowState

    1. 对 WindowToken 的子窗口进行比较排序,1和-1表示相对顺序
    1. 返回 true,则是 1,表示插入在后面。 也就是 newWindow 的 mBaseLayer 大于原来的
    1. 返回 false,则是 -1,表示插入在前面,也就是 newWindow 的 mBaseLayer 小于原来的

简单来说就是比较 mBaseLayer 来判断当前新添加的是放在哪个位置。 正常情况都是按顺添加,也就是后添加的在最上面。

这个 mBaseLayer 在 创建 WindowState 赋值,逻辑也比较简单,应用窗口的值计算后就是 21000 ,假设有2个应用窗口,那值都是一样的,就按序添加了。

这个值自己可以根据窗口类型计算,也可以使用命名 “adb shell dumpsys window windows” dump,然后搜 “mBaseLayer=” 确认。

addWindow 流程到这也就结束了,在三个流程里算比较简单的了,就做了2件事:

    1. 创建 WindowState
    1. 挂载到窗口树上

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

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

相关文章

网络安全概述:从认知到实践

一、定义 网络安全&#xff0c;即致力于保护网络系统所涵盖的硬件、软件以及各类数据&#xff0c;切实保障其免遭破坏、泄露或者篡改等不良情形的发生。 二、重要性 个人层面&#xff1a;着重于守护个人隐私以及财产安全&#xff0c;为个人在网络世界中的各项活动提供坚实的保…

Redis篇(Redis原理 - 数据结构)(持续更新迭代)

目录 一、动态字符串 二、intset 三、Dict 1. 简介 2. Dict的扩容 3. Dict的rehash 4. 知识小结 四、ZipList 1. 简介 2. ZipListEntry 3. Encoding编码 五、ZipList的连锁更新问题 六、QuickList 七、SkipList 八、RedisObject 1. 什么是 redisObject 2. Redi…

使用JavaScript写一个网页端的四则运算器

目录 style(内联样式表部分) body部分 html script 总的代码 网页演示 style(内联样式表部分) <style>body {font-family: Arial, sans-serif;display: flex;justify-content: center;align-items: center;height: 100vh;background-color: #f0f0f0;}.calculator {…

Python开发环境配置(mac M2)

1. 前言 作为一名程序员&#xff0c;工作中需要使用Python进行编程&#xff0c;甚至因为项目需要还得是不同版本的Python如何手动管理多个版本的Python&#xff0c;如何给Pycharm&#xff08;IDE&#xff09;配置对应的interpreter等&#xff0c;都成为一个 “不熟练工” 的难…

使用百度文心智能体创建多风格表情包设计助手

文章目录 一、智能定制&#xff0c;个性飞扬二、多元风格&#xff0c;创意无限 百度文心智能体平台为你开启。百度文心智能体平台&#xff0c;创建属于自己的智能体应用。百度文心智能体平台是百度旗下的智能AI平台&#xff0c;集成了先进的自然语言处理技术和人工智能技术&…

基于SpringBoot实现QQ邮箱发送短信功能 | 免费短信服务

开发学习过程中有个短信发送功能&#xff0c;阿里云腾讯云等等都要money&#xff0c;听说qq邮箱可以实现免费发送邮箱的功能&#xff08;短信发送的平替&#xff09;&#xff0c;就用这个来实现&#xff01;&#xff01;&#xff01;【找了好多好多方法才成功的啊啊啊啊&#x…

【Linux】进程第三弹(虚拟地址空间)

目录 现象 底层原因 数据不发生修改 数据修改 小总结 地址空间本质 为什么要有地址空间 现象 来看代码&#xff1a; #include <stdio.h> #include <unistd.h> #include <sys/types.h>int val 50;int main() {printf("father process is running…

标准正态分布的数据 tensorflow 实现正态分布图,python 编程,数据分析和人工智能

import tensorflow as tf import matplotlib.pyplot as plt # 设置随机种子以获得可重复的结果 tf.random.set_seed(42) # 生成正态分布的数据 # mean0 和 stddev1 表示生成标准正态分布的数据 # shape(1000,) 表示生成1000个数据点 data tf.random.normal(mean0, stddev1, …

【python】追加写入excel

输出文件运行前&#xff08;有两张表&#xff0c;“表1”和“Sheet1”&#xff09;&#xff1a; 目录 一&#xff1a;写入单表&#xff08;删除所有旧工作表&#xff0c;写入新表&#xff09;二&#xff1a;写入多表&#xff08;删除所有旧工作表&#xff0c;写入新表&#x…

HTML5实现唐朝服饰网站模板源码

文章目录 1.设计来源1.1 网站首页-界面效果1.2 唐装演变-界面效果1.3 唐装配色-界面效果1.4 唐装花纹-界面效果1.5 唐装文化-界面效果 2.效果和源码2.1 动态效果2.2 源代码 源码下载万套模板&#xff0c;程序开发&#xff0c;在线开发&#xff0c;在线沟通 作者&#xff1a;xcL…

[C++][第三方库][Websocket]详细讲解

目录 1.Websocket 协议1.介绍2.原理简介 2.Websocketpp1.介绍2.安装 3.常用接口4.使用 1.Websocket 协议 1.介绍 WebSocket是从HTML5开始支持的一种网页端和服务端保持长连接的消息推送机制产生原因&#xff1a; 传统的web程序都是属于"一问一答"的形式 即客户端给…

Python 语言学习——应用1.2 数字图像处理(第二节,变换)

目录 1.基础知识 1.图像几何变换概念 2.图像几何变换方式 3.插值运算 4.几何变换步骤 2.各类变换 1.位置变换 2.形状变换 3.代数运算 3.实战演练 1.基础知识 1.图像几何变换概念 在图像处理过程中&#xff0c;为了观测需要&#xff0c;常常需要对 图像进行几何变换&am…

如何创建免费版本的ABP分离模块?

由于ABP最近官方大改革&#xff0c;我们打开ABP.IO 官方会发现通过Cli创建模板的时候不能创建Trered类型的了 就是创建一个分层的解决方案&#xff0c;其中Web和Http API层在物理上是分开的。如果不勾选&#xff0c;则创建一个分层的解决方案&#xff0c;它不那么复杂&#xf…

PasteForm最佳CRUD实践,实际案例PasteTemplate详解之3000问(三)

作为“贴代码”力推的一个CRUD实践项目PasteTemplate,在对现有的3个项目进行实战后效果非常舒服&#xff01;下面就针对PasteForm为啥我愿称为最佳CRUD做一些回答: 哪里可以下载这个PasteForm的项目案例 目前“贴代码”对外使用PasteForm的项目有"贴Builder(PasteSpide…

【Android 13源码分析】Activity生命周期之onCreate,onStart,onResume-2

忽然有一天&#xff0c;我想要做一件事&#xff1a;去代码中去验证那些曾经被“灌输”的理论。                                                                                  – 服装…

微服务Sleuth解析部署使用全流程

目录 1、Sleuth链路追踪 1、添加依赖 2、修改日志配置文件 3、测试 2、zipkin可视化界面 1、docker安装 2、添加依赖 3、修改配置文件 4、查看页面 5、ribbon配置 1、Sleuth链路追踪 sleuth是链路追踪框架&#xff0c;用于在微服务架构下开发&#xff0c;各个微服务之…

[水墨:创作周年纪念] 特别篇!

本篇是特别篇&#xff01;&#xff01; 个人主页水墨不写bug // _ooOoo_ // // o8888888o // // 88" . "88 …

GO网络编程(二):客户端与服务端通信【重要】

本节是新知识&#xff0c;偏应用&#xff0c;需要反复练习才能掌握。 目录 1.C/S通信示意图2.服务端通信3.客户端通信4.通信测试5.进阶练习&#xff1a;客户端之间通信 1.C/S通信示意图 客户端与服务端通信的模式也称作C/S模式&#xff0c;流程图如下 其中P是协程调度器。可…

《CUDA编程》5.获得GPU加速的关键

从本章起&#xff0c;将关注CDUA程序的性能&#xff0c;即执行速度 1 用CUDA事件计时 在前几章中&#xff0c;使用的是C的<time.h>库进行程序运行计时&#xff0c;CUDA也提供了一种基于CUDA event的计时方式&#xff0c;用来给一段CUDA代码进行计时&#xff0c;这里只介…