简介基于Android Studio开发的记账本App完整项目源码专门面向Android初学者、课程设计及期末大作业场景可帮助快速理解一个可运行App的完整实现流程。项目包含账单记录、分类统计、数据持久化等核心功能模块代码结构清晰、注释规范下载导入即可直接运行无需额外修改。压缩包内共56个文件涵盖Java源码、XML布局与配置文件、Gradle构建脚本、PNG图标资源等整体仅166KB轻量且工程完整。目前已有1878人学习下载属于95分以上的高分项目具备良好的参考价值。通过阅读源码可系统学习Activity界面搭建、SQLite数据库存储、RecyclerView列表展示、图表统计等Android常用技术同时了解Gradle配置与项目组织方式便于后续在此基础上扩展账单分类、预算提醒等功能非常适合毕业设计或期末项目借鉴。1. 拿到记账本项目源码第一天别急着双击打开很多人下载了android-bookkeeping-master这个工程第一反应是解压后用 Android Studio 直接 Open 然后等 Gradle 同步。这个项目能跑但如果你只做到这一步通常会在同步阶段卡十几分钟甚至直接失败。原因不是代码本身有问题而是 Android Studio 版本、JDK 版本和 Gradle 插件版本三者不匹配。记账本这个项目最大的参考价值不只是一个能录入和统计账单的 App而是它把「本地数据存储 列表展示 简单图表」这三个 Android 开发高频模块串成了完整闭环适合期末作业、课程设计也适合想快速搭一个单机数据应用的开发者。这篇文章会把它的目录结构、数据库设计、UI 交互和打包验证逐层拆开。2. 工程目录与 Gradle 配置先把构建链路打通2.1 从根目录看懂这是一个什么结构的项目解压后你会看到android-bookkeeping-master目录核心内容不是根目录那一堆.gitignore和gradlew.bat而是app模块内部的代码。先看整体目录树android-bookkeeping-master/ ├── build.gradle # 根构建脚本声明插件版本 ├── settings.gradle # 模块声明 ├── gradle.properties # JVM 参数配置 ├── gradlew / gradlew.bat # Gradle 包装器脚本 ├── gradle/ │ └── wrapper/ │ └── gradle-wrapper.properties # 锁定 Gradle 版本 └── app/ ├── build.gradle # app 模块构建配置 └── src/ └── main/ ├── java/ ... # 源码目录 └── res/ # 布局、图片、values 资源这个结构是标准的 Android 单模块工程。settings.gradle里如果只写了include :app说明项目没有依赖其他本地模块gradle-wrapper.properties里的distributionUrl决定了下载的 Gradle 版本。如果你本机只有高版本 Android Studio比如狮子鱼或新北极狐系列而项目锁的是 Gradle 6.5首次同步会重新下载对应版本到~/.gradle/wrapper/dists网络差的时候会一直卡在Gradle sync。2.2 app 模块的关键配置参数打开app/build.gradle核心配置如下android { compileSdkVersion 30 buildToolsVersion 30.0.3 defaultConfig { applicationId com.example.bookkeeping minSdkVersion 21 targetSdkVersion 30 versionCode 1 versionName 1.0 } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile(proguard-android-optimize.txt), proguard-rules.pro } } } dependencies { implementation androidx.appcompat:appcompat:1.2.0 implementation com.google.android.material:material:1.3.0 implementation androidx.constraintlayout:constraintlayout:2.0.4 implementation androidx.recyclerview:recyclerview:1.1.0 }参数说明compileSdkVersion决定编译时使用的 SDK API 级别minSdkVersion 21意味着 App 支持 Android 5.0 及以上设备targetSdkVersion 30则告诉系统在 Android 11 上启用高版本的行为变更。依赖里值得注意是 RecyclerView 和 Material分别负责列表和界面控件。这里没有引入 Room 或 GreenDAO说明项目采用的是原生 SQLite 方案好处是少一层框架封装期末答辩时更容易说清楚数据存储细节。提示如果你把compileSdkVersion改到 33 以上而targetSdkVersion还停留在 30个别系统弹窗权限行为会不同建议保持项目原始配置不变优先保证能跑。2.3 同步失败的常见原因和对应处理Gradle 同步失败有几种情况distributionUrl指向的版本下载超时、google()仓库访问慢、JDK 版本过高导致 Gradle 无法解析。检查顺序如下# 查看当前 JDK 版本 java -version # 查看 gradle 锁定的版本 cat gradle/wrapper/gradle-wrapper.properties如果 JDK 是 15 以上而项目用的 Gradle 低于 7.0会出现Unsupported class file major version报错。解决办法不是升级项目的 Gradle而是在 Android Studio 里把 Gradle JDK 切成 11 或 8Settings - Build Tools - Gradle - Gradle JDK。另外可以在gradle.properties里加上国内镜像源重新同步会快很多org.gradle.jvmargs-Xmx2048m -Dfile.encodingUTF-8 android.useAndroidXtrue android.enableJetifiertrue这组参数的逻辑是给 Gradle 分配 2GB 堆内存同时开启 AndroidX 转换。如果项目本身用的就是 AndroidX 依赖enableJetifier可以保留如果去掉它也能跑建议关掉以缩短构建时间。3. 账目数据的本地存储核心数据库设计与 DAO 层实现3.1 记账本的表结构设计是评分重点记账本 App 的数据核心是收入支出记录表设计直接决定后续统计的便捷程度。这个项目里的建表语句类似下面这样CREATE TABLE account ( id INTEGER PRIMARY KEY AUTOINCREMENT, type TEXT NOT NULL, -- 类型income / expense category TEXT NOT NULL, -- 分类餐饮 / 交通 / 工资等 amount REAL NOT NULL, -- 金额单位元 remark TEXT, -- 备注 create_time TEXT NOT NULL, -- 记账时间 2025-01-15 14:30 update_time TEXT );字段说明id用自增主键是 Android 本地 App 的常见做法避免使用 UUID 字符串主键因为自增整数在 SQLite 中占用空间更小、索引效率更高type用字符串而不是整数是为了让type1这种硬编码不出现在业务代码里income和expense语义更明确amount用 REAL 而不是 INTEGER是为了支持小数点后两位的金额如果你对精度要求更高可以乘 100 存成整数但期末项目直接存 REAL 足够。3.2 SQLiteOpenHelper 的覆写逻辑项目里没有用 ORM 框架而是继承SQLiteOpenHelperpublic class AccountDbHelper extends SQLiteOpenHelper { private static final String DB_NAME bookkeeping.db; private static final int DB_VERSION 1; public AccountDbHelper(Context context) { super(context, DB_NAME, null, DB_VERSION); } Override public void onCreate(SQLiteDatabase db) { db.execSQL(CREATE TABLE account (...);); } Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL(DROP TABLE IF EXISTS account); onCreate(db); } }这里有个值得讨论的点onUpgrade直接DROP TABLE再重建开发调试阶段没问题但如果 App 已经发布且用户本地有数据这个操作会清空所有记账记录。正式项目应该在版本升级时走ALTER TABLE或数据迁移不过在期末作业的场景下数据持久化只要求在 App 生命周期内和重启后都存在直接删除重建也是验收组能接受的做法。3.3 增删改查封装事务和参数绑定DAO 层建议都通过ContentValues来写避免字符串拼接 SQL。读取时用Cursor遍历并手动关闭资源public long insertAccount(AccountBean bean) { SQLiteDatabase db dbHelper.getWritableDatabase(); ContentValues values new ContentValues(); values.put(type, bean.getType()); values.put(category, bean.getCategory()); values.put(amount, bean.getAmount()); values.put(remark, bean.getRemark()); values.put(create_time, bean.getCreateTime()); return db.insert(account, null, values); } public ListAccountBean queryByMonth(String month) { SQLiteDatabase db dbHelper.getReadableDatabase(); String sql SELECT * FROM account WHERE create_time LIKE ? ORDER BY create_time DESC; Cursor cursor db.rawQuery(sql, new String[]{ month % }); ListAccountBean list new ArrayList(); while (cursor.moveToNext()) { // 遍历列封装 AccountBean } cursor.close(); return list; }insert方法的返回值是自增主键 id如果返回 -1 说明插入失败。queryByMonth里用LIKE匹配月份前缀配合create_time的yyyy-MM-dd格式能直接筛出某月所有记录。注意rawQuery的第二个参数是占位符数组即使只有一个查询条件也必须传数组不能传null否则某些 ROM 上会直接崩溃。提示统计收入和支出时需要分别查两次SUM(amount)要在下面给出具体写法。3.4 统计当月收支差的 SQL列表页顶部通常会显示「本月收入 / 本月支出 / 结余」这个统计可以用两个 SQL 搞定不需要一次查出全部记录到内存中计算public double queryMonthSum(String type, String month) { SQLiteDatabase db dbHelper.getReadableDatabase(); String sql SELECT SUM(amount) FROM account WHERE type ? AND create_time LIKE ?; Cursor cursor db.rawQuery(sql, new String[]{ type, month % }); double total 0; if (cursor.moveToFirst()) { total cursor.getDouble(0); } cursor.close(); return total; }用SUM聚合函数的好处是大数据量下性能稳定几十万条记录也不会发生内存溢出。这里面的getDouble(0)取的是聚合结果需要注意当该月没有任何记录时SUM返回NULLCursor 的getDouble(0)会得到 0这个行为在不同版本的 SQLite 中一致但你自己写的时候加一个cursor.isNull(0)判断会更严谨。4. UI 交互层RecyclerView 列表与统计视图实现4.1 主界面的布局拆解主界面一般是一个Activity底部放RecyclerView列表顶部放当月统计右下角悬浮按钮。activity_main.xml简化结构如下androidx.constraintlayout.widget.ConstraintLayout LinearLayout idlayout_summary TextView idtv_income/ TextView idtv_expense/ TextView idtv_balance/ /LinearLayout androidx.recyclerview.widget.RecyclerView idrv_account layout_width0dp layout_height0dp constraint_top_toBottomOflayout_summary constraint_bottom_toBottomOfparent/ com.google.android.material.floatingactionbutton.FloatingActionButton idfab_add constraint_bottom_toBottomOfparent constraint_end_toEndOfparent/ /androidx.constraintlayout.widget.ConstraintLayout布局这里要注意的是rv_account的高度约束如果不把它约束在layout_summary下方列表会覆盖到统计区。这种嵌套ConstraintLayout的做法比多层LinearLayout更容易维护。顶部三个TextView分别显示本月收入、支出和结余金额格式化推荐用DecimalFormatprivate String formatAmount(double amount) { DecimalFormat df new DecimalFormat(0.00); return df.format(amount); }DecimalFormat(0.00)会保留两位小数并四舍五入比如3.1显示为3.10如果要显示千分位改成#,##0.00。4.2 RecyclerView 适配器如何绑定数据列表项展示的字段包括图标、分类名、备注、金额和时间。适配器的核心在于onBindViewHolder里对收入支出的颜色区分public class AccountAdapter extends RecyclerView.AdapterAccountAdapter.VH { private ListAccountBean dataList; Override public void onBindViewHolder(NonNull VH holder, int position) { AccountBean bean dataList.get(position); holder.tvCategory.setText(bean.getCategory()); holder.tvRemark.setText(bean.getRemark()); holder.tvTime.setText(bean.getCreateTime()); String amountStr bean.getType().equals(income) ? formatAmount(bean.getAmount()) : - formatAmount(bean.getAmount()); holder.tvAmount.setText(amountStr); holder.tvAmount.setTextColor(bean.getType().equals(income) ? Color.parseColor(#2E7D32) : Color.parseColor(#C62828)); } public void setData(ListAccountBean list) { this.dataList list; notifyDataSetChanged(); } }这里有几个容易踩的坑。第一setData里直接替换dataList引用再调用notifyDataSetChanged如果外部持有旧列表引用会出现数据刷新无效建议使用clear()加addAll()的方式。第二notifyDataSetChanged会刷新所有可见 item数据量大时会有肉眼可见的卡顿由于这里是按月分页查询数据量通常不会过百条这个方案够用。第三formatAmount必须放在onBindViewHolder里调用不能提前放到AccountBean的数据库字段上存储。4.3 统计图表的实现方式选型很多记账本会在收入支出旁边放一个饼图。期末作业里用第三方库MPAndroidChart是最快的方案在build.gradle中加入依赖implementation com.github.PhilJay:MPAndroidChart:v3.1.0然后初始化饼图PieChart pieChart findViewById(R.id.pie_chart); PieData pieData new PieData(entries); pieChart.setData(pieData); pieChart.invalidate();但要注意这个库需要网络下载依赖且体积较大。如果你希望离线可用更推荐用Canvas自己画饼图实现思路是先算出总金额再按每个分类的百分比绘制Canvas.drawArc代码量并不大。期末答辩时自己画图的加分效果更好因为评委可以直接问你实现逻辑而不是只会调用第三方接口。4.4 添加账单的交互流程点击浮动按钮弹出Dialog或跳转AddAccountActivity表单里需要输入金额、选择分类、切换收入或支出类型。这里的常见问题是键盘弹起后布局被顶上去处理方式是在AndroidManifest.xml的对应Activity上配置activity android:name.AddAccountActivity android:windowSoftInputModeadjustResize /adjustResize会压缩 Activity 内容区域给键盘让位adjustPan则是整体上移对于表单布局前者更合适。保存成功后返回主界面onResume里重新查询当月数据并刷新适配器这个做法比startActivityForResult拿返回值更简单并且在业务上更一致每次回到列表页都是最新数据。5. 打包验证与进阶扩展签名、导出和看日志5.1 在模拟器或真机上验证核心流程代码逻辑没问题后先用模拟器跑一遍完整流程。重点检查四件事# 1. 冷启动后数据库是否自动创建 adb shell run-as com.example.bookkeeping ls /data/data/com.example.bookkeeping/databases/ # 2. 插入一条记录后查看表内容 adb shell run-as com.example.bookkeeping sqlite3 bookkeeping.db select * from account; # 3. 查看崩溃日志 adb logcat -s AndroidRuntime:E # 4. 观察主线程是否有 SQLite 操作卡顿 adb logcat -s System.out:V ActivityTaskManager:Irun-as只能对 debug 签名的应用生效这也是为什么期末项目用自动签名调试即可。第四步观察的卡顿如果 SQL 查询在主线程执行且数据量大你会看到Choreographer跳过帧的警告。由于记账本单月数据量小这个风险可忽略但如果演示时频繁掉帧需要把查询操作线程化或者把统计金额放到内存中预计算。5.2 生成可安装的 APK 与签名配置如果不配置签名直接Build - Build Bundle(s) / APK(s) - Build APK(s)也能生成 debug 包但这个包不能换机安装到别人的手机上。要生成安装包在app/build.gradle中配置签名android { signingConfigs { release { storeFile file(keystore/release.jks) storePassword 123456 keyAlias bookkeeping keyPassword 123456 } } buildTypes { release { signingConfig signingConfigs.release minifyEnabled false } } }然后运行./gradlew assembleRelease产物在app/build/outputs/apk/release/app-release.apk。从 Android 7.0 开始系统要求应用安装时校验签名Debug 和 Release 签名不同时需要先卸载旧包再安装新包否则会报INSTALL_FAILED_UPDATE_INCOMPATIBLE。如果演示时遇到这个报错直接卸载重装即可不必纠结签名算法。5.3 从源码包到答辩演示的快速演进如果当前项目只做了基础的增删改查和月度汇总以下几个扩展按性价比排序导出 CSV、增加搜索、支持多账本。导出 CSV 是最适合课堂展示的功能因为用户可以直观看到数据文件并且不依赖网络。给项目增加导出的核心逻辑大致为public void exportCsv(Context context, String month) throws IOException { File dir context.getExternalFilesDir(export); File csvFile new File(dir, bookkeeping_ month .csv); FileOutputStream fos new FileOutputStream(csvFile); OutputStreamWriter writer new OutputStreamWriter(fos, StandardCharsets.UTF_8); writer.write(类型,分类,金额,备注,时间\n); ListAccountBean list queryByMonth(month); for (AccountBean bean : list) { writer.write(String.format(%s,%s,%.2f,%s,%s\n, bean.getType(), bean.getCategory(), bean.getAmount(), bean.getRemark(), bean.getCreateTime())); } writer.flush(); writer.close(); }context.getExternalFilesDir不需要申请存储权限文件不会出现在系统相册等位置但可以用adb pull导出到电脑上打开查看。如果你用MediaStore写入公共 Download 目录展示说服力更强那需要处理 Android 11 的分区存储权限复杂度明显上升。期末答辩场景下我一般建议用getExternalFilesDir理由是演示结束可以直接传文件给评委看又不用处理权限弹窗。CSV 文件用 Excel 打开后中文可能乱码根源是 Excel 默认用 GBK 读取。解决方案是导出时加一个 BOM 头在写第一行前先输出new byte[]{(byte)0xEF, (byte)0xBB, (byte)0xBF}这样 Windows 下双击 CSV 文件内容不会乱码。到这里你再回头看这个项目的源码你会发现它的坑不在业务复杂度上而是配置环境、数据库 Schema 匹配和签名导出这三条链路把这三点跑顺了后面不管换什么账本项目都能快速上手。本文还有配套的精品资源点击获取
