Android备忘录App实战:Room+ViewModel+Material3开发指南

简介:本资源是一份基于Android Studio开发的备忘录App完整项目源码,面向Android初学者与课程设计实践者,聚焦移动端基础功能实现与工程化开发流程训练。项目涵盖用户界面搭建、SQLite本地数据持久化、备忘增删改查、时间排序检索、通知提醒集成等核心模块,帮助学习者系统掌握Android应用开发的关键技术栈。压缩包共81个文件,含10个Java业务逻辑类、34个XML布局与配置文件、11个PNG/WebP图标资源、3个Gradle构建脚本及1个可直接安装的APK调试包,整体2.61MB,结构清晰,适配Android Studio主流版本。目前已有736人学习下载,资源包含完整可运行工程(含memo.jks签名文件、gradlew环境脚本及.gitignore规范),无需额外配置即可导入编译,特别适合课设实践、毕业设计参考与Android入门项目复现。

1. 这不是“Hello World”练习:一个能真正在手机里存下你明天会议纪要的备忘录App,从Android Studio里长出来

你肯定试过在 Android Studio 里新建一个 Empty Activity,敲几行TextView.setText("Hello"),然后点 Run —— 模拟器一闪而过,日志里刷出一堆D/OpenGLRenderer,最后你关掉 IDE,心里清楚:这根本不算“做了一个 App”。真正的分水岭不在编译通过,而在你第一次把写好的备忘录 APK 装进自己手机,打开、输入“下午三点和客户对齐UI稿”,点保存,锁屏再亮起,它还在那里,没丢、没崩、没清空。这个基于 Android Studio 的备忘录 App 设计,就是冲着这个“真实可用”去的:它用Room做本地持久化(不是 SharedPreferences 存字符串那种玄学),支持带时间戳的增删改查,界面用Material3组件搭得干净,关键——所有代码都按 Android 官方推荐的分层架构(UI → ViewModel → Repository → DAO)组织,不是一坨 Activity 里塞满findViewById和 SQL 字符串。它适合刚啃完《第一行代码》第 8 章、正卡在“怎么让数据活过页面重建”的开发者,也适合需要快速交付一个轻量级内部工具的某公司前端转岗同学。别被标题里的“设计”二字骗了——这里没有 PPT 架构图,只有你能复制粘贴、改个包名就能跑起来的.java和.xml。


2. 为什么不用 SQLiteOpenHelper?Room 不是炫技,是少写 37 行样板代码和 2 个崩溃现场

2.1 Room 的底层逻辑:SQL 是你的契约,不是你的牢笼

很多新手一看到 Room 就想绕道,觉得“又要学注解又要写 DAO 又要建 Entity,还不如直接db.execSQL("INSERT...")来得痛快”。但现实是:当你在onCreate()里手写CREATE TABLE notes (id INTEGER PRIMARY KEY, title TEXT, content TEXT, created_at INTEGER),三个月后需求变了——要加个is_pinned BOOLEAN DEFAULT 0字段,你得写ALTER TABLE notes ADD COLUMN is_pinned INTEGER DEFAULT 0,还得在onUpgrade()里判断版本号、执行迁移。稍有疏忽,用户升级后打开 App,CursorWindowAllocationException直接报给你看。Room 把这个过程反转了:你只定义 Java/Kotlin 类(Entity),Room 在编译期自动生成CREATE TABLE语句和ContentValues绑定逻辑。它强制你把“数据结构”和“操作逻辑”分离——Entity 描述“是什么”,DAO 描述“能做什么”,Repository 描述“谁来协调”。这种分离不是为了面试吹架构,而是当你某天发现created_at字段类型从Long改成String(比如要兼容 ISO8601 格式),你只需要改 Entity 里的字段声明和 DAO 里的查询返回类型,Room 编译器会立刻告诉你哪几个@Query里的SELECT语句漏改了strftime函数调用。这种编译期检查,比运行时NullPointerException好救十倍。

2.2 三步落地:从建模到插入,一个完整闭环

我们先定义备忘录的核心数据结构。注意@PrimaryKey(autoGenerate = true)和@ColumnInfo(name = "created_at")的写法——这是 Room 要求的显式契约,不能省:

// app/src/main/java/com/example/memo/Note.java @Entity(tableName = "notes") public class Note { @PrimaryKey(autoGenerate = true) public long id; @ColumnInfo(name = "title") public String title; @ColumnInfo(name = "content") public String content; @ColumnInfo(name = "created_at") public long createdAt; // Unix timestamp in milliseconds @ColumnInfo(name = "updated_at") public long updatedAt; }

接着是数据访问接口(DAO)。这里重点看@Insert(onConflict = OnConflictStrategy.REPLACE)——它解决的是“同名笔记重复创建”的经典问题。如果你不做这个声明,两次插入相同id会直接抛SQLiteConstraintException:

// app/src/main/java/com/example/memo/NoteDao.java @Dao public interface NoteDao { @Insert(onConflict = OnConflictStrategy.REPLACE) long insert(Note note); @Update void update(Note note); @Delete void delete(Note note); @Query("SELECT * FROM notes ORDER BY updated_at DESC") LiveData<List<Note>> getAllNotes(); @Query("SELECT * FROM notes WHERE id = :id") Note getNoteById(long id); }

最后是数据库类。Room.databaseBuilder()的第三个参数"memo_database"是数据库文件名,必须唯一;fallbackToDestructiveMigration()是开发阶段的后悔药(见后文避坑章),上线前必须移除:

// app/src/main/java/com/example/memo/AppDatabase.java @Database(entities = {Note.class}, version = 1, exportSchema = false) public abstract class AppDatabase extends RoomDatabase { private static AppDatabase INSTANCE; public abstract NoteDao noteDao(); public static AppDatabase getDatabase(final Context context) { if (INSTANCE == null) { synchronized (AppDatabase.class) { if (INSTANCE == null) { INSTANCE = Room.databaseBuilder( context.getApplicationContext(), AppDatabase.class, "memo_database" ).fallbackToDestructiveMigration() // 开发期临时开关! .build(); } } } return INSTANCE; } }

提示:exportSchema = false是为了减小 APK 体积。Room 默认会把数据库 schema 导出为 JSON 文件用于版本管理,但对单机备忘录这种无服务端同步的场景,它纯属冗余。上线前务必设为true并提交 schema 文件到 Git,否则后续升级会因找不到旧版 schema 而失败。


3. ViewModel + LiveData:为什么你的备忘录一横屏就丢数据?答案在生命周期感知上

3.1 Activity 重建时的数据断崖:从onSaveInstanceState()到ViewModel的进化

假设你在MainActivity里声明了一个List<Note> notes = new ArrayList<>();,在onCreate()里从 Room 加载数据并填充它。当用户旋转屏幕,系统销毁并重建 Activity,notes这个引用直接变成null,界面上的 RecyclerView 一片空白——你得重写onSaveInstanceState(Bundle)把整个List序列化进去,再在onRestoreInstanceState()里反序列化。但Note里如果有Bitmap或InputStream,这条路直接堵死。ViewModel的价值就在这里:它由ViewModelProvider管理,生命周期绑定到Activity所在的LifecycleOwner,而不是Activity本身。只要Activity没被系统杀死(比如用户按 Home 键切走),ViewModel实例就一直活着。你把数据加载逻辑从Activity移到ViewModel,LiveData<List<Note>>作为可观察数据源,Activity只负责订阅和更新 UI。旋转?内存不足?ViewModel里的notes丝毫无损。

3.2 从零写一个 NotesViewModel:把 Room 的 LiveData 接口串起来

NotesViewModel不继承AndroidViewModel(它需要Application上下文,而我们只用 Room,不需要),直接继承ViewModel即可。关键点在于getAllNotes()返回的是LiveData<List<Note>>,它天然支持观察者模式,且线程安全:

// app/src/main/java/com/example/memo/NotesViewModel.java public class NotesViewModel extends ViewModel { private final NoteDao noteDao; private final LiveData<List<Note>> allNotes; public NotesViewModel(@NonNull Application application) { super(); AppDatabase db = AppDatabase.getDatabase(application.getApplicationContext()); noteDao = db.noteDao(); allNotes = noteDao.getAllNotes(); // Room 自动生成的 LiveData 查询 } public LiveData<List<Note>> getAllNotes() { return allNotes; } public void insert(Note note) { new InsertAsyncTask(noteDao).execute(note); } public void update(Note note) { new UpdateAsyncTask(noteDao).execute(note); } public void delete(Note note) { new DeleteAsyncTask(noteDao).execute(note); } // 异步任务封装,避免在主线程执行数据库写入 private static class InsertAsyncTask extends AsyncTask<Note, Void, Void> { private final NoteDao mAsyncTaskDao; InsertAsyncTask(NoteDao dao) { mAsyncTaskDao = dao; } @Override protected Void doInBackground(Note... notes) { mAsyncTaskDao.insert(notes[0]); return null; } } private static class UpdateAsyncTask extends AsyncTask<Note, Void, Void> { private final NoteDao mAsyncTaskDao; UpdateAsyncTask(NoteDao dao) { mAsyncTaskDao = dao; } @Override protected Void doInBackground(Note... notes) { mAsyncTaskDao.update(notes[0]); return null; } } private static class DeleteAsyncTask extends AsyncTask<Note, Void, Void> { private final NoteDao mAsyncTaskDao; DeleteAsyncTask(NoteDao dao) { mAsyncTaskDao = dao; } @Override protected Void doInBackground(Note... notes) { mAsyncTaskDao.delete(notes[0]); return null; } } }

在MainActivity中使用它,只需两步:获取ViewModel实例,然后观察LiveData:

// app/src/main/java/com/example/memo/MainActivity.java public class MainActivity extends AppCompatActivity { private NotesViewModel notesViewModel; private NoteAdapter adapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); RecyclerView recyclerView = findViewById(R.id.recycler_view); adapter = new NoteAdapter(); recyclerView.setAdapter(adapter); recyclerView.setLayoutManager(new LinearLayoutManager(this)); notesViewModel = new ViewModelProvider(this).get(NotesViewModel.class); notesViewModel.getAllNotes().observe(this, notes -> { // 数据更新时自动回调,无需手动刷新 adapter.submitList(notes); }); } }

注意:new ViewModelProvider(this).get(NotesViewModel.class)中的this是MainActivity,它实现了ViewModelStoreOwner接口。ViewModelProvider会根据this的哈希值缓存NotesViewModel实例,确保旋转后拿到的是同一个对象。


4. 避坑:那些让你在凌晨两点对着 Logcat 咬牙切齿的 Room + ViewModel 组合雷区

4.1 现象:App 启动时报java.lang.IllegalStateException: Cannot access database on the main thread

原因:Room 默认禁止在主线程执行数据库写入(insert/update/delete),因为 SQLite 是阻塞 I/O,会卡住 UI 线程。但@Query的读操作(如getAllNotes())返回LiveData是允许的,因为 Room 内部已做了线程切换。你如果在ViewModel里直接调用noteDao.insert(note),就会触发此异常。
解决:必须用异步方式。上面NotesViewModel里的InsertAsyncTask就是标准解法。Kotlin 用户可用viewModelScope.launch { }+withContext(Dispatchers.IO),Java 用户坚持用AsyncTask(虽然已废弃,但在 AndroidX 中仍可用)或ExecutorService。切记:getAllNotes()可以直接调,insert()必须异步。

4.2 现象:修改 Note 后 UI 不刷新,submitList()调用了但 RecyclerView 没变

原因:LiveData的观察者只在数据“变化”时触发。Room的getAllNotes()返回的LiveData是基于数据库变更通知的,但submitList()传入的是一个新List对象。如果Note类没重写equals()和hashCode(),ListAdapter的DiffUtil无法识别哪些项是新增、修改或删除,导致submitList()认为“数据没变”,跳过刷新。
解决:给Note类补全equals()和hashCode()。Android Studio 可右键 → Generate →equals()andhashCode(),勾选所有字段。这是ListAdapter正常工作的前提,不是可选项。

4.3 现象:升级 App 后,老用户打开闪退,Logcat 显示android.database.sqlite.SQLiteException: no such column: is_pinned

原因:你新增了is_pinned字段,但没写数据库迁移逻辑。fallbackToDestructiveMigration()在开发期很爽,但上线后绝对不能留——它会直接删掉用户所有旧数据,重建新表。生产环境必须用addMigrations()。
解决:定义迁移脚本。例如从 v1 升到 v2:

static final Migration MIGRATION_1_2 = new Migration(1, 2) { @Override public void migrate(@NonNull SupportSQLiteDatabase database) { database.execSQL("ALTER TABLE notes ADD COLUMN is_pinned INTEGER NOT NULL DEFAULT 0"); } }; // 在 AppDatabase.build() 时添加: .fallbackToDestructiveMigration() // 先注释掉! .addMigrations(MIGRATION_1_2)

提示:Migration 脚本必须严格测试。用Room.inMemoryDatabaseBuilder()创建内存库,在单元测试里跑一遍MIGRATION_1_2,验证is_pinned字段是否成功添加且默认值为 0。

4.4 现象:LiveData观察者被多次触发,每次旋转屏幕就多注册一个监听

原因:observe()的第二个参数是Observer,如果在onCreate()里反复observe(),每次重建 Activity 都会新增一个观察者,导致数据更新时多个submitList()被调用,RecyclerView 刷新混乱。
解决:observe()必须配对LifecycleOwner。this(即MainActivity)实现了LifecycleOwner,LiveData会自动在Activity销毁时移除观察者。只要确保observe()只在onCreate()里调用一次,就绝不会泄漏。不要用observeForever(),那玩意儿得手动removeObserver(),极易翻车。


5. Material3 + Navigation:让备忘录不止能用,还能让人愿意多看两眼

5.1 为什么选 Material3 而不是 AppCompat?颜色系统是生产力

Android Studio 新建项目默认用Theme.Material3.*,这不是跟风。Material3 的动态色彩(Dynamic Color)能自动提取壁纸主色,生成整套 harmonious 色板。对备忘录这种高频使用的工具 App,视觉一致性直接降低认知负荷。你不需要手写 20 个colorPrimary、colorOnSurface,只需在themes.xml里启用:

<!-- app/src/main/res/values/themes.xml --> <style name="Theme.Memo" parent="Theme.Material3.DayNight"> <item name="android:windowSplashScreenBackground">@color/splash_background</item> <!-- 启用动态色彩 --> <item name="android:forceDarkAllowed">false</item> </style>

然后在MainActivity的onCreate()里加一行:

AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);

系统会自动根据深色模式开关和壁纸,调整FloatingActionButton的背景色、TextField的光标色、甚至Chip的边框色。用户换张壁纸,你的 App 就焕然一新——这种体验,AppCompat做不到。

5.2 Navigation Component:告别startActivity()的意大利面条式跳转

备忘录至少有三个界面:列表页(MainActivity)、编辑页(EditNoteActivity)、详情页(NoteDetailActivity)。如果全用Intent跳转,EditNoteActivity里要getIntent().getLongExtra("note_id", -1),NoteDetailActivity里又要getIntent().getStringExtra("note_title")……参数传递像击鼓传花,错一个就NullPointerException。Navigation Component 把路由集中到nav_graph.xml里,用<argument>显式声明参数类型和默认值:

<!-- app/src/main/res/navigation/nav_graph.xml --> <navigation xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/nav_graph" app:startDestination="@id/listFragment"> <fragment android:id="@+id/listFragment" android:name="com.example.memo.ListFragment" android:label="Notes"> <action android:id="@+id/action_list_to_edit" app:destination="@id/editFragment" /> </fragment> <fragment android:id="@+id/editFragment" android:name="com.example.memo.EditFragment" android:label="Edit Note"> <argument android:name="noteId" app:argType="long" android:defaultValue="-1" /> </fragment> </navigation>

在ListFragment里跳转时,用findNavController().navigate()传参,类型安全:

// ListFragment.java Bundle bundle = new Bundle(); bundle.putLong("noteId", note.getId()); findNavController().navigate(R.id.action_list_to_edit, bundle);

EditFragment里用NavArgs自动生成的getArguments()获取,IDE 会提示noteId字段,再也不用getLongExtra()猜参数名。


6. 最后一道防线:用 Instrumented Test 验证“保存”按钮真的把数据落盘了

6.1 为什么单元测试不够?Room 的@Dao方法必须在真实数据库上跑

你可以在JUnit里 mockNoteDao,验证insert()方法被调用了。但这只能证明“代码路径走通了”,不能证明“数据真的写进了/data/data/com.example.memo/databases/memo_database”。SQLite 是系统级组件,涉及文件 I/O、事务、锁机制,只有在真实设备或模拟器上运行的 Instrumented Test 才能覆盖。AndroidX Test 提供了TestDatabase工厂,让我们在测试中创建一个独立的、不污染主数据库的测试库:

// app/src/androidTest/java/com/example/memo/NoteDaoTest.java @RunWith(AndroidJUnit4.class) public class NoteDaoTest { private AppDatabase database; private NoteDao noteDao; @Before public void createDb() { Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); database = Room.inMemoryDatabaseBuilder(context, AppDatabase.class) .allowMainThreadQueries() // 测试中允许主线程查询,简化写法 .build(); noteDao = database.noteDao(); } @After public void closeDb() { database.close(); } @Test public void insertAndGetNote() throws Exception { // Given Note note = new Note(); note.title = "Test Title"; note.content = "Test Content"; note.createdAt = System.currentTimeMillis(); note.updatedAt = note.createdAt; // When long id = noteDao.insert(note); Note retrieved = noteDao.getNoteById(id); // Then assertNotNull(retrieved); assertEquals("Test Title", retrieved.title); assertEquals("Test Content", retrieved.content); } }

6.2 关键参数说明与执行命令

  • Room.inMemoryDatabaseBuilder(...)创建的是内存数据库,进程退出即销毁,完全隔离;
  • .allowMainThreadQueries()是测试专用开关,生产代码严禁开启;
  • InstrumentationRegistry.getInstrumentation().getTargetContext()提供的是被测 App 的上下文,能访问其assets和databases目录;
  • 运行命令:在终端执行./gradlew connectedAndroidTest,或在 Android Studio 的Build Variants面板中将testBuildType切为debug,右键测试类 →Run 'NoteDaoTest'。

从那以后我每次重构NoteDao的@Query语句,或者新增一个@Insert方法,都会先补一个对应的 Instrumented Test。不是为了覆盖率数字好看,而是当insertAndGetNote()绿了,我知道这行 SQL 在真实 SQLite 引擎里跑通了——它不会在用户手机上因为某个未声明的NOT NULL约束而静默失败。这种确定性,是深夜收到崩溃报告时,我能睡着的唯一后悔药。希望帮到你。

本文还有配套的精品资源,点击获取

相关新闻

P0087轨压过低别急着换泵:柴油共轨免拆诊断实战

P0087轨压过低别急着换泵:柴油共轨免拆诊断实战

下午两点多&#xff0c;拖车送来一台道奇2500&#xff0c;客户第一句话就是&#xff1a;昨天还好好的&#xff0c;今天死活打不着。接上诊断仪一读&#xff0c;故障码P0087油轨压力过低。旁边的小修理厂已经下了结论——高压泵坏了&#xff0c;换泵报价高得吓人。我在维修行当干…

2026/10/20 9:42:38 阅读更多 →
PyTorch图像分割入门:从Unet复现到数据质量诊断

PyTorch图像分割入门:从Unet复现到数据质量诊断

简介&#xff1a;本资源是面向深度学习初学者与进阶实践者的PyTorch模型复现代码合集&#xff0c;聚焦图像分割、视觉Transformer及生成式建模等核心方向&#xff0c;涵盖Unet、Vision Transformer&#xff08;ViT&#xff09;、MAE、DDPM等主流架构的完整可运行实现。压缩包共…

2026/10/20 9:42:38 阅读更多 →
8086汇编四例:大数相乘、计算器、文本比较与贪吃蛇的避坑指南

8086汇编四例:大数相乘、计算器、文本比较与贪吃蛇的避坑指南

简介&#xff1a;北理工《汇编语言与接口技术》课程配套汇编资料&#xff0c;覆盖大数相乘、计算器、文本比较与贪吃蛇四个典型项目&#xff0c;适合需要完成汇编上机作业或课设的本科生参考。其中大数相乘实现100位以上十进制整数乘法&#xff0c;计算器结合Windows界面与浮点…

2026/10/20 9:42:38 阅读更多 →

最新新闻

文件夹.EXE病毒专杀工具:Windows资源管理器劫持急救方案

文件夹.EXE病毒专杀工具:Windows资源管理器劫持急救方案

