Android SystemUI组件(08)睡眠灭屏 锁屏处理流程

该系列文章总纲链接:专题分纲目录 Android SystemUI组件


本章关键点总结 & 说明:

说明:本章节持续迭代之前章节的思维导图,主要关注左侧上方锁屏分析部分 睡眠灭屏 即可。

Power按键的处理逻辑最终是由PhoneWindowManager来完成。想了解更多可参考输入子系统的相关文章。整理如下:

Android Framework 输入子系统(01)核心机制 inotify和epoll

Android Framework 输入子系统(02)核心机制 双向通信(socketpair+binder)

Android Framework 输入子系统(03)输入系统框架

Android Framework 输入子系统(04)InputReader解读

Android Framework 输入子系统(05)InputDispatcher解读

Android Framework 输入子系统(06)Global Key 一键启动 应用程序案例

Android Framework 输入子系统(07)APP建立联系

Android Framework 输入子系统(08)View基础(activity window decor view)

Android Framework 输入子系统(09)InputStage解读

Android Framework 输入子系统(10)Input命令解读

Android Framework 输入子系统(11)sendevent与getevent命令解读

本章我们只关注与Power按键相关的内容,InputManagerService处理的按键事件,最终会传递到PhoneWindowManager的interceptKeyBeforeQueueing方法中处理。我们就从这里入手逐步分析。

1 从PhoneWindowManager到PowerManagerService的处理

public class PhoneWindowManager implements WindowManagerPolicy {static final String TAG = "WindowManager";static final boolean DEBUG = false;//...//关键流程step1public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {//...//表示屏幕是否点亮final boolean interactive = (policyFlags & FLAG_INTERACTIVE) != 0;final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;final boolean canceled = event.isCanceled();//获取按键编码final int keyCode = event.getKeyCode();final boolean keyguardActive = (mKeyguardDelegate == null ? false :(interactive ?isKeyguardShowingAndNotOccluded() :mKeyguardDelegate.isShowing()));boolean isWakeKey = (policyFlags & WindowManagerPolicy.FLAG_WAKE) != 0|| event.isWakeKey();//...// Handle special keys.switch (keyCode) {//...case KeyEvent.KEYCODE_POWER: {result &= ~ACTION_PASS_TO_USER;isWakeKey = false; // wake-up will be handled separatelyif (down) {//亮屏 step1 按下power按键,亮屏流程interceptPowerKeyDown(event, interactive);} else {//抬起power按键interceptPowerKeyUp(event, interactive, canceled);}break;}case KeyEvent.KEYCODE_SLEEP: {result &= ~ACTION_PASS_TO_USER;if (!mPowerManager.isInteractive()) {useHapticFeedback = false; // suppress feedback if already non-interactive}mPowerManager.goToSleep(event.getEventTime(),PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON, 0);isWakeKey = false;break;}case KeyEvent.KEYCODE_WAKEUP: {result &= ~ACTION_PASS_TO_USER;isWakeKey = true;break;}//...}//...if (isWakeKey) {wakeUp(event.getEventTime(), mAllowTheaterModeWakeFromKey);}return result;}//...//关键流程step2private void interceptPowerKeyUp(KeyEvent event, boolean interactive, boolean canceled) {final boolean handled = canceled || mPowerKeyHandled;mScreenshotChordPowerKeyTriggered = false;cancelPendingScreenshotChordAction();cancelPendingPowerKeyAction();if (!handled) {// Figure out how to handle the key now that it has been released.mPowerKeyPressCounter += 1;final int maxCount = getMaxMultiPressPowerCount();final long eventTime = event.getDownTime();if (mPowerKeyPressCounter < maxCount) {// This could be a multi-press.  Wait a little bit longer to confirm.// Continue holding the wake lock.Message msg = mHandler.obtainMessage(MSG_POWER_DELAYED_PRESS,interactive ? 1 : 0, mPowerKeyPressCounter, eventTime);msg.setAsynchronous(true);mHandler.sendMessageDelayed(msg, ViewConfiguration.getDoubleTapTimeout());return;}// No other actions.  Handle it immediately.powerPress(eventTime, interactive, mPowerKeyPressCounter);}// Done.  Reset our state.finishPowerKeyPress();}//...//关键流程step3private void powerPress(long eventTime, boolean interactive, int count) {if (mScreenOnEarly && !mScreenOnFully) {return;}if (count == 2) {powerMultiPressAction(eventTime, interactive, mDoublePressOnPowerBehavior);} else if (count == 3) {powerMultiPressAction(eventTime, interactive, mTriplePressOnPowerBehavior);} else if (interactive && !mBeganFromNonInteractive) {//关键流程step4 除了无效处理意外,都会调用到PowerManagerService中的goToSleep方法switch (mShortPressOnPowerBehavior) {case SHORT_PRESS_POWER_NOTHING:break;case SHORT_PRESS_POWER_GO_TO_SLEEP:mPowerManager.goToSleep(eventTime,PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON, 0);break;case SHORT_PRESS_POWER_REALLY_GO_TO_SLEEP:mPowerManager.goToSleep(eventTime,PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON,PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE);break;case SHORT_PRESS_POWER_REALLY_GO_TO_SLEEP_AND_GO_HOME:mPowerManager.goToSleep(eventTime,PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON,PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE);launchHomeFromHotKey();break;}}}//...
}

