Android-DeviceStorageMonitorService 流程分析

DeviceStorageMonitorService 流程分析


引言

DeviceStorageMonitorService 是 Android 系统服务,负责监控各存储卷的可用空间,并在逼近阈值时回收缓存、发送广播、推送通知,以避免磁盘进入低态。其类注释原文:

Service that monitors and maintains free space on storage volumes. As the free space on a volume nears the threshold defined by StorageManager#getStorageLowBytes(File), this service will clear out cached data to keep the disk from entering this low state.

它继承 SystemService,在 SystemServer 启动阶段被实例化并发布,服务名 devicestoragemonitor(line 155),可通过 dumpsys devicestoragemonitorcmd shell 进行调试。

本文将从类结构、关键阈值、状态机、checkLow/checkHigh 两条主流程、通知与广播分发、Shell 调试接口等方面,逐段对照源码解析。


一、服务定位与类结构

DeviceStorageMonitorService 继承自 SystemService(line 71):

scala 复制代码
public class DeviceStorageMonitorService extends SystemService {
    private static final String TAG = "DeviceStorageMonitorService";
    static final String SERVICE = "devicestoragemonitor";
    ...
}

1.1 核心成员

成员 类型 行号 作用
mHandlerThread / mHandler HandlerThread / Handler 159-160 后台线程执行检查任务
mStates ArrayMap<UUID, State> 100 按 volume UUID 缓存每个卷的内部状态
mSeq AtomicInteger 95 广播序号,测试与去重用
mForceLevel volatile int 97 测试用强制级别
mNotifManager NotificationManager 92 推送低存储通知
mCacheFileDeletedObserver CacheFileDeletedObserver 150 监听缓存目录删除事件

1.2 发布服务

onStart()(line 308-333)中:

scss 复制代码
publishBinderService(SERVICE, mRemoteService);          // 对外 Binder
publishLocalService(DeviceStorageMonitorInternal.class, mLocalService);  // 进程内 LocalService
mHandler.obtainMessage(MSG_CHECK_LOW).sendToTarget();   // 触发首次检查

同时初始化 TV 通知通道(若设备为 Leanback/TV,line 319-325),并启动 CacheFileDeletedObserver

1.3 类结构图

scss 复制代码
SystemServiceDeviceStorageMonitorService-ArrayMap mStates-Handler mHandler-AtomicInteger mSeq-int mForceLevel+onStart()-checkLow()-checkHigh()-updateNotifications()-updateBroadcasts()State+int level+long lastUsableBytes+isEntering()+isLeaving()+levelToString()CacheFileDeletedObserverDeviceStorageMonitorInternalmStates 持有publishLocalService

二、关键常量与阈值(本文件硬编码)

DeviceStorageMonitorService 自身只硬编码了几个与检查频率、日志、预留相关的常量,真正的高低水位阈值并不在本文件定义,而是在运行时通过 StorageManager 动态获取。

常量 行号 作用
DEFAULT_LOG_DELTA_BYTES 64 MiB(MEBIBYTES.toBytes(64)) 83 可用空间变化超过此值时写 EventLog
LOW_CHECK_INTERVAL 1 分钟(MINUTE_IN_MILLIS) 84 低水位检查轮询间隔
HIGH_CHECK_INTERVAL 10 小时(10 * HOUR_IN_MILLIS) 85 高水位检查轮询间隔
BOOT_IMAGE_STORAGE_REQUIREMENT 250 MiB 90 默认卷上为 boot image 预留的空间;低于此值也判 LEVEL_LOW

运行时阈值(来自 StorageManager)

checkLow() 中(line 184-185):

ini 复制代码
final long fullBytes = storage.getStorageFullBytes(file);
final long lowBytes  = storage.getStorageLowBytes(file);

这两个值由 StorageManager 基于 storage_threshold_percentage / storage_threshold_max_bytes 等系统资源与 DeviceConfig 配置计算得出,本类不直接定义具体数值。


三、实际设备阈值(历史会话 dumpsys 实测)

在 2026-07-20 的历史分析会话中,用户从设备抓取了 dumpsys devicestoragemonitor 输出:

ini 复制代码
console:/ # dumpsys devicestoragemonitor
Known volumes:
  Default:
    level=NORMAL lastUsableBytes=7817580544
    lowBytes=524288000 fullBytes=50000000
    path=/data
mSeq=1 mForceState=UNKNOWN

