Android ViewModel源码解析:配置变更、生命周期与实战技巧
Jetpack 这个系列第一篇出来后不少做应用重构的朋友在后台问我ViewModel 已经用得挺熟了为什么还要去啃它的源码我的回答是如果你只是停留在by viewModels()的用法层面你是写不出既抗造又好维护的页面的。这次把 ViewModel 从创建到销毁整条链路讲透你就明白它为什么叫“组件”而不是“工具类”。做一个稍微复杂的电商详情页你会发现旋转屏幕后填写了一半的收货地址没了滑动到第三步的图片预览又跳回第一步网络请求还会莫名其妙重复发好几遍。ViewModel 就是官方针对这些场景给出的一套组件级方案但在项目里真正决定它能不能发挥价值的往往是你对它在配置变更、进程重建、生命周期回调这些关键节点上到底怎么运作的理解。这篇文章我会沿着一条主线走它为了解决什么问题而存在、内部三件套是怎么协作的、源码链路里旋转屏幕时到底发生了什么、以及你能直接抄进项目里的姿势和坑。1. 项目整体认知ViewModel到底解决什么问题1.1 没有ViewModel的日子手动保存状态的痛苦经历先说一个我早期的真实体验。当时做一个信息录入类的页面不复杂就是几个输入框加一个图片选择列表。测试时旋转了下屏幕结果用户刚填好的表单内容全没了选中的图片列表也回到了初始状态。那时候第一反应是赶紧把内容写到onSaveInstanceState里这个函数确实能把轻量数据放进 Bundle但等你把每个输入框、每个选中项、每个列表状态都手动塞进去代码已经变得非常恶心而且 Bundle 本身还有大小和类型限制存图片路径、缓存列表、分页信息都相当勉强。更麻烦的是请求问题。列表页旋转后 Activity 销毁重建onCreate又跑了一遍原有的网络请求也被触发一次。如果你没有做结果缓存用户看到的不是“刷新”而是“加载中”的闪烁严重的甚至会因为回调里的 View 已经失效而直接崩掉。当时也想过用单例来做临时数据中转但单例一旦 Activity 退出后没及时清理内存泄漏就成了定时炸弹。ViewModel 的价值恰恰就在这里它把页面里那些“只跟当前界面相关、不该随旋转丢失、又不值得持久化到数据库”的状态从 Activity 中抽离到一个独立对象里由官方机制保证它在配置变更期间存活。这相当于给你的页面准备了一个“出趟门回来东西还在原位”的抽屉而不是每次出门前匆匆忙忙把所有家当往兜里塞。1.2 配置变更与进程被杀的区别ViewModel能救哪个很多人有个认知误区觉得 ViewModel 是“持久化数据”的。这个必须纠正ViewModel 本质上是内存对象它解决的是配置变更configuration change带来的重建问题不是进程被杀后的数据恢复问题。配置变更指旋转屏幕、切换系统语言、深色模式切换这类事件。这些场景下 Activity 会被销毁重建但整个应用进程还活着堆内存还在系统也允许你在旧 Activity 销毁时把一些对象“托管”出去重建后再交回来。ViewModel 就是利用这个机制活下来的。进程被杀是另一回事。用户把应用切到后台系统内存紧张时直接回收掉整个进程清理掉所有堆内存Activity、ViewModel 全部烟消云散。当用户回到应用时系统会尝试恢复 Activity但你的 ViewModel 对象不会自动回来。要恢复这类场景你需要SavedStateHandle或onSaveInstanceState配合把轻量状态写进 Bundle由系统在进程重建时回传。所以正确的分工是ViewModel 负责解决旋转等配置变更时的状态保持SavedStateHandle 负责解决进程被杀后的临时状态恢复数据库和文件负责真正持久化。搞混这三层后面排查问题时会白费很多功夫。2. 原理拆解ViewModel是怎么“活”过配置变更的2.1 核心三件套ViewModelStoreOwner、ViewModelStore、ViewModelProviderViewModel 能在配置变更后存活靠的是三个核心接口和类之间的协作ViewModelStoreOwner、ViewModelStore、ViewModelProvider。ViewModelStoreOwner是一个接口定义了getViewModelStore()方法。Activity 和 Fragment 都实现了它意味着它们是 ViewModel 的“宿主容器”。没有这个宿主ViewModel 就没有存身之所。ViewModelStore是真正存放 ViewModel 的容器内部结构就是一个HashMapString, ViewModel按 key 存、按 key 取。它有几个关键方法get(key)、put(key, viewModel)、clear()。整个 store 的生命周期由一个宿主负责维护。ViewModelProvider是使用入口负责帮你从 store 里拿到目标 ViewModel。它的逻辑很直白先从 store 里通过 key 找找到且类型匹配就直接返回找不到就用内部的 Factory 创建新实例再放进 store。Factory 则是创建 ViewModel 的工厂接口只有一个方法create(ClassT modelClass): T。默认实现DefaultViewModelProviderFactory会尝试通过无参构造newInstance()创建实例。如果你需要给 ViewModel 传参就要自己提供 Factory。这里值得注意的设计意图是Provider 依赖的是“这个 Activity 对应哪个 ViewModelStore”而不是“Activity 的实例是谁”。所以哪怕 Activity 换了个新实例只要它从系统手里拿回了同一个 ViewModelStore就能从里面取出同一个 ViewModel。这就像一个储物柜锁和钥匙换了但柜子还是原来那个柜子里面的东西自然还在。2.2 源码链路走查旋转屏幕时究竟发生了什么把上面这些类串起来走一遍旋转屏幕的完整链路。第一步屏幕旋转触发配置变更系统决定销毁旧的 Activity创建新的 Activity。旧 Activity 会在销毁流程中走到onRetainNonConfigurationInstance()这个方法。ComponentActivity已经重写了它在处理时会构造一个内部对象NonConfigurationInstances把当前 Activity 持有的ViewModelStore实例装进去交给系统临时保留。这个对象不会随 Activity 销毁而消失而是由系统放在“非配置实例”通道里等着新 Activity 来取。第二步新 Activity 创建时会在onCreate前调用的getLastNonConfigurationInstance()方法中取回之前保存的NonConfigurationInstances再从里面恢复出那个一模一样、还是原来内存地址的ViewModelStore实例重新赋给新 Activity。第三步代码里调用ViewModelProvider.get(ViewModel::class.java)时Provider 从新 Activity 拿到的 store 里查询目标 key发现旧实例还在于是直接返回。你看到的现象就是“Activity 变了ViewModel 没变”。看一段简化版的ViewModelProvider.get()内部逻辑就能感受到这个流程的简单和优雅public T extends ViewModel T get(NonNull String key, NonNull ClassT modelClass) { // 1. 先从 store 里查命中直接返回 ViewModel viewModel mViewModelStore.get(key); if (modelClass.isInstance(viewModel)) { return (T) viewModel; } // 2. 没找到就通过 Factory 创建放回 store viewModel mFactory.create(modelClass); mViewModelStore.put(key, viewModel); return (T) viewModel; }整个过程最核心的一句话是旋转时被保留下来的不是 Activity 实例而是 Activity 内部那棵 ViewModel 存储树。旧 Activity 销毁前把树托管给系统新 Activity 创建后把同一棵树接回来。这个机制在 Fragment 的场景里同样成立只是托管链路的载体换成 FragmentManager 的NonConfig机制。2.3 onCleared()的精确触发时机与边界条件ViewModel 的回调方法只有一个onCleared()它是资源释放和任务取消的唯一时机。很多人都知道它会在页面退出时调用但具体什么时候不调用往往说不清。关键就在 ActivityonDestroy()里的这段逻辑override fun onDestroy() { // 判断当前销毁是不是因为配置变更 if (!isChangingConfigurations) { viewModelStore.clear() } }当用户按返回键退出、或者调用finish()时isChangingConfigurations为 falseActivity 销毁后才会调用viewModelStore.clear()进而触发每个 ViewModel 的onCleared()。当只是旋转屏幕或切换暗黑模式时isChangingConfigurations为 true即使 Activity 的onDestroy被执行了store 也不会被 clearViewModel 也就不会收到onCleared()。这个判断从根上保证了 ViewModel 在配置变更期间不会“假死”或提前释放。Fragment 场景还有一个容易踩的点Fragment 被替换时如果还在回退栈里它的 ViewModelStore 不会 clear只有 Fragment 真正被移除、且不再存在于回退栈时才会触发清理。所以不要指望 Fragment 从回退栈弹出来时onCleared()一定马上执行。这属于正常机制不是 Bug。3. 生命周期感知的完整闭环ViewModel如何与Lifecycle协同3.1 ViewModel决不会给你View引用这是一条铁律ViewModel 名字里带“View”但它跟 View 没有任何直接关联这是一个非常容易误用的点。ViewModel 的存续期比 Activity 和 Fragment 长如果在 ViewModel 里持有 Activity、View 或 LifecycleOwner 的引用页面退出但 ViewModel 还没清理时这些引用会阻止 GC 回收整个 UI 树造成内存泄漏。LeakCanary 上报的很大一类问题就是这么来的。从设计意图上看ViewModel 是“面向 UI 状态”的不是“面向 UI 控件”的。它只存储和加工数据列表数据、加载状态、用户输入、临时缓存。UI 层应该观察 ViewModel 暴露出的数据然后自己去渲染。LiveData、StateFlow、Flow 这些响应式组件就是为了这个“数据驱动 UI”的链路存在的。有个直观的类比ViewModel 是门店后厨Activity 是前台服务员。后厨只管把菜做好放到出餐口前台自己去取、去端给顾客。如果后厨派人跑到前厅站着等服务员点完菜再炒整个门店就乱套了生命周期稍长一点这个人还会赖在店里不走。3.2 对比一下ViewModel、SavedInstanceState、持久化的分工搞清楚 ViewModel 的位置最好的办法是把它和另外两层存储机制放在一张表里对比。维度ViewModelSavedStateHandle / Bundle数据库 / 文件本质载体内存对象Bundle 序列化数据持久化存储旋转屏幕时存活继续使用系统自动保留不受影响进程被回收时丢失可恢复轻量数据不受影响容量限制内存足够即行有大小限制不适合大对象无上限典型用途页面数据、列表缓存、UI 状态表单临时草稿、筛选条件、滚动位置用户信息、业务数据这个表格写出来你会发现页面状态的解决方案从来不是“二选一”而是三层叠加。ViewModel 管内存态SavedStateHandle 管进程重建恢复数据库管持久业务数据。每一层的工作边界都别越界代码的稳定性就会好很多。我曾经见过一个项目执着于把所有状态都塞进 ViewModel结果进程被杀后用户返回页面一片空白那就是没有第二层兜底。4. 实操经验ViewModel的正确使用姿势与进阶模式4.1 构造参数与工厂模式什么时候写ViewModelProvider.Factory想用好 ViewModel第一个绕不开的问题就是构造参数。默认情况下ViewModelProvider 通过无参构造函数创建 ViewModel所以下面的代码能直接跑通class SimpleViewModel : ViewModel() { val title MutableStateFlow() }可一旦你的 ViewModel 需要依赖 Repository、数据源或者某个业务参数比如“详情页的 ArticleId”你就会遇到经典的Cannot create an instance of class ViewModel报错。解决办法就是自定义 Factory。现在用 Kotlin 写已经不需要手写一长串模板了class ArticleViewModel( private val articleId: Long, private val repo: ArticleRepository ) : ViewModel() { companion object { fun factory(articleId: Long): ViewModelProvider.Factory { return viewModelFactory { initializer { ArticleViewModel(articleId, ArticleRepository()) } } } } }Activity 里搭配by viewModels委托使用private val articleVM: ArticleViewModel by viewModels { ArticleViewModel.factory(articleId intent.getLongExtra(id, 0L)) }注意viewModelFactory和initializer这两个 API 来自lifecycle-viewmodel-ktx需要单独引入依赖。使用 Factory 的模式比把参数塞到SavedStateHandle或者全局变量里都要清晰。构造器里直接声明依赖测试时就能传入假的 Repository这也是推荐依赖注入的起点。如果你还在用AndroidViewModel拿 Application那属于历史遗留方案能用但不够优雅建议尽早迁到构造器注入。4.2 跨Fragment共享数据的官方正确思路Activity 内多个 Fragment 想共享数据常见做法有 EventBus、共享单例其实官方早就给出了更安全的方式使用同一宿主 Activity 的 ViewModelStore。在 Fragment 里不要用by viewModels()它会把 ViewModel 存到 Fragment 自己的 store要用by activityViewModels()class SharedViewModel : ViewModel() { private val _refreshState MutableStateFlowRefreshState(RefreshState.Idle) val refreshState: StateFlowRefreshState _refreshState.asStateFlow() fun onRefresh() { ... } }// FragmentA 和 FragmentB 都写这一行 private val sharedVM: SharedViewModel by activityViewModels()这里面的原理不神秘Fragment 调用activityViewModels()时使用的是宿主 Activity 的ViewModelStore所以几个 Fragment 取到的都是同一个实例。相比 EventBus它没有“发送了没人收”的问题所有数据都天然跟着宿主 Activity 走宿主退出时自动清理不会出现全局单例残留。在实际项目里要特别注意一点不要直接把MutableStateFlow或MutableLiveData整个暴露给其他 Fragment。更好的做法是用私有可变版本加公有只读版本再配合状态类做单向数据流。否则业务复杂以后你会发现自己都不知道谁改了数据。4.3 与协程、Flow、LiveData组合的推荐写法ViewModel 和协程的搭配官方已经默认集成好了。只要引入lifecycle-viewmodel-ktxViewModel 内部就会多一个viewModelScope()它默认跑在Dispatchers.Main.immediate生命周期跟随 ViewModelonCleared()时自动取消所有协程。这意味着你在 ViewModel 里发起的网络请求用户退出页面时不会被遗留到后台继续空转也不会因为回调里还拿着 View导致崩溃或泄漏。看一个比较推荐的写法class HomeViewModel : ViewModel() { private val repo HomeRepository() val uiState: StateFlowHomeUiState repo.fetchHomeData() .map { HomeUiState.Success(it) } .catch { emit(HomeUiState.Error(it)) } .stateIn( scope viewModelScope, started SharingStarted.WhileSubscribed(5000), initialValue HomeUiState.Loading ) }关于 LiveData 和 Flow我的建议是状态类数据优先用StateFlow一次性事件则用SharedFlow或回调接口。StateFlow天然携带最新值访问value就能拿到当前状态还支持丰富的操作符跟协程衔接顺畅。不过要提醒一句在 UI 层用repeatOnLifecycle包住 collect防止页面切到后台后依然持续接收数据流。这块不小心很容易成为性能上“看不见的敌人”。5. 常见问题与排查记录我踩过的坑下面整理一份我在实际项目中踩过、也帮别人排查过的问题清单每一条都不是冷僻场景而是很容易在日常开发中反复出现。问题现象原因与处理ViewModel 持有 View 引用LeakCanary 报告页面销毁后仍有泄漏ViewModel 生命周期比 View 长不能在 ViewModel 里保存 Activity / View / Fragment 引用改用数据回调驱动 UI页面旋转后再次发起网络请求明显多发了一倍请求页面闪烁一次请求写在了onCreate里且 ViewModel 数据是空的应该在 ViewModel init 中发请求并通过状态缓存判断是否已有数据Fragment 共享数据时取到不同实例两个 Fragment 状态不同步一个用了viewModels()另一个用了activityViewModels()两者作用域不同必须全部指向同一个宿主建议全局统一进程被回收后页面状态丢失用户切后台一段时间再回来页面数据空白只有 ViewModel 没有配SavedStateHandle临时状态需要走进程重建恢复通道无限刷新或重复加载数据流被多个观察者重复订阅stateIn策略没配好或 UI 层没在repeatOnLifecycle中收集统一数据入口并限制订阅生命周期在 Fragment 中依赖 activityViewModels 但页面销毁不调 onCleared关闭页面后数据仍占用资源若宿主 Activity 仍在存活Fragment 显式移除后才触发清理不要把页面级的资源放在共享 ViewModel 里无限累积排查时的思路也有规律先确认“ViewModel 实例是不是同一个”再确认“是哪个 ViewModelStore 在管”最后才去怀疑“是配置变更的问题还是进程被回收的问题”。三个问题定位清楚这类错误基本上十有八九能找到根因。另一个容易忽视的是SavedStateHandle的添加时机。很多人只会在遇到进程恢复问题时才想起来加但临时状态保存这件事最好在 ViewModel 设计的初始阶段就放进方案里。比如一个订单筛选页筛选条件、搜索关键词、滚动位置都建议通过SavedStateHandle保存。这样用户切后台再回来体验是完整的不用额外请求一次数据。6. 扩展认识从原理出发看懂ViewModel的开发演进6.1 SavedStateHandle让配置变更与进程回收都不怕前文反复提到SavedStateHandle这里展开细讲一下。它是 ViewModel 构造器中的一个特殊参数由系统在创建 ViewModel 时自动提供。日常写法极其简洁class CountViewModel( private val savedStateHandle: SavedStateHandle ) : ViewModel() { val count savedStateHandle.getStateFlow(count, 0) fun increase() { savedStateHandle[count] (savedStateHandle[count] as? Int ?: 0) 1 } }内部机制上SavedStateHandle包装了一个 Bundle。当 Activity 因配置变更或进程回收重建时系统会从 Bundle 中恢复数据并把对应的SavedStateHandle传给新 ViewModel。它比onSaveInstanceState的手写版本更友好还支持getStateFlow、getLiveData这类响应式 API。要注意SavedStateHandle不适合存大对象和二进制数据。它最终走的是 Bundle 通道超过一定体积会触发TransactionTooLargeException。列表数据还是放 ViewModel 内存里SavedStateHandle 只处理轻量状态。6.2 从Factory演进看DI框架的介入理解 Factory 原理之后你会更容易理解 Hilt 这类依赖注入框架在 ViewModel 上做了什么。Hilt 通过HiltViewModel注解自动生成 ViewModel 的 Factory并把SavedStateHandle和业务依赖一起注入到构造器里HiltViewModel class DashboardViewModel Inject constructor( private val repo: DashboardRepository, private val savedStateHandle: SavedStateHandle ) : ViewModel()这样项目里就不再需要手写companion object { factory() }依赖关系一目了然测试时也方便替换 mock。但如果你没搞懂默认 Factory 和无参构造的关系遇到 Hilt 集成报错时依然会一头雾水。就见过的案例来说很多人看到Cannot create an instance of class ViewModel就只知道上网搜实际只要沿着 Factory 这条线马上就能定位到“哪个依赖没注入”和“哪个构造器没有被正确理解”。原理在手解决问题的速度会快很多。我个人在实际项目里的体会是ViewModel 真正厉害的地方不是某个 API而是它逼着你把“界面”和“状态”拆开。早期写页面时Activity 里塞满了数据请求、刷新逻辑、状态判断后期加一个需求要动好几处。用 ViewModel 之后我习惯先把页面会出现的所有状态写成一个不可变状态模型再围绕它组织数据流和交互逻辑最后才去写 UI。这个思考顺序一旦建立重构页面就像重新排列组合积木而不是推倒重砌墙。如果你打算把现有项目迁移到更清晰的架构上建议从最常动的那个页面开始试把状态抽进 ViewModel再逐步推广效果会比想象中明显。

相关新闻

初创企业建站选型实测:BBWEYY、Codex+亚马逊AWS、比文云与Dreamweaver的低成本验证与上线速度对比(含TaoToken统一Key接入)

初创企业建站选型实测:BBWEYY、Codex+亚马逊AWS、比文云与Dreamweaver的低成本验证与上线速度对比(含TaoToken统一Key接入)

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 11:33:28 阅读更多 →
AI日报制作全流程:从信息采集到深度解读的实操指南

AI日报制作全流程:从信息采集到深度解读的实操指南

1. 一份AI日报的诞生逻辑每天早上八点半,我会准时打开自己的信息聚合面板,把过去24小时里散落在各个角落的AI动态过一遍。这个习惯坚持了快两年,起因很简单——信息太碎了,碎到你刷一上午手机,可能只记住了三条真正有用…

2026/10/12 11:33:28 阅读更多 →
ESP8266开关控制负载:GPIO驱动与安全隔离实战指南

ESP8266开关控制负载:GPIO驱动与安全隔离实战指南

1. 为什么“开关控制负载”是ESP8266入门真正的分水岭很多人把MicroPython ESP8266入门理解成“点亮LED”或“串口打印Hello World”,但真正跨过那道门槛的,从来不是语法,而是第一次亲手让一块芯片去驱动现实世界中的物理设备。开关控制负载—…

2026/10/12 11:33:28 阅读更多 →

最新新闻

AI工程化能力三支柱:数据智能、模型架构与系统交付

AI工程化能力三支柱:数据智能、模型架构与系统交付

1. 这不是“学AI”,而是构建一套可迁移、能落地的工程化能力体系很多人点开“AI培训学什么”这类标题,第一反应是找速成班、抄代码、背模型名字——结果学了三个月,连数据怎么清洗都卡在pandas报错上;或者花两万块报了“大模型实战…

2026/10/13 16:50:38 阅读更多 →
ASCII码与汉字编码:从乱码到UTF-8的完整解析

ASCII码与汉字编码:从乱码到UTF-8的完整解析

1. 从一段乱码说起:为什么你需要搞懂ASCII码你有没有遇到过这种情况:打开一个文本文件,里面全是“锟斤拷”或者“ ”这样的乱码,明明文件大小正常,内容却完全没法看。或者你在写程序的时候,把一个中文字符串…

2026/10/13 16:50:38 阅读更多 →
Spark on YARN GPU推理实战:资源调度、配置与性能调优

Spark on YARN GPU推理实战:资源调度、配置与性能调优

1. 为什么要在 Spark on YARN 上跑 GPU 推理把 AI 模型推理塞进 Spark on YARN 这套体系里,第一次听到的人多半会皱眉:推理不是应该用专门的在线服务框架吗,为什么要绕这么大一圈?我最初接手这个方向时也有同样的疑问,…

2026/10/13 16:50:38 阅读更多 →
MSP430G2553驱动RC522门禁系统实战:低功耗SPI时序与ISO14443-A协议精解

MSP430G2553驱动RC522门禁系统实战:低功耗SPI时序与ISO14443-A协议精解

简介:本资源是一套基于MSP430微控制器与MFRC522 RFID模块的嵌入式门禁系统完整工程,面向嵌入式初学者、电子设计竞赛备赛者及物联网硬件开发爱好者,解决低功耗门禁原型快速验证与软硬协同开发问题。压缩包共20个文件,含6个核心头文…

2026/10/13 16:50:38 阅读更多 →
IS95前向链路Rake接收机仿真:从物理层结构到多径合并的完整实现

IS95前向链路Rake接收机仿真:从物理层结构到多径合并的完整实现

简介:这份资源面向通信工程、无线通信课程学习者及CDMA仿真入门者,聚焦IS-95前向链路系统的建模与验证,帮助理解扩频调制、多径衰落与RAKE接收等核心机制。压缩包共13个文件,以10个m脚本、2个mat数据文件和1份doc说明文档为主&…

2026/10/13 16:50:38 阅读更多 →
SwiftUI中的Agent+Skill框架:摆脱if-else,构建可扩展的任务调度系统

SwiftUI中的Agent+Skill框架:摆脱if-else,构建可扩展的任务调度系统

项目名我起了个SwiftUI-Agent-Skill,翻译过来没啥歧义:一套跑在SwiftUI应用里的Agent技能框架。Agent在这里不是玄学,就是一个负责拆解用户意图、派发任务、汇总结果的调度核心;Skill则是挂在它下面的一个个独立能力模块&#xff…

2026/10/13 16:49:38 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目,我最大的感受是,大部分坑不是出在技术前沿,而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节,最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了,很难不碰到舵机控制这道坎。做智能小车要转方向,做机械臂要抬关节,做云台要偏航俯仰,背后都是同一个东西在干活:舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”?——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台?或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜?又或者,在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/13 16:22:04 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 21:05:09 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →