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

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

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

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

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

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

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

1. dump内容的打印

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

# WindowManagerServiceRootWindowContainer 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@OverrideString getName() {return "ROOT";}

dumpChildrenNames的实现在WindowContainer的父类ConfigurationContainer中

# ConfigurationContainerpublic 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::runSystemServer::startOtherServicesWindowManagerService::initActivityManagerService::setWindowManagerActivityTaskManagerService::setWindowManagerRootWindowContainer::setWindowManagerDisplayContent::initDisplayContent::configureSurfacesDisplayAreaPolicy.Provider::instantiate  -- 创建 DefaultTaskDisplayArea和输入法容器DisplayAreaPolicy.Provider::configureTrustedHierarchyBuilder -- 开始配置图层的FeatureDisplayAreaPolicyBuilder::buildPendingArea::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);......}

继续看构建流程

# RootWindowContainervoid 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) {// 构建一个SurfaceControlfinal 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的内部类DefaultProvidermDisplayAreaPolicy = 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
# DisplayContentString getName() {return "Display " + mDisplayId + " name=\"" + mDisplayInfo.name + "\"";}

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

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

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

# DisplayAreaPolicy.Provider@Overridepublic 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 配置层级的支持的FeatureconfigureTrustedHierarchyBuilder(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.Providerprivate 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.BuilderBuilder(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.BuilderBuilder 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() {// 默认为trueif (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方法 (决定窗口挂载在哪一层)

   # WindowManagerPolicydefault 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 =2if (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 thingreturn  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 volumereturn  22;case TYPE_SYSTEM_OVERLAY:// the on-screen volume indicator and controller shown when the user// changes the device volumereturn  canAddInternalSystemWindow ? 23 : 10;case TYPE_NAVIGATION_BAR:// the navigation bar, if available, shows atop most thingsreturn  24;case TYPE_NAVIGATION_BAR_PANEL:// some panels (e.g. search) need to show on top of the navigation barreturn  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 dialogsreturn  canAddInternalSystemWindow ? 27 : 9;case TYPE_MAGNIFICATION_OVERLAY:// used to highlight the magnified portion of a displayreturn  28;case TYPE_DISPLAY_OVERLAY:// used to simulate secondary display devicesreturn  29;case TYPE_DRAG:// the drag layer: input for drag-and-drop is associated with this window,// which sits above all other focusable windowsreturn  30;case TYPE_ACCESSIBILITY_OVERLAY:// overlay put by accessibility services to intercept user interactionreturn  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 layerreturn  35;default:Slog.e("WindowManager", "Unknown window type: " + type);return 3;}}

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

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

    # DisplayAreaPolicy.Providerprivate 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-31if (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这个集合中

    # HierarchyBuilderprivate final ArrayList<DisplayAreaPolicyBuilder.Feature> mFeatures = new ArrayList<>();HierarchyBuilder addFeature(DisplayAreaPolicyBuilder.Feature feature) {mFeatures.add(feature);return this;}DisplayAreaPolicyBuilder::setRootHierarchy方法很简单,就是把添加了ImeContainer和5个Feature的HierarchyBuilder设置给DisplayAreaPolicyBuilder# DisplayAreaPolicyBuilderDisplayAreaPolicyBuilder 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.PendingAreastatic class PendingArea {final int mMinLayer; // 最小层级final ArrayList<PendingArea> mChildren = new ArrayList<>();// 有Children说明也是一个容器final Feature mFeature; //  当前支持的Featurefinal PendingArea mParent; // 有父亲int mMaxLayer; // 最大层级// 从这几个成员变量其实能感觉到和上一篇画的层级树的图有点那味了@Nullable DisplayArea mExisting; // 当前存在的容器boolean mSkipTokens = false; // 只有输入法和应用会为truePendingArea(Feature feature, int minLayer, PendingArea parent) {mMinLayer = minLayer;mFeature = feature;mParent = parent;}......}

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