换算可得:

  • lowBytes = 524288000 B = 500 MiB
  • fullBytes = 50000000 B ≈ 47.68 MiB
  • lastUsableBytes = 7817580544 B ≈ 7.28 GiB(当前可用,远大于阈值,故 level=NORMAL)

结合源码逻辑,本设备的实际触发点为:

计算式 实测值 触发行为
缓存回收触发点 lowBytes * 3 / 2(line 190) 750 MiB usableSpace < 750 MiB,调用 freeStorage(uuid, lowBytes*2, 0)
缓存回收目标 lowBytes * 2(line 194) 1000 MiB 将空间清回到 lowBytes 的 200% 水平
LEVEL_LOW 触发 usableBytes <= lowBytes(line 216) ≤ 500 MiB 发送低存储广播/通知
LEVEL_FULL 触发 usableBytes <= fullBytes(line 214) ≤ ~47.68 MiB 发送满存储广播
附加 LOW 规则 usableBytes < 250 MiB 且为默认卷(line 219) < 250 MiB 即使未到 lowBytes 也判 LOW

注:这 500 MiB 是该设备运行时由 StorageManager.getStorageLowBytes() 计算的结果(通常来自系统资源 storage_threshold_percentage_defaultstorage_threshold_max_bytes_default,约总空间 5% 与某绝对下限取最大),并非本文件硬编码的值。


四、状态机:四级 State

State(line 106-148)定义了四个级别:

arduino 复制代码
private static final int LEVEL_UNKNOWN = -1;
private static final int LEVEL_NORMAL  = 0;
private static final int LEVEL_LOW     = 1;
private static final int LEVEL_FULL    = 2;

4.1 进入/离开语义

isEntering / isLeaving(line 124-137)定义了"跨越"语义:

arduino 复制代码
private static boolean isEntering(int level, int oldLevel, int newLevel) {
    return newLevel >= level && (oldLevel < level || oldLevel == LEVEL_UNKNOWN);
}
private static boolean isLeaving(int level, int oldLevel, int newLevel) {
    return newLevel < level && (oldLevel >= level || oldLevel == LEVEL_UNKNOWN);
}

关键理解:

  • LEVEL_NORMAL(0) → LEVEL_FULL(2),对 LEVEL_LOW(1) 而言同时 isEntering(LOW)isEntering(FULL) ------一次跨越两级会触发两个级别的"进入"逻辑。
  • LEVEL_FULL(2) → LEVEL_NORMAL(0),对 LOW 和 FULL 同时判定为 isLeaving
  • oldLevel == LEVEL_UNKNOWN 时无论新级别高低,只要 newLevel >= level 就视为 entering、newLevel < level 就视为 leaving,用于测试强制流转。

4.2 状态机图

sql 复制代码
usableBytes > lowBytes
usableBytes <= lowBytes
usableBytes <= fullBytes
usableBytes <= lowBytes
(或默认卷<250MiB)
usableBytes <= fullBytes
usableBytes > fullBytes
usableBytes > lowBytes
跨越式下跌
跨越式回升
UNKNOWN
NORMAL
LOW
FULL
mForceLevel 测试模式
oldLevel 固定取 UNKNOWN

4.3 级别字符串

levelToString(line 139-147)将级别转为可读字符串,供 dumpsys 输出使用。


五、checkLow 全流程(核心)

checkLow()(line 177-249)是本服务的核心逻辑,自启动起每 LOW_CHECK_INTERVAL(1 分钟)循环一次。

5.1 流程图

ini 复制代码
是
否
是
否
是
否
是
否
是
否
是
否
onStart / checkMemory
发送 MSG_CHECK_LOW
Handler.checkLow
遍历 storage.getWritablePrivateVolumes
每个 vol
取 fullBytes/lowBytes
usableSpace < lowBytes*3/2?
pm.freeStorage uuid, lowBytes*2, 0
前置缓存回收到200%水位
跳过清理
重读 usableBytes/totalBytes
mForceLevel != UNKNOWN?
newLevel = mForceLevel
oldLevel = UNKNOWN
usableBytes <= fullBytes?
newLevel = FULL
usableBytes <= lowBytes?
newLevel = LOW
默认卷 且 <250MiB?
newLevel = NORMAL
记录 EventLog
若变化>64MiB 或级别变
updateNotifications
updateBroadcasts
state.level = newLevel
还有下一卷?
自调度
1分钟后 MSG_CHECK_LOW
10小时后 MSG_CHECK_HIGH