2 从PowerManagerService到Notifier的处理

从PhoneWindowManager的interceptKeyBeforeQueueing方法入口开始分析,最终会到达PowerManagerService的gotoSleep方法(从PowerManager到PowerManagerService的调用就不在分析了,这个属于binder通信的范畴),对应的代码实现如下:

public final class PowerManagerService extends SystemServiceimplements Watchdog.Monitor {private static final String TAG = "PowerManagerService";private static final boolean DEBUG = false;//...//关键流程step1private final class BinderService extends IPowerManager.Stub {@Override // Binder callpublic void goToSleep(long eventTime, int reason, int flags) {if (eventTime > SystemClock.uptimeMillis()) {throw new IllegalArgumentException("event time must not be in the future");}mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null);final int uid = Binder.getCallingUid();final long ident = Binder.clearCallingIdentity();try {goToSleepInternal(eventTime, reason, flags, uid);} finally {Binder.restoreCallingIdentity(ident);}}//...}//...//关键流程step2private void goToSleepInternal(long eventTime, int reason, int flags, int uid) {synchronized (mLock) {if (goToSleepNoUpdateLocked(eventTime, reason, flags, uid)) {updatePowerStateLocked();}}}//...//关键流程step3private boolean goToSleepNoUpdateLocked(long eventTime, int reason, int flags, int uid) {if (eventTime < mLastWakeTime|| mWakefulness == WAKEFULNESS_ASLEEP|| mWakefulness == WAKEFULNESS_DOZING|| !mBootCompleted || !mSystemReady) {return false;}try {//...mLastSleepTime = eventTime;mSandmanSummoned = true;setWakefulnessLocked(WAKEFULNESS_DOZING, reason);// Report the number of wake locks that will be cleared by going to sleep.int numWakeLocksCleared = 0;final int numWakeLocks = mWakeLocks.size();for (int i = 0; i < numWakeLocks; i++) {final WakeLock wakeLock = mWakeLocks.get(i);switch (wakeLock.mFlags & PowerManager.WAKE_LOCK_LEVEL_MASK) {case PowerManager.FULL_WAKE_LOCK:case PowerManager.SCREEN_BRIGHT_WAKE_LOCK:case PowerManager.SCREEN_DIM_WAKE_LOCK:numWakeLocksCleared += 1;break;}}// Skip dozing if requested.if ((flags & PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE) != 0) {reallyGoToSleepNoUpdateLocked(eventTime, uid);}} return true;}//...//关键流程step4private boolean reallyGoToSleepNoUpdateLocked(long eventTime, int uid) {if (eventTime < mLastWakeTime || mWakefulness == WAKEFULNESS_ASLEEP|| !mBootCompleted || !mSystemReady) {return false;}try {mDirty |= DIRTY_WAKEFULNESS;mWakefulness = WAKEFULNESS_ASLEEP;setInteractiveStateLocked(false, PowerManager.GO_TO_SLEEP_REASON_TIMEOUT);}return true;}//关键流程step5private void setInteractiveStateLocked(boolean interactive, int reason) {if (mInteractive != interactive) {finishInteractiveStateChangeLocked();mInteractive = interactive;mInteractiveChanging = true;mNotifier.onInteractiveStateChangeStarted(interactive, reason);}}//...//关键流程step6private void finishInteractiveStateChangeLocked() {if (mInteractiveChanging) {mNotifier.onInteractiveStateChangeFinished(mInteractive);mInteractiveChanging = false;}}//...
}

3 Notifier的处理

Notifier的处理包含2个层面,一个是发送SCREEN_OFF的广播,通知其他子系统亮屏的消息。一个是通过PhoneWindowManager的处理来逐层执行对应回调onScreenTurnedOff方法。