4.2 构建PendingArea树

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

    # DisplayAreaPolicyBuilder.HierarchyBuilderprivate 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为nullfinal 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++) {// 拿到当前需要处理的Featurefinal Feature feature = mFeatures.get(i);PendingArea featureArea = null;// 内部循环,37次for (int layer = 0; layer < maxWindowLayerCount; layer++) {// 如果这个层级,支持当前Featureif (feature.mWindowLayers[layer]) {//判断是否复用 PendingArea (同一个feature才复用,否则创建新的)if (featureArea == null || featureArea.mParent != areaForLayer[layer]) {// 创建新的 PendingArea,作为上一层级的子节点,用于当前层级,并且双向奔赴,设置为各自的孩子或者父亲// 注意第一个参数featurefeatureArea = 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的值// 定义一个叶子节点用的 PendingAreaPendingArea leafArea = null;int leafType = LEAF_TYPE_TOKENS;// 定义leafTypefor (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为nullleafArea = new PendingArea(null /* feature */, layer, areaForLayer[layer]);// 注意是添加到孩子,而不是跟上一次循环直接修改areaForLayerareaForLayer[layer].mChildren.add(leafArea);leafType = type;// 应用类型处理if (leafType == LEAF_TYPE_TASK_CONTAINERS) {// 添加 TaskDisplayArea 到应用程序层级addTaskDisplayAreasToApplicationLayer(areaForLayer[layer]);// 添加 DisplayAreaGroup 到应用程序层级addDisplayAreaGroupsToApplicationLayer(areaForLayer[layer],displayAreaGroupHierarchyBuilders);// 跳过创建 Tokens,即不创建 Tokens,即使没有 TaskleafArea.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.HierarchyBuilderprivate static final int LEAF_TYPE_TASK_CONTAINERS = 1; // APPprivate static final int LEAF_TYPE_IME_CONTAINERS = 2; // 输入法private static final int LEAF_TYPE_TOKENS = 0; // 默认

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

    # DisplayAreaPolicyBuilder.HierarchyBuilderprivate 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.HierarchyBuilderprivate 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”这个设置为mExistingleafArea.mExisting = mTaskDisplayAreas.get(i);leafArea.mMaxLayer = APPLICATION_LAYER;// parentPendingArea.mChildren本来为大家都一样的Leaf,又添加了一个DefaultTaskDisplayAreaparentPendingArea.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.PendingAreavoid 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转换成DisplayAreafor (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就是DisplayContentparent.addChild(area, WindowContainer.POSITION_TOP);if (child.mFeature != null) {// 让Feature对应的容器里添加创建的DisplayAreaareas.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@Nullableprivate DisplayArea createArea(DisplayArea<DisplayArea> parent,DisplayArea.Tokens[] areaForLayer) {// 只有输入法和应用层mExisting有值if (mExisting != null) {if (mExisting.asTokens() != null) { // 只有输入法满足// Store the WindowToken container for layersfillAreaForLayers(mExisting.asTokens(), areaForLayer);}// 然后将mExisting作为结果返回return mExisting;}// mSkipTokens为true则返回,应用和IME创建的PendingAreaif (mSkipTokens) {return null;}// 2. 定义DisplayArea的typeDisplayArea.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.PendingAreaprivate 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的定义了

# DisplayAreaPolicyBuilderstatic 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.mzph.cn/diannao/54032.shtml

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

相关文章

学习整理vue前端框架项目目录结构的含义

学习整理vue前端框架项目目录结构的含义 1、目录结构2、结构含义 1、目录结构 2、结构含义

EasyExcel拿表头(二级表头)爬坑,invokeHeadMap方法

OK&#xff0c;不废话&#xff0c;直接开干!说实话是有些坑&#xff0c;或者是我不会用吧 模板如下&#xff1a; invokeHeadMap 这个方法其实针对第一行就是表头的完全没问题。针对第二行的&#xff0c;我DEBUG拿到的是这样很明显&#xff0c;他拿到了第一行&#xff1b;既然…

PLC-Recorder 对西门子传动的采集方法

目录 一、增加西门子传动的通道 二、变量配置 2.1 语法格式 a&#xff1a;装置号 b&#xff1a;参数号 c&#xff1a;参数下标 2.2 配置及连接测试情况 三、正式采集 四、小结 PLC-Recorder V3.4.0版本增加了西门子传动参数采集的功能&#xff08;增加了传动的设备类型&a…

详细介绍 Servlet 基本概念——以餐厅服务员为喻

什么是Servlet Servlet 是 Java EE&#xff08;Java Platform, Enterprise Edition&#xff09;中用于构建动态 web 应用的组件。它在服务器端运行&#xff0c;并能够处理客户端请求&#xff0c;生成动态响应。以下是对 Servlet 的详细介绍&#xff1a; 1. Servlet 的基本概念…

【Linux 20】进程控制

文章目录 &#x1f308; 一、创建线程⭐ 1. 线程创建函数⭐ 3. 给线程传参⭐ 4. 创建多线程⭐ 5. 获取线程 ID &#x1f308; 二、终止线程⭐1. 使用 return 终止线程⭐ 2. 使用 pthread_exit 函数终止线程⭐ 3. 使用 pthread_cancel 函数终止线程 &#x1f308; 三、等待线程⭐…

召回02 Swing 召回通道

为了避免小圈子重合却误判物品相似度很高&#xff1a;降低小圈子对相似度的影响。

cc2530按键中断实现控制LED

1开启中断的步骤 1-1使能端口组的中断 IEN1 IEN2 实例 IEN2 | 0x10 //使能P1口中断 1-2 端口中断屏蔽 P0IEN和P1IEN P2IEN 实例 P1IEN |0x10&#xff1b; //使能P1_2口中断 1-3设置触发方式 PICTL 实例 PICTL |0X02 ;//P1_3到P1_2口下降沿触发 1-4设置中断优先级…

Android 13 固定systemUI的状态栏为黑底白字,不能被系统应用或者三方应用修改

目录 一.背景 二.思路 三.代码流程 1.colos.xml自定义颜色 2.设置状态栏的背景颜色 3.对View进行操作 ①.对Clock(状态栏左侧的数字时钟)进行操作 ②.对电池(BatteryMeterView)进行操作 4.锁屏状态栏 5.patch汇总 一.背景 客户需求将状态栏固定成黑底白字,并且不能让系…

