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

news2024/9/22 11:25:46

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

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

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

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

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

当前为第二篇,第一篇对窗口树有一个简单的认识后,本篇介绍窗口树的构建代码流程。
整个过程会相对无聊,但是不讲代码的技术文章就是耍流氓。

1. dump内容的打印

先看dump的数据在代码中是如何定义的

# WindowManagerService
    RootWindowContainer mRoot;

    private void doDump(FileDescriptor fd, PrintWriter pw, String[] args, boolean useProto) {
        ......
            // containers
            } else if ("containers".equals(cmd)) {
                synchronized (mGlobalLock) {
                    mRoot.dumpChildrenNames(pw, " ");
                    pw.println(" ");
                    mRoot.forAllWindows(w -> {pw.println(w);}, true /* traverseTopToBottom */);
                }
                return;
            } else if ("trace".equals(cmd)) {
        ......
    }
    //  打印的name
    @Override
    String getName() {
        return "ROOT";
    }

dumpChildrenNames的实现在WindowContainer的父类ConfigurationContainer中

# ConfigurationContainer
    public void dumpChildrenNames(PrintWriter pw, String prefix) {
        final String childPrefix = prefix + " ";
        pw.println(getName()
                + " type=" + activityTypeToString(getActivityType())
                + " mode=" + windowingModeToString(getWindowingMode())
                + " override-mode=" + windowingModeToString(getRequestedOverrideWindowingMode())
                + " requested-bounds=" + getRequestedOverrideBounds().toShortString()
                + " bounds=" + getBounds().toShortString());
        for (int i = getChildCount() - 1; i >= 0; --i) {
            final E cc = getChildAt(i);
            // 打印 # 加角标
            pw.print(childPrefix + "#" + i + " ");
            cc.dumpChildrenNames(pw, childPrefix);
        }
    }

可以看到从RootWindowContainer开始递归打印。 这也就是dump到的窗口容器层级树的内容。比如最开始的RootWindowContainer::getName返回的内容就是 “ROOT”。

2. 层级树的构建

2.1 调用链

SystemServer::run
    SystemServer::startOtherServices
        WindowManagerService::init
        ActivityManagerService::setWindowManager
            ActivityTaskManagerService::setWindowManager
                RootWindowContainer::setWindowManager
                    DisplayContent::init
                        DisplayContent::configureSurfaces
                            DisplayAreaPolicy.Provider::instantiate  -- 创建 DefaultTaskDisplayArea和输入法容器
                                DisplayAreaPolicy.Provider::configureTrustedHierarchyBuilder -- 开始配置图层的Feature
                                DisplayAreaPolicyBuilder::build
                                    PendingArea::instantiateChildren  -- 开始递归构建层级树
                                    RootDisplayArea::onHierarchyBuilt -- 构建完成

2.2 前期的一些调用链

调用链前面这段可以知道,在系统启动的时候就触发了这段逻辑,这也就是为什么刚进入launcher就可以dump出整个结构树的原因。
setWindowManager 方法传递的参数是WMS, WMS的启动是tartOtherServices中,而RootWindowContainer则是WMS的一个成员变量,RootWindowContainer是层级树中的跟容器,在WMS构建函数中创建。

# WindowManagerService
    // The root of the device window hierarchy.
    RootWindowContainer mRoot;
    // WMS构造函数
    private WindowManagerService(......) {
        ......
        mRoot = new RootWindowContainer(this);
        ......
    }

继续看构建流程

# RootWindowContainer
    void setWindowManager(WindowManagerService wm) {
        mWindowManager = wm;
        mDisplayManager = mService.mContext.getSystemService(DisplayManager.class);
        mDisplayManager.registerDisplayListener(this, mService.mUiHandler);
        mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);

        final Display[] displays = mDisplayManager.getDisplays();
        //  遍历每个屏幕
        for (int displayNdx = 0; displayNdx < displays.length; ++displayNdx) {
            final Display display = displays[displayNdx];
            // 重点*1:为每一个 Display 挂载一个 DisplayContent 节点
            final DisplayContent displayContent = new DisplayContent(display, this);
            addChild(displayContent, POSITION_BOTTOM);
            if (displayContent.mDisplayId == DEFAULT_DISPLAY) {
                mDefaultDisplay = displayContent;
            }
        }
    
        // 重点*2  TaskDisplayArea相关
        final TaskDisplayArea defaultTaskDisplayArea = getDefaultTaskDisplayArea();
        defaultTaskDisplayArea.getOrCreateRootHomeTask(ON_TOP);
        positionChildAt(POSITION_TOP, defaultTaskDisplayArea.mDisplayContent,
                false /* includingParents */);
    }

重点解析:

  1. 这段代码也就能看出,为什么说一个DisplayContent就代表着1个屏幕了。
  2. 处理TaskDisplayArea相关(这里窗口层级树已经构建完成了)
    上篇看层级树知道TaskDisplayArea就是放应用相关容器的,目前先不看这块,先跟踪DisplayContent下的逻辑, 现在需要看DisplayContent的构造方法,因为里面开始构造这个屏幕下层级树。(不考虑多屏幕的情况)

3. DisplayContent 开始构造当前屏幕的层级树

# DisplayContent
    // 2个参数为Display和跟容器
    DisplayContent(Display display, RootWindowContainer root) {
        ......
        // 创建事务
        final Transaction pendingTransaction = getPendingTransaction();
        // 重点开始构建层级树
        configureSurfaces(pendingTransaction);
        // 执行事务
        pendingTransaction.apply();
        ......
    }

    private void configureSurfaces(Transaction transaction) {
        // 构建一个SurfaceControl
        final SurfaceControl.Builder b = mWmService.makeSurfaceBuilder(mSession)
                .setOpaque(true)
                .setContainerLayer()
                .setCallsite("DisplayContent");
        // 设置名字后构建 (Display 0 name="XXX")
        mSurfaceControl = b.setName(getName()).setContainerLayer().build();

        // 重点* 设置策略并构建显示区域层次结构
        if (mDisplayAreaPolicy == null) {
            // WMS的getDisplayAreaPolicyProvider方法按返回 DisplayAreaPolicy.Provider
            // 然后其 instantiate的实现 目前只有DisplayAreaPolicy的内部类DefaultProvider
            
            mDisplayAreaPolicy = mWmService.getDisplayAreaPolicyProvider().instantiate(
                    mWmService, this /* content */, this /* root */,
                    mImeWindowsContainer);
        }
        // 事务相关设置
        transaction
                .setLayer(mSurfaceControl, 0)
                .setLayerStack(mSurfaceControl, mDisplayId)
                .show(mSurfaceControl)
                .setLayer(mOverlayLayer, Integer.MAX_VALUE)
                .show(mOverlayLayer);
    }

这一块我们目前可以忽略transaction的代码只需要关心中间“getDisplayAreaPolicyProvider”这一块就好了,这段是层级树的主流程。

tips:

  1. 方法最开始的 setName设置name在层级树是能找到对应名字的
  2. 注意instantiate倒数第3第4都个参数传递的都是this,也就是DisplayContent, 因为 DisplayContent这是的父类是RootDisplayArea