3.1 从Notifier最终发送SCREEN_OFF广播

针对发送广播的逻辑处理流程,代码逻辑流程如下:

//Notifier
final class Notifier {private static final String TAG = "PowerManagerNotifier";private static final boolean DEBUG = false;//...//关键流程step1public void onInteractiveStateChangeFinished(boolean interactive) {synchronized (mLock) {if (!interactive) {if (mActualPowerState != POWER_STATE_ASLEEP) {mActualPowerState = POWER_STATE_ASLEEP;mPendingGoToSleepBroadcast = true;if (mUserActivityPending) {mUserActivityPending = false;mHandler.removeMessages(MSG_USER_ACTIVITY);}mHandler.post(new Runnable() {@Overridepublic void run() {int why = WindowManagerPolicy.OFF_BECAUSE_OF_USER;switch (mLastGoToSleepReason) {case PowerManager.GO_TO_SLEEP_REASON_DEVICE_ADMIN:why = WindowManagerPolicy.OFF_BECAUSE_OF_ADMIN;break;case PowerManager.GO_TO_SLEEP_REASON_TIMEOUT:why = WindowManagerPolicy.OFF_BECAUSE_OF_TIMEOUT;break;}EventLog.writeEvent(EventLogTags.POWER_SCREEN_STATE, 0, why, 0, 0);mPolicy.goingToSleep(why);mActivityManagerInternal.goingToSleep();}});updatePendingBroadcastLocked();}}}}//...//关键流程step2private void updatePendingBroadcastLocked() {if (!mBroadcastInProgress&& mActualInteractiveState != INTERACTIVE_STATE_UNKNOWN&& (mPendingWakeUpBroadcast || mPendingGoToSleepBroadcast|| mActualInteractiveState != mBroadcastedInteractiveState)) {mBroadcastInProgress = true;mSuspendBlocker.acquire();Message msg = mHandler.obtainMessage(MSG_BROADCAST);msg.setAsynchronous(true);mHandler.sendMessage(msg);}}//...//关键流程step3private final class NotifierHandler extends Handler {public NotifierHandler(Looper looper) {super(looper, null, true /*async*/);}@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case MSG_BROADCAST:sendNextBroadcast();break;//...}}}//消息处理:当设备从睡眠状态唤醒时,会发送 ACTION_SCREEN_ON 广播;当设备准备进入睡眠状态时,会发送 ACTION_SCREEN_OFF 广播。private void sendNextBroadcast() {final int powerState;synchronized (mLock) {//mBroadcastedInteractiveState相关处理//...mBroadcastStartTime = SystemClock.uptimeMillis();powerState = mBroadcastedInteractiveState;}EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_SEND, 1);// 根据电源状态发送相应的广播if (powerState == INTERACTIVE_STATE_AWAKE) {sendWakeUpBroadcast(); //发送唤醒广播} else {sendGoToSleepBroadcast(); //发送睡眠广播}}//...//消息处理:发送有序广播private void sendGoToSleepBroadcast() {if (ActivityManagerNative.isSystemReady()) {// 发送广播关键API,参数解读如下:// mScreenOnIntent 是一个 Intent,包含了唤醒屏幕的信息// UserHandle.ALL 表示这个广播会发送给所有用户// mWakeUpBroadcastDone 是一个 BroadcastReceiver,用于在广播完成后接收回调// mHandler 是一个 Handler,用于处理广播完成后的回调// 0 是一个 flags,表示广播的权限// 最后的 null, null 是额外的参数,这里没有使用mContext.sendOrderedBroadcastAsUser(mScreenOffIntent, UserHandle.ALL, null,mGoToSleepBroadcastDone, mHandler, 0, null, null);} else {EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_STOP, 3, 1);sendNextBroadcast();}}//...
}

这条逻辑的处理主要是发送睡眠广播为主。接下来分析从mPolicy.goingToSleep(why);出发的灭屏回调处理流程。

3.2 PhoneWindowManager的goingToSleep方法处理灭屏回调方法

这里主要是从到PhoneWindowManager的wakingUp(policy.goingToSleep)方法,再到各层的onScreenTurnedOff方法的处理逻辑流程,从onInteractiveStateChangeFinished处开始,代码实现如下:

//Notifier
final class Notifier {private static final String TAG = "PowerManagerNotifier";private static final boolean DEBUG = false;//...public void onInteractiveStateChangeFinished(boolean interactive) {synchronized (mLock) {if (!interactive) {if (mActualPowerState != POWER_STATE_ASLEEP) {mActualPowerState = POWER_STATE_ASLEEP;mPendingGoToSleepBroadcast = true;if (mUserActivityPending) {mUserActivityPending = false;mHandler.removeMessages(MSG_USER_ACTIVITY);}mHandler.post(new Runnable() {@Overridepublic void run() {int why = WindowManagerPolicy.OFF_BECAUSE_OF_USER;switch (mLastGoToSleepReason) {case PowerManager.GO_TO_SLEEP_REASON_DEVICE_ADMIN:why = WindowManagerPolicy.OFF_BECAUSE_OF_ADMIN;break;case PowerManager.GO_TO_SLEEP_REASON_TIMEOUT:why = WindowManagerPolicy.OFF_BECAUSE_OF_TIMEOUT;break;}EventLog.writeEvent(EventLogTags.POWER_SCREEN_STATE, 0, why, 0, 0);mPolicy.goingToSleep(why);mActivityManagerInternal.goingToSleep();}});updatePendingBroadcastLocked();}}}}//...
}

这里会调用到PhoneWindowManager的goingToSleep方法。代码实现如下:

//phonewindowmanager@Overridepublic void goingToSleep(int why) {EventLog.writeEvent(70000, 0);synchronized (mLock) {mAwake = false;mKeyguardDrawComplete = false;updateWakeGestureListenerLp();updateOrientationListenerLp();updateLockScreenTimeout();}if (mKeyguardDelegate != null) {mKeyguardDelegate.onScreenTurnedOff(why);}}

这里会调用到KeyguardDelegate的onScreenTurnedOff方法。代码实现如下:
 

//KeyguardDelegatepublic void onScreenTurnedOff(int why) {if (mKeyguardService != null) {mKeyguardService.onScreenTurnedOff(why);}mKeyguardState.offReason = why;mKeyguardState.screenIsOn = false;}

这里会调用到KeyguardServiceWrapper的onScreenTurnedOff方法。代码实现如下:

//wrapper@Override // Binder interfacepublic void onScreenTurnedOff(int reason) {try {mService.onScreenTurnedOff(reason);} catch (RemoteException e) {Slog.w(TAG , "Remote Exception", e);}}

这里会调用到KeyguardService的onScreenTurnedOff方法。代码实现如下:

//KeyguardService//binder@Override // Binder interfacepublic void onScreenTurnedOff(int reason) {checkPermission();mKeyguardViewMediator.onScreenTurnedOff(reason);}

这里会调用到KeyguardViewMediator的onScreenTurnedOff方法。这里才是真正的逻辑实现,代码实现如下:

//KeyguardViewMediatorpublic void onScreenTurnedOff(int why) {synchronized (this) {// 将屏幕状态设置为关闭mScreenOn = false;// 重置关键的锁屏完成等待状态resetKeyguardDonePendingLocked();// 标记未运行隐藏动画mHideAnimationRun = false;// 判断是否需要立即锁屏// 如果设置为按下电源键立即锁屏,或者设备没有设置安全措施,则立即锁屏final boolean lockImmediately =mLockPatternUtils.getPowerButtonInstantlyLocks() || !mLockPatternUtils.isSecure();// 通知屏幕已关闭notifyScreenOffLocked();// 如果存在退出安全模式的回调,执行回调并设置为nullif (mExitSecureCallback != null) {// 执行回调以通知锁屏退出的结果try {mExitSecureCallback.onKeyguardExitResult(false);} catch (RemoteException e) {// 如果远程调用失败,记录错误Slog.w(TAG, "Failed to call onKeyguardExitResult(false)", e);}mExitSecureCallback = null;// 如果锁屏没有被外部启用,隐藏锁屏if (!mExternallyEnabled) {hideLocked();}} else if (mShowing) {// 如果锁屏当前正在显示,重置锁屏状态resetStateLocked();} else if (why == WindowManagerPolicy.OFF_BECAUSE_OF_TIMEOUT ||(why == WindowManagerPolicy.OFF_BECAUSE_OF_USER && !lockImmediately)){// 如果屏幕是因为超时关闭,或者用户主动关闭但不需要立即锁屏,// 则计划延迟显示锁屏doKeyguardLaterLocked();} else {// 否则,立即显示锁屏doKeyguardLocked(null);}}// 通知更新监控器屏幕已关闭,并传递关闭的原因KeyguardUpdateMonitor.getInstance(mContext).dispatchScreenTurndOff(why);}