《SpringBoot+Vue》Chapter01_SpringBoot介绍

SpringBoot的介绍 简单来说&#xff0c;SpringBoot就是Spring提供的用于Web开发的脚手架框架。配置简单、上手快速 SpringBoot的特性 自带tomcat、Jetty服务器可以部署war包自动配置Spring框架和第三方框架能够提供应用的健康监控和配置的监控没有代码生成&#xff0c;并且尽可…

爬虫逆向学习(六):补环境过某数四代

声明&#xff1a;本篇文章内容是整理并分享在学习网上各位大佬的优秀知识后的实战与踩坑记录 引用博客&#xff1a; https://blog.csdn.net/shayuchaor/article/details/103629294 https://blog.csdn.net/qq_36291294/article/details/128600583 https://blog.csdn.net/weixin_…

C++_20_多态

多继承会造成 菱形继承** 使用虚继承来解决 不是给爷爷类加 也不是给子类加 是给父类加 虚基指针和虚基表 多态 概念&#xff1a; 概念&#xff1a; 一个事物的多种形态&#xff0c;简称多态 如&#xff1a; 对象的多态 ​ 张三 ​ 在对象面前 怂 ​ 在朋友面前 谄媚 ​ 在父…

python 读取excel数据存储到mysql

一、安装依赖 pip install mysql-connector-python 二、mysql添加表students CREATE TABLE students (ID int(11) NOT NULL AUTO_INCREMENT,Name varchar(50) DEFAULT NULL,Sex varchar(50) DEFAULT NULL,PRIMARY KEY (ID) ) ENGINEInnoDB AUTO_INCREMENT13 DEFAULT CHARSETu…

二十三种设计模式之原型模式

一.什么是原型模式 ‌‌原型模式是一种创建型对象设计模式&#xff0c;它通过复制一个已经创建的实例&#xff08;即原型对象&#xff09;来创建一个和原型对象相同的新对象。‌ 这种模式在面向对象软件设计中非常有用&#xff0c;因为它允许通过复制现有对象来快速生成多个相似…

springboot修改组件扫描包位置

步骤很详细&#xff0c;直接上教程 问题分析 默认情况下组件扫描包范围为启动类所在包及其子包 解决方法 我们只需要在启动类上面加个注解配置扫描范围 效果演示 温馨提示 非必要不建议修改&#xff0c;按规范创建项目结构一般不会出现这个问题

AI+代码审核平台CodeSec获CCIA中国网络安全创新创业大赛总决赛三等奖

近日&#xff0c;由中央网信办指导&#xff0c;中国网络安全产业联盟&#xff08;CCIA&#xff09;主办的2024年中国网络安全创新创业大赛总决赛及颁奖典礼在国家网络安全宣传周落下帷幕。开源网安“AI代码审核平台CodeSec V4.0” 凭借在AI方向的技术创新、技术突破及功能应用创…

热门远程控制工具大盘点,职场必备

如果你想要进行远程数据操作那向日葵远程控制软件你肯定听说过吧。如果你是想要远程运维&#xff0c;远程办公&#xff0c;数据传输&#xff0c;这些远程控制工具都可以实现。这次我将介绍几款我身边小伙伴都在使用的远程控制工具。 1.向日葵远程控制 链接直达&#xff1a;ht…

Python 数学建模——Prophet 时间序列预测

文章目录 前言原理使用方法&#xff08;初级&#xff09;代码实例Prophet 高级应用add_seasonality 添加自定义周期性add_regressor 添加外生变量交叉检验 前言 Prophet 是 Facebook 团队开发的一个时间序列分析工具&#xff0c;相比传统的 ARMA 时间序列分析&#xff0c;能够综…

常见 HTTP 状态码详解与Nginx 文件上传大小限制

在我们日常使用 Nginx 搭建网站或应用服务时&#xff0c;可能会遇到很多与文件上传和请求响应相关的问题。今天我们就来聊聊 如何限制文件上传的大小&#xff0c;并介绍一些常见的 HTTP 状态码 及其在 Nginx 中的处理方式。 一、文件上传大小限制 有时&#xff0c;我们需要限…

通过覆写 url_for 将 flask 应用部署到子目录下

0. 缘起 最近用 flask 写了一个 web 应用&#xff0c;需要部署到服务器上。而服务器主域名已经被使用了&#xff0c;只能给主域名加个子目录进行部署&#xff0c;比如主域名 example.org &#xff0c;我需要在 example.org/flask 下部署。这时 flask 应用里的内部连接们就出现…

github远程仓库环境搭建及使用

目录 1、创建一台虚拟机 centos 源的配置 备份源 修改源 重新加载缓存 安装软件 配置epel 2、关闭防火墙和selinux 关闭防火墙 临时关闭SELinux 永久关闭SELinux&#xff1a;编辑SELinux的配置文件 配置文件的修改内容 3、git是本地仓库&#xff0c;linux系统中一…