【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.rhkb.cn/news/426810.html

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

相关文章

房产销售系统:SpringBoot技术应用案例

第二章关键技术的研究 2.1相关技术 房产销售系统是在Java MySQL开发环境的基础上开发的。Java是一种服务器端脚本语言&#xff0c;易于学习&#xff0c;实用且面向用户。全球超过35&#xff05;的Java驱动的互联网站点使用Java。MySQL是一个数据库管理系统&#xff0c;因为它的…

代理导致的git错误

问题&#xff1a; 今天在clone时出现如下错误&#xff1a; fatal: unable to access https://github.com/NirDiamant/RAG_Techniques.git/: Failed to connect to 127.0.0.1 port 10089 after 2065 ms: Couldnt connect to server真是让人感到奇怪&#xff01;就在前天&#…

伪工厂模式制造敌人

实现效果 1.敌人方实现 敌人代码 using UnityEngine; using UnityEngine.UI;public class EnemyBasics : MonoBehaviour {public int EnemySpeed { get; internal set; }public int EnemyAttackDistance { get; internal set; }public int EnemyChaseDistance { get; interna…

初识 C++ ( 1 )

引言&#xff1a;大家都说c是c的升级语言。我不懂这句话的含义后来看过解释才懂。 一、面向过程语言和面向对象语言 我们都知道C语言是面向过程语言&#xff0c;而C是面向对象语言&#xff0c;说C和C的区别&#xff0c;也就是在比较面向过程和面向对象的区别。 1.面向过程和面向…

WebGL系列教程九(动画)

目录 1 前言2 绘制立方体并进行纹理映射3 动画思路4 开始绘制4.1 在顶点着色器中声明旋转矩阵4.2 获取旋转矩阵变量并进行赋值4.3 计算角度4.4 每一帧都去绘制4.5 效果4.6 完整代码 5 总结 1 前言 上一篇我们讲了WebGL中的基础语法&#xff0c;现在我们已经讲过了三维物体的绘制…

TDengine 与 SCADA 强强联合:提升工业数据管理的效率与精准

随着时序数据库&#xff08;Time Series Database&#xff09;的日益普及&#xff0c;越来越多的工业自动化控制&#xff08;工控&#xff09;人员开始认识到其强大能力。然而&#xff0c;时序数据库在传统实时数据库应用领域&#xff0c;特别是在过程监控层的推广仍面临挑战&a…

【数据结构】排序算法---冒泡排序

文章目录 1. 定义2. 算法步骤3. 动图演示4. 性质5. 算法分析6. 代码实现C语言PythonJavaCGo 结语 1. 定义 冒泡排序&#xff08;英语&#xff1a;Bubble sort&#xff09;是一种简单的排序算法。它重复地走访过要排序的数列&#xff0c;一次比较两个元素&#xff0c;如果它们的…

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

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

ipython里如何用?快速查阅帮助

1、&#xff1f;用于查询函数帮助文档&#xff0c;??用于查询带源码的帮助文档 ?用于搜索内容&#xff0c;*作为通配符。

C++调用C# DLL之踩坑记录

C是非托管代码&#xff0c;C#则是托管代码&#xff0c;无法直接调用 CLR的介绍见CLR简介 MSDN提到了两种非托管-托管的交互技术&#xff1a;CLR Interop和COM Interop 后者要将C# 类库注册为COM组件&#xff0c;本文只探讨CLR&#xff0c;要通过C CLR写中间层代码 方式一&…

IDEA 通义灵码 插件使用体验

目录 前言 主要功能 演示代码 解释代码 生成单元测试 生成代码注释 生成优化建议 代码片段补全 总结 前言 自从 AI 技术开始大规模应用&#xff0c;老板就想让下面的牛马借助 AI 工具来提高编码效率&#xff0c;由于团队都没有在实际编码中深度使用过 AI 工具&#x…

Miracast/WifiDisplay开发相关的深入调研分析-android投屏实战开发

Miracast/WifiDisplay概念介绍 Miracast Miracast是由Wi-Fi联盟于2012年所制定&#xff0c;以Wi-Fi直连&#xff08;Wi-Fi Direct&#xff09;为基础的无线显示标准。支持此标准的消费性电子产品&#xff08;又称3C设备&#xff09;可透过无线方式分享视频画面&#xff0c;例如…

VirtualBox 克隆已有的虚拟机

【前提】已经存在一个CentOS 7 虚拟机 【需求】克隆出来一个虚拟机,用于本机 【操作】 1.右击已有的虚拟机 -> 选择克隆 2.给新虚拟机起个名称 以及 生成新的MAC地址 3.克隆 4.修改网络和主机名称 # 修改网络编辑以下2个文件 vi /etc/sysconfig/network-scripts/ifcfg-enp…

Java之内部类

目录 实例内部类 静态内部类 局部内部类 匿名内部类 下面将讲解实例内部类&#xff0c;静态内部类&#xff0c;局部内部类和匿名内部类。 实例内部类 实例内部类&#xff08;也称为非静态内部类&#xff09;依赖于外部类的实例。这意味着&#xff0c;要创建实例内部类的实…

Kubernetes从零到精通(12-Ingress、Gateway API)

Ingress和Gateway API都是Kubernetes中用于管理外部访问集群服务的机制&#xff0c;但它们有不同的设计理念和适用场景。它们的基本原理是通过配置规则&#xff0c;将来自外部的网络流量路由到Kubernetes集群内部的服务上。 Ingress/Gateway API和Service Ingress/Gateway API…

Qt窗口——QToolBar

文章目录 工具栏创建工具栏设置toolTip工具栏配合菜单栏工具栏浮动状态 工具栏 QToolBar工具栏是应用程序中集成各种功能实现快捷键使用的一个区域。 可以有多个&#xff0c;也可以没有。 创建工具栏 #include "mainwindow.h" #include "ui_mainwindow.h&qu…

ARM 工业边缘计算机与 C# 编程的完美融合

在工业领域&#xff0c;随着智能化和数字化的不断推进&#xff0c;ARM 工业边缘计算机凭借其出色的性能和低功耗等优势&#xff0c;逐渐成为众多应用场景的重要支撑。而 C# 编程语言的强大功能和广泛适用性&#xff0c;使其在与 ARM 工业边缘计算机的结合中展现出了巨大的潜力。…

壹嘉情,中国与世界经济文化交流的新桥梁

壹嘉情正在全球华商领域迅速崛起。作为意大利华商总会的中国分部&#xff0c;壹嘉情承载着推动两岸及全球华商深度合作、实现资源共享和互利共赢的使命。它的成立标志着意大利华商总会在全球战略布局上的重要一步&#xff0c;同时也昭示了全球化浪潮中&#xff0c;华人企业正加…

LNMP的简单安装(ubuntu)

LNMP介绍 LNMP 是一种常见的开源软件组合&#xff0c;用于搭建高效的网站服务器环境。LNMP 代表以下四个组件&#xff1a; Linux&#xff1a;操作系统。Linux 是一种稳定、可靠、安全的开源操作系统&#xff0c;常用于服务器环境&#xff0c;特别是在企业级部署中。它负责底层…

小程序——生命周期

文章目录 运行机制更新机制生命周期介绍应用级别生命周期页面级别生命周期组件生命周期生命周期两个细节补充说明总结 运行机制 用一张图简要概述一下小程序的运行机制 冷启动与热启动&#xff1a; 小程序启动可以分为两种情况&#xff0c;一种是冷启动&#xff0c;一种是热…