android 13 WMS/AMS系统开发-窗口层级相关DisplayArea,WindowContainer

文章详细介绍了WindowContainer类及其子类在Android窗口管理中的作用,包括RootWindowContainer、DisplayContent、TaskDisplayArea等组件,以及窗口层级的概念和展示。通过dumpsys命令的输出,展示了窗口树的结构,并解释了各层级窗口的关系,帮助理解Android系统的窗口组织和显示顺序。

WindowContainer类相关类解释

官方注释:

/**
 * Defines common functionality for classes that can hold windows directly or through their
 * children in a hierarchy form.
 * The test class is {@link WindowContainerTests} which must be kept up-to-date and ran anytime
 * changes are made to this class.
 */

给可以直接持有窗口的自己或它的孩子定义了一些公共的方法和属性,像RootWindowContainer、DisplayContent、DisplayArea、DisplayArea.Tokens、TaskDisplayArea、Task、ActivityRecord、WindowToken、WindowState都是直接或间接的继承该类。

class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<E>
        implements Comparable<WindowContainer>, Animatable, SurfaceFreezer.Freezable,
        InsetsControlTarget {

    /**
     * The parent of this window container.
     * For removing or setting new parent {@link #setParent} should be used, because it also
     * performs configuration updates based on new parent's settings.
     */
    private WindowContainer<WindowContainer> mParent = null;

    // List of children for this window container. List is in z-order as the children appear on
    // screen with the top-most window container at the tail of the list.
    protected final WindowList<E> mChildren = new WindowList<E>();
}

这里面主要的重要要成员变量就是mParent和mChildren,一个代表父节点一个代表子节点,而且子节点的list顺序代表就是z轴的层级显示顺序,list尾巴在比list的头的z轴层级要高。

RootWindowContainer:根窗口容器,树的根是它。通过它遍历寻找,可以找到窗口树上的窗口。它的孩子是DisplayContent。
  DisplayContent:该类是对应着显示屏幕的,Android是支持多屏幕的,所以可能存在多个DisplayContent对象。上图只画了一个对象的结构,其他对象的结构也是和画的对象的结构是相似的。
DisplayArea:该类是对应着显示屏幕下面的,代表一组窗口合集,具有多个子类,如Tokens,TaskDisplayArea等
  TaskDisplayArea:它为DisplayContent的孩子,对应着窗口层次的第2层。第2层作为应用层,看它的定义:int APPLICATION_LAYER = 2,应用层的窗口是处于第2层。TaskDisplayArea的孩子是Task类,其实它的孩子类型也可以是TaskDisplayArea。而Task的孩子则可以是ActivityRecord,也可以是Task。
  Tokens:代表专门包含WindowTokens的容器,它的孩子是WindowToken,而WindowToken的孩子则为WindowState对象。WindowState是对应着一个窗口的。
  ImeContainer:它是输入法窗口的容器,它的孩子是WindowToken类型。WindowToken的孩子为WindowState类型,而WindowState类型则对应着输入法窗口。
  Task:任务,它的孩子可以是Task,也可以是ActivityRecord类型。
  ActivityRecord:是对应着应用进程中的Activity的。ActivityRecord是继承WindowToken的,它的孩子类型为WindowState。
  WindowState:WindowState是对应着一个窗口的。

窗口层级分析WindowContainer相关子类关联:
这里先我们用一个dumpsys命令来看看层级结构相关的输出
test@test:~$ adb shell dumpsys activity containers

