
简介这套基于Android Studio实现的订餐系统完整源码面向安卓初学者、移动应用课程设计者及外卖点餐类项目开发者演示了Material Design设计规范下从用户注册登录到下单分享的完整业务链路。应用包含欢迎页、底部三栏导航首页/购物车/我的、美食列表与详情、可折叠标题栏、多次添加购物车、长按删除商品、提交订单后下拉刷新、个人中心侧滑菜单、订单查看与删除以及调用系统分享等功能模块划分清晰便于学习和二次开发。包内共690个文件压缩包30.69MB以java源码、xml界面布局、json配置、gradle构建文件为主体同时包含class与dex编译产物、png/jpg图片等导入Android Studio即可运行调试。目前已有13187人学习/下载适合作为课程设计、毕业设计参考也适合想通过完整实战项目提升Android开发能力的读者。1. 从需求到落地的整体设计思路1.1 为什么选订餐系统作为项目载体我先交代一下背景。这个项目是我给一个准备找工作的学弟做的参考项目也是很多计算机专业学生课程设计的经典选题。订餐系统之所以值得写是因为它几乎覆盖了 Android 开发的全部基本功UI 布局、事件监听、数据持久化、列表展示、页面跳转、SharedPreferences 应用甚至还能延伸到网络请求和第三方 SDK 接入。一个项目做下来从界面到逻辑到数据存储整条链路都打通了。另外还有一个很现实的原因订餐系统的业务场景足够贴近生活。用户点菜、加购物车、提交订单这一套流程做出来以后拿去面试也好、交课程设计也好别人一听就能明白你的项目是干什么的不需要你花十分钟解释业务背景。相比九九乘法表、记账本这类项目订餐系统在功能完整度上明显高一个档次。说一下项目定位我做的这个版本是单机本地版不依赖服务器。用户注册登录、浏览菜品、加入购物车、提交订单、查看历史订单全部数据存在手机本地 SQLite 数据库里。这样做的原因有两个一是方便快速跑通完整流程不需要额外搭后台二是把复杂度和工作量控制在一个人能独立完成的范围内。如果你后续想扩展网络版本地版的核心逻辑依然可以复用改动成本并不高。1.2 技术选型与方案对比技术选型上我用的是 Java XML 布局的经典组合而不是 Kotlin Compose。原因很朴素教程多、参考资料多、遇到问题随便一搜就有答案。对于刚接触 Android 开发的人Java 版本的排错成本比 Kotlin 低一个量级尤其是涉及 SQLite 和 ListView 这些老牌组件时网上的现成案例几乎全是 Java 写的。数据存储选 SQLite 也是同样的逻辑。虽然 Room 是 Google 官方的推荐方案但 SQLiteOpenHelper 的底层逻辑更直观每一个数据库操作步骤都是显式的写 SQL、执行、关游标、关数据库。这对理解数据流有很大的帮助。等你把 SQLiteOpenHelper 玩明白了再切换到 Room 基本上就是一个上午的事因为 Room 底层的核心思路就是封装了一层 SQLite。开发环境方面直接选当前最新稳定版 Android Studio下载安装后 SDK 管理器会自动拉取对应的 Android SDK 和 Build Tools。这里有一个很多人容易踩的坑Android Studio 默认的虚拟机镜像AVD下载源在国外首次创建模拟器时如果卡在下载界面可以在 SDK Manager 里配置镜像地址再重试。不过这个问题在不同网络环境下表现不一样后面我会单独说。2. 开发环境准备与工程搭建2.1 安装与基础配置这一步我实际操作了两次第一次在 Windows 笔记本上第二次在 Mac 上踩的坑基本一致所以流程还算有代表性。从官网下载 Android Studio 安装包Windows 版直接双击安装Mac 版把 .dmg 拖进 Applications 目录这一步没有任何技术含量。第一次启动的时候会有一个引导流程选择安装类型Standard 即可、选择主题建议选深色长时间盯屏幕会舒服很多、下载 SDK 组件。SDK 组件下载这个环节最容易出问题。引导程序默认会下载最新版本的 Android SDK Platform而且下载进度条经常长时间不动。我的经验是如果超过 10 分钟进度条毫无变化直接把安装向导关掉重开一次很多时候第二次就能正常继续。另外建议手动勾选 SDK Tools 里的 Android SDK Platform-Tools这个是 adb 命令行工具后面调试和查看数据库都会用到。还有一个很多新手问的问题Android Studio 怎么设置中文。说实话国内的汉化方式是在 Android Studio 的 Plugins 市场搜 Chinese Language Pack 这个插件安装后重启就是中文界面。但我个人不建议这么做因为大部分报错信息、文档、社区帖子都是英文的长期依赖中文界面反而会增加你排查问题的难度。代码开发和文档阅读本身就是英语环境趁早适应没坏处。2.2 创建项目与工程结构规划打开 Android Studio选择 New Project在模板列表里选 Empty Views Activity。如果你看到的是 Empty Activity也没关系本质是一样的只是一个基于 View 系统、一个可能默认走 Compose我建议选带 Views 关键字的模板因为我们后面要用 XML 布局 Java 代码这套经典方案。项目结构规划上我提前分好了包路径这一点非常重要。不要把所有 Activity 都堆在默认的包下面那样后期包管理会非常混乱。我的分包思路是这样的com.example.ordering ├── activity - 页面类LoginActivity, RegisterActivity, MainActivity ├── adapter - 列表适配器DishAdapter, OrderAdapter ├── bean - 实体类User, Dish, Order ├── db - 数据库辅助类DBHelper, CartDao, UserDao └── util - 工具类ToastUtil, SharedPrefsUtil这样的分包结构一眼就能看明白哪个类负责什么功能。很多人做项目做到后期自己都不知道某个类应该放在哪里往往就是前期规划没做到位。项目创建完成后先把 res/values/strings.xml 里的应用名称改成你想要的名字比如 美食订餐或者天天点餐这个名称会直接显示在手机桌面和应用管理列表里。接着在 AndroidManifest.xml 里检查一下主 Activity 是否配置了 LAUNCHER 意图过滤器确保安装后点击图标能正确进入第一个页面。3. 数据库设计与核心功能实现3.1 数据库表结构与 SQLiteOpenHelper 封装订餐系统的数据模型我一共设计了四张表用户表、菜品表、购物车表、订单表。下面这个是建表语句和表结构的核心设计-- 用户表 CREATE TABLE user ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE NOT NULL, password TEXT NOT NULL ); -- 菜品表 CREATE TABLE dish ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, price REAL NOT NULL, image INTEGER, category TEXT, description TEXT ); -- 购物车表 CREATE TABLE cart ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, dish_id INTEGER NOT NULL, quantity INTEGER NOT NULL DEFAULT 1 ); -- 订单表 CREATE TABLE orders ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, total_price REAL NOT NULL, order_time TEXT NOT NULL, status TEXT DEFAULT 已完成 );这里有一个细节我吃过亏要特别提醒为什么是 orders 而不是 order。因为 order 是 SQL 中的关键字用来做 ORDER BY 排序你用 order 作为表名在 SQLite 里会直接报语法错误。这个问题在写 SQL 的人身上时不时就会出现属于很低级但很影响进度的错误。数据库辅助类我统一继承 SQLiteOpenHelper在 onCreate 方法里执行建表语句在 onUpgrade 方法里面做表结构升级。有一个比较容易忽视的坑SQLiteOpenHelper 的构造函数里有一个版本号参数如果你后续修改了建表语句但版本号没有 1那么 onUpgrade 不会被触发新表结构就不会生效。你是不是经常觉得我明明改了代码为什么数据库还是老结构八成就卡在这个版本号上。3.2 用户注册登录模块的实现逻辑用户模块是系统的入口也是所有功能的前提。注册页面的逻辑很简单检查用户名是否为空 → 检查两次密码是否一致 → 调用 UserDao 的 register 方法查重 → 插入记录 → 跳转登录页。登录页注意细节用 SharedPreferences 保存登录状态和当前用户 ID这样用户杀掉应用重新打开时不需要再次登录。这个体验优化看起来很基础但实际项目中很多新手会漏掉导致每次冷启动都要重新输账号密码体验非常割裂。// 保存登录态 SharedPreferences sp getSharedPreferences(user_info, MODE_PRIVATE); SharedPreferences.Editor editor sp.edit(); editor.putInt(user_id, user.getId()); editor.putString(username, user.getUsername()); editor.putBoolean(is_login, true); editor.apply();记住用 apply() 而不是 commit()。前者是异步写入不阻塞 UI 线程避免在界面切换时出现瞬时卡顿后者是同步写入在数据量极小时差距不明显但一旦数据多了主线程卡顿的概率会上升。3.3 菜品列表展示与图片加载主界面承载的是菜品浏览功能。我使用的是 RecyclerView 搭配 CardView 卡片布局每个卡片展示菜品图片、名称、价格和加入购物车按钮。数据从本地数据库的 dish 表读取适配器将行数据绑定到 item 布局。菜品图片的处理方式值得一提。因为没有网络环境我用的图片全部是 res/drawable 目录下的本地资源文件。菜品表中的 image 字段存的是资源 ID一个整数数据库查出来之后直接用imageView.setImageResource(dish.getImage())设置图片。这种方式优点是简单快捷不需要引入任何图片加载框架缺点是图片资源必须预先打包进 APK 里扩展菜品时必须重新发布安装包。如果你后续想改成联网版本只需要把 image 字段改成图片 URL然后用 Glide 或者 Coil 这类图片加载库替换 setImageResource 那行代码即可。这个改造点非常清晰也是面试官喜欢问的一个扩展性问题。3.4 购物车与订单提交的完整链路购物车是订餐系统最核心的业务环节需要同时处理两件数据当前用户是谁、他选了哪些菜、每道菜多少份。我实现的是按用户维度保存购物车数据用户 A 和用户 B 各自有独立的购物车互不干扰。加入购物车时先查一下该用户的购物车里有没有同一道菜。如果有数量加一如果没有插入一条新记录。这个先查再改的逻辑在数据库操作里非常常见我来举个例子你可以看一下具体的 SQLpublic void addToCart(int userId, int dishId) { SQLiteDatabase db getWritableDatabase(); Cursor cursor db.rawQuery( SELECT id, quantity FROM cart WHERE user_id ? AND dish_id ?, new String[]{String.valueOf(userId), String.valueOf(dishId)}); if (cursor.moveToFirst()) { int id cursor.getInt(0); int quantity cursor.getInt(1); db.execSQL(UPDATE cart SET quantity ? WHERE id ?, new String[]{String.valueOf(quantity 1), String.valueOf(id)}); } else { ContentValues values new ContentValues(); values.put(user_id, userId); values.put(dish_id, dishId); values.put(quantity, 1); db.insert(cart, null, values); } // 用完记得关游标 cursor.close(); }下单流程就三步从购物车表查出该用户的所有记录 → 根据菜品单价和数量算总价 → 在 orders 表插入一条订单记录并清空购物车。清空购物车时用delete(cart, user_id ?, new String[]{userId})一次删干净这里同样要记得把用户 ID 作为条件不然会把所有人的购物车都清掉这是新手最容易忽视的一点。3.5 AndroidManifest 配置与页面注册写好的 Activity 必须在 AndroidManifest.xml 里注册。很多新手写了一个新 Activity运行时报错 Unable to find explicit activity class八成就是忘了在 Manifest 里加声明。另外如果你要把 Activity 作为应用的启动入口还需要加上两个 intent-filterintent-filter action android:nameandroid.intent.action.MAIN / category android:nameandroid.intent.category.LAUNCHER / /intent-filter这两个配置缺一不可。MAIN 声明它是程序的入口LAUNCHER 声明它在桌面显示图标。缺了 MAIN 会找不到入口缺了 LAUNCHER 应用安装完桌面上根本没有图标。这是个一分钟能踩完、一晚上找不到原因的问题。4. 界面布局与交互细节的打磨4.1 布局架构与常用控件选择整体界面我采用的是双布局策略登录/注册这种表单型页面用 ScrollView 嵌套 LinearLayout保证在小屏手机上不会被输入法顶出屏幕主界面和菜品列表用 ConstraintLayout 作为根布局配合不同控件的约束条件实现卡片自适应效果。菜品卡片通过 CardView 实现圆角和阴影效果这种基于 Material Design 的组件在视觉上能让列表看起来专业很多。如果你用的是纯 LinearLayout 嵌套很难做出这种层次感。CardView 使用起来也很简单自身就带有 elevation 属性控制阴影高度。主界面底部我用了 BottomNavigationView 来做菜单切换三个 tab 分别对应点餐购物车我的。tab 切换时用 Fragment 替换容器内容这样比直接跳转 Activity 的体验更流畅因为切换过程不重绘整页底栏也不会闪一下。4.2 点击事件与数据回显的常见误区列表项里面的加入购物车按钮点击事件需要在 Adapter 里监听。这里有一个容易踩的坑RecyclerView 的 item 点击和按钮点击要区分好往往需要在 Activity 里通过接口回调处理否则拿不到点击对应的菜品 ID。我的写法是在 Adapter 中定义一个接口回调构造时传入 MainActivity 的实例去处理。购物车页面的数量加减按钮、结算按钮以及订单页面的再来一单按钮都要对应到具体的业务方法。如果某一瞬间发现点击按钮没有反应优先检查两点布局中按钮是否被其他控件遮挡或者事件监听是否在正确位置注册。这两类问题在实际开发中出现频率最高尤其被遮挡这种布局预览里看不到一运行就出问题。4.3 数据可见性调试数据库的三个常用方法联调过程中最令人头疼的问题是代码逻辑看起来没问题但数据表现和预期不一致。为了排查这类问题我全程开着 Android Studio 的 Device Explorer 看应用沙盒里的数据库文件。定位路径一般是/data/data/com.example.ordering/databases/ordering.dbDevice Explorer 里可以直接把这个文件导出到本地再用 SQLite 浏览器打开查看。另外一个更轻量的方法是把 SQLiteOpenHelper 类里临时加一段 log 输出每次增删改查后打印数据库状态不过这个方法在正式发布前要记得移除不然日志会刷屏。模拟器自带的 Terminal 也可以直接跑adb shell查看但我个人更推荐先把数据库导出到电脑上看因为可视化工具看表结构、比对订单数据效率确实高很多。5. 常见问题与排查技巧实录5.1 编译期问题错误类型具体表现解决方案R 文件找不到代码里引用 R.id 报红检查 XML 布局文件是否有报错layout 文件名是否合法不能大写开头Gradle 同步失败Sync 标红依赖拉不下来检查网络状态或配置国内镜像仓库并重新 Sync资源文件报错drawable 文件名有特殊字符资源文件命名只能用小写字母、数字和下划线编译期问题一般都有明确报错按图索骥处理即可。最烦的是那些不报错但运行起来不符合预期的问题这类通常需要仔细排查运行逻辑。5.2 运行期问题错误类型具体表现排查方向应用闪退点击某个按钮就退出了查看 Logcat 日志重点找 FATAL EXCEPTION 和 Caused by页面白屏Activity 打开后一片空白检查 setContentView 对应的布局文件是否有运行时崩溃按钮无响应点击没有反应检查按钮是否在布局中被其他 View 遮挡或点击事件未注册数据库字段为空页面能打开但列表没数据验证 SQL 语句的查询条件确认表名和字段名拼写一致闪退问题中占比最高的空指针异常九成发生在控件初始化之前就去使用它。比如在 onCreate 里先执行了数据加载再去 findViewById 找控件。解决的铁律是先 findViewById再操作控件。此外 SQLite 的游标使用后不关闭长期跑也会在日志中刷一大堆内存警告这个习惯要养成。5.3 关于模拟器的一个小技巧如果用 Android Studio 自带模拟器首次启动会非常慢这是冷启动的正常现象耐心等几十秒就好。但如果持续卡在黑屏转圈可以尝试两个方法一是 Tools → AVD Manager 里点下拉箭头执行 Cold Boot Now相当于冷启动重置二是在创建 AVD 时选一个较低版本的 API比如 API 30 或 28会比最新版 API 跑得流畅很多。如果你有真机直接用开发者模式里开启的 USB 调试连电脑调试速度会有明显提升。最后分享一点我的实操体会这个项目从头到尾我在本地完整跑了三遍第一遍搭框架熟悉流程第二遍解决各种运行时异常第三遍是给学弟演示完整操作。印象最深的问题反而是在写 user 表之后自己配了个错误的版本号导致后续改动一直没有生效排查了将近一个下午。现在回头看很多诡异问题的根源都是很小的细节比如关键字作为表名、忘记关闭资源、逻辑里保存了错误的 SharedPreferences 字段。如果你也要做类似的完整项目有一点建议是有价值的先把数据表和基础工具类搭好再动手写界面。因为 UI 和业务的联调往往在数据层稳定后才会变得相对顺畅这也是我做这个项目时觉得最值得优化的顺序。本文还有配套的精品资源点击获取