# DisplayContent
    String getName() {
        return "Display " + mDisplayId + " name=\"" + mDisplayInfo.name + "\"";
    }

mDisplayId 如果只有一个屏幕就是 0 ,所以dump到层级树中的这句信息

  #0 Display 0 name="Built-in Screen"

就是在这里设置的,后面的"Built-in Screen"对应的应该就是mDisplayInfo.name了。
接下来继续看主流程:

# DisplayAreaPolicy.Provider
        @Override
        public DisplayAreaPolicy instantiate(WindowManagerService wmService,
                DisplayContent content, RootDisplayArea root,
                DisplayArea.Tokens imeContainer) {

            // 重点*1. 创建一个名为 "DefaultTaskDisplayArea" 的对象作为应用窗口的默认容器(第三个参数Feature为FEATURE_DEFAULT_TASK_CONTAINER)
            final TaskDisplayArea defaultTaskDisplayArea = new TaskDisplayArea(content, wmService,
                    "DefaultTaskDisplayArea", FEATURE_DEFAULT_TASK_CONTAINER);
            final List<TaskDisplayArea> tdaList = new ArrayList<>();
            //  实际上只有1个元素
            tdaList.add(defaultTaskDisplayArea);

            // Define the features that will be supported under the root of the whole logical
            // display. The policy will build the DisplayArea hierarchy based on this.
            // 传递RootDisplayArea(DisplayContent)构建出一个层级树的数据结构
            final HierarchyBuilder rootHierarchy = new HierarchyBuilder(root);
            // Set the essential containers (even if the display doesn't support IME).
            // 设置输入法容器
            rootHierarchy.setImeContainer(imeContainer).setTaskDisplayAreas(tdaList);

            // 这个条件满足,肯定是被信任的
            if (content.isTrusted()) {
                // 重点* 2 配置层级的支持的Feature
                configureTrustedHierarchyBuilder(rootHierarchy, wmService, content);
            }

            // Instantiate the policy with the hierarchy defined above. This will create and attach
            // all the necessary DisplayAreas to the root.
            // 重点* 3 真正开始构建层级树
            return new DisplayAreaPolicyBuilder().setRootHierarchy(rootHierarchy).build(wmService);
        }

这个方法是在DisplayContent构造函数掉进来的,注意最后2个参数,root表示跟容器,imeContainer则是输入法容器,在DisplayContent中传过来的,然后被通过setImeContainer设置给了HierarchyBuilder。
重点分析:

  1. “DefaultTaskDisplayArea” 终于出现了, 可以看到确实是TaskDisplayArea对象,然后FEATURE_DEFAULT_TASK_CONTAINER这个ID的值就是1, 那也就是在第二层,和层级树是对应的,然后构建了一个List,但是这个集合就这一个元素。
  2. 配置层级的支持的Feature
  3. 开始真正的构建

3.1 配置Feature

发现层级树中一共就出现了5个Feature就是在当前方法中配置的,分别如下:
WindowedMagnification
HideDisplayCutout
OneHanded
FullscreenMagnification
ImePlaceholder

# DisplayAreaPolicy.Provider
        private void configureTrustedHierarchyBuilder(HierarchyBuilder rootHierarchy,
                WindowManagerService wmService, DisplayContent content) {
            // WindowedMagnification should be on the top so that there is only one surface
            // to be magnified.
            rootHierarchy.addFeature(new Feature.Builder(wmService.mPolicy, "WindowedMagnification",
                    FEATURE_WINDOWED_MAGNIFICATION)
                    .upTo(TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY)
                    .except(TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY)
                    // Make the DA dimmable so that the magnify window also mirrors the dim layer.
                    .setNewDisplayAreaSupplier(DisplayArea.Dimmable::new)
                    .build());
            if (content.isDefaultDisplay) {
                // Only default display can have cutout.
                // See LocalDisplayAdapter.LocalDisplayDevice#getDisplayDeviceInfoLocked.
                rootHierarchy.addFeature(new Feature.Builder(wmService.mPolicy, "HideDisplayCutout",
                        FEATURE_HIDE_DISPLAY_CUTOUT)
                        .all()
                        .except(TYPE_NAVIGATION_BAR, TYPE_NAVIGATION_BAR_PANEL, TYPE_STATUS_BAR,
                                TYPE_NOTIFICATION_SHADE)
                        .build())
                        .addFeature(new Feature.Builder(wmService.mPolicy, "OneHanded",
                                FEATURE_ONE_HANDED)
                                .all()
                                .except(TYPE_NAVIGATION_BAR, TYPE_NAVIGATION_BAR_PANEL,
                                        TYPE_SECURE_SYSTEM_OVERLAY)
                                .build());
            }
            rootHierarchy
                    .addFeature(new Feature.Builder(wmService.mPolicy, "FullscreenMagnification",
                            FEATURE_FULLSCREEN_MAGNIFICATION)
                            .all()
                            .except(TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY, TYPE_INPUT_METHOD,
                                    TYPE_INPUT_METHOD_DIALOG, TYPE_MAGNIFICATION_OVERLAY,
                                    TYPE_NAVIGATION_BAR, TYPE_NAVIGATION_BAR_PANEL)
                            .build())
                    .addFeature(new Feature.Builder(wmService.mPolicy, "ImePlaceholder",
                            FEATURE_IME_PLACEHOLDER)
                            .and(TYPE_INPUT_METHOD, TYPE_INPUT_METHOD_DIALOG)
                            .build());
    }

这里执行了5次addFeature,每次对应一个Feature刚好是5个。Feature.Builder构造一个Feature对象,代码如下

# DisplayAreaPolicy.Feature.Builder
            Builder(WindowManagerPolicy policy, String name, int id) {
                mPolicy = policy;
                mName = name;
                mId = id;
                mLayers = new boolean[mPolicy.getMaxWindowLayer() + 1];
            }

注意后面2个参数,第二个为name,就是名字,后面的是ID,根据使用的地方肯定是定义了对应ID的。
留意下mLayers,mPolicy.getMaxWindowLayer()返回36所以是定义了一个长度为37的boolean类型数组,如果为ture表示这个图层支持这个Feature,为false反之。

5个Feature对应的ID如下,并且有相应的注释:

    # DisplayAreaOrganizer
        /**
         * Display area that can be magnified in
         * ......
         */
        public static final int FEATURE_WINDOWED_MAGNIFICATION = FEATURE_SYSTEM_FIRST + 4;

        /**
         * Display area for hiding display cutout feature
         * @hide
         */
        public static final int FEATURE_HIDE_DISPLAY_CUTOUT = FEATURE_SYSTEM_FIRST + 6;

        /**
         * Display area for one handed feature
         */
        public static final int FEATURE_ONE_HANDED = FEATURE_SYSTEM_FIRST + 3;

        /**
         * Display area that can be magnified in
         * ......
         */
        public static final int FEATURE_FULLSCREEN_MAGNIFICATION = FEATURE_SYSTEM_FIRST + 5;

        /**
         * Display area that the IME container can be placed in. Should be enabled on every root
         * hierarchy if IME container may be reparented to that hierarchy when the IME target changed.
         * @hide
         */
        public static final int FEATURE_IME_PLACEHOLDER = FEATURE_SYSTEM_FIRST + 7;