ACTIVITY MANAGER CONTAINERS (dumpsys activity containers)
ROOT type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
  #0 Display 0 name="Built-in Screen" type=undefined mode=fullscreen override-mode=fullscreen requested-bounds=[0,0][1440,2960] bounds=[0,0][1440,2960]
   #2 Leaf:36:36 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
    #1 WindowToken{6e2757c type=2024 android.os.BinderProxy@6d43102} type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
     #0 81ffe05 ScreenDecorOverlayBottom type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
    #0 WindowToken{98a9487 type=2024 android.os.BinderProxy@2130925} type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
     #0 ac8fb4 ScreenDecorOverlay type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
   #1 HideDisplayCutout:32:35 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
    #2 OneHanded:34:35 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
     #0 FullscreenMagnification:34:35 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
      #0 Leaf:34:35 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
    #1 FullscreenMagnification:33:33 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
     #0 Leaf:33:33 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
    #0 OneHanded:32:32 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
     #0 Leaf:32:32 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
   #0 WindowedMagnification:0:31 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
    #6 HideDisplayCutout:26:31 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
     #0 OneHanded:26:31 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
      #2 FullscreenMagnification:29:31 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
       #0 Leaf:29:31 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
      #1 Leaf:28:28 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
      #0 FullscreenMagnification:26:27 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
       #0 Leaf:26:27 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
    #5 Leaf:24:25 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
     #1 WindowToken{696f278 type=2024 android.os.BinderProxy@31aecde} type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
      #0 cf5fd51 pip-dismiss-overlay type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
     #0 WindowToken{e74c3eb type=2019 android.os.BinderProxy@d19030} type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
      #0 e2b3548 NavigationBar0 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
    #4 HideDisplayCutout:18:23 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
     #0 OneHanded:18:23 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
      #0 FullscreenMagnification:18:23 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
       #0 Leaf:18:23 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
    #3 OneHanded:17:17 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
     #0 FullscreenMagnification:17:17 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
      #0 Leaf:17:17 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
       #0 WindowToken{34b13c0 type=2040 android.os.BinderProxy@710d2c1} type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
        #0 d7c18f9 NotificationShade type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
    #2 HideDisplayCutout:16:16 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
     #0 OneHanded:16:16 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
      #0 FullscreenMagnification:16:16 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
       #0 Leaf:16:16 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
    #1 OneHanded:15:15 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
     #0 FullscreenMagnification:15:15 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
      #0 Leaf:15:15 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
       #0 WindowToken{e183269 type=2000 android.os.BinderProxy@e488916} type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
        #0 5dfa4ee StatusBar type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
    #0 HideDisplayCutout:0:14 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
     #0 OneHanded:0:14 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
      #1 ImePlaceholder:13:14 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
       #0 ImeContainer type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
        #0 WindowToken{9439bd0 type=2011 android.os.Binder@f6126f6} type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
         #0 6060c0a InputMethod type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
      #0 FullscreenMagnification:0:12 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
       #2 Leaf:3:12 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
        #0 WindowToken{58fdd21 type=2038 android.os.BinderProxy@ff11113} type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
         #0 cd4a10f ShellDropTarget type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
       #1 DefaultTaskDisplayArea type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
        #2 Task=1 type=home mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
         #0 Task=8 type=home mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
          #0 ActivityRecord{6733b50 u0 com.android.launcher3/.uioverrides.QuickstepLauncher} t8} type=home mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
           #0 6e08fe2 com.android.launcher3/com.android.launcher3.uioverrides.QuickstepLauncher type=home mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
        #1 Task=3 type=undefined mode=fullscreen override-mode=fullscreen requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
        #0 Task=4 type=undefined mode=fullscreen override-mode=fullscreen requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
         #1 Task=6 type=undefined mode=multi-window override-mode=multi-window requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
         #0 Task=5 type=undefined mode=multi-window override-mode=multi-window requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
       #0 Leaf:0:1 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
        #0 WallpaperWindowToken{76eaad0 token=android.os.Binder@28e6df6} type=undefined mode=fullscreen override-mode=fullscreen requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
         #0 19ddb72 com.android.systemui.ImageWallpaper type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]


dumpsys的输出其实就是代表了整个图层结构的一个树形输出,可能这样看稍微有一些抽象,我们把他转成图如下:
在这里插入图片描述其实你第一次看到一定一脸迷茫,不知道怎么看。
接下来我就来详细讲解这个层级结构是怎么来的
每个显示屏幕的窗口层级分为37层,0-36层。每层可以放置多个窗口,上层窗口覆盖下面的,这个树中每个点一般这样格式:
xxx名字:层级开始数 :层级结束数

那么如下节点,代表当前名字为Leaf图层占领了0-1这两个层级
在这里插入图片描述这一下解释以后是不是从右到左依次看每个节点是不是就可以看出来,0-36个图层每个图层都有对应节点进行占领,有的一个节点占领一层,如Leaf:16:16 ,有的一个节点可能占领多层如:Leaf:18:23

