Android Framework底层原理之WMS的启动流程

一 概述

今天,我们介绍 WindowManagerService(后续简称 WMS)的启动流程,WMS 是 Android 系统中,负责窗口显示的的服务。在 Android 中它也起着承上启下的作用。

如下图,就是《深入理解 Android》书籍中的一张图。

图中展示了,WMS 在 Android 系统的地位,它作为中间层,连接了上层的 View 框架和下层的 SurfaceFingler。了解了 WMS 的工作机制,我们就彻底打通了上层 VIew 到底层 Surface,甚至到显示器如何显示的逻辑。

接下来,我们依旧从 WMS 的启动开始,来看 WMS 是如何启动的。

二 从 SystemServer 开始

2.1 startOtherServices

[frameworks/base/services/java/com/android/server/SystemServer.java]
private void startOtherServices(@NonNull TimingsTraceAndSlog t) {WindowManagerService wm = null;// 这里传入的 PhoneWindowManager 就是 WMS 中的 WindowManagerPolicywm = WindowManagerService.main(context, inputManager, !mFirstBoot, mOnlyCore,new PhoneWindowManager(), mActivityManagerService.mActivityTaskManager);ServiceManager.addService(Context.WINDOW_SERVICE, wm, /* allowIsolated= */ false,DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PROTO);// 将 WMS 设置到 AMS 中mActivityManagerService.setWindowManager(wm);wm.onInitReady();...try {wm.displayReady();} catch (Throwable e) {reportWtf("making display ready", e);}...try {wm.systemReady();} catch (Throwable e) {reportWtf("making Window Manager Service ready", e);}...// 更新上下文中,关于显示窗口相关的属性final Configuration config = wm.computeNewConfiguration(DEFAULT_DISPLAY);DisplayMetrics metrics = new DisplayMetrics();context.getDisplay().getMetrics(metrics);context.getResources().updateConfiguration(config, metrics);}

和 AMS 不同的是,WMS 的启动是在 SystemServer 的 startOtherServices 中,启动过程依旧是我们之前提过的构造、注册,只是少了 onStart 这个步骤。

并且,在 WMS 启动之后,还会陆续调用一些其他的函数

  • onInitReady
  • displayReady
  • systemReady
  • updateConfiguration

接下来,我们会根据 WMS 启动过程中调用的函数,以此查看它们具体的实现原理。

从 AMS 和 WMS 的启动,我们可以看出来,它们都是隶属于 SystemServer 进程的,根据之前我们对应用和 AMS 的了解,也经常看到它们交互的流程中,有 WMS 的身影,所以虽然说是应用和 AMS,WMS 的通信,实际上就是应用和 SystemServer 进程的通信。

2.2 main

[frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java]
public static WindowManagerService main(final Context context, final InputManagerService im,final boolean showBootMsgs, final boolean onlyCore, WindowManagerPolicy policy,ActivityTaskManagerService atm) {return main(context, im, showBootMsgs, onlyCore, policy, atm,new DisplayWindowSettingsProvider(), SurfaceControl.Transaction::new, Surface::new,SurfaceControl.Builder::new);
}public static WindowManagerService main(final Context context, final InputManagerService im,final boolean showBootMsgs, final boolean onlyCore, WindowManagerPolicy policy,ActivityTaskManagerService atm, DisplayWindowSettingsProviderdisplayWindowSettingsProvider, Supplier<SurfaceControl.Transaction> transactionFactory,Supplier<Surface> surfaceFactory,Function<SurfaceSession, SurfaceControl.Builder> surfaceControlFactory) {DisplayThread.getHandler().runWithScissors(() ->sInstance = new WindowManagerService(context, im, showBootMsgs, onlyCore, policy,atm, displayWindowSettingsProvider, transactionFactory, surfaceFactory,surfaceControlFactory), 0);return sInstance;
}

在 WMS 的 main 函数中主要做了两件事

  1. 创建了一个 WMS 对象
  2. 将这个 WMS 对象传递给了 DisplayThread

我们首先看这个 WMS 对象的构造函数

三 WindowManagerService

3.1 WMS 的构造函数

private WindowManagerService(Context context, InputManagerService inputManager,boolean showBootMsgs, boolean onlyCore, WindowManagerPolicy policy,ActivityTaskManagerService atm, DisplayWindowSettingsProviderdisplayWindowSettingsProvider, Supplier<SurfaceControl.Transaction> transactionFactory,Supplier<Surface> surfaceFactory,Function<SurfaceSession, SurfaceControl.Builder> surfaceControlFactory) {installLock(this, INDEX_WINDOW);// ActivityTaskManagerServicemGlobalLock = atm.getGlobalLock();mAtmService = atm;mContext = context;mIsPc = mContext.getPackageManager().hasSystemFeature(FEATURE_PC);mAllowBootMessages = showBootMsgs;...// 输入法管理mInputManager = inputManager; // Must be before createDisplayContentLocked.// 显示管理mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);// Surface 图像相关mSurfaceControlFactory = surfaceControlFactory;mTransactionFactory = transactionFactory;mSurfaceFactory = surfaceFactory;mTransaction = mTransactionFactory.get();mPolicy = policy;// 窗口动画mAnimator = new WindowAnimator(this);// 根窗口容器mRoot = new RootWindowContainer(this);final ContentResolver resolver = context.getContentResolver();mUseBLAST = Settings.Global.getInt(resolver,Settings.Global.DEVELOPMENT_USE_BLAST_ADAPTER_VR, 1) == 1;mSyncEngine = new BLASTSyncEngine(this);mWindowPlacerLocked = new WindowSurfacePlacer(this);mTaskSnapshotController = new TaskSnapshotController(this);mWindowTracing = WindowTracing.createDefaultAndStartLooper(this,Choreographer.getInstance());LocalServices.addService(WindowManagerPolicy.class, mPolicy);mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);mKeyguardDisableHandler = KeyguardDisableHandler.create(mContext, mPolicy, mH);mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);if (mPowerManagerInternal != null) {mPowerManagerInternal.registerLowPowerModeObserver(new PowerManagerInternal.LowPowerModeListener() {@Overridepublic int getServiceType() {return ServiceType.ANIMATION;}@Overridepublic void onLowPowerModeChanged(PowerSaveState result) {synchronized (mGlobalLock) {final boolean enabled = result.batterySaverEnabled;if (mAnimationsDisabled != enabled && !mAllowAnimationsInLowPowerMode) {mAnimationsDisabled = enabled;dispatchNewAnimatorScaleLocked(null);}}}});mAnimationsDisabled = mPowerManagerInternal.getLowPowerState(ServiceType.ANIMATION).batterySaverEnabled;}mScreenFrozenLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "SCREEN_FROZEN");mScreenFrozenLock.setReferenceCounted(false);mDisplayNotificationController = new DisplayWindowListenerController(this);// AMS 相关mActivityManager = ActivityManager.getService();mActivityTaskManager = ActivityTaskManager.getService();mAmInternal = LocalServices.getService(ActivityManagerInternal.class);mAtmInternal = LocalServices.getService(ActivityTaskManagerInternal.class);... 
}

WMS 的构造函数的代码非常长,其中有包含各种服务,我们这里只关注和我们应用 Activity 相关的,还有和显示相关的窗口容器 RootWindowContainer,还有和刷新相关的 Surface

3.2 runWithScissors

另外,在 main 函数中,还调用了一个 runWithScissors,这个函数是 Handler 中定义的函数,这里我们简单看一下。

public final boolean runWithScissors(@NonNull Runnable r, long timeout) {if (r == null) {throw new IllegalArgumentException("runnable must not be null");}if (timeout < 0) {throw new IllegalArgumentException("timeout must be non-negative");}if (Looper.myLooper() == mLooper) {r.run();return true;}BlockingRunnable br = new BlockingRunnable(r);return br.postAndWait(this, timeout);
}

runWithScissors 是 Handler 中的函数,用一句话概括就是,如果发送消息的线程与 Handler 处理的线程相同,就直接调用。如果不同,就阻塞调用

3.3 onInitReady

public void onInitReady() {initPolicy();// Add ourself to the Watchdog monitors.Watchdog.getInstance().addMonitor(this);createWatermark();showEmulatorDisplayOverlayIfNeeded();
}

3.4 initPolicy

private void initPolicy() {UiThread.getHandler().runWithScissors(new Runnable() {@Overridepublic void run() {WindowManagerPolicyThread.set(Thread.currentThread(), Looper.myLooper());// mPolicy 其实就是 PhoneWindowManager【5.1】mPolicy.init(mContext, WindowManagerService.this, WindowManagerService.this);}}, 0);
}

mPolicy 其实是 PhoneWindowManager,runWithScissors 前面介绍过了,如果是当前线程,就直接运行,如果不是当前线程,就阻塞运行。

所以这里是在 android.ui 线程中,运行 mPolicy 的初始化逻辑

3.5 createWatermark

void createWatermark() {if (mWatermark != null) {return;}File file = new File("/system/etc/setup.conf");FileInputStream in = null;DataInputStream ind = null;try {in = new FileInputStream(file);ind = new DataInputStream(in);String line = ind.readLine();if (line != null) {String[] toks = line.split("%");if (toks != null && toks.length > 0) {// TODO(multi-display): Show watermarks on secondary displays.final DisplayContent displayContent = getDefaultDisplayContentLocked();mWatermark = new Watermark(displayContent, displayContent.mRealDisplayMetrics,toks, mTransaction);mTransaction.apply();}}} ...
}

createWatermark 创建系统水印(只能显示文字)。

3.6 displayReady

[frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java]
public void displayReady() {synchronized (mGlobalLock) {if (mMaxUiWidth > 0) {mRoot.forAllDisplays(displayContent -> displayContent.setMaxUiWidth(mMaxUiWidth));}applyForcedPropertiesForDefaultDisplay();mAnimator.ready();mDisplayReady = true;// createWatermark重新配置所有显示器大小mRoot.forAllDisplays(DisplayContent::reconfigureDisplayLocked);// 是否触屏mIsTouchDevice = mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN);mIsFakeTouchDevice = mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FAKETOUCH);}try {// ATMSmActivityTaskManager.updateConfiguration(null);} catch (RemoteException e) {}
}

displayReady 就是初始化显示器大小。

3.7 systemReady

[frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java]
public void systemReady() {mSystemReady = true;// 调用 PhoneWindowManager 的 systemReadymPolicy.systemReady();// 调用 DisplayPolicy 的 systemReadymRoot.forAllDisplayPolicies(DisplayPolicy::systemReady);// 调用 TaskSnapshotController 的 systemReadymTaskSnapshotController.systemReady();mHasWideColorGamutSupport = queryWideColorGamutSupport();mHasHdrSupport = queryHdrSupport();// 加载系统设置UiThread.getHandler().post(mSettingsObserver::loadSettings);// VRIVrManager vrManager = IVrManager.Stub.asInterface(ServiceManager.getService(Context.VR_SERVICE));if (vrManager != null) {try {final boolean vrModeEnabled = vrManager.getVrModeState();synchronized (mGlobalLock) {vrManager.registerListener(mVrStateCallbacks);if (vrModeEnabled) {mVrModeEnabled = vrModeEnabled;mVrStateCallbacks.onVrStateChanged(vrModeEnabled);}}} catch (RemoteException e) {// Ignore, we cannot do anything if we failed to register VR mode listener}}
}

3.8 computeNewConfiguration

public Configuration computeNewConfiguration(int displayId) {synchronized (mGlobalLock) {return computeNewConfigurationLocked(displayId);}
}

3.9 computeNewConfigurationLocked

private Configuration computeNewConfigurationLocked(int displayId) {if (!mDisplayReady) {return null;}final Configuration config = new Configuration();final DisplayContent displayContent = mRoot.getDisplayContent(displayId);displayContent.computeScreenConfiguration(config);return config;
}

displayId 表示的是显示设备的 id,这两段代码,就是通过 mRoot(RootWindowContainer)获取显示指定设备的 DisplayContent。说人话就是,通过显示器的 id,获取显示器对应的实例类 DisplayContent

四 DisplayThread

[frameworks/base/services/core/java/com/android/server/DisplayThread.java]
public final class DisplayThread extends ServiceThread {private static DisplayThread sInstance;private static Handler sHandler;private DisplayThread() {// DisplayThread 运行重要的东西,但 AnimationThread 更重要。因此,优先级设置为 THREAD_PRIORITY_DISPLAY + 1// THREAD_PRIORITY_DISPLAY 的值为 -4,super("android.display", Process.THREAD_PRIORITY_DISPLAY + 1, false /*allowIo*/);}

DisplayThread 就是 Android 中的 android.display 线程,它的优先级为 THREAD_PRIORITY_DISPLAY + 1,THREAD_PRIORITY_DISPLAY 值为 -4,所以 android.display 线程的优先级是 -3。

再回到 【2.2】中,WMS 的 main 函数是通过 android.display 线程完成的,并且在 android.display 线程中,对 android.ui 线程进行了初始化。

五 UIThread

public final class UiThread extends ServiceThread {private static final long SLOW_DISPATCH_THRESHOLD_MS = 100;private static final long SLOW_DELIVERY_THRESHOLD_MS = 200;private static UiThread sInstance;private static Handler sHandler;private UiThread() {super("android.ui", Process.THREAD_PRIORITY_FOREGROUND, false /*allowIo*/);}@Overridepublic void run() {// Make sure UiThread is in the fg stune boost groupProcess.setThreadGroup(Process.myTid(), Process.THREAD_GROUP_TOP_APP);super.run();}

UiThread 和 DisplayThread 一样,也是继承自 ServiceThread,它的线程名是 android.ui,优先级是 THREAD_PRIORITY_FOREGROUND,值为-2,所以 android.ui 线程的优先级是 -2。

六 PhoneWindowManager

PhoneWindowManager 是 WindowManagerPolicy 的实现类,它定义了手机窗口、处理输入事件以及与系统 UI 交互的策略和行为。

PhoneWindowManager 中的函数有很多,这里我们列举一个,其中的按键分发的函数 interceptKeyBeforeQueueing。

6.1 interceptKeyBeforeQueueing

[frameworks\base\services\core\java\com\android\server\policy\PhoneWindowManager.java]
public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {final int keyCode = event.getKeyCode();final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;boolean isWakeKey = (policyFlags & WindowManagerPolicy.FLAG_WAKE) != 0|| event.isWakeKey();if (!mSystemBooted) {// 系统启动前,只监听电源按键if (down && (keyCode == KeyEvent.KEYCODE_POWER|| keyCode == KeyEvent.KEYCODE_TV_POWER)) {wakeUpFromPowerKey(event.getDownTime());} else if (down && (isWakeKey || keyCode == KeyEvent.KEYCODE_WAKEUP)&& isWakeKeyWhenScreenOff(keyCode)) {wakeUpFromWakeKey(event);}// 拦截掉电源键return 0;}// interceptKeyBeforeQueueing 主要就是对手机按键的事件拦截,这里我们简单列举几个// 返回键,音量键等等。switch (keyCode) {case KeyEvent.KEYCODE_BACK: {if (down) {mBackKeyHandled = false;} else {if (!hasLongPressOnBackBehavior()) {mBackKeyHandled |= backKeyPress();}// Don't pass back press to app if we've already handled it via long pressif (mBackKeyHandled) {result &= ~ACTION_PASS_TO_USER;}}break;}case KeyEvent.KEYCODE_VOLUME_DOWN:case KeyEvent.KEYCODE_VOLUME_UP:

返回值是一个整型,如果是 0 就表示被拦截,一开始的电源按键我们就看到了,在系统没有启动前,电源按键只能用于系统启动,但是系统启动后,电源按键就可以用于其他的作用了,例如语音助手。

PhoneWindowManager 这个类主要的作用有:

  1. 按键的分发
  2. 窗口的管理

这里我们就不扩展了,后面遇到实际的场景再来说明。

七 总结

WMS 的启动流程比较简单,主要就是启动了两个线程 "android.display""android.ui"

如果你还没有掌握Framework,现在想要在最短的时间里吃透它,可以参考一下《Android Framework核心知识点》,里面内容包含了:Init、Zygote、SystemServer、Binder、Handler、AMS、PMS、Launcher……等知识点记录。

《Framework 核心知识点汇总手册》:https://qr18.cn/AQpN4J

Handler 机制实现原理部分:
1.宏观理论分析与Message源码分析
2.MessageQueue的源码分析
3.Looper的源码分析
4.handler的源码分析
5.总结

Binder 原理:
1.学习Binder前必须要了解的知识点
2.ServiceManager中的Binder机制
3.系统服务的注册过程
4.ServiceManager的启动过程
5.系统服务的获取过程
6.Java Binder的初始化
7.Java Binder中系统服务的注册过程

Zygote :

  1. Android系统的启动过程及Zygote的启动过程
  2. 应用进程的启动过程

AMS源码分析 :

  1. Activity生命周期管理
  2. onActivityResult执行过程
  3. AMS中Activity栈管理详解

深入PMS源码:

1.PMS的启动过程和执行流程
2.APK的安装和卸载源码分析
3.PMS中intent-filter的匹配架构

WMS:
1.WMS的诞生
2.WMS的重要成员和Window的添加过程
3.Window的删除过程

《Android Framework学习手册》:https://qr18.cn/AQpN4J

  1. 开机Init 进程
  2. 开机启动 Zygote 进程
  3. 开机启动 SystemServer 进程
  4. Binder 驱动
  5. AMS 的启动过程
  6. PMS 的启动过程
  7. Launcher 的启动过程
  8. Android 四大组件
  9. Android 系统服务 - Input 事件的分发过程
  10. Android 底层渲染 - 屏幕刷新机制源码分析
  11. Android 源码分析实战

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

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

相关文章

【C++】STL初识

1.STL的基本概念 2.vector存放内置数据类型 #include <iostream> using namespace std; #include <vector> #include <algorithm>void MyPrint(int val) {cout << val << endl; }void test01() {//创建vector容器对象&#xff0c;并且通过模板参…

Harbor企业镜像仓库部署(本地)

简述&#xff1a; Docker 官方镜像仓库是用于管理公共镜像的地方&#xff0c;大家可以在上面找到想要的镜像&#xff0c;也可以把自己的镜像推送上去。但是有时候服务器无法访问互联网&#xff0c;或者不希望将自己的镜像放到互联网上&#xff0c;那么就需要用到 Docker Regis…

Leetcode-每日一题【剑指 Offer 15. 二进制中1的个数】

题目 编写一个函数&#xff0c;输入是一个无符号整数&#xff08;以二进制串的形式&#xff09;&#xff0c;返回其二进制表达式中数字位数为 1 的个数&#xff08;也被称为 汉明重量).&#xff09;。 提示&#xff1a; 请注意&#xff0c;在某些语言&#xff08;如 Java&…

【Docker】Windows下docker环境搭建及解决使用非官方终端时的连接问题

目录 背景 Windows Docker 安装 安装docker toolbox cmder 解决cmder 连接失败问题 资料获取方法 背景 时常有容器方面的需求&#xff0c;经常构建调试导致测试环境有些混乱&#xff0c;所以想在本地构建一套环境&#xff0c;镜像调试稳定后再放到测试环境中。 Windows …

多线程与高并发--------线程池

线程池 一、什么是线程池 在开发中&#xff0c;为了提升效率的操作&#xff0c;我们需要将一些业务采用多线程的方式去执行。 比如有一个比较大的任务&#xff0c;可以将任务分成几块&#xff0c;分别交给几个线程去执行&#xff0c;最终做一个汇总就可以了。 比如做业务操…

Windows电脑快速搭建FTP服务教程

FTP介绍 FTP&#xff08;File Transfer Protocol&#xff09;是一种用于在计算机网络上进行文件传输的标准协议。它提供了一种可靠的、基于客户端-服务器模型的方式来将文件从一个主机传输到另一个主机。在本文中&#xff0c;我将详细介绍FTP的工作原理、数据传输模式以及常见…

数据结构【第4章】——栈与队列

队列是只允许在一端进行插入操作、而在另-端进行删除操作的线性表。 栈 栈与队列&#xff1a;栈是限定仅在表尾进行插入和删除操作的线性表。 我们把允许插入和删除的一端称为栈顶&#xff08;top&#xff09;&#xff0c;另一端称为栈底&#xff08;bottom&#xff09;&…

VBA技术资料MF42:VBA_从Excel中上面的单元格复制公式

【分享成果&#xff0c;随喜正能量】唯有梦想才配让你不安&#xff0c;唯有行动才能解除你的不安.绳锯木断&#xff0c;水滴石穿。也许你现在做的事情很小&#xff0c;只要你能日积月累的坚持下去&#xff0c;才会发现意义非凡。所谓的成功&#xff0c;便是别人失败的时候你还在…

微服务与Nacos概述-2

微服务间消息传递 微服务是一种软件开发架构&#xff0c;它将一个大型应用程序拆分为一系列小型、独立的服务。每个服务都可以独立开发、部署和扩展&#xff0c;并通过轻量级的通信机制进行交互。 应用开发 common模块中包含服务提供者和服务消费者共享的内容 provider模块是…

10-1_Qt 5.9 C++开发指南_Data Visualization实现数据三维显示

Data Visualization 是 Qt 提供的用于数据三维显示的模块。在 Qt 5.7 以前只有商业版才有此模块&#xff0c;而从Qt5.7 开始此模块在社区版本里也可以免费使用了。Data Visualization 用于数据的三维显示&#xff0c;包括三维柱状图、三维空间散点、三维曲面等。Data Visualiza…

鉴源实验室丨汽车网络安全攻击实例解析(二)

作者 | 田铮 上海控安可信软件创新研究院项目经理 来源 | 鉴源实验室 社群 | 添加微信号“TICPShanghai”加入“上海控安51fusa安全社区” 引言&#xff1a;汽车信息安全事件频发使得汽车行业安全态势愈发紧张。这些汽车网络安全攻击事件&#xff0c;轻则给企业产品发布及产品…

高效数据传输:轻松上手将Kafka实时数据接入CnosDB

本篇我们将主要介绍如何在 Ubuntu 22.04.2 LTS 环境下&#xff0c;实现一个KafkaTelegrafCnosDB 同步实时获取流数据并存储的方案。在本次操作中&#xff0c;CnosDB 版本是2.3.0&#xff0c;Kafka 版本是2.5.1&#xff0c;Telegraf 版本是1.27.1 随着越来越多的应用程序架构转…

无涯教程-Perl - redo函数

描述 此函数将重新启动当前循环,而不会强制判断控制语句。块中不再执行任何语句。如果存在继续块,将不会执行。如果指定了LABEL,则在LABEL标识的循环开始时重新开始执行。 语法 以下是此函数的简单语法- redo LABELredo返回值 此函数不返回任何值。 例 以下是显示其基本…

用友时空KSOA SQL注入漏洞复现(HW0day)

0x01 产品简介 用友时空KSOA是建立在SOA理念指导下研发的新一代产品&#xff0c;是根据流通企业最前沿的I需求推出的统一的IT基础架构&#xff0c;它可以让流通企业各个时期建立的IT系统之间彼此轻松对话&#xff0c;帮助流通企业保护原有的IT投资&#xff0c;简化IT管理&#…

以商业大数据技术助力数据合规流通体系建立,合合信息参编《数据经纪从业人员评价规范》团标

经国务院批准&#xff0c;由北京市人民政府、国家发展和改革委员会、工业和信息化部、商务部、国家互联网信息办公室、中国科学技术协会共同主办的2023 全球数字经济大会于近期隆重召开。由数交数据经纪&#xff08;深圳&#xff09;有限公司为主要发起单位&#xff0c;合合信息…

深度剖析堆栈指针

为什么打印root的值与&root->value的值是一样的呢 测试结果&#xff1a; *号一个变量到底取出来的是什么&#xff1f; 以前我写过一句话&#xff0c;就是说&#xff0c;如果看到一个*变量&#xff0c;那就是直逼这个变量所保存的内存地址&#xff0c;然后取出里面保存的…

Skeleton-Aware Networks for Deep Motion Retargeting

Skeleton-Aware Networks for Deep Motion Retargeting解析 摘要1. 简介2. Related Work2.1 运动重定向&#xff08;Motion Retargeting&#xff09;2.2 Neural Motion Processing 3. 概述&#xff08;Overview&#xff09;4. 骨骼感知深度运动处理4.1 运动表征4.2 骨架卷积4.3…

Spring Boot + Vue3前后端分离实战wiki知识库系统<十二>--用户管理单点登录开发一

目标&#xff1a; 在上一次Spring Boot Vue3前后端分离实战wiki知识库系统&#xff1c;十一&#xff1e;--文档管理功能开发三我们已经完成了文档管理的功能模块开发&#xff0c;接下来则开启新模块的学习---用户登录&#xff0c;这块还是有不少知识点值得学习的&#xff0c;…

机器人CPP编程基础-02变量Variables

机器人CPP编程基础-01第一个程序Hello World 基础代码都可以借助人工智能工具进行学习。 C #include<iostream>using namespace std;main() {//Declaring an integer type variable A, allocates 4 bytes of memory.int A4;cout<<A <<endl;//Prints the a…

Matlab绘制圆形(rectangle函数、viscircles函数和圆的参数方程)

基于matlab绘制圆形 一、rectangle函数 对于绘制圆心坐标为&#xff08;x&#xff0c;y&#xff09;半径为r的圆形&#xff0c;函数为&#xff1a; x0; y0; r1; rectangle(Position, [x-r,y-r,2*r,2*r], Curvature, [1 1],EdgeColor, r); axis equalEdgeColor表示颜色 二、…