
简介基于Android Studio开发的个人记账工具APP源码专为Android初学者与需要完整实战项目的开发者设计覆盖日常记账核心场景收支记录、当天与当月汇总、历史账单查询、月度图表与百分比分析以及按关键词搜索全部收支明细可满足个人记账的绝大多数需求。项目共包含164个文件类型涵盖png图片资源、xml布局与样式配置、java逻辑代码、gradle构建脚本及数据库辅助类等压缩包仅346KB结构精简便于快速导入分析。源码中重点实践了Activity页面跳转与传值、Fragment碎片加载、自定义对话框与软键盘、ListView/GridView适配器、Android自带SQLite数据库增删改查以及MPAndroidChart第三方框架绘制柱状图等关键技术。已有2779人学习下载适合用来巩固Android基础知识点、理解完整的APP开发流程也可作为课程设计或毕业设计的直接参考帮助掌握从界面绘制到数据存储、图表展示的完整实现思路。1. 拿到就能跑的Android个人记账App源码先看它值不值得你花一个晚上拆一遍说到Android Studio里的个人记账APP源码很多人的第一反应是“这种课设项目网上一抓一大把”真正打开工程后才开始后悔要么Gradle版本跟本机Android Studio对不上要么包名里藏着一堆旧签名配置要么编译过了但数据库表的字段跟界面对不上。这套基于Android Studio开发的个人记账工具APP源码属于那种“打开就能编译、编译就能装”的完整工程不是零散的几个Activity文件拼凑出来的Demo。它能解决的核心问题很明确把从零手写一个记账应用需要的表设计、增删改查、列表刷新和图表统计这些基础工作量直接省掉你拿到手之后要做的不是重写而是替换业务逻辑、换图标、改包名然后出包。适合几类人正在做课程设计或毕业设计的本科生刚学完四大组件想找一个完整项目做参考的Android初学者以及想给自己做一个离线记账工具但不想从空工程开始写的开发者。2. 先把工程蓝图看清Gradle配置、Manifest权限与SQLite表设计拿到任何一个Android源码包我习惯先不开Android Studio而是直接把压缩包解压用文本编辑器打开三个文件settings.gradle、app/build.gradle、AndroidManifest.xml。个人记账APP的锅八成出在Gradle版本和数据库表结构上看这三个文件能提前避开大半。2.1 拿到源码先看这三样Gradle版本、Manifest权限与包结构先看根目录下的gradle/wrapper/gradle-wrapper.properties里面写着这个工程锁定的Gradle版本。再看app/build.gradle里的compileSdk、minSdk、targetSdk和依赖列表。常见个人记账源码的配置区间是compileSdk 30~34、minSdk 21~26、targetSdk 30~34这决定了你本机Android Studio版本不能太低。比如工程用的是AGP 7.4.2 Gradle 7.5那Android Studio建议用2022.3以上的版本低于这个版本会直接提示插件版本不受支持。. ├── app/ │ ├── build.gradle # 应用模块构建配置依赖和签名都在这里 │ ├── src/main/ │ │ ├── AndroidManifest.xml # 组件声明、权限声明、应用入口 │ │ ├── java/com/example/bookkeeping/ │ │ │ ├── db/ # SQLiteOpenHelper和表结构 │ │ │ ├── ui/ # Activity和Adapter │ │ │ ├── model/ # 账单、分类的实体类 │ │ │ └── utils/ # 日期格式化、金额处理工具 │ │ └── res/ # 布局、图片、颜色、字符串资源 │ ├── proguard-rules.pro # 混淆规则图表类库一般要在这里加keep │ └── src/test/ # 单元测试目录 ├── build.gradle # 顶层构建文件声明依赖仓库 └── settings.gradle # 模块引进配置大多数记账源码是单模块工程app一个模块就够了。看包结构的时候重点看db包和ui包如果db包里有单独的BillDao或者DBOpenHelper类说明作者把数据库操作做了封装改起来会比把SQL直接写在Activity里的版本舒服很多。Manifest文件里要重点确认两点第一记账APP是纯本地应用正常情况下不需要网络权限如果Manifest里出现了INTERNET权限多半是里面嵌了广告SDK或者统计SDK个人使用建议直接删掉第二确认MainActivity是不是配置了exportedtrueAndroid 12之后如果没配这个属性而MainActivity又有LAUNCHER意图安装时会被系统拦截。2.2 本地数据怎么存SQLite表结构与索引设计个人记账APP的数据存储方案第一选择从来不是Room或者GreenDAO而是原生SQLite。原因不复杂记账数据是结构化数据单机使用没有多端同步需求SQLiteOpenHelper写起来虽然啰嗦但零第三方依赖即使断网环境下Gradle拉不到新依赖代码也能正常编译。这套源码里通常会有两到三张表核心是账单表和分类表。-- 分类表预置餐饮、交通、购物、工资等用户可增删 CREATE TABLE category ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, icon TEXT, type INTEGER NOT NULL DEFAULT 0 -- 0支出 1收入 ); -- 账单表每一笔收支记录 CREATE TABLE bill ( id INTEGER PRIMARY KEY AUTOINCREMENT, amount REAL NOT NULL, category_id INTEGER NOT NULL, remark TEXT, date_millis INTEGER NOT NULL, -- 毫秒时间戳避免字符串日期比较大小 create_time INTEGER NOT NULL, FOREIGN KEY (category_id) REFERENCES category(id) ); -- 日期查询高频给时间戳建索引 CREATE INDEX idx_bill_date ON bill(date_millis);表设计里有几个点很关键。金额字段用REAL而不是INTEGER因为涉及小数展示的时候用String.format(%.2f, amount)格式化不然浮点运算会出现0.1 0.2 0.30000000000000004这种尴尬。category_id用外键关联而不是直接存分类名字符串好处是改分类名只改category表历史账单不需要跟着更新。date_millis用长整型时间戳而不是年月日整数字段因为按周、按月、按年汇总时一条WHERE date_millis BETWEEN ? AND ?就能搞定而且建索引后查询效率比字符串日期好得多。这三个设计决策是判断一个记账源码值不值得看的关键——如果它用字符串存日期后面做月统计报表的时候一定会在SQL里写一堆substr函数维护起来很痛苦。3. 核心功能逐行拆解记一笔、账单列表与图表统计的完整调用链数据库结构看明白了接下来就是顺着使用场景读代码。记账APP的用户路径非常短打开App → 记一笔 → 看账单列表 → 看图表。这套源码能不能用就看这四步在代码里是否闭环。3.1 记一笔支出从界面到数据库的调用链加一笔账单的入口常见的有两种一种是点击悬浮按钮弹出一个带金额键盘的Dialog另一种是跳转到独立的AddBillActivity。不管哪种最终都要走同一个数据库插入方法。标准的写法是封装在BillDao或者DBOpenHelper里。public long insertBill(double amount, long categoryId, String remark, long dateMillis) { SQLiteDatabase db this.getWritableDatabase(); ContentValues values new ContentValues(); values.put(amount, amount); values.put(category_id, categoryId); values.put(remark, remark null ? : remark.trim()); values.put(date_millis, dateMillis); values.put(create_time, System.currentTimeMillis()); long rowId db.insert(bill, null, values); db.close(); return rowId; }这段逻辑的核心是三件事把界面传进来的参数封装进ContentValues调用insert写入返回rowId。rowId等于-1时说明插入失败UI层拿到这个值要弹Toast提示不能让用户以为记上了其实没记上。remark做空值判断是必要的因为很多源码在布局里给备注EditText设了默认提示文本用户不输入时拿到的是空字符串而不是null直接入库会导致后续查询判断remark null失效。金额校验一般在UI层做而不是放到这个插入方法里。常见误用是只在提交按钮的点击事件里判断amount 0但像12.345这种三位小数的情况就漏掉了。正确做法是提交前用BigDecimal做保留两位小数处理或者用输入框的InputFilter限制小数点后只能输入两位后者的用户体验更好因为用户输完第三位数字根本输不进去。3.2 账单列表的加载与刷新查询最近30天数据列表页通常是RecyclerView搭配Item布局数据源来自一次数据库查询。这里最常见的实现是把账单表和分类表做JOIN一次性查出分类名称避免遍历时再逐条查分类表。SELECT b.id, b.amount, b.remark, b.date_millis, c.name AS category_name, c.icon AS category_icon FROM bill b LEFT JOIN category c ON b.category_id c.id WHERE b.date_millis BETWEEN ? AND ? ORDER BY b.date_millis DESC, b.id DESC;LEFT JOIN而不是INNER JOIN是有原因的如果用户在账单记录里删掉了某个分类INNER JOIN会导致历史账单直接消失而LEFT JOIN至少还能显示出账单只是分类名变成nullUI层可以做空值兜底显示“未分类”。ORDER BY date_millis DESC之后再加id DESC是为了处理同一毫秒内插入多条账单的情况保证最新插入的排在最前面。日期范围的计算有个现成套路传进来的两个参数分别是“今天零点的时间戳”和“当前时间的时间戳”。很多源码在这里翻车直接用了System.currentTimeMillis() - 30L * 24 * 60 * 60 * 1000这样算出来的起点是30天前的当前时刻而不是30天前的零点导致界面上显示“最近30天”但实际包含的是29天23小时的数据。正确做法是先把当前日期转成LocalDate再minusDays(29)和atStartOfDay拿到零点最后转成毫秒传入查询。3.3 统计图表本月支出分布是怎么画出来的图表数据不是在前端算的而是数据库聚合查询的结果。按分类汇总月支出SQL很直接SELECT c.name AS category_name, SUM(b.amount) AS total_amount FROM bill b LEFT JOIN category c ON b.category_id c.id WHERE b.date_millis ? AND b.date_millis ? AND b.amount 0 GROUP BY b.category_id ORDER BY total_amount DESC;GROUP BY b.category_id按分类分组SUM(b.amount)算出每个分类的总支出ORDER BY total_amount DESC让占比最高的分类排在前面。注意这里WHERE条件只能有date_millis的范围过滤和amount 0不能把HAVING total_amount 0当成筛选条件写在WHERE里因为聚合函数的结果不能用WHERE过滤会直接报错。拿到Cursor之后遍历组装饼图数据。这里有个接口适配的细节值得多看两眼图表库的PieEntry需要value和label两个参数但很多分类名很长直接塞进饼图会让图例挤成一团。常见的处理是在组装时截断字符串超过四个字符的显示为“前四个字…”这样副作用最小。ListPieEntry entries new ArrayList(); while (cursor.moveToNext()) { String name cursor.getString(0); float total cursor.getFloat(1); if (name.length() 4) { name name.substring(0, 4) …; } entries.add(new PieEntry(total, name)); }4. 从源码到APKAndroid Studio导入、编译与打包的实操路径工程本身再好编译不过等于零。大多数人在“源码能跑”这个问题上花的冤枉时间比写源码的时间还多。这一章把从下载源码到手机上弹出图标的全过程走一遍重点说版本匹配和依赖下载两个关卡。4.1 环境版本怎么定JDK、AGP、Gradle与Android Studio的匹配关系先给一张常规模板具体以源码里的gradle-wrapper.properties为准Gradle版本AGP版本JDK要求建议Android Studio版本7.57.4.2JDK 11Android Studio 2022.38.08.0.xJDK 17Android Studio 2023.16.7.14.2.2JDK 8/11Android Studio 4.2版本匹配的判断依据很简单app/build.gradle里plugins块中的AGP版本决定Gradle最低版本Gradle版本决定JDK版本。如果你本机JDK是17而工程Gradle是7.5构建时会报Unsupported class file major version 61意思就是Gradle太老跑不动JDK 17编译出来的代码。解决办法不是换JDK而是打开gradle-wrapper.properties把distributionUrl升级到8.0以上同时把AGP版本升到8.0.x——这是最省事的路径。4.2 导入项目的正确姿势先配local.properties再等构建很多新手直接把项目文件夹拖进Android Studio然后干等左下角进度条。正确姿势是先手动指定SDK路径省得AS弹窗让你选选错还会引发一连串奇怪报错。# local.properties 放在项目根目录与 settings.gradle 同级 sdk.dirD\:\\Android\\SdkWindows下D:\Android\Sdk里的反斜杠要转义成\\这是第一个坑。第二个坑是local.properties这个文件不要提交到Git仓库因为它记录的是你本机的绝对路径别人clone下来之后这个文件是失效的Android Studio会自己重新生成。导入时如果看到“Unresolved reference: R”或者“Cannot resolve symbol R”通常不是代码问题而是构建还没完成等第一次Gradle Sync跑完再写代码R文件会自动生成。4.3 依赖下载失败与构建超时换镜像和锁版本个人记账源码的依赖一般不多通常只有一个MPAndroidChart图表库和appcompat、material、constraintlayout。但国内网络环境下google()和mavenCentral()两个仓库经常拉得很慢甚至直接超时。处理方法是在顶层build.gradle的allprojects里加阿里云镜像并把镜像仓库放在最前面。allprojects { repositories { maven { url https://maven.aliyun.com/repository/google } maven { url https://maven.aliyun.com/repository/public } maven { url https://maven.aliyun.com/repository/gradle-plugin } google() mavenCentral() } }镜像地址的优先级很关键google镜像要放在google()之前才能截住AndroidX和Material库的下载请求gradle-plugin镜像要放在google()之前才能截住AGP插件本身的下载。依赖下载失败时报错信息里会明确写出是哪个库的哪个版本拉不下来比如Could not resolve com.github.PhilJay:MPAndroidChart:v3.1.0这时候先去检查这个版本在镜像仓库里是否存在而不是盲目换版本号。构建超时还有一个隐藏原因Gradle守护进程内存不够。在gradle.properties里加一句org.gradle.jvmargs-Xmx2048m重启Android Studio一般能解决。5. 避坑与常见问题把源码跑通过程中的五条血泪经验编译能过只是第一步把App装到手机上开始记了几笔账之后各种藏在代码深处的问题才浮出来。这一章列的都是同类源码里反复出现的高频问题按“现象 → 原因 → 解决”的顺序写方便翻车时直接对照。5.1 屏幕旋转后填了一半的账单数据全丢了转个手机金额、备注、分类选择全没了。原因是Activity默认在配置变化时重建EditText里的内容没有保存机制。解决方法是重写onSaveInstanceState把金额和备注存进去onCreate里再读回来更省事的是在Manifest给这个Activity加android:configChangesorientation|screenSize让Activity不重建。但注意加了configChanges之后换肤、字体大小调整这些系统配置变化也不会触发重建需要自己处理onConfigurationChanged对记账这种轻量应用来说加configChanges是最稳的后悔药。5.2 添加新分类后App直接崩溃logcat里报sqlite约束失败现象是点“保存分类”按钮后闪退进入数据库目录看category表发现主键冲突或者name字段为null。原因大多是两个一是主键没有配AUTOINCREMENT删除几条记录后新插入的id复用导致重复二是插入分类时没有做重名校验两条“餐饮”同时存在查询时Cursor出现两行相同名称数据显示混乱。解决方法是插入前先查一遍SELECT COUNT(*) FROM category WHERE name ? AND type ?数量大于0时直接Toast提示“该分类已存在”。这属于典型的R文件不报错但运行时翻车排查时先看logcat里SQLiteConstraintException的堆栈指向哪一行。5.3 图表不显示白屏加一个“No Data”文案装了App记了十几笔账饼图却空白。常见原因是时间范围写死了。很多源码默认图表页只统计“当月”但如果设备日期是自然月第一天而账单都记在昨天date_millis ?这个条件的起点是本月1日零点昨天的账单当然不在范围内。另一个原因是聚合SQL里GROUP BY之后恰好有的分类支出合计为零PieEntry(value0)在某些图表库版本里不会绘制。排查方法是在onCreate里加Log打印SQL参数肉眼确认时间戳范围对不对别一上来就怀疑图表库有问题。5.4 修改数据库表结构后旧版本升级直接崩开发阶段改了BillTable里的字段加了一列is_deleted然后直接运行App还没进主界面就闪退logcat报no such column: is_deleted。原因很直接SQLiteOpenHelper的onUpgrade没写或者DATABASE_VERSION没加1App还是按旧版本号打开了旧表。解决方法是在onCreate旁边补上Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { if (oldVersion 2) { db.execSQL(ALTER TABLE bill ADD COLUMN is_deleted INTEGER DEFAULT 0); } }如果是开发前期的临时表结构变动图省事就卸载重装但如果已经装到测试机上积累了一批测试数据卸载重装会把账目清空务必先做数据备份。5.5 图标和启动图不对Android 12以上安装后被系统打上兼容性标签源码里默认的ic_launcher是老的mipmap格式在Android 12以上某些机型上装完长按图标会提示“此应用专为旧版Android打造”。原因是应用没有适配自适应图标也就是缺mipmap-anydpi-v26/ic_launcher.xml。解决方法是右键res目录新建Image Asset把前景图和背景色配一遍系统会自动生成anydpi-v26下的XML文件。这个过程顺手把图标换了也算给源码做个性化改造。6. 源码还能怎么改预算模块、CSV导出与自签名APK把源码跑通只是开始真正让这个工具顺手的是按自己习惯改造。这里给出三个成本低、收益明确的改造方向每个方向都带着可直接落地的代码骨架。6.1 加一个预算进度条预算功能不需要建表一个SharedPreferences就够。存预算总额、刷新界面时查询当月支出总额然后用ProgressBar显示比例。关键点在于每月重置拿Calendar判断当前月份变化时清零或重新读取。进度条超过100%变红色这个视觉反馈能有效让人控制支出。6.2 导出CSV备份到本地记账数据是本地库不导出的话一卸载全没。写一个导出方法把bill表连表查询的结果写成CSV文件放到Download目录。Excel打开会乱码原因是UTF-8编码的CSV没有BOM头写文件时先写入\uFEFF就能解决。注意导出要放在子线程直接在主线程写文件账单一多会卡顿。FileWriter writer new FileWriter(csvFile); writer.write(\uFEFF); // 加BOMExcel打开不乱码 String header 日期,分类,金额,备注\n; writer.write(header); // 遍历账单 Cursor按行写入 writer.flush(); writer.close();6.3 自签名APK从源码到安装包的最后一步用Android Studio的Build Generate Signed Bundle/APK一步步填签名信息就能生成release包。没有签名密钥就点“Create new…”新建一个jks文件密码要记牢忘记密码等于丢掉发布身份。命令行打包党可以在app/build.gradle里配signingConfigs然后跑gradlew assembleRelease产物在app/build/outputs/apk/release/下。这套源码做到这一步已经不只是“跑通了”而是变成了一个有自己签名、有图标、有数据备份习惯的个人记账工具。从那以后我每次拿到新的Android源码包第一件事永远是先开gradle-wrapper.properties和数据库建表语句先确认版本和表结构再谈功能——工具问题不解决代码再漂亮也白搭。希望帮到你。本文还有配套的精品资源点击获取