这里总结下,onScreenTurnedOff 关键逻辑解读如下:

  1. 当屏幕关闭时,这个方法会被调用。
  2. 如果设备设置为按下电源键立即锁定,或者设备没有设置安全措施(如PIN、密码、图案),则变量 lockImmediately 会被设置为 true。
  3. 根据 why 参数的值(表示屏幕关闭的原因),决定是否立即调用 doKeyguardLocked 或者延迟调用 doKeyguardLaterLocked(也会调用doKeyguardLocked )。

接下来关于doKeyguardLocked的处理,可以参考如下文章:

Android SystemUI组件(07)锁屏KeyguardViewMediator分析

参考其第二部分2.2 即可。

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

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

相关文章

【数据结构】图的最小生成树

快乐的流畅&#xff1a;个人主页 个人专栏&#xff1a;《C游记》《进击的C》《Linux迷航》 远方有一堆篝火&#xff0c;在为久候之人燃烧&#xff01; 文章目录 引言一、最小生成树的概念二、Kruskal算法2.1 思想2.2 实现 三、Prim算法3.1 思想3.2 实现 四、Kruskal和Prim的对比…

Spring Task 调度任务

Spring Task是调度任务框架&#xff0c;通过配置&#xff0c;程序可以按照约定的时间自动执行代码逻辑&#xff0c;基于注解方式实现需要如下注解&#xff1a; Component 任务调度类交给Spring IOC容器管理EnableScheduling 启用 Spring 的定时任务&#xff08;Scheduling&…

索尼MDR-M1:超宽频的音频盛宴,打造沉浸式音乐体验

在音乐的世界里&#xff0c;每一次技术的突破都意味着全新的听觉体验。 索尼&#xff0c;作为音频技术的先锋&#xff0c;再次以其最新力作——MDR-M1封闭式监听耳机&#xff0c;引领了音乐界的新潮流。 这款耳机以其超宽频播放和卓越的隔音性能&#xff0c;为音乐爱好者和专…

k8s中,ingress的实现原理,及其架构。

图片来源&#xff1a;自己画的 图片来源&#xff1a;k8s官网 首先&#xff0c;什么是ingress? 是服务还是控制器&#xff1f; 都不精确 ingress是一个api资源 service和deployment也是api资源。 这几个相互协作&#xff0c;组建成一个对外提供服务的架构。 ingress提供的…

[C++]使用纯opencv部署yolov11目标检测onnx模型

yolov11官方框架&#xff1a;https://github.com/ultralytics/ultralytics 【算法介绍】 在C中使用纯OpenCV部署YOLOv11进行目标检测是一项具有挑战性的任务&#xff0c;因为YOLOv11通常是用PyTorch等深度学习框架实现的&#xff0c;而OpenCV本身并不直接支持加载和运行PyTor…

系统安全 - RedisMySQL安全及实践

文章目录 导图Redis 安全潜在的安全风险防护措施密码认证命令重命名权限最小化日志和审计 Red网络隔离 MySQL 安全认证和授权文件操作风险传输和存储加密最小权限原则审计 总结 导图 Redis 安全 Redis的设计初衷是为了在可信环境下提供高性能的KV数据库服务&#xff0c;因此它…

FiBiNET模型实现推荐算法

1. 项目简介 A031-FiBiNET模型项目是一个基于深度学习的推荐系统算法实现&#xff0c;旨在提升推荐系统的性能和精度。该项目的背景源于当今互联网平台中&#xff0c;推荐算法在电商、社交、内容分发等领域的广泛应用。推荐系统通过分析用户的历史行为和兴趣偏好&#xff0c;预…

【NIO基础】NIO(非阻塞 I/O)和 IO(传统 I/O)的区别,以及 NIO 的三大组件详解