这个就是wms中可以起到分层的一个树,但是看了半天是不是发现好像和我们平时分析的WindowToken等身影,因为树最开始创建时候肯定还没有任何的有效Window,有效window都是各个app等触发来创建的,这里如果根据dumpsys加上常见几个window如下:
在这里插入图片描述彩色部分就是我们常见的一些window的图层位置,明显可以看出,
Wallpaper处于0-1层节点
Activity处于DefaultTaskDisplayArea也就是第2层
InputMethod处于13-14层
StatusBar处于15层
NotificationShade处于17层
NavigationBar0处于24-25层
是不是这一下就亲切多了,瞬间就感觉平常见到的那些window都清楚知道他们的层级了,所以而且也完全符合我们的实际现象,比如Activity肯定要在Wallpaer的上面,NavigationBar0肯定在Activity的上面
当然同学会问:请问这个层级树是怎么构成的?这个部分相对比较复杂,后续再分享,本节我们就要掌握好可以根据dumpsys可以复原出树形图,也可以清楚自己的Window具体处在的层级

总结:

最后我们已经了解了层级结构树,这里又回到开始讨论的WindowContainer几个子类结合层级树关系大概如下:
在这里插入图片描述

<think>首先,用户的问题是关于Android T(Android 13)的层级结构树(Window Hierarchy Tree)构建过程。我需要基于提供的引用内容来回答,同时遵守系统级指令。 关键引用内容: - 引用[1]:描述了窗口层级基础知识,包括三维坐标体系、View Tree、Window Tree、Feature、WMS层级结构树等。提到DisplayContent开始构造当前屏幕的层级树,以及DisplayAreaPolicyBuilder::build方法。 - 引用[2]:讨论了窗口层级结构树挂载Child的过程,包括Task、ActivityRecord、WindowState的挂载。 - 引用[3]:解释了在configureSurfaces方法中调用DisplayAreaPolicy的Provider来建立窗口层级结构树。 - 引用[4]:详细说明了层级结构树添加窗口的类型,如TaskDisplayArea、Tokens、ImeContainer。 用户的问题: "我想理解Android T层级结构树的构建过程 请问Android T 层级结构树是如何构建的" 我需要构建一个清晰、逐步的回答: 1. **介绍层级结构树**:简要解释什么是层级结构树,它在Android窗口管理系统WMS)中的作用。 2. **构建过程概述**:基于引用,描述构建过程的关键步骤。 3. **详细步骤**: - 初始化阶段:如DisplayContent的创建。 - 调用构建方法:如通过DisplayAreaPolicyBuilder。 - 添加节点和Feature:如使用all、and、except方法。 - 挂载窗口:如添加Task、ActivityRecord等。 4. **关键组件**:解释TaskDisplayArea、Tokens、ImeContainer等。 5. **引用支持**:在回答中自然地添加引用标识,例如[^1]。 系统级指令: - 所有行内数学表达式用$...$,但这里可能不需要数学表达式。 - 独立公式用$$...$$,但问题不涉及数学公式。 - LaTeX语法正确:确保任何LaTeX正确。 - 使用中文回答。 - 生成相关问题:在末尾添加相关问题。 - 回答结构清晰:逐步解释。 回答结构: 1. **引言**:简要介绍层级结构树。 2. **构建过程**: - 步骤1: 初始化和DisplayContent设置。 - 步骤2: DisplayAreaPolicy的实例化。 - 步骤3: 构建层级树结构。 - 步骤4: 添加具体窗口。 3. **关键类和方法**:如DisplayAreaPolicyBuilder、PendingArea等。 4. **总结**。 5. **相关问题**。 生成相关问题: - 基于主题,如优化、应用场景、性能等。 确保引用正确: - 引用[1]:覆盖基础知识、构建流程。 - 引用[3]:configureSurfaces方法和DisplayAreaPolicy。 - 引用[4]:窗口类型和挂载。 - 引用[2]:挂载过程。 最终回答用中文。</think>### Android T 层级结构树构建过程 Android T(Android 13)的窗口层级结构树(Window Hierarchy Tree)是窗口管理系统WMS)的核心组件,用于管理和渲染所有窗口(如Activity、状态栏、输入法)。它基于三维坐标体系(Z-order)组织窗口,确保正确的叠加顺序和交互。构建过程涉及多个步骤,从初始化到最终挂载窗口节点。以下是基于引用内容的逐步解析,过程清晰可靠。 #### 1. **初始化和DisplayContent设置** - 层级结构树的构建始于屏幕的初始化。当系统启动或屏幕配置改变时,WMS 创建 `DisplayContent` 对象,代表一个物理显示设备(如主屏幕)。`DisplayContent` 作为层级树的根节点,负责协调所有子窗口容器。 - 在 `configureSurfaces` 方法中,WMS 调用 `DisplayAreaPolicy` 的 Provider 来实例化层级树。这一步确保 SurfaceFlinger(负责渲染)正确绑定: ```java private void configureSurfaces(Transaction transaction) { if (mDisplayAreaPolicy == null) { mDisplayAreaPolicy = mWmService.getDisplayAreaPolicyProvider().instantiate( mWmService, this /* content */, this /* root */, mImeWindowsContainer); } } ``` 这里,`mDisplayAreaPolicy` 被初始化,定义了层级树的策略和结构[^3]。 #### 2. **DisplayAreaPolicyBuilder 构建层级骨架** - `DisplayAreaPolicyBuilder` 是核心类,用于定义层级树的骨架。它使用 Feature(功能区域,如状态栏区域、导航栏区域)来划分层级- **Feature 方法**:如 `all()`、`and()`、`except()`,用于指定哪些窗口类型属于特定层级。例如: - `all()` 表示所有窗口都挂载到该 Feature。 - `except()` 用于排除某些窗口类型。 - **层级分配**:`getWindowLayerFromTypeLw()` 方法决定窗口的初始挂载点(如状态栏挂载到顶层)。这基于窗口类型(如 `TYPE_STATUS_BAR`)。 - 构建过程调用 `DisplayAreaPolicyBuilder::build()` 方法,生成一个树状结构: - 根节点是 `DisplayContent`。 - 中间节点是 `DisplayArea`(如 `FeatureArea`),代表逻辑分组。 - 叶子节点是 `DisplayArea.Tokens` 或 `TaskDisplayArea`,直接容纳窗口对象。 这个过程创建了一个“PendingArea”数据结构,表示待实例化的层级分支[^1]。 #### 3. **实例化层级树节点** - PendingArea 结构被转换为具体的 `DisplayArea` 节点: - **Feature 相关节点**:每个 Feature(如状态栏Feature)生成一个 `DisplayArea`,作为父容器。 - **Leaf 节点**:叶子节点包括: - `TaskDisplayArea`:容纳 App 窗口(如 Activity、Task)。这是默认的 App 容器。 - `DisplayArea.Tokens`:容纳非 App 窗口(如系统对话框、壁纸)。例如,`WallpaperWindowToken` 是壁纸的专用容器。 - `ImeContainer`:专门容纳输入法窗口- 方法 `PendingArea::instantiateChildren()` 负责递归实例化子节点,确保树结构完整。 最终,层级树形成一个多层结构,例如: ``` DisplayContent (根) ├── FeatureArea (e.g., StatusBar) │ └── Leaf (Tokens for StatusBar) ├── TaskDisplayArea (Default for Apps) │ └── Task → ActivityRecord → WindowState └── ImeContainer (for Input Method) ``` 这种设计支持高效的窗口管理和 Z-order 计算[^1][^4]。 #### 4. **挂载具体窗口** - 层级树构建完成后,窗口对象(如 `WindowState`、`ActivityRecord`)被挂载到叶子节点: - **App 窗口挂载**:Activity 的 `Task` 和 `ActivityRecord` 挂载到 `TaskDisplayArea`。例如,Launcher Activity 通过 `WindowManager.addView()` 添加后,其 `WindowState` 链接到 `TaskDisplayArea`。 - **系统窗口挂载**:状态栏、导航栏等通过类似方式挂载到 `Tokens` 节点。例如,状态栏作为 `TopBar` 挂载到对应的 Feature 区域。 - **输入法窗口**:挂载到 `ImeContainer`,确保输入法总是位于顶层。 挂载过程在 `WindowManagerService` 中处理,例如引用[2]展示的 QuickstepLauncher Activity 挂载流程[^2][^4]。 #### 关键点总结 - **目的**:层级结构树确保窗口按 Z-order 正确渲染,避免重叠冲突,并支持动态更新(如旋转屏幕)。 - **性能优化**:树结构允许高效查询和更新,时间复杂度通常为 $O(\log n)$。 - **可靠性**:基于 Android 开源代码,构建过程在系统启动或配置变更时自动触发[^1][^3][^4]。 如果您有特定场景(如多屏支持)的疑问,我可以进一步展开。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

千里马学框架

帮助你了,就请我喝杯咖啡

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值