简介:本资源是一份面向高校移动开发初学者的Android期末大作业实战项目,聚焦记账类应用开发全流程,适用于K12信息技术拓展及高职本科Android课程实践。项目基于Android Studio开发,采用Java语言实现,完整覆盖UI设计、SQLite本地数据存储、收支分类管理、账目隐藏/删除、图表可视化分析(含MPAndroidChart集成)及数据清空等核心功能,代码规范、逻辑清晰,曾获教师评分95分,具备较强的教学示范性与工程参考价值。压缩包共1065个文件,包含36个Java源码、262个XML布局与配置文件、146个PNG图标资源、130个JSON配置及构建产物(如APK、DEX、CLASS等),整体体积19.33MB,结构完整,开箱即用。已有3624人学习下载,提供可直接安装运行的APK、全量源码及多场景运行截图,便于快速验证功能、理解MVC分层结构与数据库操作细节。
1. 为什么期末大作业选了记账App:选题逻辑与交付物拆解
1.1 选题时最该考虑的不是"炫技",而是"能不能按时交付"
安卓期末大作业,最忌讳的就是选题时太贪心。我在选这个题目之前,先把确定性的条件列了一遍:课程只讲到了Activity、RecyclerView、SQLite、基础网络请求;周末只有两到三天可以集中开发;最后要提交的东西不只是源码,还包括导出的APK和运行截图。这三条一框,选项就窄了——既要保证源码里包含老师要求的技术点,又不能在短期内写不完。
记账App正好卡在这个位置上。它的需求足够明确:记账、查账、统计,任何一个做过安卓App开发的人都能在脑海里立刻勾勒出界面长什么样。它不像天气App那样依赖第三方API的稳定性,也不像即时通讯那样需要处理复杂的消息推送。做一个本地单机的记账软件,数据自己存自己读,不依赖服务器,哪怕答辩现场断了网也能完整演示——这一点在期末阶段非常加分。
当时我还对比过其他几个常见选题。待办清单看起来更简单,但功能单一,答辩时老师容易追问"你的项目难点在哪里",很难答出有分量的东西。计算器项目也一样,交互逻辑太少,源码页数撑不起来。音乐播放器涉及音频焦点、后台播放、通知栏控制,工作量又不小。综合下来,记账App是在"工作量可衡量"和"难点可讲清楚"之间最平衡的选择。
1.2 交付物提前列好:源码、APK、运行截图一个都不能少
期末大作业的交付标准往往被一句话带过:"请提交你的项目源码和App运行效果。"这句话听起来简单,但到了截止那一刻,你才会发现每个东西都有坑。源码提交不是把Android Studio里的项目文件夹压缩一下就行,老师打开之后要能直接Build。如果项目里带了本机的debug签名配置、多余的gradle缓存路径,或者依赖版本写死到和老师本机冲突,轻则扣分,重则直接被判定为"无法运行"。
导出的APK则需要用正式签名打包,否则到了老师手里安装时会提示"应用未安装"或者"签名不一致"。我这次统一用的release构建,签名文件是单独生成的release.jks,并写进了build.gradle。运行截图也不是随手截图就完事,至少要覆盖三个场景:列表页有数据、新增一条支出记录、统计页图表变化。这三张截图对应了老师最容易检查的三个功能点——数据展示能力、数据写入能力、数据计算能力。
1.3 核心关键词拆解:这个项目到底"含"了什么
再回头拆标题里的"含源码+导出app+运行截图",这三样东西其实是三个维度的证据。源码证明你会写代码、代码结构能通过审查;导出的APK证明你能独立完成构建、签名、打包这条完整链路;运行截图证明你的App不是只在你的电脑上能跑,而是可以在真实设备环境里正常启动、操作、展示。很多同学只交了源码,Android Studio里点运行一切正常,但老师根本没有你的开发环境,代码在他手上就是一堆静态文本。把APK和截图补上是成本很低、收益却很高的动作。
2. 数据库设计:一张表和一个帮助类背后藏着的期末考点
2.1 表结构设计:字段不是越多越好,但要覆盖全部功能点
记账App的数据库设计,我用了一张主表搞定所有业务,表名就叫account_record。设计时反复确认每一个字段都能对应到界面上某个输入控件,不会出现"存了但用不到"的冗余字段。
字段这么定的:
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | INTEGER | 主键,自增 |
| type | INTEGER | 1代表支出,0代表收入 |
| category | TEXT | 分类名称,如"餐饮""交通""工资" |
| amount | REAL | 金额,保留两位小数 |
| note | TEXT | 备注,可为空 |
| date | TEXT | 日期,格式yyyy-MM-dd |
为什么type用0/1整数而不是存字符串"收入/支出"?主要原因是统计SQL写起来方便。按类型分组时直接GROUP BY type,配合SUM聚合函数,一行SQL就能算出总收入、总支出。如果存的是中文,聚合结果排序就会变得别扭,也有可能出现编码不一致导致的分组异常。这里用整数还省了存储,虽然省的那点空间在单机App里微不足道,但写SQL的判断条件时更清爽。
category之所以单独列出来而不是写死进代码,是因为后面做统计图表时,饼图需要按分类聚合金额。比如"餐饮""交通"各自花了多少,占总支出的百分比,这些数据全靠category字段做GROUP BY。如果把分类写死在代码里,界面选择是个下拉框,数据库存的是下拉框index,那分析报表时还得做一次index到文字的映射,平白多了层麻烦。我在这个项目里直接把分类名称文本存进库,牺牲了一点点数据库规范化,换来了查询代码的可读性和统计逻辑的简单。
2.2 SQLiteOpenHelper的写法与版本管理的坑
数据库帮助类继承SQLiteOpenHelper,DATABASE_VERSION设成1。这里有一个细节值得注意:onCreate里只负责建表,onUpgrade里写的才是后期改表的逻辑。期末项目通常不需要升级数据库,但老师问到"如果以后要加一个预算表,你怎么处理"时,你得能答上来——版本号+1,onUpgrade里执行ALTER TABLE。
我建表时把amount字段设计为REAL而不是INTEGER,当时犹豫了一下。账本里金额会出现带小数的情况,比如买杯奶茶13.5元。如果定义成INTEGER,插入13.5会被SQLite自动转成13,统计出来的账目和实际录入对不上,排查起来很隐蔽。用REAL类型虽然存储上会有浮点数精度问题,但账目金额最多也就两位小数,实际使用中误差完全可以接受。如果真想做得严谨,可以把金额以"分"为单位存成INTEGER,显示时再除以100,但这就增加了录入时的换算逻辑,期末项目没必要上这个复杂度。
插入数据的写法,推荐用ContentValues而不是拼SQL字符串:
ContentValues values = new ContentValues(); values.put("type", isExpense ? 1 : 0); values.put("category", category); values.put("amount", amount); values.put("note", note); values.put("date", date); db.insert("account_record", null, values);用ContentValues的好处有两个:一是避免了字符串拼接SQL带来的语法错误和注入风险——虽然单机App谈注入有点小题大做,但代码习惯要从现在就养好;二是insert方法的返回值是long,可以直接判断是否插入成功,便于界面上给用户反馈。
2.3 统计SQL:期末考试最容易被单独拿出来问的部分
记账App的统计模块必须能回答两类问题:第一类是"我这个月一共花了多少钱",第二类是"花钱都花在哪些地方了"。前者是一条极简单的SQL:
SELECT type, SUM(amount) FROM account_record WHERE date BETWEEN '2025-01-01' AND '2025-01-31' GROUP BY type;BETWEEN的边界包括起始两天,所以在代码里构造日期范围时要确认清楚。我通常是先取当月第一天,再取下个月第一天,然后用date >= firstDay AND date < nextMonthFirstDay这套写法,逻辑上更不容易踩到边界问题。
第二类问题用分类聚合:
SELECT category, SUM(amount) AS total FROM account_record WHERE type = 1 AND date >= ? AND date < ? GROUP BY category ORDER BY total DESC;这里有个容易踩的坑:如果你在SQL里用了中文别名,比如AS total没问题,但如果改成SUM(amount) 总额,某些手机ROM的标准SQLite库会报语法错误。我在模拟器上调试时踩过一次,最后统一用英文字段名做别名,解析也用cursor.getColumnIndex("total"),稳定多了。
3. 核心页面流转:从新增记一笔到列表实时刷新的完整链路
3.1 新增记账页面的交互与输入校验
新增记账我单独写了一个AddRecordActivity,而不是用Dialog。理由很简单:期末答辩时要给老师演示"点击之后进入新页面、填写、保存、返回列表看到新数据"这个过程,一个独立页面在演示时脉络更清楚,老师也容易理解Activity之间的跳转逻辑。如果用Dialog,功能上没问题,但讲布局和生命周期时会绕一些。
页面里面有几个控件:支出/收入切换用的是RadioGroup,分类用Spinner,金额是EditText,备注也是EditText,最后是个保存按钮。这里最核心的校验逻辑在金额EditText上。我给它设置了InputFilter,只允许输入一个小数点,小数点后最多两位。实现方法不复杂:
mAmountEditText.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL);但光靠这个setInputType还不够完整,因为它仍然允许输入"12.3.4"这种值。我在保存按钮的点击事件里又做了一次兜底校验:先把字符串转成BigDecimal,捕获NumberFormatException,再判断数值必须大于0。这样即使有人绕过输入框的限制,保存逻辑也不会把脏数据写进数据库。
保存成功之后,我没有直接startActivity回到列表页,而是用setResult(RESULT_OK, intent)配合finish(),让列表页在onActivityResult里刷新数据。这是期末答辩时一个非常加分的细节:让老师看到你懂得Activity之间的数据回传,而不是一味地用每次启动都重新查库这种简单方案。
3.2 列表适配器:RecyclerView的内部类与点击回调
列表页是MainActivity里嵌一个RecyclerView,数据源是ArrayList,适配器RecordAdapter继承RecyclerView.Adapter。物品项布局里显示分类图标、分类名称、备注、日期、金额。金额的显示做了个样式区分:支出显示成黑色,数字前带减号;收入显示成绿色,数字前带加号。这个视觉细节成本很低,但会显著提升App的完成度。
适配器内部需要处理好两件事:一是ViewHolder的复用,二是item的长按删除和点击编辑。长按删除做成AlertDialog确认弹窗,确认后再执行数据库删除和列表刷新。这里我推荐把数据库操作放在子线程里做,用Handler切回主线程刷新UI。期末项目数据量不大,直接主线程操作也感觉不到卡顿,但答辩老师可能会问ANR问题。你在适配器里展示过子线程+Handler的写法,回答这类型问题时底气会足很多。
列表刷新的方式也值得一提。一开始我图省事,删完数据后重新查一次全表,把整个列表notifyDataSetChanged。数据量小的时候没问题,但这不是好习惯。正确做法是适配器提供removeItem(int position)方法,先删数据库,成功后用notifyItemRemoved(position)做局部刷新。不过要注意,如果删的是中间项,position需要按当前列表位置精确计算,处理起来有些细节。期末项目中你用notifyDataSetChanged能跑通,但答辩时能在代码里看到notifyItemRemoved的调用,会让老师觉得你的水平不止于"能跑"。
3.3 日期处理和默认值:时间戳、格式化与真实体验
记账App涉及日期,最怕的是不同判断逻辑里的日期格式不统一。我在util包下建了一个DateUtils类,统一返回三种格式:yyyy-MM-dd用于数据库存储和列表显示,yyyy-MM用于按月统计,yyyyMMdd用于某些需要字符串比较的场景。当天日期用SimpleDateFormat格式化Date得到String,再用这个String作为新增记录时的默认日期。
有一个体验上的小坑:如果日期播提示让你手动输入,很多用户会懒得改,最终导致账本日期错位。我在新增页面放置了日期选择按钮,用DatePickerDialog让用户选择,默认值是今天的日期。用户不点,就是今天,点了就改成选中的值。DatePickerDialog是系统原生控件,不需要额外引库,期末项目的UI风格也能保持一致。
4. 统计图表:为什么用MPAndroidChart而不是自己画View
4.1 图表库选型对比:三个方案的真实体验
统计页的图表我先后考虑过三种实现:MPAndroidChart、HelloCharts、完全自定义View。先说结论,最终用的是MPAndroidChart,理由不是它最先进,而是它文档全、案例多、遇到问题能搜到答案。期末项目最怕的就是卡在某个第三方库的某个方法上报一堆自己看不懂的Error,而MPAndroidChart在我能搜到的社区讨论量上明显占优。
HelloCharts我简单尝试过,绘制效果也不错,但它的维护频率相对低,部分方法和新版Android的兼容性有问题。自定义View是最难控的风险,画一个简单饼图或许两百行代码能搞定,但涉及扇形点击高亮、文字避让、动画过渡时,工作量就完全不可控了。期末阶段的时间成本不允许我在一个图表上深耕两个星期。
还有一点要说明:MPAndroidChart本身是通过jcenter还是mavenCentral分发有过历史变动,新版本的项目里不要在build.gradle里写老的classpath了,直接这样引入:
dependencies { implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0' }如果你在同步项目时发现拉不到这个依赖,检查一下项目级build.gradle里有没有配置maven仓库地址。新版Android Studio默认加了mavenCentral,但部分旧模板只配了google和jcenter,jcenter已经停止服务,这很可能是你依赖拉不下来的元凶。
4.2 按分类聚合的饼图:数据准备与渲染
饼图的业务需求是展示"钱都花在哪儿了",我写了一个getExpenseStatsByCategory方法。它执行前面第2.3节提到的那条GROUP BY SQL,返回一个List ,每个对象里存category和totalAmount。
拿到数据之后填充饼图:
List<PieEntry> entries = new ArrayList<>(); for (CategoryStat stat : list) { entries.add(new PieEntry(stat.getTotalAmount(), stat.getCategory())); } PieDataSet dataSet = new PieDataSet(entries, ""); dataSet.setColors(low); // 一组预定义的颜色 PieData data = new PieData(dataSet); mPieChart.setData(data); mPieChart.invalidate();这里有两个参数需要调:一是dataSet.setValueFormatter,默认显示的是数值,我改成显示百分比,格式是两位小数加百分号;二是mPieChart.setUsePercentValues(true),这个要不要开得看你实际想在扇区上显示什么;三是当某类别金额为0时,PieEntry不会自动过滤,需要自己在list构建时跳过为零的数据,否则饼图会出现一个理论上的空白扇区。
4.3 按月份趋势的柱状图:SQL查询与X轴标签处理
柱状图我用来展示近6个月的支出趋势,x轴是月份,y轴是金额。每月支出用这条SQL计算:
SELECT strftime('%Y-%m', date) AS month, SUM(amount) FROM account_record WHERE type = 1 GROUP BY month ORDER BY month;strftime是SQLite内置函数,可以在SQL里直接完成日期格式化。如果你的日期字段不是标准格式,strftime会返回NULL,所以前面才反复强调date的存储格式必须统一为yyyy-MM-dd。如果你用yyyy年MM月这种格式存,这里的聚合就会失败——不要问我怎么知道的。
柱状图的x轴标签默认会全部显示出来,如果月份太多会挤成一团。我设置成标签数量自适应,让图表只显示首尾和中间几个点:xAxis.setLabelCount(3, true)。这里有个陷阱:第二个参数force设为true表示强制显示指定个数,但实际的起始位置不一定如你所愿,需要配合setAxisMinimum和setAxisMaximum一起调。最稳妥的做法还是给xAxis设置ValueFormatter,把索引值转成对应月份字符串,这样即使显示间距有点怪,至少文字不会错位。
5. 导出APK和运行截图:提交期末大作业的最后一公里
5.1 签名配置:从debug到release的完整过程
很多同学习惯在Android Studio里直接点Run,生成的是debug版APK,安装到别人手机上偶尔会报"与现有应用签名不同"的错。这篇项目要交付的是可以直接安装的release包,所以签名这一步不能省。
我在项目根目录准备好keystore文件后,在app/build.gradle里配置:
android { signingConfigs { release { storeFile file("release.jks") storePassword "your-password" keyAlias "your-alias" keyPassword "your-password" } } buildTypes { release { minifyEnabled false signingConfig signingConfigs.release } } }第一次生成keystore的命令是keytool -genkeypair -v -keystore release.jks -keyalg RSA -keysize 2048 -validity 36500 -alias your-alias。这个过程会要求输入口令和姓名、组织、城市等信息,这些信息不会显示在App里,随便填统一的测试信息就可以,但口令一定要记住,后面签名和升级都必须用到。
配置好之后,Build菜单下选Generate Signed Bundle / APK,勾选APK,下一步选release签名文件,最后在输出目录里就能看到app-release.apk。这个文件就是可以拿出去安装的正式包。建议自己先在另一台设备或者模拟器上重新安装一遍,确认签名没问题再提交。
5.2 运行截图的要素:哪些地方一定要截到
运行截图的质量直接影响老师的第一印象。我整理出的截图清单是这样的:
- 首页/列表页,此时列表里已经有3-5条记录,收入支出都有,最好能看到日期、分类、金额三个核心信息。
- 新增记录页,展示表单填写中或已填好的状态,能看到分类选择和金额输入。
- 统计页的饼图,展示分类占比,这是最容易出"亮点图"的一屏。
- 统计页的柱状图,展示月度趋势,体现App有数据随时间变化的能力。
截图的时候我踩过一个细节问题:模拟器默认分辨率是1080x1920,直接截图是没问题的,但如果你用Android Studio的模拟器自带截图按钮,会截到模拟器的整个窗口,包括侧边的操作栏。最好用adb命令截图:adb exec-out screencap -p > screenshot.png,这样截的是纯屏幕内容,没有模拟器外壳。
5.3 演示视频:要不要录、怎么录
老师没要求录视频时,我建议你也顺带录一段两分钟的操作演示。录屏工具用模拟器自带的Record Screen功能或者手机系统的屏幕录制都行。节奏建议是:打开App看到列表页 → 点新增按钮 → 填写一条支出记录 → 保存回到列表 → 切到统计页看到图表数据变化,全过程旁白简单说明每个操作对应哪段代码逻辑。
录完的视频如果需要压缩,剪映或者格式工厂就能处理,导出设置为720p、十几秒即可,文件大小控制在25MB以内。如果你要提交到某个在线平台,把视频放在项目仓库的release附件里,比直接发网盘链接要规范得多。
6. 答辩时会被问到的几个高频问题:提前把答案准备好
6.1 数据持久化:为什么选SQLite而不是Room或文件存储
答辩老师看到你的项目用SQLite,第一个可能问的就是"为什么不用Room"。答案模板是这样:数据库采用SQLiteOpenHelper这种更底层的API,是为了把数据库底层原理看清楚——建表、插入、查询、聚合都是手写SQL,对理解关系型数据库的基础操作有直观帮助。Room是在SQLite之上的封装,封装程度高,更适合大型项目快速开发,但作为期末项目,展示对底层SQL的掌握是加分项。
如果老师继续追"你用文件存储不行吗",可以说:账本数据存在结构化关系,需要按类型、分类、月份做聚合统计,SQLite的GROUP BY和SUM可以高效完成这类计算,文件存储需要自己遍历并逐条计算,代码复杂且容易出错。
6.2 RecyclerView和ListView:为什么选前者
RecyclerView在期末项目里的出场率非常高,老师也喜欢拿它做切入点。你要能说清楚两者核心差异:ListView的ViewHolder模式需要自己写,而且默认不支持局部刷新;RecyclerView强制使用ViewHolder复用机制,还支持LayoutManager切换线性布局和网格布局,配合ItemDecoration可以灵活控制分割线。这个项目里列表页正是用RecyclerView实现的,至少说明你赶了这个技术趋势。
6.3 性能问题:数据量大时你的App会卡吗
单机本地数据库,数据量一般不会太大,几千条记录SQLite完全能扛住。但答辩老师可能故意问"如果用户记了十年账,上十万条记录,你的SQL会慢吗"。正确的回答思路是:目前的表结构在id、type、date字段上都建了索引,查询时用WHERE条件走索引,通常能保持不错的性能;如果量级再大,会考虑按年份分表,或者把统计结果做缓存。有没有真去建索引不太重要,关键是你能说出一个合理的优化方向。
这三个问题答好之后,答辩的正文环节基本就稳了。接下来的提问就是围绕你写在README里的技术亮点展开,只要代码确实是你自己写的,这些细节都能对答如流。
最后说个个人的体会。我给自己定了"三件套"的交付标准,源码、APK、截图在动手开发之前就明确好,开发过程中每完成一个模块就顺手截一张图,最后一天只花一两个小时整理交付物,整个流程就不会拖到截止前熬夜。你要是最近也在选期末大作业的题目,拿这份清单当个参考,提前把数据库表、页面流转、统计SQL画在一张纸上再开工,效率会高很多。
本文还有配套的精品资源,点击获取