目录 1、NIO 2、NIO 和 IO 的区别 1. 阻塞 vs 非阻塞 2. 一个线程 vs 多个连接 3. 面向流 vs 面向缓冲 4. 多路复用 3、Channel & Buffer (1&#xff09;Channel&#xff1a;双向通道 (2&#xff09;Buffer&#xff1a;缓冲区 (3&#xff09;ByteBuffer&#xff…

用Arduino单片机读取PCF8591模数转换器的模拟量并转化为数字输出

PCF8591是一款单芯片&#xff0c;单电源和低功耗8位CMOS数据采集设备。博文[1]对该产品已有介绍&#xff0c;此处不再赘述。但该博文是使用NVIDIA Jetson nano运行python读取输入PCF8591的模拟量的&#xff0c;读取的结果显示在屏幕上&#xff0c;或输出模拟量点亮灯。NVIDIA J…

计算机毕业设计 基于Python的智能文献管理系统的设计与实现 Python+Django+Vue 前后端分离 附源码 讲解 文档

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…

讯飞星火编排创建智能体学习(四):网页读取

目录 引言 网页读取节点 如何生成网址 测试 引言 在讯飞星火编排创建智能体学习&#xff08;三&#xff09;&#xff1a;搜索工具-CSDN博客中&#xff0c;我介绍了如何用搜索工具从网上搜索车次信息。不过&#xff0c;在测试中我们也发现讯飞星火的这个工具并不是特别完善&…

誉天Linux云计算课程学什么?为什么保障就业?

一个IT工程师相当于干了哪些职业? 其中置顶回答生动而形象地描绘道&#xff1a; 一个IT工程师宛如一个超级多面手&#xff0c;相当于——加班狂程序员测试工程师实施工程师网络工程师电工装卸工搬运工超人。 此中酸甜苦辣咸&#xff0c;相信很多小伙伴们都深有体会。除了典…

ESP01 AT指令学习

一 、AT指令 测试指令&#xff1a;ATCWMODE? 参数及取值范围 cwmode&#xff08;1-3&#xff09; 查询指令&#xff1a; ATCWMODE&#xff1f; 当前cwmode的取值 3 设置指令&#xff1a; ATCWMODE3 设置当前的cwmode为 3 1、station 模式 连接到其他wifi 2、softA…

Unity实战案例全解析:RTS游戏的框选和阵型功能(5)阵型功能 优化

前篇&#xff1a;Unity实战案例全解析&#xff1a;RTS游戏的框选和阵型功能&#xff08;4&#xff09;阵型功能-CSDN博客 本案例来源于unity唐老狮&#xff0c;有兴趣的小伙伴可以去泰克在线观看该课程 我只是对重要功能进行分析和做出笔记分享&#xff0c;并未无师自通&#x…

SpringBoot3+Druid YAML配置

背景 Druid连接池是阿里巴巴开源的数据库连接池项目。Druid连接池为监控而生&#xff0c;内置强大的监控功能&#xff0c;监控特性不影响性能。功能强大&#xff0c;能防SQL注入&#xff0c;内置Loging能诊断Hack应用行为。现在已经SpringBoot3&#xff0c;Druid的配置也需要随…

Yolov11项目实战1:道路缺陷检测系统设计【Python源码+数据集+运行演示】

一、项目背景 随着城市化进程的加速和交通网络的不断扩展&#xff0c;道路维护成为城市管理中的一个重要环节。道路缺陷&#xff08;如裂缝、坑洞、路面破损等&#xff09;不仅影响行车安全&#xff0c;还会增加车辆的磨损和维修成本。传统的道路缺陷检测方法主要依赖人工巡检…

HarmonyOS/OpenHarmony Audio 实现音频录制及播放功能

关键词&#xff1a;audio、音频录制、音频播放、权限申请、文件管理 在app的开发过程中时常会遇见一些需要播放一段音频或进行语音录制的场景&#xff0c;那么本期将介绍如何利用鸿蒙 audio 模块实现音频写入和播放的功能。本次依赖的是 ohos.multimedia.audio 音频管理模块&am…

前缀和算法详解

对于查询区间和的问题&#xff0c;可以预处理出来一个前缀和数组 dp&#xff0c;数组中存储的是从下标 0 的位置到当前位置的区间和&#xff0c;这样只需要通过前缀和数组就可以快速的求出指定区间的和了&#xff0c;例如求 l ~ r 区间的和&#xff0c;就可以之间使用 dp[l - 1…

河南做网站与SEO:如何提升搜索引擎排名

河南做网站与SEO&#xff1a;如何提升搜索引擎排名 在当今数字化时代&#xff0c;越来越多的企业意识到互联网的重要性&#xff0c;特别是在河南这样一个快速发展的地区&#xff0c;建立一个优秀的网站已经成为企业发展的必要条件。而在建立网站的同时&#xff0c;SEO&#xff…

Spring Gateway学习

系列文章目录 JavaSE基础知识、数据类型学习万年历项目代码逻辑训练习题代码逻辑训练习题方法、数组学习图书管理系统项目面向对象编程&#xff1a;封装、继承、多态学习封装继承多态习题常用类、包装类、异常处理机制学习集合学习IO流、多线程学习仓库管理系统JavaSE项目员工…