简介:这是一份面向计算机相关专业学生的移动开发期末大作业资源,主题为备忘录App,适合正在准备课程大作业、毕业设计或需要项目实战练习的学习者参考。项目经导师指导并通过评审,得分98分,源码均经本地编译与严格调试,可正常运行,难度适中。压缩包共67个文件,约4.07MB,以22个xml布局文件、10个java源码、10个webp与5个jpg图片资源为主,另含gradle构建脚本、properties配置、apk安装包及导入说明文档,覆盖界面布局、业务逻辑、资源素材与构建配置等模块。已有150人学习下载。资源同时提供Android导入步骤及常见问题解决办法文档与项目截图,便于读者快速理解工程结构、复现运行效果,并对照源码梳理备忘录应用的实现思路,适合作为课程设计参考与移动开发入门练手项目。
1. 移动开发期末大作业备忘录 App:一份能直接跑起来的高分源码包
期末周临近,移动开发这门课的大作业往往是压垮绩点的最后一根稻草。老师要求做一个能增删改查、有本地存储、界面像样的 App,但课上讲的 Fragment 生命周期还没搞明白,Android Studio 的 Gradle 同步又红了一片。这份「移动开发期末大作业-备忘录 App」源码包,就是冲着这个场景来的:它是一套完整的 Android 工程,包含可导入的文档和项目运行截图,功能覆盖备忘录的增删改查、分类标签、时间戳记录,适合正在赶大作业的本科生、中职移动应用开发模块 A 的学生,以及需要一份可参考工程结构的自学者。你拿到手就能导入 Android Studio 跑起来,改改包名和界面就能交,省下的时间够你把答辩 PPT 过三遍。
2. 备忘录 App 的工程结构与技术选型:为什么这么搭
2.1 从源码目录看模块划分
拿到一份源码包,第一件事不是急着点 Run,而是先看目录结构。这份工程的根目录下是标准的 Gradle 多模块布局,app模块承载全部业务代码,gradle目录放 wrapper 配置,根目录的build.gradle和settings.gradle负责全局依赖管理。进入app/src/main/java后,包名通常按com.example.memo这类命名,下面再分activity、adapter、db、bean四个子包。activity放页面逻辑,adapter管 RecyclerView 的列表渲染,db封装 SQLite 操作,bean是备忘录实体类。这种分法不花哨,但胜在清晰,老师翻代码的时候一眼能看出你懂分层。
res目录同样值得细看。layout下每个 Activity 对应一个 XML,drawable放图标和背景,values里的colors.xml、strings.xml、styles.xml把硬编码抽干净。很多同学写大作业时把颜色和文字直接写死在布局里,后期改一个主题色要翻十几个文件,这份工程没这个毛病。AndroidManifest.xml里注册了所有 Activity,主入口通常是MainActivity,负责加载列表。
2.2 技术栈选型:SQLite + RecyclerView 的组合逻辑
为什么用 SQLite 而不是 Room?这是很多人会问的。Room 确实是 Google 官方推荐的 ORM 框架,但对期末大作业来说,SQLite 配合SQLiteOpenHelper的手写方式反而更稳妥。原因有三:第一,SQLite 是 Android 内置的,不需要额外引入依赖,Gradle 同步失败的概率低;第二,手写 SQL 语句在答辩时更容易讲清楚「我做了什么」,老师也更认可你对底层存储的理解;第三,这份工程的db包已经把建表、增删改查封装成MemoDao类,调用方只需要传参,复杂度已经被吃掉了。
RecyclerView 替代 ListView 也是同理。ListView 虽然简单,但它在 item 复用上的机制不如 RecyclerView 灵活,而且 RecyclerView 是现在面试和实际开发中的主流。工程里的MemoAdapter继承RecyclerView.Adapter,内部用ViewHolder缓存控件引用,onBindViewHolder里绑定数据。这个模式你背下来,以后写任何列表都能套。
2.3 导入工程前必须核对的三个参数
在 Android Studio 里点「Import Project」之前,先确认三件事,否则大概率翻车。
第一,Gradle 版本。打开gradle/wrapper/gradle-wrapper.properties,看distributionUrl那一行。如果写的是gradle-7.x,你的 Android Studio 版本不能太老,建议用 Arctic Fox 或更新的版本。如果本地已经下载过对应版本的 Gradle,可以把distributionUrl改成file:///本地路径,避免联网下载卡住。
第二,compileSdk 和 targetSdk。在app/build.gradle里找到这两个值。常见的是 31 或 32,对应 Android 12。如果你的 SDK Manager 里没装这个版本的 SDK,要么去装,要么把值改成你已有的版本,但注意改完之后appcompat库的版本也要跟着调,否则会报资源找不到。
第三,JDK 版本。Android Studio 内置了 JDK,但如果你在命令行跑./gradlew assembleDebug,系统环境变量里的 JDK 必须是 11 或 17。用java -version查一下,如果是 1.8,Gradle 7 以上会直接拒绝执行。
# 查看当前 JDK 版本 java -version # 查看 Gradle wrapper 配置的版本 cat gradle/wrapper/gradle-wrapper.properties | grep distributionUrl # 查看 app 模块的编译配置 cat app/build.gradle | grep -E "compileSdk|targetSdk|minSdk"这三条命令跑完,你心里就有数了。版本对不上就改,别硬跑,硬跑的结局就是 Gradle 报一堆看不懂的错,然后你开始怀疑人生。
2.4 数据库表结构与实体类映射
备忘录的核心数据就一张表。工程里的MemoDao在onCreate里执行建表语句,字段一般包括id(主键自增)、title(标题)、content(正文)、category(分类)、create_time(创建时间)、update_time(更新时间)。bean包下的Memo类跟这张表一一对应,每个字段有 getter 和 setter。
// Memo.java 实体类示例 public class Memo { private int id; private String title; private String content; private String category; private String createTime; private String updateTime; // 构造方法、getter、setter 省略 }// MemoDao.java 建表与插入示例 public class MemoDao { private SQLiteDatabase db; public MemoDao(Context context) { MemoDbHelper helper = new MemoDbHelper(context); db = helper.getWritableDatabase(); } // 插入一条备忘录 public long insert(Memo memo) { ContentValues values = new ContentValues(); values.put("title", memo.getTitle()); values.put("content", memo.getContent()); values.put("category", memo.getCategory()); values.put("create_time", memo.getCreateTime()); values.put("update_time", memo.getUpdateTime()); return db.insert("memo", null, values); } // 查询所有备忘录,按更新时间倒序 public List<Memo> queryAll() { List<Memo> list = new ArrayList<>(); Cursor cursor = db.query("memo", null, null, null, null, null, "update_time DESC"); while (cursor.moveToNext()) { Memo memo = new Memo(); memo.setId(cursor.getInt(cursor.getColumnIndex("id"))); memo.setTitle(cursor.getString(cursor.getColumnIndex("title"))); memo.setContent(cursor.getString(cursor.getColumnIndex("content"))); memo.setCategory(cursor.getString(cursor.getColumnIndex("category"))); memo.setCreateTime(cursor.getString(cursor.getColumnIndex("create_time"))); memo.setUpdateTime(cursor.getString(cursor.getColumnIndex("update_time"))); list.add(memo); } cursor.close(); return list; } }insert方法用ContentValues装数据,这是 Android 推荐的参数绑定方式,能防止 SQL 注入。queryAll里用db.query而不是裸写SELECT *,好处是参数化查询更安全,排序字段update_time DESC保证最近改过的备忘录排在最上面。cursor.close()这行千万别漏,漏了就是内存泄漏,跑久了 App 会卡。
3. 从导入到运行:备忘录 App 的完整复现步骤
3.1 导入工程与 Gradle 同步
打开 Android Studio,选「Open」而不是「New Project」,定位到源码包解压后的根目录,点 OK。首次打开时右下角会提示 Gradle Sync,等它跑完。如果卡在「Download gradle-7.x-all.zip」超过五分钟,说明网络拉不动,这时候有两个选择:一是手动下载对应版本的 Gradle 压缩包,放到~/.gradle/wrapper/dists/对应目录下;二是改gradle-wrapper.properties里的distributionUrl指向本地文件。
同步成功后,左侧 Project 面板应该能看到app模块带一个绿色小圆点。如果app模块图标是灰色的,说明 Gradle 没识别到,右键app选「Reload Gradle Project」再试。
3.2 修改包名与 App 名称
直接交一份包名是com.example.memo的作业,老师一眼就知道你是抄的。改包名分两步:先在app/build.gradle里改applicationId,然后在AndroidManifest.xml里改package属性。改完之后,Android Studio 会提示你「Sync Now」,点一下让它重新索引。
App 名称在res/values/strings.xml里,找到app_name这一项,改成你自己的名字,比如「我的备忘录」。改完运行,桌面图标下面的文字就变了。
<!-- strings.xml 修改示例 --> <resources> <string name="app_name">我的备忘录</string> <string name="add_memo">新建备忘录</string> <string name="delete_memo">删除</string> <string name="edit_memo">编辑</string> </resources>strings.xml的好处是所有文案集中管理,以后要做多语言适配,只需要再加一个values-en/strings.xml就行。很多同学把文字直接写在布局 XML 里,后期改起来痛苦不堪,这个习惯趁早改掉。
3.3 运行到模拟器或真机
点工具栏上的绿色三角形,选择目标设备。如果没有设备,点「Device Manager」创建一个。建议选 Pixel 5 或类似机型,API 级别选 30 或 31,跟工程的compileSdk保持一致。
真机调试的话,手机开开发者模式,打开 USB 调试,用数据线连电脑。Android Studio 顶部设备列表里会出现你的手机型号。点 Run,等 APK 安装完成,App 会自动启动。
首次启动时,如果 App 闪退,先看 Logcat。Logcat 是 Android 开发的黑匣子,所有崩溃信息都在里面。常见错误是Caused by: android.database.sqlite.SQLiteException: no such table: memo,这说明数据库建表语句没执行。检查MemoDbHelper的onCreate方法,确认DATABASE_VERSION是不是 1,如果之前装过旧版本,把 App 卸载重装,或者把版本号加 1 触发onUpgrade。
3.4 核心功能验证清单
App 跑起来之后,按下面这个清单过一遍,确保每个功能都正常。
| 功能 | 操作 | 预期结果 |
|---|---|---|
| 新建备忘录 | 点加号,输入标题和内容,保存 | 列表顶部出现新条目 |
| 编辑备忘录 | 点某条备忘录,修改内容,保存 | 该条目内容更新,时间戳变化 |
| 删除备忘录 | 长按或左滑某条,确认删除 | 该条目从列表消失 |
| 分类筛选 | 切换分类标签 | 列表只显示对应分类的条目 |
| 数据持久化 | 杀掉 App 重新打开 | 之前的数据还在 |
数据持久化这一项最能体现 SQLite 的价值。如果你杀掉 App 再打开,数据没了,说明你写的是内存存储,赶紧回去检查MemoDao是不是真的写进了数据库。
4. 避坑与排查:导入这份源码时最容易翻车的五个地方
4.1 Gradle 同步报错「Could not find method implementation()」
现象:打开工程后 Gradle Sync 失败,报错说找不到implementation方法。
原因:build.gradle里的implementation是 Gradle 3.4 之后才引入的配置方式,如果你的 Gradle 版本太老,或者build.gradle文件被改成了旧语法,就会报这个错。
解决:检查gradle-wrapper.properties里的 Gradle 版本,确保是 4.0 以上。如果版本没问题,检查app/build.gradle顶部是不是有apply plugin: 'com.android.application',没有这行的话,android {}块里的配置全都不生效。
4.2 模拟器启动后 App 闪退,Logcat 显示「ClassNotFoundException」
现象:APK 安装成功,点图标启动,瞬间闪退,Logcat 里一堆红色。
原因:AndroidManifest.xml里注册的 Activity 类名跟实际 Java 文件对不上,或者包名改了之后 Manifest 里的package属性没同步改。
解决:打开AndroidManifest.xml,逐个核对<activity android:name=".MainActivity">这类声明,确保类名和路径正确。改过包名的话,用 Android Studio 的「Refactor > Rename」功能全局替换,别手动改,手动改容易漏。
4.3 数据库插入成功但查询为空
现象:新建备忘录时提示保存成功,但列表里什么都没有。
原因:insert方法返回了行号,说明数据确实写进去了,但queryAll查不出来。大概率是查询条件写错了,或者Cursor没有移动到第一行之前。
解决:检查queryAll里cursor.moveToNext()是不是在while循环里。如果写成了if,只会读一条。另外确认db.query的表名跟建表语句里的表名一致,大小写敏感。
4.4 RecyclerView 不显示数据
现象:数据查出来了,List<Memo>的 size 大于 0,但界面上空白一片。
原因:MemoAdapter的getItemCount()返回了 0,或者onBindViewHolder里没有正确设置 TextView 的文本。
解决:在getItemCount()里打印list.size(),确认返回值正确。然后在onBindViewHolder里加一行Log.d("Adapter", "binding position: " + position),看有没有走到绑定逻辑。如果getItemCount返回 0,说明list是空的,回去检查queryAll的返回值有没有传给 Adapter。
4.5 时间戳显示为乱码或默认值
现象:备忘录列表里的时间显示成null或者1970-01-01。
原因:create_time字段在插入时没有赋值,或者用了System.currentTimeMillis()但格式化的时候用了错误的SimpleDateFormat模式。
解决:在insert之前,给Memo对象的createTime和updateTime赋上当前时间字符串。格式化用new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()),注意MM是月份,mm是分钟,写反了就会得到奇怪的结果。
// 正确的时间格式化 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()); String now = sdf.format(new Date()); memo.setCreateTime(now); memo.setUpdateTime(now);Locale.getDefault()这行别省,省了在某些机型上会报警告,虽然不影响运行,但代码规范分会被扣。
5. 进阶技巧:让这份大作业多拿五分的三个改动
5.1 给备忘录加一个搜索框
老师看大作业的时候,最烦的就是「只能增删改查」的流水账。加一个搜索功能,成本很低,但印象分拉满。在MainActivity的布局顶部加一个SearchView,然后在MemoDao里加一个searchByKeyword方法。
// MemoDao.java 搜索方法 public List<Memo> searchByKeyword(String keyword) { List<Memo> list = new ArrayList<>(); String sql = "SELECT * FROM memo WHERE title LIKE ? OR content LIKE ? ORDER BY update_time DESC"; Cursor cursor = db.rawQuery(sql, new String[]{"%" + keyword + "%", "%" + keyword + "%"}); while (cursor.moveToNext()) { Memo memo = new Memo(); memo.setId(cursor.getInt(cursor.getColumnIndex("id"))); memo.setTitle(cursor.getString(cursor.getColumnIndex("title"))); memo.setContent(cursor.getString(cursor.getColumnIndex("content"))); memo.setCategory(cursor.getString(cursor.getColumnIndex("category"))); memo.setCreateTime(cursor.getString(cursor.getColumnIndex("create_time"))); memo.setUpdateTime(cursor.getString(cursor.getColumnIndex("update_time"))); list.add(memo); } cursor.close(); return list; }rawQuery里的?是占位符,new String[]里的两个参数分别对应title和content的模糊匹配。%是通配符,%keyword%表示包含 keyword 的任意字符串。这个方法写完之后,在MainActivity里监听SearchView的onQueryTextChange,实时调用searchByKeyword并刷新 Adapter。
5.2 用 Material Design 组件提升界面质感
原工程的界面如果用的是原生Button和EditText,看起来会比较朴素。把build.gradle里的 Material 库依赖加上,然后把按钮换成MaterialButton,输入框换成TextInputLayout包裹的TextInputEditText,整个 App 的观感会立刻上一个档次。
// app/build.gradle 添加 Material 依赖 dependencies { implementation 'com.google.android.material:material:1.6.1' // 其他依赖保持不变 }加完依赖后 Sync 一下,然后在布局 XML 里用com.google.android.material.button.MaterialButton替换Button。TextInputLayout的hint属性可以做出浮动标签的效果,比传统的EditText提示文字好看得多。这个改动不需要动 Java 代码,纯 XML 替换,十分钟就能搞定。
5.3 导出数据为 CSV 文件
如果想让大作业显得更有「工程感」,加一个导出功能。在MemoDao里写一个exportToCsv方法,把查询结果写成 CSV 格式,存到getExternalFilesDir目录下。
// MemoDao.java 导出 CSV public void exportToCsv(Context context) throws IOException { List<Memo> list = queryAll(); File dir = context.getExternalFilesDir(null); File csvFile = new File(dir, "memo_export.csv"); FileWriter writer = new FileWriter(csvFile); writer.write("id,title,content,category,create_time,update_time\n"); for (Memo memo : list) { writer.write(memo.getId() + "," + memo.getTitle() + "," + memo.getContent() + "," + memo.getCategory() + "," + memo.getCreateTime() + "," + memo.getUpdateTime() + "\n"); } writer.close(); }getExternalFilesDir(null)拿到的是 App 专属的外部存储目录,不需要申请存储权限,Android 10 以上也能直接用。导出的 CSV 文件可以用 Excel 打开,答辩的时候展示一下,老师会觉得你考虑到了数据迁移的场景。注意FileWriter用完要close(),不然文件内容可能不完整。
这三个改动加起来不到两百行代码,但能让你的大作业从「及格」变成「优秀」。我当年第一次交移动开发大作业的时候,就是只做了增删改查,结果只拿了个中。后来帮学弟改作业,加了搜索和 Material Design,直接拿了优。从那以后我每次做大作业,都会强制自己至少加一个「超出要求」的功能,这个习惯一直保持到现在。希望帮到你。
本文还有配套的精品资源,点击获取