简介:这是一份面向Android初学者与餐饮应用开发练习者的移动点餐项目源码,包含ph2移动客户端与ph2web服务端两部分,可用于学习Android应用从界面到网络通信的完整开发流程。压缩包共149个文件,约2.68MB,以55个class编译文件、23个java源码、20个xml布局与配置、39个png图片资源为主,另含jar依赖、apk安装包及ap_、dex等构建产物,覆盖客户端与服务端的核心代码结构。客户端部分涉及菜单浏览、购物车与订单提交、HTTP数据交互及SQLite本地存储;服务端则包含请求处理、数据库管理与RESTful接口设计等实践内容。目前已有164人学习下载,适合希望通过真实项目理解模块化设计、网络编程与前后端协作的读者参考借鉴。
1. 移动点餐源代码:从扫码到出单,一套能跑通的 Android 点餐系统
前阵子帮一个做餐饮 SaaS 的朋友看项目,他手里有一套移动点餐源代码,想改成支持多门店的版本,结果卡在订单状态同步上——顾客端显示已下单,后厨端却迟迟不刷新。翻完代码发现,问题出在本地数据库和远端接口的时序没对齐。这套源码本身功能是完整的:顾客扫码进店、浏览菜单、加购物车、下单支付,后厨接单出餐,管理端改菜品改价格,一条链路全通。技术栈是 Android 原生加 Java/Kotlin,后端常见做法是 Spring Boot 配 MySQL,接口走 RESTful。适合谁?想快速搭一套点餐 Demo 的学生、要二次开发餐饮小程序的独立开发者、以及需要给客户演示点餐流程的产品团队。下面按「能干什么 → 怎么跑 → 怎么改」拆一遍。
2. 项目架构与目录结构:先看清模块边界再动手
2.1 整体分层:表现层、业务层、数据层怎么切
这套移动点餐源代码的 Android 端采用经典的三层结构,没有上 MVVM 的 DataBinding 那套重家伙,而是用 Activity/Fragment 直接持有 Presenter 或 ViewModel,对新手友好,改起来也快。表现层负责扫码、菜单渲染、购物车弹窗、订单列表;业务层处理加菜减菜、价格计算、订单状态机;数据层分两块——本地 SQLite 存购物车和缓存菜单,远端 Retrofit + OkHttp 调后端接口。
我一般会先看app/src/main/java/com/example/ordering/下的包结构,通常长这样:
com.example.ordering ├── ui/ # Activity、Fragment、Adapter │ ├── menu/ # 菜单浏览 │ ├── cart/ # 购物车 │ ├── order/ # 订单确认与列表 │ └── admin/ # 管理端入口 ├── data/ # 数据层 │ ├── local/ # SQLite Helper、DAO │ ├── remote/ # Retrofit Service、ApiClient │ └── model/ # 实体类:Dish、Order、CartItem ├── business/ # 业务逻辑:OrderManager、CartManager └── util/ # 工具类:PriceUtil、TimeUtil这个划分的好处是,你要换后端接口,只动data/remote;要改价格规则,只动business。常见做法是把ApiClient做成单例,Retrofit 的 baseUrl 抽到BuildConfig或gradle.properties,方便切测试环境和生产环境。
2.2 关键依赖与版本:Gradle 里哪些不能乱动
打开app/build.gradle,核心依赖一般这几条:
implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation 'com.squareup.retrofit2:converter-gson:2.9.0' implementation 'com.squareup.okhttp3:logging-interceptor:4.9.3' implementation 'androidx.recyclerview:recyclerview:1.2.1' implementation 'com.google.android.material:material:1.6.0' implementation 'androidx.room:room-runtime:2.4.2' annotationProcessor 'androidx.room:room-compiler:2.4.2'Retrofit 和 Gson 负责网络与 JSON 解析,Room 是 SQLite 的封装,比手写SQLiteOpenHelper省事。注意 Room 的 compiler 必须用annotationProcessor,用implementation会编译不过。如果源码里用的是kotlin-kapt,那就对应换成kapt。版本号别随意升,Retrofit 2.9 和 OkHttp 4.9 是稳定搭配,升到 OkHttp 5.x 会有 API 变动。
2.3 后端接口约定:订单和菜单的数据长什么样
后端这块,源码通常带一个简单的 Spring Boot 工程,或者只给接口文档。核心接口就几个:
| 接口 | 方法 | 路径 | 说明 |
|---|---|---|---|
| 获取菜单 | GET | /api/menu/list | 返回菜品列表,含分类、价格、图片 |
| 提交订单 | POST | /api/order/create | 传桌号、菜品明细、总价 |
| 查询订单 | GET | /api/order/detail | 按订单号查状态 |
| 更新菜品 | POST | /api/admin/dish/update | 管理端改价改库存 |
菜单返回的 JSON 里,categoryId用来分组,status字段控制是否上架。订单状态一般用数字枚举:0 待支付、1 已支付、2 制作中、3 已完成、4 已取消。这个状态机是后面避坑的重点,先记住。
3. 跑起来:从导入 Android Studio 到扫码点单成功
3.1 环境准备与项目导入
先把源码解压,用 Android Studio 的「Open an existing project」选根目录,不要选app子目录。等 Gradle sync 完成,如果卡在下载依赖,检查gradle-wrapper.properties里的 distributionUrl 是不是官方地址,国内网络常见做法是换成镜像,但注意别引入来路不明的仓库。
SDK 版本看build.gradle里的compileSdk和targetSdk,一般 30 或 31。如果本地没装对应版本,Android Studio 会提示下载。minSdk通常是 21 或 23,意味着 Android 5.0/6.0 以上都能跑。
提示:导入后先别急着 Run,点一下 Build → Make Project,把编译错误先暴露出来,比直接跑模拟器省时间。
3.2 配置后端地址与启动服务
源码里后端地址通常写死在ApiClient或Constants类里,类似:
public class ApiClient { // 模拟器访问本机用 10.0.2.2,真机用局域网 IP private static final String BASE_URL = "http://10.0.2.2:8080/"; private static Retrofit retrofit; public static Retrofit getInstance() { if (retrofit == null) { OkHttpClient client = new OkHttpClient.Builder() .addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)) .connectTimeout(10, TimeUnit.SECONDS) .build(); retrofit = new Retrofit.Builder() .baseUrl(BASE_URL) .client(client) .addConverterFactory(GsonConverterFactory.create()) .build(); } return retrofit; } }10.0.2.2是 Android 模拟器访问宿主机 localhost 的固定地址,真机调试要改成电脑的局域网 IP,比如192.168.1.100。后端如果是 Spring Boot 工程,用mvn spring-boot:run或直接跑 main 方法启动,默认端口 8080。启动后先用浏览器或 Postman 访问http://localhost:8080/api/menu/list,确认返回 JSON 再跑 App。
3.3 模拟器运行与扫码入口
Run 到模拟器后,App 启动页一般会请求菜单接口,成功则进主页。扫码功能依赖zxing或ML Kit,模拟器没摄像头,常见做法是提供一个「模拟扫码」按钮,直接填桌号进菜单。真机则调startActivityForResult拉起扫码页。
// 模拟扫码:直接传桌号,方便调试 findViewById(R.id.btn_mock_scan).setOnClickListener(v -> { Intent intent = new Intent(this, MenuActivity.class); intent.putExtra("table_no", "A01"); // 桌号写死,真机从扫码结果取 startActivity(intent); });桌号是订单的关键字段,后端靠它区分送餐位置。调试阶段写死没问题,上线前必须换成真实扫码结果。菜单页用 RecyclerView 按分类渲染,点「+」加购物车,购物车数据先写本地 Room,点「去结算」再调下单接口。
3.4 下单到出单的完整验证
下单成功后,App 跳订单详情页,轮询或长连接查状态。后端收到订单,后厨端(可能是另一个平板 App 或 Web 页面)拉取待制作列表。验证链路是否通,就看三件事:订单号是否唯一、总价是否和购物车一致、状态是否从 1 变到 2。如果后厨端不刷新,先查后端有没有推消息,再看 App 轮询间隔是不是太长。
4. 核心模块拆解:购物车、订单状态机与网络层
4.1 购物车逻辑:本地 Room 怎么存怎么算
购物车是点餐系统的核心,这套源码用 Room 存CartItem,字段包括dishId、name、price、count。加菜时先查本地有没有同dishId,有就count+1,没有就插入。
@Dao public interface CartDao { @Query("SELECT * FROM cart_item WHERE dishId = :dishId LIMIT 1") CartItem findByDishId(int dishId); @Insert(onConflict = OnConflictStrategy.REPLACE) void insert(CartItem item); @Update void update(CartItem item); @Query("SELECT SUM(price * count) FROM cart_item") double getTotalPrice(); }getTotalPrice直接用 SQL 聚合算总价,比在 Java 里循环累加更稳,避免浮点误差累积。注意价格字段用double还是BigDecimal——源码里常见用double,但涉及优惠券分摊时会有精度问题,二次开发建议换BigDecimal或存「分」为单位的长整型。
4.2 订单状态机:0 到 4 的流转不能乱
订单状态是踩坑重灾区。源码里通常定义一个OrderStatus枚举或常量类:
public class OrderStatus { public static final int PENDING_PAY = 0; // 待支付 public static final int PAID = 1; // 已支付 public static final int COOKING = 2; // 制作中 public static final int DONE = 3; // 已完成 public static final int CANCELED = 4; // 已取消 }状态流转必须单向:0→1→2→3,取消只能从 0 或 1 进 4。后端更新状态时要加条件,比如UPDATE orders SET status=2 WHERE id=? AND status=1,防止并发下状态跳变。App 端轮询到状态变化后,用DiffUtil刷新列表,别直接notifyDataSetChanged,否则闪烁。
4.3 网络层封装:Retrofit 接口与错误处理
Retrofit 的 Service 接口定义很直白:
public interface OrderService { @GET("api/menu/list") Call<MenuResponse> getMenuList(); @POST("api/order/create") Call<OrderResponse> createOrder(@Body OrderRequest request); @GET("api/order/detail") Call<OrderDetail> getOrderDetail(@Query("orderNo") String orderNo); }调用时用enqueue异步,回调里判response.isSuccessful(),失败看errorBody。常见做法是包一层ApiCallback统一处理 token 过期和网络异常。注意OrderRequest里的菜品列表要用List<CartItem>,Gson 序列化时字段名要和后端一致,不一致用@SerializedName标注。
5. 避坑与排查:订单不同步、价格错乱、扫码失败
5.1 订单状态不同步:顾客端已支付,后厨端没反应
现象是顾客付完钱,订单详情显示「已支付」,后厨列表还是空的。原因通常是后端更新状态后没有推送,App 端轮询间隔设了 30 秒甚至更长。解决:把轮询间隔降到 3~5 秒,或者后端加 WebSocket 推。如果源码里用的是Handler.postDelayed轮询,记得在onDestroy里removeCallbacks,否则页面关了还在跑,内存泄漏。
5.2 总价对不上:购物车算出来和订单金额差几分钱
现象是购物车显示 58.60,提交后订单变成 58.59。原因是double累加精度丢失,或者后端重新算了一遍用了不同规则。解决:统一在客户端算好总价传给后端,后端只校验不重算;或者两边都用「分」为单位的长整型。源码里如果price是double,二次开发时优先改这个。
5.3 扫码没反应:模拟器无摄像头,真机权限没给
现象是点扫码按钮黑屏或直接返回。原因:模拟器没摄像头硬件;真机没申请CAMERA权限。解决:调试用模拟扫码入口;真机在AndroidManifest.xml加<uses-permission android:name="android.permission.CAMERA"/>,运行时用ActivityCompat.requestPermissions动态申请。Android 6.0 以上不动态申请会直接崩。
5.4 菜单图片不显示:Glide 没配或网络明文限制
现象是菜单列表文字正常,图片全是占位图。原因:没加 Glide 依赖,或者 Android 9 以上默认禁止明文 HTTP。解决:加implementation 'com.github.bumptech.glide:glide:4.13.0',并在AndroidManifest.xml的<application>加android:usesCleartextTraffic="true",或者后端上 HTTPS。生产环境建议上 HTTPS,明文只用于调试。
5.5 后厨端重复出单:接口没做幂等
现象是同一订单在后厨列表出现两次。原因:顾客端网络抖动重试,后端createOrder没做幂等。解决:下单时客户端生成唯一requestId,后端用它去重;或者订单号用「桌号+时间戳+随机数」保证唯一。这个坑在真实餐厅高峰期特别致命,二次开发务必补上。
6. 二次开发改造:多门店、支付对接与性能优化
拿到这套移动点餐源代码,最常改的三个方向是多门店、接真实支付、以及性能优化。多门店的核心是给所有接口加storeId参数,本地 Room 的菜单表加storeId字段,切换门店时清空购物车重新拉菜单。支付对接一般是接微信或支付宝的 SDK,把原来的「模拟支付」按钮换成调起支付,支付回调里再更新订单状态——注意回调可能重复,状态更新要幂等。
性能上,菜单图片多的时候 RecyclerView 会卡,我一般会做三件事:Glide 加override(200,200)限制解码尺寸、RecyclerView.setHasFixedSize(true)、以及分页加载。订单列表用DiffUtil替代notifyDataSetChanged,滑动明显顺滑。
验证改造是否成功,我习惯写一个简单的检查清单:切门店后菜单是否变、购物车是否清空、下单后订单号是否带门店前缀、支付回调后状态是否只变一次。每次改完都跑一遍这个清单,比事后查日志快得多。
从那以后我每次拿到一套点餐源码,都强制先跑通「扫码→加菜→下单→后厨出单」这条最小闭环,再动任何一行代码。希望帮到你。
本文还有配套的精品资源,点击获取