根据注释能知道这个Feature代表这个图层具体用于什么特征了。

然后还看到all(),and(),except()等方法。

3.1.1 all,and,except方法

    # DisplayAreaPolicy.Feature.Builder

         Builder all() {
            Arrays.fill(mLayers, true);
            return this;
        }

        Builder and(int... types) {
            for (int i = 0; i < types.length; i++) {
                int type = types[i];
                set(type, true);
            }
            return this;
        }

        Builder except(int... types) {
            for (int i = 0; i < types.length; i++) {
                int type = types[i];
                set(type, false);
            }
            return this;
        }

        Builder upTo(int typeInclusive) {
            // 根据传入的type计算到图层
            final int max = layerFromType(typeInclusive, false);
            for (int i = 0; i < max; i++) {
                mLayers[i] = true;
            }
            set(typeInclusive, true);
            return this;
        
        }
        private void set(int type, boolean value) {
            mLayers[layerFromType(type, true)] = value;
            ......
        }

        private int layerFromType(int type, boolean internalWindows) {
            return mPolicy.getWindowLayerFromTypeLw(type, internalWindows);
        }

        Feature build() {
            // 默认为true
            if (mExcludeRoundedCorner) {
                // Always put the rounded corner layer to the top most layer.
                mLayers[mPolicy.getMaxWindowLayer()] = false;
            }
            return new Feature(mName, mId, mLayers.clone(), mNewDisplayAreaSupplier);
        }

mLayers前面说过是一个长度为37的数组,set方法就是将参数的这个图层,对应的boolean设置为true, 换句话说就是指定某个图层是否支持这个Feature。

all():将所有数组所有值都设为true,表示每个图层都支持这个Feature

and(): 将指定某个图层支持这个Feature

except():将指定某个图层不支持这个Feature

upTo(): 将支持Feature的图层设置为从0到typeInclusive

build():将数组的最后最后一个设置为false,剔除最后一层

这里的几个方法都会调用到layerFromType,根据layerFromType方法的调用知道具体逻辑在WindowManagerPolicy::getWindowLayerFromTypeLw方法控制的.
这段代码有点长是因为好多case,但是总体逻辑并不复杂,主要关注传入的WindowType和返回的Layertype,其实就是返回层级树中所在的图层。

3.1.2 重点:getWindowLayerFromTypeLw方法 (决定窗口挂载在哪一层)

   # WindowManagerPolicy
       default int getWindowLayerFromTypeLw(int type, boolean canAddInternalSystemWindow,
               boolean roundedCornerOverlay) {
           // Always put the rounded corner layer to the top most.
           // 第二个参数为false,这里忽略
           if (roundedCornerOverlay && canAddInternalSystemWindow) {
               return getMaxWindowLayer();
           }
           // 根据这2个type名字也知道表示 APP图层,对应的值是1-99,如果处于这直接就返回APPLICATION_LAYER =2
           if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
               return APPLICATION_LAYER;
           }
           // 然后开始根据各个WindowType,去返回其在层级树中所在的图层
           switch (type) {
               case TYPE_WALLPAPER:
                   // wallpaper is at the bottom, though the window manager may move it.
                   return  1;
               case TYPE_PRESENTATION:
               case TYPE_PRIVATE_PRESENTATION:
               case TYPE_DOCK_DIVIDER:
               case TYPE_QS_DIALOG:
               case TYPE_PHONE:
                   return  3;
               case TYPE_SEARCH_BAR:
                   return  4;
               case TYPE_INPUT_CONSUMER:
                   return  5;
               case TYPE_SYSTEM_DIALOG:
                   return  6;
               case TYPE_TOAST:
                   // toasts and the plugged-in battery thing
                   return  7;
               case TYPE_PRIORITY_PHONE:
                   // SIM errors and unlock.  Not sure if this really should be in a high layer.
                   return  8;
               case TYPE_SYSTEM_ALERT:
                   // like the ANR / app crashed dialogs
                   // Type is deprecated for non-system apps. For system apps, this type should be
                   // in a higher layer than TYPE_APPLICATION_OVERLAY.
                   return  canAddInternalSystemWindow ? 12 : 9;
               case TYPE_APPLICATION_OVERLAY:
                   return  11;
               case TYPE_INPUT_METHOD:
                   // on-screen keyboards and other such input method user interfaces go here.
                   return  13;
               case TYPE_INPUT_METHOD_DIALOG:
                   // on-screen keyboards and other such input method user interfaces go here.
                   return  14;
               case TYPE_STATUS_BAR:
                   return  15;
               case TYPE_STATUS_BAR_ADDITIONAL:
                   return  16;
               case TYPE_NOTIFICATION_SHADE:
                   return  17;
               case TYPE_STATUS_BAR_SUB_PANEL:
                   return  18;
               case TYPE_KEYGUARD_DIALOG:
                   return  19;
               case TYPE_VOICE_INTERACTION_STARTING:
                   return  20;
               case TYPE_VOICE_INTERACTION:
                   // voice interaction layer should show above the lock screen.
                   return  21;
               case TYPE_VOLUME_OVERLAY:
                   // the on-screen volume indicator and controller shown when the user
                   // changes the device volume
                   return  22;
               case TYPE_SYSTEM_OVERLAY:
                   // the on-screen volume indicator and controller shown when the user
                   // changes the device volume
                   return  canAddInternalSystemWindow ? 23 : 10;
               case TYPE_NAVIGATION_BAR:
                   // the navigation bar, if available, shows atop most things
                   return  24;
               case TYPE_NAVIGATION_BAR_PANEL:
                   // some panels (e.g. search) need to show on top of the navigation bar
                   return  25;
               case TYPE_SCREENSHOT:
                   // screenshot selection layer shouldn't go above system error, but it should cover
                   // navigation bars at the very least.
                   return  26;
               case TYPE_SYSTEM_ERROR:
                   // system-level error dialogs
                   return  canAddInternalSystemWindow ? 27 : 9;
               case TYPE_MAGNIFICATION_OVERLAY:
                   // used to highlight the magnified portion of a display
                   return  28;
               case TYPE_DISPLAY_OVERLAY:
                   // used to simulate secondary display devices
                   return  29;
               case TYPE_DRAG:
                   // the drag layer: input for drag-and-drop is associated with this window,
                   // which sits above all other focusable windows
                   return  30;
               case TYPE_ACCESSIBILITY_OVERLAY:
                   // overlay put by accessibility services to intercept user interaction
                   return  31;
               case TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY:
                   return 32;
               case TYPE_SECURE_SYSTEM_OVERLAY:
                   return  33;
               case TYPE_BOOT_PROGRESS:
                   return  34;
               case TYPE_POINTER:
                   // the (mouse) pointer layer
                   return  35;
               default:
                   Slog.e("WindowManager", "Unknown window type: " + type);
                   return 3;
           }
       }

