
简介这是一份面向高校学生与Android初学者的移动开发期末大作业完整资料主题为记账类App适合需要完成课程设计、实训项目或自学Java安卓开发的人群参考。资源包共1065个文件约19.33MB涵盖36个java源码文件、262个xml布局与配置、146张png及webp图片素材、130个json数据文件以及apk安装包、gradle构建脚本、jar依赖库等完整保留了Android Studio工程结构与可运行产物。项目实现了收支记录、分类账目管理、数据库存储、记录隐藏与删除、图表化收支分析及一键清空数据等功能并附有运行截图便于对照界面效果。目前已有3630人学习下载读者可借此获得一套可直接编译运行的课程设计范例理解Java语言下Activity、SQLite数据库与图表组件的实际配合方式同时参考评分较高的实现思路与目录组织用于快速搭建自己的记账应用或完成答辩准备。1. 安卓期末大作业记账app从源码到导出APK一套能交差的完整链路每年期末季安卓开发课的群里总会冒出同一个问题大作业做什么最稳我的答案是记账app。原因很直接——它覆盖了安卓课程里几乎全部核心考点Activity生命周期、RecyclerView列表、SQLite本地存储、Fragment切换、自定义Dialog、数据统计图表。老师一看就知道你确实动手写了而不是从网上扒了个商城项目改了个名字。但真正让大多数人翻车的不是功能本身而是最后一步源码在Android Studio里跑得好好的导出APK装到手机上要么闪退要么白屏要么数据存不进去。这篇内容就是围绕「安卓期末大作业-记账app」这条链路把源码结构、关键实现、导出APK的完整流程和运行截图该截什么一次讲透。适合正在赶大作业的在校生也适合想拿一个轻量项目练手安卓开发的新手。下面从项目结构开始拆。2. 记账app的源码结构怎么搭从工程目录到数据库设计2.1 工程目录与技术选型为什么不用Room而用SQLite拿到一个安卓期末大作业的记账app第一件事是看它的工程结构是否清晰。我一般会按下面的方式组织目录这也是最常见、老师最容易看懂的搭法app/src/main/java/com/example/accountbook/ ├── MainActivity.java // 主界面承载Fragment ├── AddRecordActivity.java // 添加账目 ├── EditRecordActivity.java // 编辑账目 ├── adapter/ │ └── RecordAdapter.java // RecyclerView适配器 ├── db/ │ └── DBHelper.java // SQLiteOpenHelper ├── model/ │ └── Record.java // 账目实体类 ├── fragment/ │ ├── HomeFragment.java // 首页流水 │ └── StatsFragment.java // 统计页 └── util/ └── DateUtil.java // 日期格式化工具选型上有一个常见纠结用Room还是裸SQLite对于期末大作业我的建议是裸SQLite。原因有三点。第一课程通常教的就是SQLiteOpenHelper老师看代码时能直接对应上知识点。第二Room需要引入annotationProcessorGradle配置一旦出问题新手排查成本很高。第三裸SQLite的增删改查逻辑全部可见答辩时你能逐行讲清楚不会被问住。Room适合真实项目但期末作业的核心是展示你理解了安卓本地存储的基本原理。注意如果你的课程明确要求使用Jetpack组件那就老老实实上Room不要跟分数过不去。2.2 数据库表设计与DBHelper的关键参数记账app的数据模型不复杂一张表就够了。字段设计如下字段名类型说明idINTEGER PRIMARY KEY AUTOINCREMENT主键自增amountREAL金额支持小数typeINTEGER0支出 1收入categoryTEXT分类名称如餐饮、交通noteTEXT备注dateTEXT日期格式yyyy-MM-dd HH:mm建表语句和DBHelper的核心代码如下public class DBHelper extends SQLiteOpenHelper { private static final String DB_NAME account.db; private static final int DB_VERSION 1; public static final String TABLE_NAME record; public DBHelper(Context context) { super(context, DB_NAME, null, DB_VERSION); } Override public void onCreate(SQLiteDatabase db) { String sql CREATE TABLE TABLE_NAME ( id INTEGER PRIMARY KEY AUTOINCREMENT, amount REAL NOT NULL, type INTEGER NOT NULL, category TEXT, note TEXT, date TEXT); db.execSQL(sql); } Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL(DROP TABLE IF EXISTS TABLE_NAME); onCreate(db); } }这里有几个参数值得说清楚。DB_VERSION设为1意味着你第一次发布。如果后续改了表结构必须把版本号加1否则onUpgrade不会触发用户升级后会出现「列不存在」的崩溃。amount用REAL而不是INTEGER因为记账金额经常有小数用整数会丢精度。date存字符串而不是时间戳是为了查询和显示方便期末作业不需要考虑时区问题。提示onUpgrade里直接DROP TABLE会导致用户数据丢失。真实项目要做迁移但期末作业里这样写没问题答辩时主动说明「生产环境需要做数据迁移」反而是加分项。2.3 增删改查的封装与RecyclerView数据绑定DBHelper建好之后增删改查的封装决定了你后面写Activity时顺不顺手。我一般会在DBHelper里直接加方法而不是再抽一层Dao因为期末作业的体量不值得过度设计。public long insert(Record r) { SQLiteDatabase db getWritableDatabase(); ContentValues cv new ContentValues(); cv.put(amount, r.getAmount()); cv.put(type, r.getType()); cv.put(category, r.getCategory()); cv.put(note, r.getNote()); cv.put(date, r.getDate()); long id db.insert(TABLE_NAME, null, cv); db.close(); return id; } public ListRecord queryAll() { ListRecord list new ArrayList(); SQLiteDatabase db getReadableDatabase(); Cursor cursor db.query(TABLE_NAME, null, null, null, null, null, date DESC); while (cursor.moveToNext()) { Record r new Record(); r.setId(cursor.getInt(cursor.getColumnIndexOrThrow(id))); r.setAmount(cursor.getDouble(cursor.getColumnIndexOrThrow(amount))); r.setType(cursor.getInt(cursor.getColumnIndexOrThrow(type))); r.setCategory(cursor.getString(cursor.getColumnIndexOrThrow(category))); r.setNote(cursor.getString(cursor.getColumnIndexOrThrow(note))); r.setDate(cursor.getString(cursor.getColumnIndexOrThrow(date))); list.add(r); } cursor.close(); db.close(); return list; }queryAll里排序用date DESC保证最新的账目在最上面这是记账app的基本体验。注意cursor用完必须closedb也要close否则多次操作后会出现「database connection pool full」的异常。RecyclerView的Adapter里onBindViewHolder根据type字段设置金额颜色支出显示红色带负号收入显示绿色带正号。这个细节虽小但运行截图里一眼就能看出来老师会觉得你用心了。3. 导出APK的完整流程从签名配置到真机安装3.1 生成签名密钥并配置build.gradle源码在Android Studio里跑通只是第一步导出APK才是期末作业交差的关键。很多人卡在「Build APK」之后装到手机上提示「应用未安装」或「解析包错误」根本原因通常是签名问题。Debug版APK虽然能装但有些手机的安全策略会拦截而且老师如果要求「正式签名」你就交不了差。第一步生成签名密钥。在Android Studio菜单栏选择Build → Generate Signed Bundle / APK → APK → Create new。填写密钥库路径、密码、别名等信息。也可以用命令行生成keytool -genkeypair -v -keystore account.jks -keyalg RSA -keysize 2048 -validity 10000 -alias account参数说明-keystore指定密钥库文件名-keyalg RSA用RSA算法-keysize 2048是密钥长度-validity 10000表示有效期10000天-alias account是密钥别名。执行后会提示你输入密码和姓名等信息随便填就行期末作业不需要真实身份。第二步在app模块的build.gradle里配置signingConfigsandroid { signingConfigs { release { storeFile file(../account.jks) storePassword 123456 keyAlias account keyPassword 123456 } } buildTypes { release { minifyEnabled false signingConfig signingConfigs.release } } }minifyEnabled设为false因为期末作业不需要代码混淆开了反而可能因为反射导致崩溃。storeFile的路径用相对路径方便把整个工程拷给老师后还能编译。3.2 构建Release APK并处理常见报错配置好签名后执行构建命令./gradlew assembleReleaseWindows下用gradlew.bat assembleRelease。构建成功后APK会生成在app/build/outputs/apk/release/目录下文件名通常是app-release.apk。这一步常见的报错有三个。第一个是Keystore file not found说明storeFile路径写错了检查相对路径的基准是app模块还是根目录。第二个是password verification failed说明storePassword或keyPassword填错了注意这两个密码可以不同。第三个是Lint found fatal errors这是Lint检查拦截了构建可以在build.gradle的android块里加lintOptions { checkReleaseBuilds false }临时绕过但更好的做法是点开报错看具体问题通常是无用资源或缺少内容描述。注意如果你的项目用了第三方库Release构建时可能因为混淆规则缺失而崩溃。minifyEnabled false已经规避了这个问题但如果你开了混淆记得在proguard-rules.pro里保留模型类和数据库相关类。3.3 真机安装与运行截图该截哪几张APK构建出来后传到手机上安装。如果提示「应用未安装」先检查手机是否允许安装未知来源应用再检查APK的minSdkVersion是否高于手机系统版本。如果安装成功但打开闪退用adb logcat看崩溃日志最常见的原因是数据库操作在主线程之外没有处理好Context或者某个findViewById返回了null。运行截图是期末作业的硬性要求通常需要3到5张。我建议截这几张第一张首页流水列表展示多条账目记录金额有正有负颜色区分明显。第二张添加账目界面展示分类选择、金额输入、日期选择器。第三张统计页面展示饼图或柱状图体现数据汇总能力。第四张删除或编辑操作的确认弹窗体现交互完整性。第五张关于页面或设置页面展示版本号和作者信息。截图时注意两点一是用真机截图不要用模拟器模拟器的状态栏一眼就能看出来二是数据要真实提前录入十几条不同分类的账目不要只有两三条空荡荡的列表。老师看截图的第一印象就是「这个学生有没有认真跑过」。4. 避坑与排查记账app从编译到运行最容易翻车的5个地方4.1 数据库版本号没改导致升级后崩溃现象第一次安装运行正常改了表结构后重新安装app一打开就闪退日志显示no such column: xxx。原因DB_VERSION还是1onUpgrade没有被触发旧数据库文件里没有新列。解决每次修改表结构把DB_VERSION加1并在onUpgrade里写对应的ALTER TABLE或DROP后重建。期末作业如果不想处理迁移可以在onUpgrade里直接删表重建但要提醒自己这只是作业做法。4.2 RecyclerView不显示数据但日志无报错现象数据库里明明有数据RecyclerView就是空白Logcat没有任何异常。原因Adapter的getItemCount返回了0或者数据列表在setAdapter之后才加载没有调用notifyDataSetChanged。解决先确认getItemCount返回的是list.size()再确认数据加载完成后调用了adapter.notifyDataSetChanged()。如果用的是Fragment注意在onViewCreated里初始化RecyclerView不要在onCreateView里做数据绑定。4.3 导出APK后安装提示「解析包错误」现象Android Studio里直接Run没问题导出APK传到手机安装时提示「解析包错误」。原因最常见的是minSdkVersion设置过高手机系统版本低于最低要求。其次是APK文件传输过程中损坏或者签名不一致导致覆盖安装失败。解决检查build.gradle里的minSdkVersion期末作业建议设为21或23覆盖绝大多数手机。如果手机里已经装了Debug版先卸载再装Release版因为两者签名不同。4.4 日期显示为乱码或格式不对现象列表里的日期显示成2024-01-01 00:00:00.000或者一串时间戳数字。原因插入数据库时用了new Date().toString()或者用了SimpleDateFormat但pattern写错了。解决统一用SimpleDateFormat(yyyy-MM-dd HH:mm, Locale.getDefault())格式化后再存入数据库。查询出来直接显示不需要再解析。注意Locale参数不要省略否则在某些系统语言下会出现月份名称异常。4.5 统计页面数据对不上流水列表现象首页流水显示支出100元统计页面饼图显示支出80元。原因统计查询的SQL条件写错了比如漏了type字段的过滤或者日期范围边界处理不对。解决统计查询用SELECT category, SUM(amount) FROM record WHERE type0 GROUP BY category确保type条件和列表页一致。日期范围用date ? AND date ?注意字符串比较的边界包含问题。调试时先把SQL打印出来在SQLite工具里跑一遍看结果对不对。5. 让记账app多拿5分统计图表的轻量实现与答辩演示技巧期末大作业的评分不只是「能跑就行」同样功能的两个项目一个有统计图表、一个只有列表分数能差出10分。但引入MPAndroidChart这类第三方库会增加构建风险我一般推荐用自定义View画一个简单的饼图或柱状图代码量不大答辩时还能讲出原理。核心思路是继承View重写onDraw方法用Canvas画扇形。下面是一个饼图的关键代码片段public class PieChartView extends View { private ListFloat values; private ListInteger colors; private Paint paint; private RectF rectF; public PieChartView(Context context) { super(context); paint new Paint(Paint.ANTI_ALIAS_FLAG); paint.setStyle(Paint.Style.FILL); rectF new RectF(); } public void setData(ListFloat values, ListInteger colors) { this.values values; this.colors colors; invalidate(); // 触发重绘 } Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (values null || values.isEmpty()) return; float total 0; for (float v : values) total v; if (total 0) return; float startAngle -90; // 从正上方开始 int centerX getWidth() / 2; int centerY getHeight() / 2; int radius Math.min(centerX, centerY) - 20; rectF.set(centerX - radius, centerY - radius, centerX radius, centerY radius); for (int i 0; i values.size(); i) { float sweep values.get(i) / total * 360; paint.setColor(colors.get(i)); canvas.drawArc(rectF, startAngle, sweep, true, paint); startAngle sweep; } } }参数说明startAngle -90让第一个扇形从12点钟方向开始符合阅读习惯。sweep是每个分类占比乘以360度。rectF定义了饼图的外接矩形半径留了20像素边距避免贴边。invalidate()在数据更新后调用触发onDraw重绘。在StatsFragment里从数据库查出各分类的支出总额组装成values列表再配一组颜色值传进去。颜色可以用Color.parseColor(#FF6B6B)这样的十六进制值选五六个区分度高的就行。答辩演示时我习惯按这个顺序讲先打开首页展示流水列表说明RecyclerView和SQLite的配合再点添加按钮演示分类选择和日期选择器然后切到统计页指着饼图说「这是自定义View根据数据库的GROUP BY查询结果实时绘制」最后打开设置页展示版本号和导出功能。整个过程控制在3分钟内重点突出「数据从哪来、怎么存、怎么展示」这条链路。还有一个加分细节在关于页面加一个「导出数据」按钮把数据库里的记录导成CSV文件存到外部存储。代码只需要用到FileWriter和Cursor遍历十几行就能搞定但答辩时能体现你对文件存储的理解。老师如果问「数据能不能备份」你就有东西可讲。我自己做第一个记账app时光顾着堆功能忘了在onPause里保存输入框的内容结果用户切出去接个电话回来填了一半的账目全没了。后来养成的习惯是任何用户输入界面onPause里必须做临时保存onResume里恢复。这个习惯比多写十个功能都值钱。希望帮到你。本文还有配套的精品资源点击获取