5.2 逐段源码对照

1) 触发入口(line 296-298, 331-340)
scss 复制代码
// onStart()
mHandler.removeMessages(MSG_CHECK_LOW);
mHandler.obtainMessage(MSG_CHECK_LOW).sendToTarget();
​
// LocalService.checkMemory()
mHandler.removeMessages(MSG_CHECK_LOW);
mHandler.obtainMessage(MSG_CHECK_LOW).sendToTarget();

mHandler(line 292-304)在 handleMessage 中将 MSG_CHECK_LOW 路由到 checkLow()

2) 遍历私有卷取阈值(line 178-185)
ini 复制代码
final StorageManager storage = getContext().getSystemService(StorageManager.class);
final int seq = mSeq.get();
for (VolumeInfo vol : storage.getWritablePrivateVolumes()) {
    final File file = vol.getPath();
    final long fullBytes = storage.getStorageFullBytes(file);
    final long lowBytes  = storage.getStorageLowBytes(file);
    ...
}

只检查可写私有卷(getWritablePrivateVolumes),SD 卡等非私有卷不在本轮处理。

3) 自动清理缓存(line 190-198)
scss 复制代码
// when it's within 150% of the threshold, we try trimming usage
// back to 200% of the threshold.
if (file.getUsableSpace() < (lowBytes * 3) / 2) {
    final PackageManagerInternal pm =
            LocalServices.getService(PackageManagerInternal.class);
    try {
        pm.freeStorage(vol.getFsUuid(), lowBytes * 2, 0);
    } catch (IOException e) {
        Slog.w(TAG, e);
    }
}
  • 触发点:usableSpace < lowBytes * 3 / 2(150% ,即实测 750 MiB)
  • 清理目标:调用 freeStorage 释放缓存,回收到 lowBytes * 2(200% ,即实测 1000 MiB)水平
  • 早于正式进入 LEVEL_LOW 就开始回收,避免真正触底------这正是类注释"keep the disk from entering this low state"的实现。
4) 重新读取可用空间并定级(line 202-223)
ini 复制代码
final long usableBytes = file.getUsableSpace();
int oldLevel = state.level;
int newLevel;
if (mForceLevel != State.LEVEL_UNKNOWN) {
    oldLevel = State.LEVEL_UNKNOWN;     // 强制每次都视为"进入/离开"
    newLevel = mForceLevel;
} else if (usableBytes <= fullBytes) {
    newLevel = State.LEVEL_FULL;
} else if (usableBytes <= lowBytes) {
    newLevel = State.LEVEL_LOW;
} else if (StorageManager.UUID_DEFAULT.equals(uuid)
        && usableBytes < BOOT_IMAGE_STORAGE_REQUIREMENT) {
    newLevel = State.LEVEL_LOW;        // 默认卷为 boot image 预留 250MiB
} else {
    newLevel = State.LEVEL_NORMAL;
}

注意:清理缓存后再读一次 usableBytes,定级依据是清理后的最新值;mForceLevel 用于 shell 测试,会将 oldLevel 强制设为 LEVEL_UNKNOWN,使任何目标级别都满足 isEntering,以触发广播。

5) EventLog 记录(line 226-231)
ini 复制代码
if ((Math.abs(state.lastUsableBytes - usableBytes) > DEFAULT_LOG_DELTA_BYTES)
        || oldLevel != newLevel) {
    EventLogTags.writeStorageState(uuid.toString(), oldLevel, newLevel,
            usableBytes, totalBytes);
    state.lastUsableBytes = usableBytes;
}

当可用空间变化超过 64 MiB 或级别发生迁移时写一条 StorageState EventLog,便于事后回溯。

6) 通知与广播分发(line 233-234)
ini 复制代码
updateNotifications(vol, oldLevel, newLevel);
updateBroadcasts(vol, oldLevel, newLevel, seq);
state.level = newLevel;

详见后文第七节。

7) 自调度循环(line 241-248)
less 复制代码
if (!mHandler.hasMessages(MSG_CHECK_LOW)) {
    mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CHECK_LOW),
            LOW_CHECK_INTERVAL);     // 1 分钟后再检查
}
if (!mHandler.hasMessages(MSG_CHECK_HIGH)) {
    mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CHECK_HIGH),
            HIGH_CHECK_INTERVAL);    // 10 小时后再检查
}