代码很长不用一个个看,直接根据参数找就好,比如当参数是TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY,那对应的返回就是32。
后面看到的对应的TYPE,直接复制WindowManagerPolicy类下搜索即可。

现在再重新看看configureTrustedHierarchyBuilder方法里5个Feature到底是什么。

    # DisplayAreaPolicy.Provider
            private void configureTrustedHierarchyBuilder(HierarchyBuilder rootHierarchy,
                    WindowManagerService wmService, DisplayContent content) {
                // WindowedMagnification should be on the top so that there is only one surface
                // to be magnified.
                rootHierarchy.addFeature(new Feature.Builder(wmService.mPolicy, "WindowedMagnification",
                        FEATURE_WINDOWED_MAGNIFICATION)
                        .upTo(TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY) // 0-32
                        .except(TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY)// 32
                        // Make the DA dimmable so that the magnify window also mirrors the dim layer.
                        .setNewDisplayAreaSupplier(DisplayArea.Dimmable::new)
                        .build()); // 0-31
                if (content.isDefaultDisplay) {
                    // Only default display can have cutout.
                    // See LocalDisplayAdapter.LocalDisplayDevice#getDisplayDeviceInfoLocked.
                    rootHierarchy.addFeature(new Feature.Builder(wmService.mPolicy, "HideDisplayCutout",
                            FEATURE_HIDE_DISPLAY_CUTOUT)
                            .all() //  0-36
                            .except(TYPE_NAVIGATION_BAR, TYPE_NAVIGATION_BAR_PANEL, TYPE_STATUS_BAR,
                                    TYPE_NOTIFICATION_SHADE)//  24 25  15  17
                            .build())// 0-14   16  18-23 26-35
                            .addFeature(new Feature.Builder(wmService.mPolicy, "OneHanded",
                                    FEATURE_ONE_HANDED)
                                    .all()
                                    .except(TYPE_NAVIGATION_BAR, TYPE_NAVIGATION_BAR_PANEL,
                                            TYPE_SECURE_SYSTEM_OVERLAY)//24  25  33
                                    .build());// 0-23   26-32  34-35
                }
                rootHierarchy
                        .addFeature(new Feature.Builder(wmService.mPolicy, "FullscreenMagnification",
                                FEATURE_FULLSCREEN_MAGNIFICATION)
                                .all() // 0-36
                                .except(TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY, TYPE_INPUT_METHOD,
                                        TYPE_INPUT_METHOD_DIALOG, TYPE_MAGNIFICATION_OVERLAY,
                                        TYPE_NAVIGATION_BAR, TYPE_NAVIGATION_BAR_PANEL)// 32  13   14  28  24 25
                                .build())// 0-12 15-23  26-27 29-31 33-35
                        .addFeature(new Feature.Builder(wmService.mPolicy, "ImePlaceholder",
                                FEATURE_IME_PLACEHOLDER)
                                .and(TYPE_INPUT_METHOD, TYPE_INPUT_METHOD_DIALOG)// 13-14
                                .build());// 13-14
            }
        }

3.2 Feature总结

WindowedMagnification
拥有特征的层级: 0-31
特征描述: 支持窗口缩放的一块区域,一般是通过辅助服务进行缩小或放大

HideDisplayCutout
拥有特征的层级: 0-14 16 18-23 26-35
特征描述:隐藏剪切区域,即在默认显示设备上隐藏不规则形状的屏幕区域,比如在代码中打开这个功能后,有这个功能的图层就不会延伸到刘海屏区域。

OneHanded
拥有特征的层级:0-23 26-32 34-35
特征描述:表示支持单手操作的图层,这个功能在手机上还是挺常见的

FullscreenMagnification
拥有特征的层级:0-12 15-23 26-27 29-31 33-35
特征描述:支持全屏幕缩放的图层,和上面的不同,这个是全屏缩放,前面那个可以局部

ImePlaceholder
拥有特征的层级: 13-14
特征描述:输入法相关

再放上之前画的层级树更加清晰了

在这里插入图片描述

3.3 构建层级树 DisplayAreaPolicyBuilder::build

上面只是将5个Feature添加到了rootHierarchy的mFeatures这个集合中

    # HierarchyBuilder
        private final ArrayList<DisplayAreaPolicyBuilder.Feature> mFeatures = new ArrayList<>();

            HierarchyBuilder addFeature(DisplayAreaPolicyBuilder.Feature feature) {
                mFeatures.add(feature);
                return this;
            }

DisplayAreaPolicyBuilder::setRootHierarchy方法很简单,就是把添加了ImeContainer和5个Feature的HierarchyBuilder设置给DisplayAreaPolicyBuilder

    # DisplayAreaPolicyBuilder
        DisplayAreaPolicyBuilder setRootHierarchy(HierarchyBuilder rootHierarchyBuilder) {
            mRootHierarchyBuilder = rootHierarchyBuilder;
            return this;
        }

然后开始执行DisplayAreaPolicyBuilder::build

    # DisplayAreaPolicyBuilder
        // 这个可以忽略,没有值
        private final ArrayList<HierarchyBuilder> mDisplayAreaGroupHierarchyBuilders =
                new ArrayList<>();

        Result build(WindowManagerService wmService) {
            //  对输入参数进行验证,确保它们是有效的
            validate();

            // Attach DA group roots to screen hierarchy before adding windows to group hierarchies.
            // 重点:构建层级树
            mRootHierarchyBuilder.build(mDisplayAreaGroupHierarchyBuilders);
            // 因为mDisplayAreaGroupHierarchyBuilders没有值,后面的都可以忽略
            ......
            return new Result(wmService, mRootHierarchyBuilder.mRoot, displayAreaGroupRoots,
                    mSelectRootForWindowFunc);
        }

4. 层级结构树构造

这个mRootHierarchyBuilder就是上一小节操作的RootHierarchyBuilder,然后执行其build方法,这个方法非常重要!!!

在这里插入图片描述

在构造层级树一共分为2步:

  1. 构建PendingArea树
    1. 构建Feature相关
    2. 构建Leaf相关
  2. 根据PendingArea树构建最终的DisplayAreas树,也就是层级树

通过2个类的名字也能感觉到一些关系,毕竟叫Pending。既然要先构造PendingPendingArea,那肯定需要先看看PendingArea这个数据结构

4.1 数据结构 PendingArea简介

    # DisplayAreaPolicyBuilder.PendingArea

        static class PendingArea {
            final int mMinLayer; // 最小层级
            final ArrayList<PendingArea> mChildren = new ArrayList<>();// 有Children说明也是一个容器
            final Feature mFeature; //  当前支持的Feature
            final PendingArea mParent; // 有父亲
            int mMaxLayer; // 最大层级
            // 从这几个成员变量其实能感觉到和上一篇画的层级树的图有点那味了
            @Nullable DisplayArea mExisting; // 当前存在的容器
            boolean mSkipTokens = false; // 只有输入法和应用会为true

            PendingArea(Feature feature, int minLayer, PendingArea parent) {
                mMinLayer = minLayer;
                mFeature = feature;
                mParent = parent;
            }
            ......
        }