简介&#xff1a;这是一款专为清除“文件夹.EXE”类伪装型病毒设计的轻量级杀毒工具&#xff0c;面向普通电脑用户及初级系统维护人员&#xff0c;解决因误点伪装文件夹导致的恶意代码执行、文件隐藏、系统异常等典型感染问题。压缩包共4个文件&#xff08;443KB&#xff09;&a…

2026/10/20 10:23:05 阅读更多 →
300W人脸关键点数据集整理包:开箱即用的COCO-Keypoints工程资产

300W人脸关键点数据集整理包:开箱即用的COCO-Keypoints工程资产

简介&#xff1a;本资源是面向计算机视觉研究者与深度学习工程师的300W人脸关键点数据集整理包&#xff0c;专为面部对齐、关键点检测及人脸几何建模等任务提供高质量训练基础。资源包含2个核心NumPy数据文件&#xff08;facial_image.npy与facial_keypoints.npy&#xff09;&a…

2026/10/20 10:23:05 阅读更多 →
微博热搜爬虫实战:requests直取JSON+Excel结构化落盘

微博热搜爬虫实战:requests直取JSON+Excel结构化落盘

简介&#xff1a;本资源是一份面向Python初学者与数据采集实践者的轻量级微博热搜爬虫实战项目&#xff0c;聚焦社交媒体热门话题的自动化抓取与结构化存储。项目使用requestsBeautifulSoup实现网页请求与HTML解析&#xff0c;提取微博TOP50热搜标题及链接&#xff0c;并通过pa…

2026/10/20 10:23:05 阅读更多 →
高并发发卡系统设计:券码生成、库存扣减与核销闭环

高并发发卡系统设计:券码生成、库存扣减与核销闭环

简介&#xff1a;这是一套面向电商运营者与PHP开发者的一站式发卡平台源码&#xff0c;聚焦团购营销与虚拟商品二级交易场景&#xff0c;解决传统发卡系统缺乏用户自流转、活动运营弱、资金监管缺位等痛点。资源共2000个文件&#xff0c;主体为507个PHP后端逻辑文件、304个HTML…

2026/10/20 10:23:05 阅读更多 →
WinForm自绘登录窗体实战:无边框圆角UI与自绘控件实现解析

WinForm自绘登录窗体实战:无边框圆角UI与自绘控件实现解析

简介&#xff1a;C#登录窗体完整示例包&#xff0c;面向WinForms桌面应用初学者及中级开发者&#xff0c;可用于课程设计、项目原型或界面参考&#xff0c;集中演示了如何构建外观出众且具备完整交互逻辑的登录模块。压缩包为RAR格式&#xff0c;共70个文件&#xff0c;大小约5…

2026/10/20 10:23:05 阅读更多 →
ROS2+Autoware实车部署:跨越仿真与物理世界的三堵墙

ROS2+Autoware实车部署:跨越仿真与物理世界的三堵墙

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

2026/10/20 10:22:04 阅读更多 →

日新闻

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

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

2026/10/20 1:01:30 阅读更多 →
ESP32舵机控制指南:原理、多路驱动与避坑实战

ESP32舵机控制指南:原理、多路驱动与避坑实战

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

2026/10/20 1:01:30 阅读更多 →
位移法实现一阶低通滤波:单片机ADC采样降噪实战

位移法实现一阶低通滤波:单片机ADC采样降噪实战

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

2026/10/20 1:01:30 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型&#xff08;GMM&#xff09;的语音识别系统&#xff0c;很多人第一反应就是一堆数学公式&#xff0c;觉得门槛很高。但如果你真的在MATLAB里把它落地跑通&#xff0c;会发现核心其实就是三件事&#xff1a;把声音变成特征向量&#xff0c;用GMM给每个类别…

2026/10/20 10:02:13 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人&#xff0c;但落到具体操作上&#xff0c;无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程&#xff1a;对图像做反白、改调色板、彩色转灰度&#xff0c;最后把像素数据导出成txt文件。单看每一…

2026/10/19 17:32:41 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词&#xff0c;你会发现一个很有意思的现象&#xff1a;每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型&#xff0c;有人指的是运动控制系统&#xff0c;还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/20 2:50:50 阅读更多 →

月新闻

我发现了一个新思路:用 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/20 10:02:08 阅读更多 →
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/20 9:50:17 阅读更多 →
黑夜航拍船只数据集训练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/20 9:50:45 阅读更多 →