Android智能衣橱源码解析:SQLite+RecyclerView实现衣物管理

发布时间:2026/10/2 15:53:48
Android智能衣橱源码解析:SQLite+RecyclerView实现衣物管理
简介这是一份面向Android初学者与移动应用开发者的智能衣橱管理系统完整源码围绕天气驱动的穿衣推荐与个人衣橱管理展开适合作为课程设计、毕业设计或Android综合练习的参考项目。项目包含天气预报、穿衣推荐、衣物增减、用户增减与新服饰推送五大模块结合定位信息与用户偏好实现推荐逻辑并支持照片与分类方式存储衣物记录体现家庭多用户管理概念。资源包共99个文件以32个xml布局与配置、23个java业务代码为主辅以18个png、8个jpg及3个gif界面素材另有gradle构建脚本与说明文档压缩包约933KB结构完整便于导入Android Studio直接构建运行。目前已有68人浏览学习读者可从中获取模块划分思路、推荐算法实现方式、界面布局组织与数据存储方案适合对照源码理解Android应用从定位、网络请求到本地管理的完整开发流程。1. 从一份 Android 智能衣橱源码说起它到底能跑出什么衣柜里塞了上百件衣服每天早上还是觉得没得穿——这个问题用纸笔解决不了用 Excel 也坚持不了三天。这份基于 Android 的智能衣橱管理系统源码走的是另一条路把衣物录入、分类检索、穿搭记录、季节筛选做成一个完整的移动端应用用 SQLite 做本地持久化用 RecyclerView 撑起列表交互用 Material Design 组件把界面拉到能看的水平。它适合两类人一是正在找 Android 课程设计或毕业设计选题的学生二是想拿一个结构完整、能编译能跑的小型 App 来练手或改造成自己作品的开发者。源码包里的工程结构、数据库设计、Activity 与 Adapter 的配合方式都是可以直接拆开看、改完就能验证的。2. 工程结构与技术选型拿到源码先看哪几个文件2.1 目录骨架与 Gradle 配置解压之后你看到的应该是一个标准的 Android Studio 工程目录。核心结构大致如下SmartWardrobe/ ├── app/ │ ├── src/main/ │ │ ├── java/com/example/smartwardrobe/ │ │ │ ├── MainActivity.java │ │ │ ├── AddClothesActivity.java │ │ │ ├── ClothesDetailActivity.java │ │ │ ├── db/ │ │ │ │ ├── DBHelper.java │ │ │ │ └── ClothesDao.java │ │ │ ├── adapter/ │ │ │ │ └── ClothesAdapter.java │ │ │ └── model/ │ │ │ └── Clothes.java │ │ ├── res/ │ │ │ ├── layout/ │ │ │ ├── drawable/ │ │ │ └── values/ │ │ └── AndroidManifest.xml │ └── build.gradle ├── build.gradle ├── settings.gradle └── gradle.properties拿到工程后先别急着点 Run。打开app/build.gradle确认三件事compileSdkVersion和targetSdkVersion是多少、minSdkVersion设了什么、依赖库里有没有已经停止维护的旧版本。常见的情况是源码写于两三年前compileSdkVersion停在 30 或 31而你本地装的是 Android Studio 新版默认 SDK 已经到了 34 或更高。这时候直接同步大概率报错需要手动把版本号对齐到你本地已安装的 SDK。// app/build.gradle 关键配置段 android { compileSdkVersion 34 // 改成你本地已安装的版本 defaultConfig { applicationId com.example.smartwardrobe minSdkVersion 24 // 最低兼容 Android 7.0 targetSdkVersion 34 versionCode 1 versionName 1.0 } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } } dependencies { implementation androidx.appcompat:appcompat:1.6.1 implementation com.google.android.material:material:1.11.0 implementation androidx.recyclerview:recyclerview:1.3.2 implementation androidx.cardview:cardview:1.0.0 }这里minSdkVersion 24意味着 Android 7.0 以下的设备装不了2024 年以后这基本不是问题。compileSdkVersion和targetSdkVersion保持一致就行不用追求最新关键是和你本地 SDK Manager 里已下载的版本对上。依赖库如果源码里写的是com.android.support:appcompat-v7:28.0.0这类旧 support 库建议整体迁移到 AndroidX否则和新版 Android Studio 的默认配置冲突报错信息会让人很烦躁。2.2 数据库设计三张表撑起整个系统智能衣橱的核心逻辑不在界面在数据层。这份源码用的是 SQLite通过SQLiteOpenHelper的子类DBHelper来建库建表。常见做法是设计三张表衣物表、分类表、穿搭记录表。衣物表存每一件衣服的基本信息分类表存上衣、裤子、外套、鞋子等类别穿搭记录表存用户每次搭配了哪些衣服。// DBHelper.java 建表逻辑 public class DBHelper extends SQLiteOpenHelper { private static final String DB_NAME wardrobe.db; private static final int DB_VERSION 1; public static final String TABLE_CLOTHES clothes; public static final String TABLE_CATEGORY category; public static final String TABLE_OUTFIT outfit_record; private static final String CREATE_CLOTHES CREATE TABLE TABLE_CLOTHES ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, category_id INTEGER, color TEXT, season TEXT, image_path TEXT, note TEXT, create_time INTEGER); private static final String CREATE_CATEGORY CREATE TABLE TABLE_CATEGORY ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, icon_res TEXT); private static final String CREATE_OUTFIT CREATE TABLE TABLE_OUTFIT ( id INTEGER PRIMARY KEY AUTOINCREMENT, clothes_ids TEXT, wear_date TEXT, remark TEXT); Override public void onCreate(SQLiteDatabase db) { db.execSQL(CREATE_CLOTHES); db.execSQL(CREATE_CATEGORY); db.execSQL(CREATE_OUTFIT); initDefaultCategories(db); // 插入默认分类数据 } Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL(DROP TABLE IF EXISTS TABLE_CLOTHES); db.execSQL(DROP TABLE IF EXISTS TABLE_CATEGORY); db.execSQL(DROP TABLE IF EXISTS TABLE_OUTFIT); onCreate(db); } }clothes表里category_id是外键关联到category表的idimage_path存的是图片在设备上的文件路径而不是图片本身——这一点很关键后面讲图片处理时会展开。outfit_record表的clothes_ids用逗号分隔的字符串存一组衣物 ID这是一种偷懒但够用的做法查询时需要在代码里拆分。onUpgrade里直接删表重建开发阶段没问题上线前得改成增量迁移否则用户升级一次数据全丢。2.3 Activity 与 Adapter 的配合方式主界面MainActivity负责展示衣物列表用RecyclerView加ClothesAdapter来渲染。数据从ClothesDao查出来转成ListClothes再交给 Adapter。这个链路是 Android 列表页的标准写法但源码里有两个细节值得注意一是 Adapter 的onBindViewHolder里有没有做 ViewHolder 复用时的图片加载取消二是点击事件是通过接口回调还是直接写在 Adapter 内部。// ClothesAdapter.java 核心绑定逻辑 Override public void onBindViewHolder(NonNull ClothesViewHolder holder, int position) { Clothes item clothesList.get(position); holder.tvName.setText(item.getName()); holder.tvCategory.setText(item.getCategoryName()); holder.tvSeason.setText(item.getSeason()); // 图片加载源码里用的是本地路径 BitmapFactory if (item.getImagePath() ! null !item.getImagePath().isEmpty()) { Bitmap bitmap BitmapFactory.decodeFile(item.getImagePath()); if (bitmap ! null) { holder.ivClothes.setImageBitmap(bitmap); } else { holder.ivClothes.setImageResource(R.drawable.ic_placeholder); } } else { holder.ivClothes.setImageResource(R.drawable.ic_placeholder); } // 点击事件通过接口回调到 Activity holder.itemView.setOnClickListener(v - { if (onItemClickListener ! null) { onItemClickListener.onItemClick(position, item); } }); }BitmapFactory.decodeFile直接在主线程解码图片如果图片是手机拍照的原图一张可能好几 MB列表滑动时必然卡顿甚至 ANR。这是这份源码最典型的性能坑后面排查章节会专门讲怎么改。接口回调的方式倒是没问题比在 Adapter 里直接写startActivity要干净Activity 能拿到点击事件后自己决定跳详情页还是弹菜单。3. 从零跑起来环境配置与首次编译的完整步骤3.1 Android Studio 安装与 SDK 版本对齐如果你本地还没装 Android Studio去官网下载最新稳定版即可。安装过程中会引导你下载 SDK默认会装最新版的compileSdk。装完之后打开 SDK Manager确认以下几项已安装Android SDK Platform 34或你打算用的版本、Android SDK Build-Tools、Android Emulator、Android SDK Platform-Tools。如果源码的compileSdkVersion是 31而你没装 31 的 Platform同步时会提示缺失要么去 SDK Manager 补装要么把配置改成你已有的版本。Android Studio 首次打开工程会触发 Gradle 同步。如果源码里的 Gradle 版本和你本地的不一致会提示下载对应版本的 Gradle 包。这个过程取决于网络环境有时候会卡在Downloading gradle-x.x.x-all.zip很久。常见做法是手动下载对应版本的 Gradle 压缩包放到~/.gradle/wrapper/dists/对应目录下或者直接改gradle/wrapper/gradle-wrapper.properties里的distributionUrl指向你本地已有的版本。# gradle/wrapper/gradle-wrapper.properties distributionBaseGRADLE_USER_HOME distributionPathwrapper/dists distributionUrlhttps\://services.gradle.org/distributions/gradle-8.2-bin.zip zipStoreBaseGRADLE_USER_HOME zipStorePathwrapper/dists把gradle-8.2-bin.zip改成你本地已有的版本号比如gradle-7.5-bin.zip同步会快很多。注意 Gradle 版本和 Android Gradle Plugin 版本有对应关系不能随便降。一般 AGP 8.x 配 Gradle 8.xAGP 7.x 配 Gradle 7.x具体对应表在 Android 开发者文档里能查到。3.2 导入工程与解决依赖冲突同步通过之后先别急着 Run。打开Build菜单点Make Project让编译器把整个工程过一遍。这一步会暴露大部分编译期错误比如资源文件缺失、Java 语法不兼容、依赖库版本冲突。常见的报错有三类第一类是Duplicate class冲突通常是因为同时引入了旧 support 库和 AndroidX 库。解决办法是在gradle.properties里加上android.useAndroidXtrue和android.enableJetifiertrue让 Gradle 自动把旧库迁移到 AndroidX。第二类是Resource linking failed一般是res/values/colors.xml或styles.xml里有重复定义或格式错误。打开报错指向的文件检查有没有重复的color或style名称。第三类是Cannot resolve symbol说明依赖没拉下来。检查build.gradle里的仓库配置确保有google()和mavenCentral()。// 项目根目录 build.gradle allprojects { repositories { google() mavenCentral() } }如果公司网络或校园网对 Maven 仓库有限制可以换成国内镜像源。但注意镜像源同步有延迟某些新版本库可能还没收录遇到Could not find就换回官方源再试一次。3.3 模拟器与真机运行编译通过后创建一个模拟器。建议选 Pixel 系列系统版本选 Android 10 到 13 之间太新的版本可能对旧 targetSdk 有额外限制。创建时注意 ABI 选x86_64如果你用的是 Apple Silicon 芯片的 Mac选arm64-v8a。模拟器启动后点 Run 按钮Android Studio 会把 APK 装上去并启动主 Activity。如果模拟器跑起来很卡或者摄像头、文件存储相关功能不正常建议换真机调试。真机需要开启开发者选项和 USB 调试用数据线连上电脑后Android Studio 的设备列表里会出现你的手机型号。真机调试的好处是能测到真实的文件路径、真实的拍照流程、真实的存储权限行为这些在模拟器上经常和真机不一致。注意首次运行如果闪退先看 Logcat 里的AndroidRuntime异常堆栈。最常见的闪退原因是数据库建表 SQL 有语法错误或者AndroidManifest.xml里没注册某个 Activity。4. 核心功能拆解衣物录入、图片存储与列表检索4.1 衣物录入表单与数据校验AddClothesActivity是录入入口界面上一般有名称输入框、分类下拉、颜色选择、季节多选、图片选择按钮、备注输入框。点保存时先做非空校验再把数据组装成Clothes对象调ClothesDao.insert()写库。// AddClothesActivity.java 保存逻辑 private void saveClothes() { String name etName.getText().toString().trim(); if (TextUtils.isEmpty(name)) { etName.setError(请输入衣物名称); return; } String categoryId spinnerCategory.getSelectedItemPosition() 1 ; String color etColor.getText().toString().trim(); String season getSelectedSeasons(); // 多选拼成字符串 String note etNote.getText().toString().trim(); Clothes clothes new Clothes(); clothes.setName(name); clothes.setCategoryId(Integer.parseInt(categoryId)); clothes.setColor(color); clothes.setSeason(season); clothes.setImagePath(currentImagePath); clothes.setNote(note); clothes.setCreateTime(System.currentTimeMillis()); long rowId clothesDao.insert(clothes); if (rowId 0) { Toast.makeText(this, 保存成功, Toast.LENGTH_SHORT).show(); setResult(RESULT_OK); finish(); } else { Toast.makeText(this, 保存失败, Toast.LENGTH_SHORT).show(); } }getSelectedSeasons()是把多个 CheckBox 的选中状态拼成「春,夏,秋」这样的字符串存进一个字段。这种设计查询时需要用LIKE %春%来匹配效率不高但数据量小的时候够用。如果要做正规的季节筛选应该单独建一张关联表但课程设计级别的项目没必要过度设计。4.2 图片选择与本地存储策略图片处理是这类 App 最容易翻车的地方。源码里常见的做法是调系统相册或相机拿到content://开头的 Uri然后要么直接把 Uri 字符串存库要么把图片拷贝到 App 私有目录再把路径存库。前者的问题是一旦原图被删除或权限变更图片就加载不出来后者更稳妥但需要处理文件拷贝和路径管理。// 图片选择回调把 content Uri 拷贝到私有目录 Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode REQUEST_PICK_IMAGE resultCode RESULT_OK data ! null) { Uri sourceUri data.getData(); String fileName cloth_ System.currentTimeMillis() .jpg; File destFile new File(getExternalFilesDir(clothes), fileName); try (InputStream in getContentResolver().openInputStream(sourceUri); OutputStream out new FileOutputStream(destFile)) { byte[] buffer new byte[4096]; int len; while ((len in.read(buffer)) ! -1) { out.write(buffer, 0, len); } currentImagePath destFile.getAbsolutePath(); ivPreview.setImageBitmap(BitmapFactory.decodeFile(currentImagePath)); } catch (IOException e) { e.printStackTrace(); Toast.makeText(this, 图片保存失败, Toast.LENGTH_SHORT).show(); } } }getExternalFilesDir(clothes)拿到的是 App 私有目录下的clothes文件夹路径类似/storage/emulated/0/Android/data/com.example.smartwardrobe/files/clothes/。这个目录不需要额外申请存储权限App 卸载时会被系统清理。把图片拷贝到这里数据库里存绝对路径加载时用BitmapFactory.decodeFile读出来。注意拷贝时用了 4KB 缓冲区循环读写比一次性readAllBytes更省内存大图也不会 OOM。4.3 列表检索与条件筛选主界面的检索功能一般支持按名称模糊搜索、按分类筛选、按季节筛选。ClothesDao里对应的方法用SQLiteDatabase.query或rawQuery拼条件。// ClothesDao.java 条件查询 public ListClothes search(String keyword, int categoryId, String season) { StringBuilder sql new StringBuilder(SELECT c.*, cat.name AS category_name FROM clothes c LEFT JOIN category cat ON c.category_id cat.id WHERE 11); ListString args new ArrayList(); if (!TextUtils.isEmpty(keyword)) { sql.append( AND c.name LIKE ?); args.add(% keyword %); } if (categoryId 0) { sql.append( AND c.category_id ?); args.add(String.valueOf(categoryId)); } if (!TextUtils.isEmpty(season)) { sql.append( AND c.season LIKE ?); args.add(% season %); } sql.append( ORDER BY c.create_time DESC); Cursor cursor getReadableDatabase().rawQuery(sql.toString(), args.toArray(new String[0])); ListClothes list new ArrayList(); while (cursor.moveToNext()) { Clothes item new Clothes(); item.setId(cursor.getInt(cursor.getColumnIndexOrThrow(id))); item.setName(cursor.getString(cursor.getColumnIndexOrThrow(name))); item.setCategoryName(cursor.getString(cursor.getColumnIndexOrThrow(category_name))); item.setSeason(cursor.getString(cursor.getColumnIndexOrThrow(season))); item.setImagePath(cursor.getString(cursor.getColumnIndexOrThrow(image_path))); list.add(item); } cursor.close(); return list; }用StringBuilder拼 SQL 加参数化查询避免了 SQL 注入。LEFT JOIN把分类名称一起查出来省得在 Adapter 里再查一次数据库。ORDER BY create_time DESC让最新录入的排在最前面符合使用直觉。注意cursor.getColumnIndexOrThrow在列名写错时会直接抛异常比getColumnIndex返回 -1 再静默出错要好排查。5. 避坑与排查这份源码最容易翻车的五个地方5.1 图片加载 OOM 与列表滑动卡顿现象衣物列表滑动时明显掉帧图片多了之后 App 直接闪退Logcat 里出现OutOfMemoryError。原因BitmapFactory.decodeFile默认按原图尺寸解码手机拍照的图片动辄 3000×4000 像素一张占十几 MB 内存。列表快速滑动时同时解码多张堆内存瞬间打满。解决解码前先用BitmapFactory.Options的inJustDecodeBounds读图片尺寸按目标 ImageView 大小算inSampleSize再真正解码。或者直接引入 Glide、Picasso 这类图片加载库它们内部已经处理了采样、缓存和生命周期取消。// 手动计算采样率的工具方法 public static Bitmap decodeSampledBitmap(String path, int reqWidth, int reqHeight) { BitmapFactory.Options options new BitmapFactory.Options(); options.inJustDecodeBounds true; BitmapFactory.decodeFile(path, options); options.inSampleSize calculateInSampleSize(options, reqWidth, reqHeight); options.inJustDecodeBounds false; return BitmapFactory.decodeFile(path, options); } private static int calculateInSampleSize(BitmapFactory.Options options, int reqW, int reqH) { int inSampleSize 1; if (options.outHeight reqH || options.outWidth reqW) { int halfH options.outHeight / 2; int halfW options.outWidth / 2; while ((halfH / inSampleSize) reqH (halfW / inSampleSize) reqW) { inSampleSize * 2; } } return inSampleSize; }5.2 数据库升级导致数据丢失现象改了DB_VERSION之后重新安装之前录入的衣物全没了。原因onUpgrade里写的是DROP TABLE再onCreate版本号一升就把旧表删了。解决开发阶段可以接受但如果你打算把这个 App 当作品演示至少改成ALTER TABLE ADD COLUMN的增量迁移。更稳妥的做法是用 Room 持久化库它内置了 Migration 机制但迁移成本较高课程设计级别手动写ALTER TABLE就够了。5.3 存储权限在 Android 10 上的行为差异现象在 Android 10 以上的真机上选择图片后 App 崩溃或提示权限被拒。原因Android 10 引入了分区存储WRITE_EXTERNAL_STORAGE权限不再能随意写公共目录。源码如果还在用Environment.getExternalStorageDirectory()拼路径在新系统上会失败。解决改用getExternalFilesDir()或getFilesDir()拿 App 私有目录不需要申请存储权限。如果必须读系统相册用MediaStoreAPI 或ActivityResultContracts.PickVisualMedia来选图拿到的是content://Uri再拷贝到私有目录。5.4 Gradle 同步卡住或依赖下载失败现象打开工程后 Gradle 同步一直转圈或者报Could not resolve某个依赖。原因网络到 Maven 仓库不稳定或者 Gradle 版本和 AGP 版本不匹配。解决先检查gradle-wrapper.properties里的 Gradle 版本和build.gradle里的 AGP 版本是否对应。然后在repositories里加上国内镜像源作为备选。如果只是某个库拉不下来去 Maven Central 网页上确认该版本是否存在有时候是版本号写错了。5.5 模拟器上相机和相册功能异常现象在模拟器上点「拍照」没反应或者相册里选图后返回的 Uri 是空的。原因模拟器的相机是虚拟的部分 API 行为和真机不一致。相册选图返回的 Uri 在模拟器上可能指向一个不存在的文件。解决涉及相机、相册、文件存储的功能一律用真机测试。模拟器只用来验证界面布局和纯逻辑流程。如果手头没有多余的真机可以用 Android Studio 的 Device Mirroring 功能把真机屏幕投到电脑上操作。6. 进阶改造把这份源码变成你自己的作品6.1 用 Room 替换 SQLiteOpenHelper如果你想让项目在简历上更有说服力把DBHelper和ClothesDao换成 Room 是一个性价比很高的改造。Room 用注解定义实体和查询编译期就能检查 SQL 语法还能返回LiveData或Flow配合 ViewModel 做数据驱动 UI。// Room 实体与 Dao 示例 Entity(tableName clothes) public class Clothes { PrimaryKey(autoGenerate true) public int id; public String name; public int categoryId; public String color; public String season; public String imagePath; public String note; public long createTime; } Dao public interface ClothesDao { Query(SELECT * FROM clothes ORDER BY createTime DESC) LiveDataListClothes getAll(); Query(SELECT * FROM clothes WHERE name LIKE :kw) ListClothes searchByName(String kw); Insert long insert(Clothes clothes); Delete void delete(Clothes clothes); } Database(entities {Clothes.class}, version 1, exportSchema false) public abstract class AppDatabase extends RoomDatabase { public abstract ClothesDao clothesDao(); }改造时注意 Room 要求实体类字段要么是 public要么有 getter/setter。LiveData返回的查询在数据变化时会自动通知 UI省掉了手动notifyDataSetChanged的步骤。数据库版本号从 1 开始后续加字段时写Migration而不是fallbackToDestructiveMigration。6.2 加入穿搭推荐逻辑原始源码只有记录功能没有推荐。一个简单的推荐逻辑是根据当天温度和天气从衣物表里筛选出适合季节的上衣、裤子、外套各一件随机组合展示。温度可以从系统天气 API 获取也可以让用户手动选。推荐结果存进outfit_record表用户可以对推荐结果点赞或换一套点赞数据积累后可以用来调整推荐权重。// 简单推荐按季节筛选后随机取一套 public ListClothes recommendOutfit(String season) { ListClothes tops clothesDao.getByCategoryAndSeason(上衣, season); ListClothes pants clothesDao.getByCategoryAndSeason(裤子, season); ListClothes coats clothesDao.getByCategoryAndSeason(外套, season); ListClothes outfit new ArrayList(); if (!tops.isEmpty()) outfit.add(tops.get(new Random().nextInt(tops.size()))); if (!pants.isEmpty()) outfit.add(pants.get(new Random().nextInt(pants.size()))); if (!coats.isEmpty()) outfit.add(coats.get(new Random().nextInt(coats.size()))); return outfit; }这个逻辑很粗糙但足以让演示效果上一个台阶。后续可以加入「最近一周没穿过的优先」「颜色搭配规则」「用户历史偏好」等维度逐步把推荐做得像样。6.3 数据导出与备份衣物数据录多了之后用户会担心换手机数据丢失。加一个导出功能把数据库里的记录导成 CSV 或 JSON 文件通过系统分享面板发出去。导入时反向解析文件写回数据库。这个功能不复杂但能显著提升项目的完整度。// 导出为 JSON public String exportToJson() { ListClothes all clothesDao.getAllSync(); JSONArray array new JSONArray(); for (Clothes c : all) { JSONObject obj new JSONObject(); try { obj.put(name, c.getName()); obj.put(category, c.getCategoryName()); obj.put(color, c.getColor()); obj.put(season, c.getSeason()); obj.put(note, c.getNote()); array.put(obj); } catch (JSONException e) { e.printStackTrace(); } } return array.toString(); }导出文件写到getExternalFilesDir(export)下再用FileProvider生成一个可分享的content://Uri调Intent.ACTION_SEND发出去。注意FileProvider需要在AndroidManifest.xml里注册并配置file_paths.xml指定可分享的目录。6.4 验证改造是否成功的检查清单改完之后怎么确认没改坏我一般会走一遍这个流程冷启动 App 看主列表是否正常加载新增一件衣物带图片看保存后列表是否刷新杀进程重开看数据是否还在搜索一个关键词看筛选结果是否正确点进详情页再返回看列表位置是否保持导出一次数据用文本编辑器打开看 JSON 格式是否完整。这套走完基本能覆盖增删改查和持久化的主路径。提示每次改完数据库相关代码先把 App 卸载重装一次避免旧数据库结构和新代码冲突。开发阶段的数据不值钱别在迁移上浪费时间。从那以后我每次拿到一份课程设计源码都先跑通主流程再动代码改一处测一处绝不一次性大改。希望帮到你。本文还有配套的精品资源点击获取