PendingArea后面还有一些方法,等后面会再次具体分析,当前只有PendingArea这个数据结构是什么样就好了。

4.2 构建PendingArea树

下面这段代码比较长我再代码里加了很多注释,其实这一块就是java的循环对数据结构的处理。就和刚学java的时候看2个for循环一样。
这个方法其实是构建整个树的方法,先看一眼,后面会再具体分析。

    # DisplayAreaPolicyBuilder.HierarchyBuilder

            private final RootDisplayArea mRoot;

            private void build(@Nullable List<HierarchyBuilder> displayAreaGroupHierarchyBuilders) {
                final WindowManagerPolicy policy = mRoot.mWmService.mPolicy;
                // 定义最大层级数  37 = 36+1 
                final int maxWindowLayerCount = policy.getMaxWindowLayer() + 1;
                // 存储每个窗口层级对应的 DisplayArea.Tokens,一共37个,后续窗口挂载也是在这个数据结构上找
                // 在方法底部执行instantiateChildren的时候调用
                final DisplayArea.Tokens[] displayAreaForLayer =
                        new DisplayArea.Tokens[maxWindowLayerCount];

                // 存储每个特性对应的 DisplayArea 列表
                // mFeatures就是在configureTrustedHierarchyBuilder配置的Feature大小,很明显一共是5个
                final Map<Feature, List<DisplayArea<WindowContainer>>> featureAreas =
                        new ArrayMap<>(mFeatures.size());
                for (int i = 0; i < mFeatures.size(); i++) {
                    // 为每个feature,创建其对应的DisplayArea列表
                    featureAreas.put(mFeatures.get(i), new ArrayList<>());
                }
                // 到这里featureAreas里一共是5个值,key就是上节提到的5个Feature,value目前就是个空的List

                // -------构建PendingArea树----
                // *1 创建 PendingArea 数组,用于临时存储每个窗口层级对应的 PendingArea,也是37个
                // 后面需要关注这个areaForLayer成员的变化
                PendingArea[] areaForLayer = new PendingArea[maxWindowLayerCount];
                // 先创建个PendingArea 再让areaForLayer的37个数据都默认为root的PendingArea
                // 注意第一个参数feature为null
                final PendingArea root = new PendingArea(null, 0, null);
                Arrays.fill(areaForLayer, root);

                // 2. 构建Features的树
                // mFeatures.size为5,所以有5个大循环
                final int size = mFeatures.size();
                for (int i = 0; i < size; i++) {
                    // 拿到当前需要处理的Feature
                    final Feature feature = mFeatures.get(i);
                    PendingArea featureArea = null;
                    // 内部循环,37次
                    for (int layer = 0; layer < maxWindowLayerCount; layer++) {
                        // 如果这个层级,支持当前Feature
                        if (feature.mWindowLayers[layer]) {
                            //判断是否复用 PendingArea (同一个feature才复用,否则创建新的)
                            if (featureArea == null || featureArea.mParent != areaForLayer[layer]) {
                                // 创建新的 PendingArea,作为上一层级的子节点,用于当前层级,并且双向奔赴,设置为各自的孩子或者父亲
                                // 注意第一个参数feature
                                featureArea = new PendingArea(feature, layer, areaForLayer[layer]);
                                areaForLayer[layer].mChildren.add(featureArea);
                            }
                            areaForLayer[layer] = featureArea;
                        } else {
                            // 如果该特性不应用于当前窗口层级,则featureArea置为空。用于上面if的判断
                            featureArea = null;
                        }
                    }
                }
                // 到这里,areaForLayer这个37层就按照feature分类,有自己对应的PendingArea了。


                // 3. 构建叶子节点相关的PendingArea,注意还是操作areaForLayer数组,但是操作的是内部元素的mChildren的值

                // 定义一个叶子节点用的 PendingArea
                PendingArea leafArea = null;
                int leafType = LEAF_TYPE_TOKENS;// 定义leafType
               
                for (int layer = 0; layer < maxWindowLayerCount; layer++) {
                    //  获取每层的type,从这看type是和所在层级有关系的
                    int type = typeOfLayer(policy, layer);
                    // // 检查是否可以复用前一个层级的 Tokens,和前面的循环类似
                    if (leafArea == null || leafArea.mParent != areaForLayer[layer]
                            || type != leafType) {
                        // 创建PendingArea,注意参数,featur为null
                        leafArea = new PendingArea(null /* feature */, layer, areaForLayer[layer]);
                        // 注意是添加到孩子,而不是跟上一次循环直接修改areaForLayer
                        areaForLayer[layer].mChildren.add(leafArea);
                        leafType = type;
                        // 应用类型处理
                        if (leafType == LEAF_TYPE_TASK_CONTAINERS) {
                            // 添加 TaskDisplayArea 到应用程序层级
                            addTaskDisplayAreasToApplicationLayer(areaForLayer[layer]);
                             // 添加 DisplayAreaGroup 到应用程序层级
                            addDisplayAreaGroupsToApplicationLayer(areaForLayer[layer],
                                    displayAreaGroupHierarchyBuilders);
                            // 跳过创建 Tokens,即不创建 Tokens,即使没有 Task
                            leafArea.mSkipTokens = true;
                        } else if (leafType == LEAF_TYPE_IME_CONTAINERS) {
                            // 输入法处理
                            leafArea.mExisting = mImeContainer;
                             // 跳过
                            leafArea.mSkipTokens = true;
                        }
                    }
                    leafArea.mMaxLayer = layer;
                }
                // 计算根节点的最大层级
                root.computeMaxLayer();
                // -------构建DisplayAreas树----
                // 4. 根据之前定义的PendingArea生成最后的 DisplayAreas 树
                // 注意参数
                // We built a tree of PendingAreas above with all the necessary info to represent the
                // hierarchy, now create and attach real DisplayAreas to the root.
                root.instantiateChildren(mRoot, displayAreaForLayer, 0, featureAreas);

                // 通知根节点已经完成了所有DisplayArea的添加 (将displayAreaForLayer保存在RootDisplayArea成员变量roomAreaForLayer中,供后面逻辑使用)
                mRoot.onHierarchyBuilt(mFeatures, displayAreaForLayer, featureAreas);
            }

4.2.1 构建Feature相关

这边根据具体的执行画了几张图,先看上面Features的循环逻辑,在执行循环前数组areaForLayer和执行第一次大循环后集合如下
第一个Feature是WindowedMagnification拥有特征的层级 0-31,也就是其 前面32个为true。

在这里插入图片描述
在层级树,如果某一块都是支持同一Feature的话,可以写成 “name 起始层:结束层 ”的形式,转换后如下
转换成层级树的方式就是
在这里插入图片描述

然后第二个大循环

第二Feature是HideDisplayCutout拥有特征的层级 0-14 16 18-23 26-35

在这里插入图片描述

太长了所以第24开始换到了下一排, 规律就是结合上一次的循环,0-31以内,HideDisplayCutout的父亲都是上一次循环的WindowedMagnification,然后32之后的父亲就是默认的root了。
再转成层级树的表示形式如下:
在这里插入图片描述
按照这个规则Feature 5次全执行完后,层级树的图就是下面这个,不过做了下顺序的调整,从小到达排序