每轮结束后自续定时,不主动移除已有消息,保证立即挂起的 MSG_CHECK_* 不会被重复堆积。


六、checkHigh 流程

checkHigh()(line 257-284)处理"高水位"检查,频率为 HIGH_CHECK_INTERVAL(10 小时)。

6.1 阈值来源

ini 复制代码
final int storageThresholdPercentHigh = DeviceConfig.getInt(
        DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
        StorageManager.STORAGE_THRESHOLD_PERCENT_HIGH_KEY,
        StorageManager.DEFAULT_STORAGE_THRESHOLD_PERCENT_HIGH);

通过 DeviceConfigNAMESPACE_STORAGE_NATIVE_BOOT 读取 STORAGE_THRESHOLD_PERCENT_HIGH_KEY,默认值取 StorageManager.DEFAULT_STORAGE_THRESHOLD_PERCENT_HIGH(通常为 20,即总空间的 20%)。

6.2 清理逻辑

ini 复制代码
for (VolumeInfo vol : storage.getWritablePrivateVolumes()) {
    final File file = vol.getPath();
    if (file.getUsableSpace() < file.getTotalSpace() * storageThresholdPercentHigh / 100) {
        final PackageManagerInternal pm =
                LocalServices.getService(PackageManagerInternal.class);
        try {
            pm.freeAllAppCacheAboveQuota(vol.getFsUuid());
        } catch (IOException e) {
            Slog.w(TAG, e);
        }
    }
}

触发条件 usableSpace < totalSpace * 20% 时,调用 freeAllAppCacheAboveQuota() 清理超额的应用缓存。注意此处直接用 totalSpace * pct / 100 做整数百分比比较,与 checkLowlowBytes/fullBytes 不同源。

6.3 流程图

复制代码
是
否
MSG_CHECK_HIGH
读 storageThresholdPercentHigh
默认 20
遍历 getWritablePrivateVolumes
usableSpace < totalSpace*pct/100?
pm.freeAllAppCacheAboveQuota
跳过
10小时后 自调度

七、通知与广播分发

7.1 updateNotifications(line 506-552)

  • 进入 LOW (State.isEntering(LEVEL_LOW, ...)):构造 ACTION_MANAGE_STORAGEPendingIntent,使用 SystemNotificationChannels.ALERTS 通道弹出"低内部存储"通知,带 FLAG_NO_CLEAR 不可清除;TV 设备扩展 TvExtender 使用独立通道 devicestoragemonitor.tv(line 537);同时写 FrameworkStatsLog.LOW_STORAGE_STATE_CHANGED ... ON
  • 离开 LOW (State.isLeaving(LEVEL_LOW, ...)):cancelAsUser 取消通知,写 ...__STATE__OFF
  • LEVEL_FULL 不发额外通知,只在广播侧处理。
less 复制代码
if (State.isEntering(State.LEVEL_LOW, oldLevel, newLevel)) {
    // 构造通知并 notifyAsUser
} else if (State.isLeaving(State.LEVEL_LOW, oldLevel, newLevel)) {
    mNotifManager.cancelAsUser(uuid.toString(), SystemMessage.NOTE_LOW_STORAGE, UserHandle.ALL);
}

7.2 updateBroadcasts(line 554-591)

仅对内置私有卷广播
csharp 复制代码
if (!Objects.equals(StorageManager.UUID_PRIVATE_INTERNAL, vol.getFsUuid())) {
    // We don't currently send broadcasts for secondary volumes
    return;
}

外置卡等二级卷不发系统广播。

LOW / OK 广播
scss 复制代码
final Intent lowIntent    = new Intent(Intent.ACTION_DEVICE_STORAGE_LOW)...putExtra(EXTRA_SEQUENCE, seq);
final Intent notLowIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_OK)...putExtra(EXTRA_SEQUENCE, seq);
​
if (State.isEntering(State.LEVEL_LOW, oldLevel, newLevel)) {
    getContext().sendStickyBroadcastAsUser(lowIntent, UserHandle.ALL);
} else if (State.isLeaving(State.LEVEL_LOW, oldLevel, newLevel)) {
    getContext().removeStickyBroadcastAsUser(lowIntent, UserHandle.ALL);
    getContext().sendBroadcastAsUser(notLowIntent, UserHandle.ALL);
}

