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 中的 WindowManagerPolicy
	wm = 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, DisplayWindowSettingsProvider
		displayWindowSettingsProvider, 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, DisplayWindowSettingsProvider
		displayWindowSettingsProvider, Supplier<SurfaceControl.Transaction> transactionFactory,
		Supplier<Surface> surfaceFactory,
		Function<SurfaceSession, SurfaceControl.Builder> surfaceControlFactory) {
	installLock(this, INDEX_WINDOW);

	// ActivityTaskManagerService
	mGlobalLock = 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() {
			@Override
			public int getServiceType() {
				return ServiceType.ANIMATION;
			}

			@Override
			public 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() {
		@Override
		public 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 {
		// ATMS
		mActivityTaskManager.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 的 systemReady
	mPolicy.systemReady();
	// 调用 DisplayPolicy 的 systemReady
	mRoot.forAllDisplayPolicies(DisplayPolicy::systemReady);
	// 调用 TaskSnapshotController 的 systemReady
	mTaskSnapshotController.systemReady();
	mHasWideColorGamutSupport = queryWideColorGamutSupport();
	mHasHdrSupport = queryHdrSupport();
	// 加载系统设置
	UiThread.getHandler().post(mSettingsObserver::loadSettings);
	// VR
	IVrManager 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*/);
    }

    @Override
    public void run() {
        // Make sure UiThread is in the fg stune boost group
        Process.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 press
				if (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 源码分析实战

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

《Kali渗透基础》14. 无线渗透(四)

kali渗透 1&#xff1a;相关工具1.1&#xff1a;Aircrack-ng1.1.1&#xff1a;airmon-ng1.1.2&#xff1a;airodump-ng1.1.3&#xff1a;aireplay-ng1.1.4&#xff1a;airolib-ng1.1.5&#xff1a;bessid-ng 1.2&#xff1a;JTR1.3&#xff1a;Evil Twin Attacker1.4&#xff1…

clickhouse调研报告2

由Distributed表发送分片数据 clickhouse分区目录合并 clickhouse副本协同流程 clickhouse索引查询逻辑 clickhouse一级索引生成逻辑(两主键) clickhouse的data目录下包含如下目录: [root@brfs-stress-01 201403_10_10_0]# ll /data01/clickhouse/data total 4 drwxr-x---…

【webpack】动态配置cdn,多模板入口项目搭建

动态配置多模板 按照这个模板创建项目 安装glob,获取目录下的文件名称 封装方法utilsConfig&#xff0c;动态生产 page // pages 多入口配置 const path require("path");// glob 是 webpack 安装时依赖的一个第三方模块&#xff0c;该模块允许你使用 * 等符号, …

RaabitMQ(三) - RabbitMQ队列类型、死信消息与死信队列、懒队列、集群模式、MQ常见消息问题

RabbitMQ队列类型 Classic经典队列 这是RabbitMQ最为经典的队列类型。在单机环境中&#xff0c;拥有比较高的消息可靠性。 经典队列可以选择是否持久化(Durability)以及是否自动删除(Auto delete)两个属性。 Durability有两个选项&#xff0c;Durable和Transient。 Durable表…

图像 检测 - DETR: End-to-End Object Detection with Transformers (arXiv 2020)

图像 检测 - DETR: End-to-End Object Detection with Transformers - 端到端目标检测的Transformers&#xff08;arXiv 2020&#xff09; 摘要1. 引言2. 相关工作2.1 集预测2.2 Transformers和并行解码2.3 目标检测 3. DETR模型References 声明&#xff1a;此翻译仅为个人学习…

【VisualGLM】大模型之 VisualGLM 部署

目录 1. VisualGLM 效果展示 2. VisualGLM 介绍 3. VisualGLM 部署 1. VisualGLM 效果展示 VisualGLM 问答 原始图片 2. VisualGLM 介绍 VisualGLM 主要做的是通过图像生成文字&#xff0c;而 Stable Diffusion 是通过文字生成图像。 一种方法是将图像当作一种特殊的语言进…

SAS-数据集SQL水平合并

一、SQL水平合并基本语法 sql的合并有两步&#xff0c;step1&#xff1a;进行笛卡尔乘积运算&#xff0c;第一个表的每一行合并第二个表的每一行&#xff0c;即表a有3行&#xff0c;表b有3行&#xff0c;则合并后3*39行。笛卡尔过程包含源数据的所有列&#xff0c;相同列名会合…

mysql进阶篇(二)

前言 「作者主页」&#xff1a;雪碧有白泡泡 「个人网站」&#xff1a;雪碧的个人网站 「推荐专栏」&#xff1a; ★java一站式服务 ★ ★ React从入门到精通★ ★前端炫酷代码分享 ★ ★ 从0到英雄&#xff0c;vue成神之路★ ★ uniapp-从构建到提升★ ★ 从0到英雄&#xff…

3.2用互斥元保护共享数据

概述 于是&#xff0c;你有一个类似于上一节中链表那样的共享数据结构&#xff0c;你想要保护它免于竞争条件以及可能因此产生的不变量损坏。如果你可以将所有访问该数据结构的代码块标记为互斥的&#xff08;mutually exclusive)&#xff0c;岂不是很好&#xff1f;如果任何线…

DuDuTalk:AI语音工牌在家装行业门店销售场景有何应用价值?

随着科技的不断发展&#xff0c;人工智能技术的应用也越来越广泛。作为人工智能技术的一种应用形式&#xff0c;AI语音工牌在家装行业门店销售场景中起到了重要的作用。本文将从AI语音工牌的定义、功能、应用场景以及优势等方面&#xff0c;探讨它在家装行业门店销售场景的应用…

Qt多线程编程

本章介绍Qt多线程编程。 1.方法 Qt多线程编程通常有2种方法&#xff1a; 1)通过继承QThread类&#xff0c;实现run()方法。 2)采用QObject::moveToThread()方法。 方法2是Qt官方推荐的方法&#xff0c;本文介绍第2种。 2.步骤 1)创建Worker类 这里的Worker类就是我们需要…

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

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

音视频--视频数据传输

参考文献 H264码流RTP封装方式详解&#xff1a;https://blog.csdn.net/water1209/article/details/126019272H264视频传输、编解码----RTP协议对H264数据帧拆包、打包、解包过程&#xff1a; https://blog.csdn.net/wujian946110509/article/details/79129338H264之NALU解析&a…

汽车维修保养记录查询API:实现车辆健康状况一手掌握

在当今的数字化世界中&#xff0c;汽车维修保养记录的查询和管理变得前所未有地简单和便捷。通过API&#xff0c;我们可以轻松地获取车辆的维修和保养记录&#xff0c;从而实现对手中车辆健康状况的实时掌握。 API&#xff08;应用程序接口&#xff09;是进行数据交换和通信的标…

RocketMQ第二课-核心编程模型以及生产环境最佳实践

一、回顾RocketMQ的消息模型 ​ 上一章节我们从试验整理出了RocketMQ的消息模型&#xff0c;这也是我们使用RocketMQ时最直接的指导。 二、深入理解RocketMQ的消息模型 1、RocketMQ客户端基本流程 <dependency><groupId>org.apache.rocketmq</groupId>&…

以http_proxy和ajp_proxy方式整合apache和tomcat(动静分离)

注意&#xff1a;http_proxy和ajp_proxy的稳定性不如mod_jk 一.http_proxy方式 1.下载mod_proxy_html.x86_64 2.在apache下创建http_proxy.conf文件&#xff08;或者直接写到conf/httpd.conf文件最后&#xff09; 3.查看server.xml文件 到tomcat的安装目录下的conf/serve…

【word密码】word设置只读,如何取消?

Word文件打开之后发现是只读模式&#xff0c;那么我们如何取消word文档的只读模式呢&#xff1f;今天给大家介绍几种只读模式的取消方法。 属性只读 有些文件可能是在文件属性中添加了只读属性&#xff0c;这种情况&#xff0c;我们只需要点击文件&#xff0c;再次查看文件属…

命令模式(C++)

定义 将一个请求(行为)封装为一个对象&#xff0c;从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志&#xff0c;以及支持可撤销的操作。 应用场景 在软件构建过程中&#xff0c;“行为请求者”与“行为实现者”通常呈现一种“紧耦合”。但在某些场合——比…

后端进阶之路——深入理解Spring Security配置(二)

前言 「作者主页」&#xff1a;雪碧有白泡泡 「个人网站」&#xff1a;雪碧的个人网站 「推荐专栏」&#xff1a; ★java一站式服务 ★ ★前端炫酷代码分享 ★ ★ uniapp-从构建到提升★ ★ 从0到英雄&#xff0c;vue成神之路★ ★ 解决算法&#xff0c;一个专栏就够了★ ★ 架…

CAD绘制法兰、添加光源、材质并渲染

首先绘制两个圆柱体&#xff0c;相互嵌套 在顶部继续绘制圆柱体&#xff0c;这是之后要挖掉的部分 在中央位置绘制正方形 用圆角工具&#xff1a; 将矩形的四个角分别处理&#xff0c;效果&#xff1a; 用拉伸工具 向上拉伸到和之前绘制的圆柱体高度齐平 绘制一个圆柱体&#…