在这里插入图片描述

4.2.2 构建Leaf相关

在构建叶子节点的时候,又有一个新的东西,leafType,对应的就是叶子节点的类型,默认是LEAF_TYPE_TOKENS,一共也只定义了3个

    # DisplayAreaPolicyBuilder.HierarchyBuilder
            private static final int LEAF_TYPE_TASK_CONTAINERS = 1; // APP
            private static final int LEAF_TYPE_IME_CONTAINERS = 2; // 输入法
            private static final int LEAF_TYPE_TOKENS = 0; // 默认

然后是通过typeOfLayer方法根据当前层级返回type

    # DisplayAreaPolicyBuilder.HierarchyBuilder

            private static int typeOfLayer(WindowManagerPolicy policy, int layer) {
                if (layer == APPLICATION_LAYER) {
                    return LEAF_TYPE_TASK_CONTAINERS;
                } else if (layer == policy.getWindowLayerFromTypeLw(TYPE_INPUT_METHOD)
                        || layer == policy.getWindowLayerFromTypeLw(TYPE_INPUT_METHOD_DIALOG)) {
                    return LEAF_TYPE_IME_CONTAINERS;
                } else {
                    return LEAF_TYPE_TOKENS;
                }
            }

逻辑还是比较简单的除了输入法(13-14)和应用(2)所在的层级,均返回LEAF_TYPE_TOKENS。
经过第二个for循环后,相当于给每个Feature 都加上了一个Leaf , 然后对输入法和应用图做了单独的处理。
先看输入法的, 是把mExisting设置为了最开始从DisplayConten传进来的mImeContainer,然后mSkipTokens设置为false,表示后续的操作可以跳过。
然后看对应用图层的处理,除了也将mSkipTokens设置为false外还执行了2个方法其中第二個方法。addDisplayAreaGroupsToApplicationLayer因为内部依赖displayAreaGroupHierarchyBuilders,而目前也没看到对这个对象操作的地方,所以长度为0,可以忽略,所以只看

addTaskDisplayAreasToApplicationLayer方法即可

addTaskDisplayAreasToApplicationLayer

mTaskDisplayAreas看到应该联想到前面创建的name为“DefaultTaskDisplayArea”的那一个TaskDisplayArea,事实上也就是在那创建的,这个是和应用最相关的图层,
从代码上看也能证明:

    # DisplayAreaPolicyBuilder.HierarchyBuilder
        private final ArrayList<TaskDisplayArea> mTaskDisplayAreas = new ArrayList<>();

        HierarchyBuilder setTaskDisplayAreas(List<TaskDisplayArea> taskDisplayAreas) {
            mTaskDisplayAreas.clear();
            mTaskDisplayAreas.addAll(taskDisplayAreas);
            return this;
        }
        private void addTaskDisplayAreasToApplicationLayer(PendingArea parentPendingArea) {
            // 已知长度为1,
            final int count = mTaskDisplayAreas.size();
                
            for (int i = 0; i < count; i++) {
                PendingArea leafArea =
                        new PendingArea(null /* feature */, APPLICATION_LAYER, parentPendingArea);
                // 所以就是把“DefaultTaskDisplayArea”这个设置为mExisting
                leafArea.mExisting = mTaskDisplayAreas.get(i);
                leafArea.mMaxLayer = APPLICATION_LAYER;
                // parentPendingArea.mChildren本来为大家都一样的Leaf,又添加了一个DefaultTaskDisplayArea
                parentPendingArea.mChildren.add(leafArea);
            }
        }

这段对应用图层的处理非常的重要了,特别最下面对parentPendingArea.mChildren再次添加DefaultTaskDisplayArea的操作

经过这个循环的处理,每个Feature下面都有了对应的叶子节点,如图:

在这里插入图片描述
第二层应用层目前是有2个孩子的,一个是Lead,另一个就是DefaultTaskDisplayArea。
到目前为止,层级树雏形是有了。但是比较还是一个PendingArea数组,另外 Leaf 0:1 这种目前在代码上也还没有得到体现。

4.3 真正DisplayAreas树 PendingArea::instantiateChildren

其实从PendingArea::instantiateChildren上面源码给的2个注释也知道,前面的2个循环,只是构建了一个PendingAreas树,接下来才是真正构建层级树(DisplayAreas)
并把这个树添加到root(DisplayContent)

    # DisplayAreaPolicyBuilder.PendingArea

            void instantiateChildren(DisplayArea<DisplayArea> parent, DisplayArea.Tokens[] areaForLayer,
                    int level, Map<Feature, List<DisplayArea<WindowContainer>>> areas) {
                // 1. 子区域按照它们的最小层级进行升序排列
                mChildren.sort(Comparator.comparingInt(pendingArea -> pendingArea.mMinLayer));
                // 2. 遍历孩子将PendingArea转换成DisplayArea
                for (int i = 0; i < mChildren.size(); i++) {
                    final PendingArea child = mChildren.get(i);
                    final DisplayArea area = child.createArea(parent, areaForLayer);
                    if (area == null) {
                        // TaskDisplayArea and ImeContainer can be set at different hierarchy, so it can
                        // be null.
                        continue;
                    }
                    // 将返回的area设置为孩子,第一次执行的时候root就是DisplayContent
                    parent.addChild(area, WindowContainer.POSITION_TOP);
                    if (child.mFeature != null) {
                        // 让Feature对应的容器里添加创建的DisplayArea
                        areas.get(child.mFeature).add(area);
                    }
                    // 开始迭代构建
                    child.instantiateChildren(area, areaForLayer, level + 1, areas);
                }
            }

先解释一下3个参数
parent:根据上面代码的代码逻辑,root就是DisplayContent
areaForLayer: 这个是build方法开始创建的displayAreaForLayer
level:从哪级开始
areas: 这个也是build方法创建的map集合,key是Feature。

  1. 上来就执行了个排序,这个mChildren是啥呢?咋一看好像一点印象都没有,但是根据这个方法调用处看,他是root.instantiateChildren,
    而这个root是构建PendingAreas树时最开始创建的root,也就是我们上面图片PendingAreas树里的 root 0:0。所以他的孩子就是2次循环处理后,父亲是他的PendingArea,也就是那些feature或者leaf
  2. 这一步就是将那些PendingArea的数据结构转换为DisplayArea
    之前看过PendingArea的成员变量和构造方法,现在看看

    # DisplayAreaPolicyBuilder.PendingArea
            @Nullable
            private DisplayArea createArea(DisplayArea<DisplayArea> parent,
                    DisplayArea.Tokens[] areaForLayer) {
                // 只有输入法和应用层mExisting有值
                if (mExisting != null) {
                    if (mExisting.asTokens() != null) { 
                        // 只有输入法满足
                        // Store the WindowToken container for layers
                        fillAreaForLayers(mExisting.asTokens(), areaForLayer);
                    }
                    // 然后将mExisting作为结果返回
                    return mExisting;
                }
                // mSkipTokens为true则返回,应用和IME创建的PendingArea
                if (mSkipTokens) {
                    return null;
                }
                // 2. 定义DisplayArea的type
                DisplayArea.Type type;
                if (mMinLayer > APPLICATION_LAYER) {
                    type = DisplayArea.Type.ABOVE_TASKS;
                } else if (mMaxLayer < APPLICATION_LAYER) {
                    type = DisplayArea.Type.BELOW_TASKS;
                } else {
                    type = DisplayArea.Type.ANY;
                }
                
                if (mFeature == null) {
                    // // 3. 构建返回的leaf    注意第三个参数格式
                    final DisplayArea.Tokens leaf = new DisplayArea.Tokens(parent.mWmService, type,
                            "Leaf:" + mMinLayer + ":" + mMaxLayer);
                    fillAreaForLayers(leaf, areaForLayer); // 给对应覆盖的层级都需要赋值
                    return leaf;
                } else {
                    // 对有Feature的PendingArea返回构建
                    return mFeature.mNewDisplayAreaSupplier.create(parent.mWmService, type,
                            mFeature.mName + ":" + mMinLayer + ":" + mMaxLayer, mFeature.mId);
                }
            }

