十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

安卓记账App开发实战:从SQLite到MPAndroidChart的期末大作业全解析

安卓记账App开发实战:从SQLite到MPAndroidChart的期末大作业全解析 简介本资源是一份面向高校移动开发初学者的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。设计时反复确认每一个字段都能对应到界面上某个输入控件不会出现存了但用不到的冗余字段。字段这么定的字段名类型说明idINTEGER主键自增typeINTEGER1代表支出0代表收入categoryTEXT分类名称如餐饮交通工资amountREAL金额保留两位小数noteTEXT备注可为空dateTEXT日期格式yyyy-MM-dd为什么type用0/1整数而不是存字符串收入/支出主要原因是统计SQL写起来方便。按类型分组时直接GROUP BY type配合SUM聚合函数一行SQL就能算出总收入、总支出。如果存的是中文聚合结果排序就会变得别扭也有可能出现编码不一致导致的分组异常。这里用整数还省了存储虽然省的那点空间在单机App里微不足道但写SQL的判断条件时更清爽。category之所以单独列出来而不是写死进代码是因为后面做统计图表时饼图需要按分类聚合金额。比如餐饮交通各自花了多少占总支出的百分比这些数据全靠category字段做GROUP BY。如果把分类写死在代码里界面选择是个下拉框数据库存的是下拉框index那分析报表时还得做一次index到文字的映射平白多了层麻烦。我在这个项目里直接把分类名称文本存进库牺牲了一点点数据库规范化换来了查询代码的可读性和统计逻辑的简单。2.2 SQLiteOpenHelper的写法与版本管理的坑数据库帮助类继承SQLiteOpenHelperDATABASE_VERSION设成1。这里有一个细节值得注意onCreate里只负责建表onUpgrade里写的才是后期改表的逻辑。期末项目通常不需要升级数据库但老师问到如果以后要加一个预算表你怎么处理时你得能答上来——版本号1onUpgrade里执行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的统计模块必须能回答两类问题第一类是我这个月一共花了多少钱第二类是花钱都花在哪些地方了。前者是一条极简单的SQLSELECT 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而不是自己画View4.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和jcenterjcenter已经停止服务这很可能是你依赖拉不下来的元凶。4.2 按分类聚合的饼图数据准备与渲染饼图的业务需求是展示钱都花在哪儿了我写了一个getExpenseStatsByCategory方法。它执行前面第2.3节提到的那条GROUP BY SQL返回一个List 每个对象里存category和totalAmount。拿到数据之后填充饼图ListPieEntry 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画在一张纸上再开工效率会高很多。本文还有配套的精品资源点击获取
返回列表