ACTION_DEVICE_STORAGE_LOW 使用 sticky 广播,后注册的接收者仍可收到;离开时移除 sticky 并发 ACTION_DEVICE_STORAGE_OK

FULL / NOT_FULL 广播
scss 复制代码
if (State.isEntering(State.LEVEL_FULL, oldLevel, newLevel)) {
    getContext().sendStickyBroadcastAsUser(fullIntent, UserHandle.ALL);
} else if (State.isLeaving(State.LEVEL_FULL, oldLevel, newLevel)) {
    getContext().removeStickyBroadcastAsUser(fullIntent, UserHandle.ALL);
    getContext().sendBroadcastAsUser(notFullIntent, UserHandle.ALL);
}

广播均带 EXTRA_SEQUENCE(seq),供接收者判断新旧、去重。

7.3 广播与状态迁移对照表

oldLevel → newLevel isEntering(LOW) isLeaving(LOW) isEntering(FULL) isLeaving(FULL) 实际广播
NORMAL → LOW STORAGE_LOW(sticky)
LOW → FULL ✓(对 FULL) STORAGE_FULL(sticky)。注:对 LOW 已 entering 不重发
NORMAL → FULL ✓(对 LOW 也 entering) 实际广播 LOW + FULL 双 sticky
FULL → LOW ✓(对 FULL leaving) STORAGE_NOT_FULL;LOW 不进入(已经在 LOW)
LOW → NORMAL ✓(对 LOW 离开) STORAGE_OK
FULL → NORMAL ✓(对 LOW+FULL 都 leaving) STORAGE_OK + STORAGE_NOT_FULL

说明:由于 isEntering/isLeaving 的"跨越"语义,FULL↔NORMAL 的直接迁移会同时触发对中间级别 LOW 的进入/离开判定,从而一次迁移可能发出多组广播。


八、Shell 调试接口

DeviceStorageMonitorService 通过 Binder.mRemoteService 提供 shell 命令(line 355-368, 370-457)。dumpsys devicestoragemonitor force-low

8.1 支持的命令

命令 作用 行号
force-low [-f] 强制 mForceLevel = LEVEL_LOW,冻结状态为低 402-413
force-not-low [-f] 强制 mForceLevel = LEVEL_NORMAL,冻结为正常 414-425
reset [-f] mForceLevel = LEVEL_UNKNOWN,恢复真实值 426-437
-f 附加选项:立即触发一次 MSG_CHECK_LOW 并打印新 seq 383-394
ini 复制代码
case "force-low": {
    mForceLevel = State.LEVEL_LOW;
    int seq = mSeq.incrementAndGet();
    if ((opts & OPTION_FORCE_UPDATE) != 0) {
        mHandler.removeMessages(MSG_CHECK_LOW);
        mHandler.obtainMessage(MSG_CHECK_LOW).sendToTarget();
        pw.println(seq);
    }
} break;

均需 android.Manifest.permission.DEVICE_POWER 权限。

8.2 dumpsys 输出格式

dumpImpl(line 459-504)在无参数或 -a 时输出各卷状态:

ini 复制代码
Known volumes:
  Default:
    level=NORMAL lastUsableBytes=7817580544
    lowBytes=524288000 fullBytes=50000000
    path=/data
mSeq=1 mForceState=UNKNOWN

对照历史会话实测数据,可见 levelToString 输出、各卷的阈值与路径、以及共享的 mSeqmForceState


九、CacheFileDeletedObserver

CacheFileDeletedObserver(line 593-602)是一个 FileObserver,监听下载缓存目录(Environment.getDownloadCacheDirectory())的 DELETE 事件:

scala 复制代码
private static class CacheFileDeletedObserver extends FileObserver {
    public CacheFileDeletedObserver() {
        super(Environment.getDownloadCacheDirectory().getAbsolutePath(), FileObserver.DELETE);
    }
    @Override
    public void onEvent(int event, String path) {
        EventLogTags.writeCacheFileDeleted(path);
    }
}

每当缓存文件被删除,写一条 CacheFileDeleted EventLog,与 checkLow 的缓存回收动作配合,可事后审计被清理的文件路径。它在 onStart() 中通过 startWatching() 启动(line 313)。


十、整体时序总结