注意这里的参数areaForLayer这个是一个build方法创建的集合,也是最终层级树的体现。

  1. 方法前面mExisting.asTokens, 这个asTokens,方法定义在DisplayArea中默认返回null,只有DisplayArea.Tokens返回本身。 而ImeContainer是继承DisplayArea.Tokens的,所以有返回值。
    而对于应用层mExisting是TaskDisplayArea,不是DisplayArea.Tokens的子类,所以这个不满足,也就是说只有IME的PendingArea才会执行下面fillAreaForLayers的逻辑

# DisplayAreaPolicyBuilder.PendingArea
            private void fillAreaForLayers(DisplayArea.Tokens leaf, DisplayArea.Tokens[] areaForLayer) {
                for (int i = mMinLayer; i <= mMaxLayer; i++) {
                    areaForLayer[i] = leaf;
                }
            }

fillAreaForLayers方法也比较简单,就是将这个PendingArea的所有图层都设置传进来的leaf。那当前逻辑只处理IME的话,就是把13,14层都设置这个mExisting.
另外应用层不执行到fillAreaForLayers,执行后面的return mExisting, 这里也有个很重要的点,因为前面知道应用层的Feature有2个孩子,但是mExisting却是为DefaultTaskDisplayArea,
这也就是为什么最终层级树的第二层只有DefaultTaskDisplayArea的原因

  1. 定义了个DisplayArea的type, 也不复杂, 如果当前区域最小的图层都大于应用图层(2),那type就是ABOVE_TASKS,如果最大图层还小于应用图层(2)就是BELOW_TASKS(这个只有壁纸了),
    其他的就是ANY。目前还不知道具体用处,我认为了解即可

  2. mFeature == null的条件,在上面build方法里有2个for循环都创建了PendingArea对象,第二个创建叶子节点的时候是没有传递mFeature的。
    直接创建DisplayArea.Tokens,最重要的是第三个参数,是一个字符串,就是构建这个对象的name,看格式也是非常的清楚。其实就是层级树的Leaf节点,比如“Leaf:0:1 ”。(舒服了)

  3. 这里处理的是第一次循环对Feature构建出来的PendingArea,
    这里比较好奇的是这个mNewDisplayAreaSupplier是什么,那么就需要看Feature的定义了


    # DisplayAreaPolicyBuilder
        static class Feature {
            private final String mName;
            private final int mId;
            private final boolean[] mWindowLayers;
            private final NewDisplayAreaSupplier mNewDisplayAreaSupplier;
            // 构造函数
            private Feature(String name, int id, boolean[] windowLayers,
                NewDisplayAreaSupplier newDisplayAreaSupplier) {
                mName = name;
                mId = id;
                mWindowLayers = windowLayers;
                mNewDisplayAreaSupplier = newDisplayAreaSupplier;
            }
                static class Builder {
                ......
                // 默认为DisplayArea对象
                private NewDisplayAreaSupplier mNewDisplayAreaSupplier = DisplayArea::new;
                private boolean mExcludeRoundedCorner = true;
                    Feature build() {
                    ......
                    return new Feature(mName, mId, mLayers.clone(), mNewDisplayAreaSupplier);
                }
                }
        }
         /** Supplier interface to provide a new created {@link DisplayArea}. */
        interface NewDisplayAreaSupplier {
            DisplayArea create(WindowManagerService wms, DisplayArea.Type type, String name,
                    int featureId);
        }

mNewDisplayAreaSupplier这个对象的赋值是在Feature的构造方法,而根据代码分析,添加的5个Feature是通过Builder的方式,所以我们现在分析的
mNewDisplayAreaSupplier的值,就是定义在Feature.Builder下的默认值也就是DisplayArea对象
所以这一步就是返回了一个DisplayArea对象,然后name就是 “mFeature.mName + “:” + mMinLayer + “:” + mMaxLayer” 比如 “HideDisplayCutout:32:35”

到现在为止,层级树每个成员是如何构建,以及里面的字符串名字是怎么来的,就全都清楚了。
后面迭代也只是方法的递归而已,经过一层一层的迭代后,整个层级结构树就构建好了。
现在的层级树如下:

在这里插入图片描述

这个层级树和上一篇看 不太一样那是因为Leaf下没有内容了,应用层“DefaultTask
DisplayArea”和壁纸层也没有内容,那是因为Leaf后面的内容都是具体业务添加上去的。
所以其实对应Window的add流程,其实也就是真没添加到这个层级树的流程。后面具体分析业务的时候肯定是会有具体案例的。

5. 小结

窗口层级树这一块的代码有点抽象,代码虽然不多但是也挺绕。我写的也水平有限,学习这块最好是自己也能跟着画出一个层级树的图来。当然就算画不了,也问题不大,再怎么不济现在对层级树的概念肯定也是有了解的,也知道怎么命令看,以后实际业务经常会需要比对层级树的变化,看到多了,自如而且就清除了。虽然层级树打印的内容比较多,但是只要关注DefaultTaskDisplayArea下的内容,这一块的内容也就那么点。

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

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

相关文章

ArrayList 源码解析

ArrayList是Java集合框架中的一个动态数组实现&#xff0c;提供了可变大小的数组功能。它继承自AbstractList并实现了List接口&#xff0c;是顺序容器&#xff0c;即元素存放的数据与放进去的顺序相同&#xff0c;允许放入null元素&#xff0c;底层通过数组实现。除该类未实现同…

模板替换引擎(支持富文本动态表格)

模板替换引擎&#xff08;支持富文本动态表格&#xff09; 前言功能介绍example&#xff1a; 使用方法函数扩展系统函数自定义函数 前言 分享一下自己开源的工具——模板替换引擎 https://github.com/RwTo/template-engine 可以拿来学习设计模式或使用 感兴趣的话&#xff…

网络编程基础概述

文章目录 协议网络协议栈(osi)局域网IPIP和Mac地址端口号TCP和UDP网络字节序 协议 (网络协议的)意义:为了让计算机传输之间将信息正确传输给目标机器 不同系统之间能接入网络是因为定制了一套通用的协议以便支持不同系统间的网络通信 1.网络通信的问题: 将数据可靠的从A传给B a…

