本文还有配套的精品资源点击获取简介这个记账App能直接在安卓手机上安装使用点开就能记收支、查账单、看分类统计。附带已打包好的money.apk文件无需编译扫码或传输到手机即可安装。源码用Android Studio开发结构清晰包含app模块、gradle构建配置build.gradle、gradlew等、资源文件、混淆规则和本地环境配置local.properties。后端用MySQL存数据需要自己搭个简单服务端比如PHPMySQL或Java Spring Boot接口App里已预留网络请求逻辑和数据库字段映射。适合学生做毕业设计或课程作业导入AS就能跑改UI、调接口、换主题都方便。没有视频教程也不带说明书所有代码和配置都是真实可调试的原始工程文件不是截图或伪代码。1. 项目概述一个真正能“装上就用”的记账工具不是Demo是完整工程你有没有试过在GitHub上搜“安卓记账App源码”点开十几个仓库结果全是只有MainActivity.java、一个空Activity、几行Toast弹窗的“教学示例”或者更糟——代码里写着// TODO: 连接服务器连个网络请求的影子都没有更别说MySQL字段怎么映射、JSON怎么解析、错误怎么处理。这个项目不是那样。它是一套从手机安装包APK、到Android Studio可调试工程、再到后端数据存储逻辑全部打通的真实闭环。我把它叫作“理财小管家”不是因为它功能多么炫酷而是它真的能解决一个具体问题你今天午饭花了28块随手点两下记录完成月底想看看餐饮花了多少打开统计页数字就列在那里换新手机了把APK传过去安装登录本地账户无云同步所有历史账单都在。它不依赖任何第三方服务不强制注册不收集隐私所有数据存在你自己的MySQL数据库里——当然前提是你要搭个简单的后端接口但这个接口我后面会给你一份实测可用的PHP脚本50行以内配好数据库就能跑。核心关键词“安卓记账源码”、“MySQL记账App”、“Android Studio工程”、“个人理财App”每一个都不是虚的。它不是“含MySQL字样”的假把式而是app/src/main/java/com/example/money/network/ApiService.java里明明白白写着POST(api/record/add)build.gradle里配置着implementation androidx.lifecycle:lifecycle-viewmodel:2.6.2和implementation com.squareup.retrofit2:retrofit:2.9.0proguard-rules.pro里为Gson和Retrofit加了专门的混淆保留规则。它也不是那种“导入AS就报错17个”的残缺工程——.gitignore里排除了local.properties和.idea/gradle.properties里定义了org.gradle.jvmargs-Xmx4g -XX:MaxMetaspaceSize512m这些都是我在自己笔记本i5-1135G7 16GB内存上反复验证过的最小可行配置。学生拿去做毕设不用再花三天去查“为什么AS提示找不到R文件”也不用纠结“为什么模拟器里按钮点了没反应”因为整个流程——从扫码安装APK到修改strings.xml改APP名字再到把后端地址从http://192.168.1.100:8080改成你自己的服务器IP——每一步都是平滑的、可预期的。它不教你“什么是MVC”但它让你在改完一个分类图标后立刻看到手机界面上那个小图标变了它不讲“RESTful API设计原则”但它让你在把ApiService.addRecord()里的URL改错一个字符后Logcat里清清楚楚打出java.net.UnknownHostException然后你立刻知道该去检查网络权限或服务器状态。这就是它的价值它把“学习Android开发”这件事从抽象的概念拉回到手指在键盘上敲出一行有效代码、在屏幕上看到一个真实反馈的具体动作。2. 整体架构与设计思路为什么选择这套组合而不是其他方案2.1 前后端分离的务实选择轻量级PHP接口 原生Android客户端很多初学者一上来就想搞“Spring Boot Vue MySQL”全家桶结果光是环境配置就卡死在JDK版本和Maven镜像源上。这个项目反其道而行之后端只用最基础的PHPMySQL组合。原因很实在第一PHP运行环境极简Windows上一个XAMPP、Mac上一个MAMP、Linux上一条sudo apt install php mysql-server php-mysql命令搞定不需要理解Tomcat容器、Spring上下文、Bean生命周期这些概念第二PHP处理HTTP请求和数据库操作的语法极其直白比如插入一条收支记录核心代码就是三行$stmt $pdo-prepare(INSERT INTO records (type, amount, category, note, date) VALUES (?, ?, ?, ?, ?)); $stmt-execute([$type, $amount, $category, $note, $date]); echo json_encode([success true, id $pdo-lastInsertId()]);没有复杂的注解、没有XML配置、没有依赖注入你甚至可以把这段代码直接复制进你的add_record.php文件里改几个变量名就能跑。而Android端我们用Retrofit 2.9.0封装网络请求用Gson 2.10.1解析JSON这是目前Android社区最成熟、文档最全、出问题最容易搜到答案的组合。它不像Kotlin CoroutinesFlow那样需要理解协程作用域和生命周期感知也不像Jetpack Compose那样要重学UI构建范式。对于一个以“快速上手、稳定运行”为目标的记账工具这种“老派但可靠”的技术栈恰恰是最优解。我试过用Ktor写同样的接口代码行数差不多但部署时发现Ktor默认打包成Fat Jar启动慢、内存占用高对一个只做增删改查的小接口来说纯属杀鸡用牛刀。2.2 Android工程结构的“教科书级”规范为什么目录这样组织你拿到的工程目录里app模块是核心里面是标准的Android分层结构src/main/java下是包名com.example.money下面清晰地分为activity主界面、添加页面、统计页面、adapter列表适配器、database本地SQLite备份非必需但提供了LocalRecordDao接口、networkRetrofit接口定义、API调用封装、modelRecord.java实体类字段与MySQL表完全对应、util日期格式化、金额格式化等工具类。src/main/res里layout文件夹下每个XML文件名都对应一个Activity比如activity_main.xml、activity_add_record.xmlvalues里有strings.xml所有文字常量、colors.xml主题色、dimens.xml尺寸定义。这种结构不是为了好看而是为了可维护性。比如你想把“收入”按钮的颜色从蓝色改成绿色你只需要改colors.xml里的colorPrimary所有用到这个属性的地方自动变色你想把所有“添加记录”的文字统一改成“记一笔”只需改strings.xml里的string nameaction_add这一处。这背后是Android资源系统的强大之处——它把内容文字、颜色、尺寸和逻辑Java/Kotlin代码彻底解耦。很多学生工程之所以后期改不动就是因为所有文字都硬编码在setText(添加记录)里改一个地方漏十个地方。这个工程从第一天起就规避了这个问题。2.3 数据模型的精巧平衡MySQL表设计如何兼顾简洁与扩展性后端MySQL只有一张核心表records字段设计如下字段名类型说明idINT PRIMARY KEY AUTO_INCREMENT自增主键typeTINYINT(1)1收入2支出用数字而非字符串节省空间且查询快amountDECIMAL(10,2)金额精确到分避免浮点数精度问题categoryVARCHAR(20)分类名称如“餐饮”、“交通”、“工资”noteVARCHAR(200)备注可为空dateDATE记录日期格式YYYY-MM-DDcreated_atDATETIME DEFAULT CURRENT_TIMESTAMP创建时间用于排序为什么这么设计首先type用TINYINT而不是ENUM或VARCHAR是因为ENUM在不同MySQL版本行为不一致VARCHAR则浪费索引空间DECIMAL(10,2)是财务数据的黄金标准10位总长度足够存百亿金额2位小数确保0.1 0.2 0.3category用VARCHAR而非关联外键表是因为个人记账场景下分类数量极少通常20个强行建categories表反而增加JOIN复杂度得不偿失。而created_at字段的存在是为了未来扩展留的后门——如果哪天你想按“创建时间”而非“记录日期”排序比如补录上周的账单这个字段立刻就能用上。所有这些设计都不是拍脑袋定的而是我在用这个App记了三个月账后根据真实数据分布和查询习惯迭代出来的。比如最初没有created_at后来发现补录账单时按date排序会导致新补的记录排在最前面体验很差于是加了这个字段并在Android端Record类里也同步增加了createdAt字段和对应的Gson注解。3. 核心细节解析与实操要点从APK安装到代码修改的全流程拆解3.1 APK安装与首次使用零配置真·即装即用你拿到的money.apk文件是使用Android Studio的Build Generate Signed Bundle/APK...功能用debug keystore签名生成的。这意味着它可以在任何开启了“未知来源应用安装”的安卓手机上直接安装无需连接电脑、无需ADB命令。安装过程跟微信、支付宝一模一样点击APK文件 → 点击“安装” → 完成。安装后图标是一个蓝色的钱袋名字叫“理财小管家”。首次打开你会看到一个简洁的底部导航栏首页账单列表、添加号按钮、统计饼图图标。这里没有登录页、没有引导页、没有广告页——它假设你就是这个设备的唯一使用者。账单列表默认显示最近7天的记录按日期倒序排列。每条记录显示类型绿色“收入”或红色“支出”、金额带¥符号千分位分隔、分类、备注和日期。点击任意一条会进入详情页显示完整信息。这个“零门槛”的体验是通过两个关键点实现的第一所有网络请求的Base URL在app/src/main/java/com/example/money/network/ApiConfig.java里被硬编码为http://192.168.1.100:8080/这是一个典型的局域网IP意味着它默认指向你电脑上运行的后端服务第二App启动时会尝试调用ApiService.getRecords()获取数据如果网络不通比如后端没开它不会崩溃而是静默失败并在UI上显示“暂无账单”用户可以先点“添加”按钮录入一条本地草稿这个草稿会暂存在内存里等网络恢复后再同步。这种设计让App在“无后端”状态下依然可用极大降低了初次使用的心理门槛。3.2 Android Studio工程导入与调试避开90%新手踩的坑把整个工程文件夹拖进Android Studio它会自动识别为Gradle项目。但这里有几个关键点必须手动确认否则必然报错Gradle与AGPAndroid Gradle Plugin版本匹配打开gradle/wrapper/gradle-wrapper.properties检查distributionUrl当前是https\://services.gradle.org/distributions/gradle-8.0-bin.zip再打开项目根目录的build.gradle注意不是app/build.gradle检查plugins块里的com.android.application版本当前是8.0.2。这两个版本必须严格匹配8.0的Gradle只能配8.0.x的AGP。如果你的AS版本较老比如AS Flamingo它可能自带Gradle 7.5这时你需要点击AS右上角的“Try Again”或手动下载Gradle 8.0AS会自动缓存并切换。JDK版本设置File Project Structure SDK Location确保JDK location指向的是JDK 17不是JRE不是JDK 8或11。因为AGP 8.0要求JDK 17。如果这里错了gradlew build会直接报Unsupported class file major version 61。local.properties的生成这个文件不会随工程一起提供因为它包含你本机的SDK路径绝对路径因人而异。AS第一次导入时会自动生成它内容类似sdk.dir/Users/yourname/Library/Android/sdk ndk.dir/Users/yourname/Library/Android/sdk/ndk/25.1.8937393如果你看到Cannot resolve symbol R八成是这个文件没生成或路径错了。解决方案File Project Structure SDK Location点一下“Apply”AS会强制重写local.properties。运行前的最后检查在app/src/main/AndroidManifest.xml里确认application标签内有android:usesCleartextTraffictrue。这是因为我们的后端用的是HTTP非HTTPS而Android 9.0默认禁止明文流量。没有这行所有网络请求都会被系统拦截返回Cleartext HTTP traffic to xxx not permitted。这个细节90%的教程都不会提但它是导致“代码明明写了就是连不上服务器”的头号元凶。3.3 关键代码模块详解读懂每一行才能改好它3.3.1 网络请求封装ApiService.java与ApiManager.javaApiService.java是一个接口定义了所有后端APIpublic interface ApiService { POST(api/record/add) CallApiResponse addRecord(Body Record record); GET(api/records) CallListRecord getRecords(Query(start_date) String startDate, Query(end_date) String endDate); GET(api/categories) CallListString getCategories(); }注意Body和Query注解它们告诉Retrofit如何把Java对象转换成HTTP请求体或URL参数。真正的网络调用逻辑在ApiManager.java里public class ApiManager { private static ApiService apiService; public static ApiService getApiService() { if (apiService null) { OkHttpClient client new OkHttpClient.Builder() .connectTimeout(10, TimeUnit.SECONDS) .readTimeout(10, TimeUnit.SECONDS) .build(); Retrofit retrofit new Retrofit.Builder() .baseUrl(ApiConfig.BASE_URL) .client(client) .addConverterFactory(GsonConverterFactory.create()) .build(); apiService retrofit.create(ApiService.class); } return apiService; } }这里的关键是OkHttpClient的超时设置。我设为10秒而不是默认的无限等待是为了防止用户在地铁里信号断了App卡死在“加载中”界面。GsonConverterFactory.create()负责把JSON响应自动映射到Record对象前提是Record.java里的字段名和JSON key完全一致或用SerializedName注解指定。比如后端返回{id:1,type:2,amount:28.50,category:餐饮,note:午餐,date:2024-05-20}Gson会自动把type赋值给Record.type字段。如果你改了后端JSON的key名比如把type改成transaction_type就必须在Record.java里加上SerializedName(transaction_type)否则这个字段永远是0。3.3.2 UI与数据绑定RecordAdapter.java如何把数据变成列表RecordAdapter继承自RecyclerView.Adapter是连接数据和UI的桥梁。它的核心是onBindViewHolder方法Override public void onBindViewHolder(NonNull ViewHolder holder, int position) { Record record records.get(position); holder.tvType.setText(record.getType() 1 ? 收入 : 支出); holder.tvType.setTextColor(record.getType() 1 ? Color.GREEN : Color.RED); holder.tvAmount.setText(¥ formatAmount(record.getAmount())); holder.tvCategory.setText(record.getCategory()); holder.tvNote.setText(record.getNote()); holder.tvDate.setText(record.getDate()); }这里没有用DataBinding或ViewBinding而是最原始的findViewById因为对于一个只有5个TextView的简单Item引入Binding框架反而增加编译时间和学习成本。formatAmount()是一个工具方法它把28.5格式化成28.50确保金额显示统一。holder.tvType.setTextColor(...)这行代码实现了“收入绿色、支出红色”的视觉区分这是记账App最基础的用户体验。如果你想改这个颜色直接改这里的Color.GREEN和Color.RED就行或者更好的做法是去colors.xml里定义color nameincome_color#4CAF50/color和color nameexpense_color#F44336/color然后在这里用ContextCompat.getColor(context, R.color.income_color)来获取这样颜色管理更集中。4. 实操过程与核心环节实现手把手带你跑通从后端搭建到App联调的全链路4.1 后端MySQL服务部署三步走10分钟搞定第一步安装与初始化- Windows下载XAMPPhttps://www.apachefriends.org/安装时勾选Apache和MySQL安装完成后启动控制面板启动Apache和MySQL服务。- Mac下载MAMPhttps://www.mamp.info/安装后打开点击“Start Servers”。- LinuxUbuntubash sudo apt update sudo apt install apache2 mysql-server php libapache2-mod-php php-mysql sudo systemctl start apache2 sudo systemctl start mysql第二步创建数据库与表打开浏览器访问http://localhost/phpmyadminXAMPP/MAMP或http://localhostLinux用root用户登录。新建数据库命名为money_db字符集选utf8mb4_unicode_ci。然后执行以下SQL创建records表CREATE TABLE records ( id int NOT NULL AUTO_INCREMENT, type tinyint(1) NOT NULL COMMENT 1收入,2支出, amount decimal(10,2) NOT NULL, category varchar(20) NOT NULL, note varchar(200) DEFAULT NULL, date date NOT NULL, created_at datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_unicode_ci;第三步放置PHP接口文件在Web服务器根目录下XAMPP是xampp/htdocs/MAMP是MAMP/htdocs/Linux是/var/www/html/新建一个文件夹money_api然后在里面创建add_record.php、get_records.php、get_categories.php三个文件。以add_record.php为例内容如下?php header(Content-Type: application/json; charsetutf-8); header(Access-Control-Allow-Origin: *); header(Access-Control-Allow-Methods: POST, GET, OPTIONS); header(Access-Control-Allow-Headers: Content-Type); $host localhost; $dbname money_db; $username root; $password ; // XAMPP/MAMP默认为空Linux需设为你mysql的密码 try { $pdo new PDO(mysql:host$host;dbname$dbname;charsetutf8mb4, $username, $password); $pdo-setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $input json_decode(file_get_contents(php://input), true); $type (int)$input[type]; $amount (float)$input[amount]; $category trim($input[category]); $note trim($input[note]); $date $input[date]; $stmt $pdo-prepare(INSERT INTO records (type, amount, category, note, date) VALUES (?, ?, ?, ?, ?)); $stmt-execute([$type, $amount, $category, $note, $date]); echo json_encode([success true, id $pdo-lastInsertId()]); } catch (PDOException $e) { http_response_code(500); echo json_encode([success false, error Database error: . $e-getMessage()]); } ?提示Access-Control-Allow-Origin: *这行是关键它允许来自任何域名包括file:///协议的APK的跨域请求。生产环境请替换为你的App实际域名。4.2 Android端配置与联调让手机和电脑“说上话”第一步确认网络连通性手机和电脑必须在同一局域网同一个Wi-Fi。在手机浏览器里输入http://192.168.1.100:8080/money_api/get_categories.php把192.168.1.100换成你电脑的实际IP如果返回[]或[餐饮,交通]这样的JSON说明网络和PHP都通了。怎么查电脑IPWindows按WinR输入cmd回车输入ipconfig找IPv4 地址Mac在“系统设置网络”里看Linux用ip addr show | grep inet 。第二步修改App的Base URL打开app/src/main/java/com/example/money/network/ApiConfig.java把BASE_URL从http://192.168.1.100:8080/改成你电脑的IP比如http://192.168.31.123:8080/。注意末尾的/不能少这是Retrofit拼接URL的基础。第三步真机调试与日志观察用USB线连接手机和电脑在AS里点击绿色三角形运行。AS会自动安装APK并启动。打开Logcat窗口Alt6在过滤器里输入Retrofit或ApiManager你会看到类似这样的日志D/ApiManager: Request URL: http://192.168.31.123:8080/api/records?start_date2024-05-14end_date2024-05-20 D/ApiManager: Response Code: 200 D/ApiManager: Response Body: [{id:1,type:2,amount:28.50,category:餐饮,note:午餐,date:2024-05-20}]如果看到Response Code: 200恭喜联调成功如果看到404检查PHP文件路径如果看到java.net.ConnectException检查IP是否正确、防火墙是否阻止了8080端口Windows Defender防火墙需放行Apache。4.3 功能扩展实战给App加一个“搜索”功能现在App只能看最近7天的账单如果想查“上个月的交通费”就得手动翻页。我们来加一个搜索框。步骤如下修改布局在app/src/main/res/layout/activity_main.xml的RecyclerView上方添加一个SearchViewxml androidx.appcompat.widget.SearchView android:idid/searchView android:layout_widthmatch_parent android:layout_heightwrap_content android:layout_margin8dp app:queryHint搜索分类或备注... /修改Activity逻辑在MainActivity.java的onCreate()里找到initViews()方法添加javaSearchView searchView findViewById(R.id.searchView);searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {Overridepublic boolean onQuerySubmit(String query) {// 用户按下搜索键loadRecordsByKeyword(query);return true;}Overridepublic boolean onQueryTextSubmit(String query) {// 用户提交搜索loadRecordsByKeyword(query);return true;}});添加新的API接口在PHP端新建search_records.phpphpprepare(SELECT * FROM records WHERE category LIKE ? OR note LIKE ? ORDER BY date DESC); $likeKeyword %{$keyword}%; $stmt-execute([$likeKeyword, $likeKeyword]); echo json_encode($stmt-fetchAll(PDO::FETCH_ASSOC)); ?修改Android端API在ApiService.java里加一行java GET(api/records/search) CallListRecord searchRecords(Query(q) String keyword);实现loadRecordsByKeyword()方法调用这个新接口更新Adapter。整个过程从改XML到写PHP不到20分钟。这就是一个良好工程结构的魅力新增功能改动是局部的、可预测的而不是牵一发而动全身。5. 常见问题与排查技巧实录那些让我熬夜到凌晨三点的坑5.1 网络请求失败的四大元凶与速查表现象最可能原因排查步骤解决方案Logcat显示java.net.UnknownHostExceptionApp里的Base URL IP写错了或电脑没开Wi-Fi1. 在手机浏览器访问http://[电脑IP]:80802. 在电脑终端ping手机IP确认IP关闭电脑防火墙临时测试Logcat显示java.net.SocketTimeoutException后端PHP脚本执行超时或网络延迟高1. 直接在浏览器访问http://[IP]/money_api/get_records.php2. 看页面加载是否缓慢检查PHP脚本是否有死循环或MySQL查询是否缺少索引Logcat显示Expected BEGIN_ARRAY but was BEGIN_OBJECT后端返回的JSON格式与Android端期望的List不匹配1. 用浏览器访问API看返回是{}还是[]2. 对比CallListRecord和实际JSON如果后端返回单个对象Android端改为CallRecord如果返回数组确保PHP用json_encode($array)Logcat显示Cleartext HTTP traffic not permittedAndroid 9.0禁止HTTP明文流量1. 查AndroidManifest.xml2. 查build.gradle里的targetSdkVersion在application标签里加android:usesCleartextTraffictrue注意Cleartext HTTP traffic not permitted这个问题我第一次遇到时花了整整一个下午。因为我的targetSdkVersion是33Android 13而usesCleartextTraffic这个属性在targetSdkVersion 28时才强制生效。很多人只改了minSdkVersion忘了改targetSdkVersion结果以为是代码问题其实是配置问题。5.2 编译与构建失败的高频场景Failed to find Build Tools revision 34.0.0这是AS在build.gradle里指定了一个你本地没安装的Build Tools版本。打开AS的Settings Appearance Behavior System Settings Android SDK SDK Tools勾选Show Package Details展开Android SDK Build-Tools安装你build.gradle里写的版本比如34.0.0或者更简单把build.gradle里的buildToolsVersion 34.0.0这一行删掉AS会自动用最新版。Duplicate class androidx.core.R$attr found in modules这是依赖冲突通常是appcompat和core库版本不一致。打开app/build.gradle检查所有implementation androidx.xxx的版本号确保它们都用同一个大版本比如1.10.1。最省事的办法是在dependencies块顶部加一行gradle implementation androidx.appcompat:appcompat:1.6.1 implementation androidx.core:core:1.12.0然后SyncAS会自动帮你解决传递依赖。R cannot be resolved除了前面说的local.properties问题另一个常见原因是XML布局文件里有语法错误比如一个TextView标签没闭合。AS不会直接告诉你哪行错了只会报R找不到。解决方案逐个打开res/layout/*.xml看AS编辑器左下角有没有黄色警告灯点开它就能定位到错误行。5.3 实操心得那些文档里永远不会写的“潜规则”不要迷信“最新版”我曾经把Retrofit从2.9.0升级到2.10.0结果发现2.10.0对Android 14API 34的支持有问题Call.enqueue()回调不触发。最后降级回2.9.0问题消失。结论生产环境用经过大量验证的稳定版而不是刚发布的“最新版”。proguard-rules.pro不是摆设这个文件里有两行至关重要-keep class com.google.gson.** { *; } -keep class com.example.money.model.** { *; }第一行保护Gson的所有类防止混淆后JSON解析失败第二行保护你自己的model包确保Record类的字段名不被混淆。如果你删了这两行APK在Release模式下运行大概率会闪退因为Gson找不到amount字段了它被混淆成a了。真机调试比模拟器靠谱十倍模拟器的网络环境是虚拟的有时会表现出和真机完全不同的行为。比如模拟器里http://10.0.2.2可以访问宿主电脑但真机必须用局域网IP。所以从第一天起就用真机调试。买一根20块钱的USB-C数据线比在模拟器里折腾三天强。Git忽略local.properties是铁律这个文件里有你本机的SDK路径是绝对不能提交到Git的。.gitignore里已经写了local.properties但有时候新手会手贱把它删了然后一git push整个团队的构建就崩了。所以每次新建分支第一件事就是git status确认local.properties不在待提交列表里。6. 总结与延伸这个工程能带你走多远这个“理财小管家”工程它的终点不是一个功能完备的商业App而是一个坚实的技术跳板。你从这里出发可以向多个方向延伸而且每一步都踩在真实的技术痛点上。比如你想加“图表统计”那就要学MPAndroidChart库研究如何把ListRecord转换成PieEntry数组你想加“数据导出”那就得学Android的FileProvider和CSV文件生成你想把后端换成Spring Boot那就要理解RestController、RequestBody、MyBatis的Select注解以及如何用Postman测试接口。这些都不是空中楼阁而是你在这个工程里已经熟悉的Record实体、getRecords()方法、amount字段的自然延伸。我自己用这个工程做了三次迭代第一次只是让它跑起来第二次我加了夜间模式学会了AppCompatDelegate.setDefaultNightMode()和values-night/colors.xml第三次我把后端换成了一个极简的Node.js Express服务只用了50行代码体会到了JavaScript处理JSON的丝滑。每一次迭代我都不是在学一个孤立的知识点而是在解决一个具体的、看得见摸得着的问题。这才是技术成长最健康的路径。最后分享一个小技巧当你想修改App图标时不要只改mipmap-xxx文件夹下的PNG。去app/src/main/res/values/ic_launcher_background.xml里把color nameic_launcher_background#FFFFFF/color改成你喜欢的背景色再去app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml里把android:foreground指向的新图标确保它是一个透明背景的SVG或PNG。这样你的图标在深色模式和浅色模式下都能完美适配。这个细节很多教程都不会提但它决定了你的App在用户眼里是不是一个“用心做的产品”而不仅仅是一个“能跑的Demo”。本文还有配套的精品资源点击获取简介这个记账App能直接在安卓手机上安装使用点开就能记收支、查账单、看分类统计。附带已打包好的money.apk文件无需编译扫码或传输到手机即可安装。源码用Android Studio开发结构清晰包含app模块、gradle构建配置build.gradle、gradlew等、资源文件、混淆规则和本地环境配置local.properties。后端用MySQL存数据需要自己搭个简单服务端比如PHPMySQL或Java Spring Boot接口App里已预留网络请求逻辑和数据库字段映射。适合学生做毕业设计或课程作业导入AS就能跑改UI、调接口、换主题都方便。没有视频教程也不带说明书所有代码和配置都是真实可调试的原始工程文件不是截图或伪代码。本文还有配套的精品资源点击获取
安卓个人记账App完整可运行工程:含APK安装包、MySQL后端对接源码与AS开发环境
本文还有配套的精品资源点击获取简介这个记账App能直接在安卓手机上安装使用点开就能记收支、查账单、看分类统计。附带已打包好的money.apk文件无需编译扫码或传输到手机即可安装。源码用Android Studio开发结构清晰包含app模块、gradle构建配置build.gradle、gradlew等、资源文件、混淆规则和本地环境配置local.properties。后端用MySQL存数据需要自己搭个简单服务端比如PHPMySQL或Java Spring Boot接口App里已预留网络请求逻辑和数据库字段映射。适合学生做毕业设计或课程作业导入AS就能跑改UI、调接口、换主题都方便。没有视频教程也不带说明书所有代码和配置都是真实可调试的原始工程文件不是截图或伪代码。1. 项目概述一个真正能“装上就用”的记账工具不是Demo是完整工程你有没有试过在GitHub上搜“安卓记账App源码”点开十几个仓库结果全是只有MainActivity.java、一个空Activity、几行Toast弹窗的“教学示例”或者更糟——代码里写着// TODO: 连接服务器连个网络请求的影子都没有更别说MySQL字段怎么映射、JSON怎么解析、错误怎么处理。这个项目不是那样。它是一套从手机安装包APK、到Android Studio可调试工程、再到后端数据存储逻辑全部打通的真实闭环。我把它叫作“理财小管家”不是因为它功能多么炫酷而是它真的能解决一个具体问题你今天午饭花了28块随手点两下记录完成月底想看看餐饮花了多少打开统计页数字就列在那里换新手机了把APK传过去安装登录本地账户无云同步所有历史账单都在。它不依赖任何第三方服务不强制注册不收集隐私所有数据存在你自己的MySQL数据库里——当然前提是你要搭个简单的后端接口但这个接口我后面会给你一份实测可用的PHP脚本50行以内配好数据库就能跑。核心关键词“安卓记账源码”、“MySQL记账App”、“Android Studio工程”、“个人理财App”每一个都不是虚的。它不是“含MySQL字样”的假把式而是app/src/main/java/com/example/money/network/ApiService.java里明明白白写着POST(api/record/add)build.gradle里配置着implementation androidx.lifecycle:lifecycle-viewmodel:2.6.2和implementation com.squareup.retrofit2:retrofit:2.9.0proguard-rules.pro里为Gson和Retrofit加了专门的混淆保留规则。它也不是那种“导入AS就报错17个”的残缺工程——.gitignore里排除了local.properties和.idea/gradle.properties里定义了org.gradle.jvmargs-Xmx4g -XX:MaxMetaspaceSize512m这些都是我在自己笔记本i5-1135G7 16GB内存上反复验证过的最小可行配置。学生拿去做毕设不用再花三天去查“为什么AS提示找不到R文件”也不用纠结“为什么模拟器里按钮点了没反应”因为整个流程——从扫码安装APK到修改strings.xml改APP名字再到把后端地址从http://192.168.1.100:8080改成你自己的服务器IP——每一步都是平滑的、可预期的。它不教你“什么是MVC”但它让你在改完一个分类图标后立刻看到手机界面上那个小图标变了它不讲“RESTful API设计原则”但它让你在把ApiService.addRecord()里的URL改错一个字符后Logcat里清清楚楚打出java.net.UnknownHostException然后你立刻知道该去检查网络权限或服务器状态。这就是它的价值它把“学习Android开发”这件事从抽象的概念拉回到手指在键盘上敲出一行有效代码、在屏幕上看到一个真实反馈的具体动作。2. 整体架构与设计思路为什么选择这套组合而不是其他方案2.1 前后端分离的务实选择轻量级PHP接口 原生Android客户端很多初学者一上来就想搞“Spring Boot Vue MySQL”全家桶结果光是环境配置就卡死在JDK版本和Maven镜像源上。这个项目反其道而行之后端只用最基础的PHPMySQL组合。原因很实在第一PHP运行环境极简Windows上一个XAMPP、Mac上一个MAMP、Linux上一条sudo apt install php mysql-server php-mysql命令搞定不需要理解Tomcat容器、Spring上下文、Bean生命周期这些概念第二PHP处理HTTP请求和数据库操作的语法极其直白比如插入一条收支记录核心代码就是三行$stmt $pdo-prepare(INSERT INTO records (type, amount, category, note, date) VALUES (?, ?, ?, ?, ?)); $stmt-execute([$type, $amount, $category, $note, $date]); echo json_encode([success true, id $pdo-lastInsertId()]);没有复杂的注解、没有XML配置、没有依赖注入你甚至可以把这段代码直接复制进你的add_record.php文件里改几个变量名就能跑。而Android端我们用Retrofit 2.9.0封装网络请求用Gson 2.10.1解析JSON这是目前Android社区最成熟、文档最全、出问题最容易搜到答案的组合。它不像Kotlin CoroutinesFlow那样需要理解协程作用域和生命周期感知也不像Jetpack Compose那样要重学UI构建范式。对于一个以“快速上手、稳定运行”为目标的记账工具这种“老派但可靠”的技术栈恰恰是最优解。我试过用Ktor写同样的接口代码行数差不多但部署时发现Ktor默认打包成Fat Jar启动慢、内存占用高对一个只做增删改查的小接口来说纯属杀鸡用牛刀。2.2 Android工程结构的“教科书级”规范为什么目录这样组织你拿到的工程目录里app模块是核心里面是标准的Android分层结构src/main/java下是包名com.example.money下面清晰地分为activity主界面、添加页面、统计页面、adapter列表适配器、database本地SQLite备份非必需但提供了LocalRecordDao接口、networkRetrofit接口定义、API调用封装、modelRecord.java实体类字段与MySQL表完全对应、util日期格式化、金额格式化等工具类。src/main/res里layout文件夹下每个XML文件名都对应一个Activity比如activity_main.xml、activity_add_record.xmlvalues里有strings.xml所有文字常量、colors.xml主题色、dimens.xml尺寸定义。这种结构不是为了好看而是为了可维护性。比如你想把“收入”按钮的颜色从蓝色改成绿色你只需要改colors.xml里的colorPrimary所有用到这个属性的地方自动变色你想把所有“添加记录”的文字统一改成“记一笔”只需改strings.xml里的string nameaction_add这一处。这背后是Android资源系统的强大之处——它把内容文字、颜色、尺寸和逻辑Java/Kotlin代码彻底解耦。很多学生工程之所以后期改不动就是因为所有文字都硬编码在setText(添加记录)里改一个地方漏十个地方。这个工程从第一天起就规避了这个问题。2.3 数据模型的精巧平衡MySQL表设计如何兼顾简洁与扩展性后端MySQL只有一张核心表records字段设计如下字段名类型说明idINT PRIMARY KEY AUTO_INCREMENT自增主键typeTINYINT(1)1收入2支出用数字而非字符串节省空间且查询快amountDECIMAL(10,2)金额精确到分避免浮点数精度问题categoryVARCHAR(20)分类名称如“餐饮”、“交通”、“工资”noteVARCHAR(200)备注可为空dateDATE记录日期格式YYYY-MM-DDcreated_atDATETIME DEFAULT CURRENT_TIMESTAMP创建时间用于排序为什么这么设计首先type用TINYINT而不是ENUM或VARCHAR是因为ENUM在不同MySQL版本行为不一致VARCHAR则浪费索引空间DECIMAL(10,2)是财务数据的黄金标准10位总长度足够存百亿金额2位小数确保0.1 0.2 0.3category用VARCHAR而非关联外键表是因为个人记账场景下分类数量极少通常20个强行建categories表反而增加JOIN复杂度得不偿失。而created_at字段的存在是为了未来扩展留的后门——如果哪天你想按“创建时间”而非“记录日期”排序比如补录上周的账单这个字段立刻就能用上。所有这些设计都不是拍脑袋定的而是我在用这个App记了三个月账后根据真实数据分布和查询习惯迭代出来的。比如最初没有created_at后来发现补录账单时按date排序会导致新补的记录排在最前面体验很差于是加了这个字段并在Android端Record类里也同步增加了createdAt字段和对应的Gson注解。3. 核心细节解析与实操要点从APK安装到代码修改的全流程拆解3.1 APK安装与首次使用零配置真·即装即用你拿到的money.apk文件是使用Android Studio的Build Generate Signed Bundle/APK...功能用debug keystore签名生成的。这意味着它可以在任何开启了“未知来源应用安装”的安卓手机上直接安装无需连接电脑、无需ADB命令。安装过程跟微信、支付宝一模一样点击APK文件 → 点击“安装” → 完成。安装后图标是一个蓝色的钱袋名字叫“理财小管家”。首次打开你会看到一个简洁的底部导航栏首页账单列表、添加号按钮、统计饼图图标。这里没有登录页、没有引导页、没有广告页——它假设你就是这个设备的唯一使用者。账单列表默认显示最近7天的记录按日期倒序排列。每条记录显示类型绿色“收入”或红色“支出”、金额带¥符号千分位分隔、分类、备注和日期。点击任意一条会进入详情页显示完整信息。这个“零门槛”的体验是通过两个关键点实现的第一所有网络请求的Base URL在app/src/main/java/com/example/money/network/ApiConfig.java里被硬编码为http://192.168.1.100:8080/这是一个典型的局域网IP意味着它默认指向你电脑上运行的后端服务第二App启动时会尝试调用ApiService.getRecords()获取数据如果网络不通比如后端没开它不会崩溃而是静默失败并在UI上显示“暂无账单”用户可以先点“添加”按钮录入一条本地草稿这个草稿会暂存在内存里等网络恢复后再同步。这种设计让App在“无后端”状态下依然可用极大降低了初次使用的心理门槛。3.2 Android Studio工程导入与调试避开90%新手踩的坑把整个工程文件夹拖进Android Studio它会自动识别为Gradle项目。但这里有几个关键点必须手动确认否则必然报错Gradle与AGPAndroid Gradle Plugin版本匹配打开gradle/wrapper/gradle-wrapper.properties检查distributionUrl当前是https\://services.gradle.org/distributions/gradle-8.0-bin.zip再打开项目根目录的build.gradle注意不是app/build.gradle检查plugins块里的com.android.application版本当前是8.0.2。这两个版本必须严格匹配8.0的Gradle只能配8.0.x的AGP。如果你的AS版本较老比如AS Flamingo它可能自带Gradle 7.5这时你需要点击AS右上角的“Try Again”或手动下载Gradle 8.0AS会自动缓存并切换。JDK版本设置File Project Structure SDK Location确保JDK location指向的是JDK 17不是JRE不是JDK 8或11。因为AGP 8.0要求JDK 17。如果这里错了gradlew build会直接报Unsupported class file major version 61。local.properties的生成这个文件不会随工程一起提供因为它包含你本机的SDK路径绝对路径因人而异。AS第一次导入时会自动生成它内容类似sdk.dir/Users/yourname/Library/Android/sdk ndk.dir/Users/yourname/Library/Android/sdk/ndk/25.1.8937393如果你看到Cannot resolve symbol R八成是这个文件没生成或路径错了。解决方案File Project Structure SDK Location点一下“Apply”AS会强制重写local.properties。运行前的最后检查在app/src/main/AndroidManifest.xml里确认application标签内有android:usesCleartextTraffictrue。这是因为我们的后端用的是HTTP非HTTPS而Android 9.0默认禁止明文流量。没有这行所有网络请求都会被系统拦截返回Cleartext HTTP traffic to xxx not permitted。这个细节90%的教程都不会提但它是导致“代码明明写了就是连不上服务器”的头号元凶。3.3 关键代码模块详解读懂每一行才能改好它3.3.1 网络请求封装ApiService.java与ApiManager.javaApiService.java是一个接口定义了所有后端APIpublic interface ApiService { POST(api/record/add) CallApiResponse addRecord(Body Record record); GET(api/records) CallListRecord getRecords(Query(start_date) String startDate, Query(end_date) String endDate); GET(api/categories) CallListString getCategories(); }注意Body和Query注解它们告诉Retrofit如何把Java对象转换成HTTP请求体或URL参数。真正的网络调用逻辑在ApiManager.java里public class ApiManager { private static ApiService apiService; public static ApiService getApiService() { if (apiService null) { OkHttpClient client new OkHttpClient.Builder() .connectTimeout(10, TimeUnit.SECONDS) .readTimeout(10, TimeUnit.SECONDS) .build(); Retrofit retrofit new Retrofit.Builder() .baseUrl(ApiConfig.BASE_URL) .client(client) .addConverterFactory(GsonConverterFactory.create()) .build(); apiService retrofit.create(ApiService.class); } return apiService; } }这里的关键是OkHttpClient的超时设置。我设为10秒而不是默认的无限等待是为了防止用户在地铁里信号断了App卡死在“加载中”界面。GsonConverterFactory.create()负责把JSON响应自动映射到Record对象前提是Record.java里的字段名和JSON key完全一致或用SerializedName注解指定。比如后端返回{id:1,type:2,amount:28.50,category:餐饮,note:午餐,date:2024-05-20}Gson会自动把type赋值给Record.type字段。如果你改了后端JSON的key名比如把type改成transaction_type就必须在Record.java里加上SerializedName(transaction_type)否则这个字段永远是0。3.3.2 UI与数据绑定RecordAdapter.java如何把数据变成列表RecordAdapter继承自RecyclerView.Adapter是连接数据和UI的桥梁。它的核心是onBindViewHolder方法Override public void onBindViewHolder(NonNull ViewHolder holder, int position) { Record record records.get(position); holder.tvType.setText(record.getType() 1 ? 收入 : 支出); holder.tvType.setTextColor(record.getType() 1 ? Color.GREEN : Color.RED); holder.tvAmount.setText(¥ formatAmount(record.getAmount())); holder.tvCategory.setText(record.getCategory()); holder.tvNote.setText(record.getNote()); holder.tvDate.setText(record.getDate()); }这里没有用DataBinding或ViewBinding而是最原始的findViewById因为对于一个只有5个TextView的简单Item引入Binding框架反而增加编译时间和学习成本。formatAmount()是一个工具方法它把28.5格式化成28.50确保金额显示统一。holder.tvType.setTextColor(...)这行代码实现了“收入绿色、支出红色”的视觉区分这是记账App最基础的用户体验。如果你想改这个颜色直接改这里的Color.GREEN和Color.RED就行或者更好的做法是去colors.xml里定义color nameincome_color#4CAF50/color和color nameexpense_color#F44336/color然后在这里用ContextCompat.getColor(context, R.color.income_color)来获取这样颜色管理更集中。4. 实操过程与核心环节实现手把手带你跑通从后端搭建到App联调的全链路4.1 后端MySQL服务部署三步走10分钟搞定第一步安装与初始化- Windows下载XAMPPhttps://www.apachefriends.org/安装时勾选Apache和MySQL安装完成后启动控制面板启动Apache和MySQL服务。- Mac下载MAMPhttps://www.mamp.info/安装后打开点击“Start Servers”。- LinuxUbuntubash sudo apt update sudo apt install apache2 mysql-server php libapache2-mod-php php-mysql sudo systemctl start apache2 sudo systemctl start mysql第二步创建数据库与表打开浏览器访问http://localhost/phpmyadminXAMPP/MAMP或http://localhostLinux用root用户登录。新建数据库命名为money_db字符集选utf8mb4_unicode_ci。然后执行以下SQL创建records表CREATE TABLE records ( id int NOT NULL AUTO_INCREMENT, type tinyint(1) NOT NULL COMMENT 1收入,2支出, amount decimal(10,2) NOT NULL, category varchar(20) NOT NULL, note varchar(200) DEFAULT NULL, date date NOT NULL, created_at datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_unicode_ci;第三步放置PHP接口文件在Web服务器根目录下XAMPP是xampp/htdocs/MAMP是MAMP/htdocs/Linux是/var/www/html/新建一个文件夹money_api然后在里面创建add_record.php、get_records.php、get_categories.php三个文件。以add_record.php为例内容如下?php header(Content-Type: application/json; charsetutf-8); header(Access-Control-Allow-Origin: *); header(Access-Control-Allow-Methods: POST, GET, OPTIONS); header(Access-Control-Allow-Headers: Content-Type); $host localhost; $dbname money_db; $username root; $password ; // XAMPP/MAMP默认为空Linux需设为你mysql的密码 try { $pdo new PDO(mysql:host$host;dbname$dbname;charsetutf8mb4, $username, $password); $pdo-setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $input json_decode(file_get_contents(php://input), true); $type (int)$input[type]; $amount (float)$input[amount]; $category trim($input[category]); $note trim($input[note]); $date $input[date]; $stmt $pdo-prepare(INSERT INTO records (type, amount, category, note, date) VALUES (?, ?, ?, ?, ?)); $stmt-execute([$type, $amount, $category, $note, $date]); echo json_encode([success true, id $pdo-lastInsertId()]); } catch (PDOException $e) { http_response_code(500); echo json_encode([success false, error Database error: . $e-getMessage()]); } ?提示Access-Control-Allow-Origin: *这行是关键它允许来自任何域名包括file:///协议的APK的跨域请求。生产环境请替换为你的App实际域名。4.2 Android端配置与联调让手机和电脑“说上话”第一步确认网络连通性手机和电脑必须在同一局域网同一个Wi-Fi。在手机浏览器里输入http://192.168.1.100:8080/money_api/get_categories.php把192.168.1.100换成你电脑的实际IP如果返回[]或[餐饮,交通]这样的JSON说明网络和PHP都通了。怎么查电脑IPWindows按WinR输入cmd回车输入ipconfig找IPv4 地址Mac在“系统设置网络”里看Linux用ip addr show | grep inet 。第二步修改App的Base URL打开app/src/main/java/com/example/money/network/ApiConfig.java把BASE_URL从http://192.168.1.100:8080/改成你电脑的IP比如http://192.168.31.123:8080/。注意末尾的/不能少这是Retrofit拼接URL的基础。第三步真机调试与日志观察用USB线连接手机和电脑在AS里点击绿色三角形运行。AS会自动安装APK并启动。打开Logcat窗口Alt6在过滤器里输入Retrofit或ApiManager你会看到类似这样的日志D/ApiManager: Request URL: http://192.168.31.123:8080/api/records?start_date2024-05-14end_date2024-05-20 D/ApiManager: Response Code: 200 D/ApiManager: Response Body: [{id:1,type:2,amount:28.50,category:餐饮,note:午餐,date:2024-05-20}]如果看到Response Code: 200恭喜联调成功如果看到404检查PHP文件路径如果看到java.net.ConnectException检查IP是否正确、防火墙是否阻止了8080端口Windows Defender防火墙需放行Apache。4.3 功能扩展实战给App加一个“搜索”功能现在App只能看最近7天的账单如果想查“上个月的交通费”就得手动翻页。我们来加一个搜索框。步骤如下修改布局在app/src/main/res/layout/activity_main.xml的RecyclerView上方添加一个SearchViewxml androidx.appcompat.widget.SearchView android:idid/searchView android:layout_widthmatch_parent android:layout_heightwrap_content android:layout_margin8dp app:queryHint搜索分类或备注... /修改Activity逻辑在MainActivity.java的onCreate()里找到initViews()方法添加javaSearchView searchView findViewById(R.id.searchView);searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {Overridepublic boolean onQuerySubmit(String query) {// 用户按下搜索键loadRecordsByKeyword(query);return true;}Overridepublic boolean onQueryTextSubmit(String query) {// 用户提交搜索loadRecordsByKeyword(query);return true;}});添加新的API接口在PHP端新建search_records.phpphpprepare(SELECT * FROM records WHERE category LIKE ? OR note LIKE ? ORDER BY date DESC); $likeKeyword %{$keyword}%; $stmt-execute([$likeKeyword, $likeKeyword]); echo json_encode($stmt-fetchAll(PDO::FETCH_ASSOC)); ?修改Android端API在ApiService.java里加一行java GET(api/records/search) CallListRecord searchRecords(Query(q) String keyword);实现loadRecordsByKeyword()方法调用这个新接口更新Adapter。整个过程从改XML到写PHP不到20分钟。这就是一个良好工程结构的魅力新增功能改动是局部的、可预测的而不是牵一发而动全身。5. 常见问题与排查技巧实录那些让我熬夜到凌晨三点的坑5.1 网络请求失败的四大元凶与速查表现象最可能原因排查步骤解决方案Logcat显示java.net.UnknownHostExceptionApp里的Base URL IP写错了或电脑没开Wi-Fi1. 在手机浏览器访问http://[电脑IP]:80802. 在电脑终端ping手机IP确认IP关闭电脑防火墙临时测试Logcat显示java.net.SocketTimeoutException后端PHP脚本执行超时或网络延迟高1. 直接在浏览器访问http://[IP]/money_api/get_records.php2. 看页面加载是否缓慢检查PHP脚本是否有死循环或MySQL查询是否缺少索引Logcat显示Expected BEGIN_ARRAY but was BEGIN_OBJECT后端返回的JSON格式与Android端期望的List不匹配1. 用浏览器访问API看返回是{}还是[]2. 对比CallListRecord和实际JSON如果后端返回单个对象Android端改为CallRecord如果返回数组确保PHP用json_encode($array)Logcat显示Cleartext HTTP traffic not permittedAndroid 9.0禁止HTTP明文流量1. 查AndroidManifest.xml2. 查build.gradle里的targetSdkVersion在application标签里加android:usesCleartextTraffictrue注意Cleartext HTTP traffic not permitted这个问题我第一次遇到时花了整整一个下午。因为我的targetSdkVersion是33Android 13而usesCleartextTraffic这个属性在targetSdkVersion 28时才强制生效。很多人只改了minSdkVersion忘了改targetSdkVersion结果以为是代码问题其实是配置问题。5.2 编译与构建失败的高频场景Failed to find Build Tools revision 34.0.0这是AS在build.gradle里指定了一个你本地没安装的Build Tools版本。打开AS的Settings Appearance Behavior System Settings Android SDK SDK Tools勾选Show Package Details展开Android SDK Build-Tools安装你build.gradle里写的版本比如34.0.0或者更简单把build.gradle里的buildToolsVersion 34.0.0这一行删掉AS会自动用最新版。Duplicate class androidx.core.R$attr found in modules这是依赖冲突通常是appcompat和core库版本不一致。打开app/build.gradle检查所有implementation androidx.xxx的版本号确保它们都用同一个大版本比如1.10.1。最省事的办法是在dependencies块顶部加一行gradle implementation androidx.appcompat:appcompat:1.6.1 implementation androidx.core:core:1.12.0然后SyncAS会自动帮你解决传递依赖。R cannot be resolved除了前面说的local.properties问题另一个常见原因是XML布局文件里有语法错误比如一个TextView标签没闭合。AS不会直接告诉你哪行错了只会报R找不到。解决方案逐个打开res/layout/*.xml看AS编辑器左下角有没有黄色警告灯点开它就能定位到错误行。5.3 实操心得那些文档里永远不会写的“潜规则”不要迷信“最新版”我曾经把Retrofit从2.9.0升级到2.10.0结果发现2.10.0对Android 14API 34的支持有问题Call.enqueue()回调不触发。最后降级回2.9.0问题消失。结论生产环境用经过大量验证的稳定版而不是刚发布的“最新版”。proguard-rules.pro不是摆设这个文件里有两行至关重要-keep class com.google.gson.** { *; } -keep class com.example.money.model.** { *; }第一行保护Gson的所有类防止混淆后JSON解析失败第二行保护你自己的model包确保Record类的字段名不被混淆。如果你删了这两行APK在Release模式下运行大概率会闪退因为Gson找不到amount字段了它被混淆成a了。真机调试比模拟器靠谱十倍模拟器的网络环境是虚拟的有时会表现出和真机完全不同的行为。比如模拟器里http://10.0.2.2可以访问宿主电脑但真机必须用局域网IP。所以从第一天起就用真机调试。买一根20块钱的USB-C数据线比在模拟器里折腾三天强。Git忽略local.properties是铁律这个文件里有你本机的SDK路径是绝对不能提交到Git的。.gitignore里已经写了local.properties但有时候新手会手贱把它删了然后一git push整个团队的构建就崩了。所以每次新建分支第一件事就是git status确认local.properties不在待提交列表里。6. 总结与延伸这个工程能带你走多远这个“理财小管家”工程它的终点不是一个功能完备的商业App而是一个坚实的技术跳板。你从这里出发可以向多个方向延伸而且每一步都踩在真实的技术痛点上。比如你想加“图表统计”那就要学MPAndroidChart库研究如何把ListRecord转换成PieEntry数组你想加“数据导出”那就得学Android的FileProvider和CSV文件生成你想把后端换成Spring Boot那就要理解RestController、RequestBody、MyBatis的Select注解以及如何用Postman测试接口。这些都不是空中楼阁而是你在这个工程里已经熟悉的Record实体、getRecords()方法、amount字段的自然延伸。我自己用这个工程做了三次迭代第一次只是让它跑起来第二次我加了夜间模式学会了AppCompatDelegate.setDefaultNightMode()和values-night/colors.xml第三次我把后端换成了一个极简的Node.js Express服务只用了50行代码体会到了JavaScript处理JSON的丝滑。每一次迭代我都不是在学一个孤立的知识点而是在解决一个具体的、看得见摸得着的问题。这才是技术成长最健康的路径。最后分享一个小技巧当你想修改App图标时不要只改mipmap-xxx文件夹下的PNG。去app/src/main/res/values/ic_launcher_background.xml里把color nameic_launcher_background#FFFFFF/color改成你喜欢的背景色再去app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml里把android:foreground指向的新图标确保它是一个透明背景的SVG或PNG。这样你的图标在深色模式和浅色模式下都能完美适配。这个细节很多教程都不会提但它决定了你的App在用户眼里是不是一个“用心做的产品”而不仅仅是一个“能跑的Demo”。本文还有配套的精品资源点击获取简介这个记账App能直接在安卓手机上安装使用点开就能记收支、查账单、看分类统计。附带已打包好的money.apk文件无需编译扫码或传输到手机即可安装。源码用Android Studio开发结构清晰包含app模块、gradle构建配置build.gradle、gradlew等、资源文件、混淆规则和本地环境配置local.properties。后端用MySQL存数据需要自己搭个简单服务端比如PHPMySQL或Java Spring Boot接口App里已预留网络请求逻辑和数据库字段映射。适合学生做毕业设计或课程作业导入AS就能跑改UI、调接口、换主题都方便。没有视频教程也不带说明书所有代码和配置都是真实可调试的原始工程文件不是截图或伪代码。本文还有配套的精品资源点击获取