scss 复制代码
SystemServerDeviceStorageMonitorServiceHandler(后台线程)StorageManagerPackageManagerInternalNotificationManager应用/接收者构造 + onStart()obtainMessage(MSG_CHECK_LOW).sendToTarget()触发 checkLow()getWritablePrivateVolumesVolumeInfo[]getStorageLowBytes / getStorageFullByteslowBytes / fullBytesfile.getUsableSpace()freeStorage(uuid, lowBytes*2, 0)清理完成alt[usableSpace < lowBytes*3/2]重读 usableBytes 定级写 EventLog(变化>64MiB 或迁移)updateNotifications(进入/离开 LOW)updateBroadcasts(STORAGE_LOW/OK/FULL/NOT_FULL)loop[每个私有卷]sendMessageDelayed(MSG_CHECK_LOW, 1min)sendMessageDelayed(MSG_CHECK_HIGH, 10h)loop[每 1 分钟]SystemServerDeviceStorageMonitorServiceHandler(后台线程)StorageManagerPackageManagerInternalNotificationManager应用/接收者

附:源码行号速查表

主题 关键方法/常量 行号
类声明 class DeviceStorageMonitorService 71
服务名 SERVICE = "devicestoragemonitor" 155
检查消息 MSG_CHECK_LOW / MSG_CHECK_HIGH 80-81
日志阈值 DEFAULT_LOG_DELTA_BYTES(64 MiB) 83
检查间隔 LOW_CHECK_INTERVAL / HIGH_CHECK_INTERVAL 84-85
Boot 预留 BOOT_IMAGE_STORAGE_REQUIREMENT(250 MiB) 90
状态类 class State / 级别常量 106-110
进入/离开 isEntering / isLeaving 124-137
取得状态 findOrCreateState 162-169
checkLow 自动回收缓存 + 定级 + 通知广播 + 自调度 177-249
缓存触发 usableSpace < lowBytes*3/2 190
缓存目标 freeStorage(uuid, lowBytes*2, 0) 194
定级链 mForceLevel / full / low / 250MiB / normal 209-223
EventLog writeStorageState 228
checkHigh DeviceConfig 读 percentHigh(默认 20) 257-284
构造 HandlerThread + Handler 286-305
onStart publishBinder/Local + 启动观察器 + 首次检查 308-333
LocalService checkMemory / isMemoryLow / getMemoryLowThreshold 335-353
Shell 命令 force-low / force-not-low / reset 402-441
dump 帮助 dumpHelp 444-457
dumpImpl dumpsys 输出 459-504
通知 updateNotifications 506-552
广播 updateBroadcasts 554-591
二级卷过滤 UUID_PRIVATE_INTERNAL 判断 555-558
Sticky 广播 sendStickyBroadcastAsUser 572, 586
缓存删除观察器 CacheFileDeletedObserver 593-602

结语

DeviceStorageMonitorService 的设计精髓在于三点:

  1. 前置回收 :在真正进入低态之前(lowBytes*3/2)就启动缓存回收到 lowBytes*2,把"补刀"放在"止血"之前,避免磁盘真的触底。
  2. 分层职责 :本类只固化检查节奏、日志粒度、通知/广播协议,真正的阈值数值交给 StorageManagerDeviceConfig,便于厂商与设备配置差异化。
  3. 状态机语义 :isEntering/isLeaving 的跨越式定义,让 NORMAL↔FULL 直跳也能正确触发 LOW/FULL 双广播,避免了"中间级别被跳过"的隐蔽 bug。
相关推荐
预知同行1 小时前
从 MCP 到 CLI:AI Agent 工具链的架构演进与实战抉择
前端·面试
黄敬峰1 小时前
从零搞懂React受控与非受控组件——一个Demo串起表单处理全流程
面试
Java内核笔记2 小时前
Spring Boot 4 拥抱 Jackson 3:包名迁移、配置改名与自动配置源码剖析
java·后端
程序员黑豆2 小时前
什么是JDK以及JDK都由哪些部分组成呢
java·前端·ai编程
估值探索者2 小时前
【Python实时盯盘与预警 #08】成交额突然放大2倍?Python窗口比较抓异动
java·开发语言·python
凤山老林2 小时前
Spring Boot 定时任务进阶:动态 Cron 与集群防重实战
java·spring boot·后端·定时任务·集群定时任务
xbgRS3 小时前
spring整合mybaits
java·spring·mybatis
Tim_104 小时前
【C++】024、new[]与delete[]配对使用
java·开发语言
小黑技术栈5 小时前
Java前端基础到入门——16day
java·开发语言·前端