AtCoder ABC369 A-D题解

比赛链接:ABC369 省流&#xff1a;A<B<D<C&#xff08;题解是按照该顺序写的&#xff09; Problem A: #include <bist/stdc.h> using namespace std; int main(){int A,B;cin>>A>>B;if(AB)cout<<1<<endl;else if(abs(A-B)%20)cout&l…

织物缺陷检测系统源码分享

织物缺陷检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer Vis…

1405 问题 E: 世界杯

废话 这个题&#xff0c;我估计 22 22 22 年的时候写过一次&#xff0c;当时应该是搞明白了&#xff0c;现在重新写还是不会写&#xff0c;有点无奈 题目 问题 E: 世界杯&#xff1a;现在的 OJ 把题目加到一个活动里面去之后&#xff0c;感觉之后这个链接就访问不了了。题目…

伙房食堂电气安全新挑战:油烟潮湿环境下,如何筑起电气火灾“防火墙”?

近几年&#xff0c;随着我国经济的飞速发展&#xff0c;食堂餐饮也经历了一场变革&#xff0c;越来越多的电器走进了伙房食堂中&#xff0c;实现了电气化&#xff0c;为人们提供了高效便利的饮食服务&#xff0c;但同时也增加了火灾负荷。目前我国非常严重的电气火灾危害&#…

【STM32】DAC数字模拟转换

本篇博客重点在于标准库函数的理解与使用&#xff0c;搭建一个框架便于快速开发 目录 前言 DAC简介 DAC配置 DAC时钟使能 GPIO初始化 DAC配置 DAC使能 读写DAC值 驱动代码 MyDAC.h MyDAC.c main.c 前言 大容量的STM32F101xx和STM32F103xx产品才有DAC外设 大容量…

几种mfc140u.dll常见错误情况,以及mfc140u.dll文件修复的方法

如果你遇到与mfc140u.dll 文件相关的错误&#xff0c;这通常指的是该mfc140u.dll文件可能丢失、损坏或与您的应用程序不兼容。详细分析关于mfc140u.dll文件错误会对系统有什么影响&#xff0c;mfc140u.dll文件处于什么样的位置&#xff1f;以下是几种常见的错误情况及其修复方法…

R语言统计分析——功效分析(作图)

参考资料&#xff1a;R语言实验【第2版】 假设对于相关系数统计显著性检验&#xff0c;我们想计算一系列效应值和功效水平下所需的样本量&#xff0c;此时可用pwr.r.test()函数和for循环来完成任务。 # 加载pwr包 library(pwr) # 设置各效应值 r<-seq(0.1,0.5,0.01) # 计算…

基于JAVA的实验室智慧管理平台的设计与实现

大型管理系统用传统的手工操作方式进行信息管理容易出现数据丢失&#xff0c;统计错误等问题&#xff0c;而且劳动强度高&#xff0c;速度慢&#xff0c;耗费人力&#xff0c;物力。使用计算机可以高效、准确地完成以上工作。因此&#xff0c;用计算机的高速度和自动化来替代手…

蓝牙模块—BLE-CC41-A

1. 蓝牙的特点 蓝牙模块采用的 TI 公司设计的 CC2541芯片&#xff0c;主要面向低功耗蓝牙通信方案&#xff0c;该模块的工作频段为 2.4Ghz&#xff0c;这个频段属于国际通用频段 注意&#xff1a;蓝牙集成了一个状态指示灯&#xff0c;LED灯如果均匀慢速闪烁&#xff0c;就表示…

接口测试从入门到精通项目实战

视频网址&#xff1a;2024最新接口测试从入门到精通项目实战&#xff08;全套接口测试教程&#xff09;_哔哩哔哩_bilibili 接口架构设计分析 http协议详解 JMeter 目录介绍 常用组件 执行接口测试 接口文档&#xff1a;tlias智能学习辅助系统接口文档-V1.0-CSDN博客 实战 前…

HTB-Lame(msf利用)

前言 各位师傅大家好&#xff0c;我是qmx_07&#xff0c;今天给大家讲解 初学者渗透路径 第一个靶机Lame 渗透过程 信息搜集 服务器开放了21FTP端口、22SSH端口、445SMB端口 利用MSF模块 登录主机 工具介绍&#xff1a;Metasploit Framework&#xff08;简称 MSF&#xf…

RK3562/3588系列之6—yolov5模型的部署

RK3562/3588系列之6—yolov5模型的部署 1.yolov5模型训练2.训练好的模型转成onnx格式3.模型从onnx格式转RKNN3.1 onnx2rknn.py3.2 onnx2rknn.py3.3 直接使用rknn.api3.4 rknn_model_zoo中的转换代码3.5 LubanCat-RK系列板卡官方资料4.RK NPU c++推理4.1交叉编译4.2 开发板执行编…

校园失物招领小程序

校园失物招领小程序 weixin167校园失物招领小程序ssm 目 录 目 录 I 摘 要 III ABSTRACT IV 1 绪论 1 1.1 课题背景 1 1.2 研究现状 1 1.3 研究内容 2 2 系统开发环境 3 2.1 JSP技术 3 2.2 JAVA技术 3 2.3 MYSQL数据库 3 2.4 B/S结构 4 2.5 SSM框架技术 4 3 系…

清理C盘缓存,如何针对Windows10系统,专业地调整和优化C盘缓存设置

在Windows10系统中&#xff0c;合理地管理C盘&#xff08;通常是系统盘&#xff09;的缓存设置&#xff0c;对于保持系统性能、提升响应速度以及避免磁盘空间不足等问题至关重要。缓存主要涉及到系统文件、临时文件、应用程序缓存等多个方面。下面将详细介绍如何针对Windows10系…

【webpack4系列】webpack进阶用法(三)

文章目录 自动清理构建目录产物PostCSS插件autoprefixer自动补齐CSS3前缀移动端CSS px自动转换成rem静态资源内联多页面应用打包通用方案使用sourcemap提取页面公共资源基础库分离利⽤ SplitChunksPlugin 进⾏公共脚本分离利⽤ SplitChunksPlugin 分离基础包利⽤ SplitChunksPl…

UVA1395 Slim Span(最小生成树)

*原题链接*(洛谷) 非常水的一道题。看见让求最小边权差值的生成树&#xff0c;很容易想到kruskal。 一个暴力的想法是以每条边为最小边跑一遍kruskal&#xff0c;然后统计答案。时间复杂度&#xff0c;再看题中很小的数据范围和3s的时限。最后还真就过了。 不过我天真的想了…

【机器学习】11——矩阵求导

机器学习11——矩阵求导 打公式不太好标注&#xff0c;全图警告&#xff01;&#xff01;&#xff01; 文章目录 机器学习11——矩阵求导1.1标量对向量1.2标量对矩阵2.1向量对标量2.2向量对向量2.3向量对矩阵 1.1标量对向量 1.2标量对矩阵 X是m*n的矩阵&#xff0c;不严谨&am…