简介这是一份面向安卓初学者的单机点餐系统期末作业资源包基于Eclipse与SQLite开发采用单机运行模式绕开联网接口重点演示登录、点餐、订单查看等基础业务流程适合课程设计、毕业设计或自学Android开发时参考。包体共176个文件约33.53MB其中包含可安装的apk、java源码、xml界面布局、图片资源及数据库文件结构清晰便于对照学习和二次修改。压缩包内附有数据库说明文档账号ZYS/密码123下载后可直接运行体验。目前已有3133人浏览学习整体难度较低功能简洁非常适合需要快速完成期末作业或快速上手Android项目开发的同学。通过完整源码与运行实例可帮助理解SQLite存储、Activity界面跳转、ListView列表展示等基础知识。1. 安卓简易单机点餐系统一个期末作业被我拆成了完整工程提到安卓期末作业十个有八个是点餐系统。可如果只是把一堆控件堆在界面上数据一关 App 就没了那这台点餐机顶多算个模型。这个「安卓简易单机点餐系统」的完整工程走的是原生 Java SQLite 路线服务员在界面上选桌号、点菜下单时把购物车写进本地数据库结算完还能翻历史订单。它在课程设计的范围内跑通了一个餐厅点餐的最小闭环。适合正在赶期末作业、想搞懂数据库增删改查的安卓新手也适合想抄一套快速改造成自己毕设的开发者。2. 技术选型与整体架构原生 Java SQLite单机也能跑通点餐流程单机点餐系统最大的特点就是没有服务器所有数据都落在手机本地。这一章先把技术选型和工程结构讲清楚让你知道这个项目站在什么基础上后面看代码才不会晕。2.1 为什么选 Java 和 SQLite而不是上网络框架这个系统定位是单机版菜品、订单、明细都存储在一部手机上。选 SQLite 的理由很直接它是安卓系统内置的关系型数据库不需要装服务端不用联网课堂演示不会因为网络环境翻车。用 Java 写界面逻辑是因为期末作业阶段大家对 Kotlin 未必熟悉Java 的教程最多、报错最好搜遇到问题不至于卡死。既然数据全在本地也就没有了登录鉴权和服务器接口Activity 之间用 Intent 传购物车数据就够了。这套工程的依赖只有 androidx 兼容包没有引入第三方网络框架和图片加载库。菜品图片我都没用网络图直接放资源 drawable这样 App 在任何环境启动都不会因为图片 URL 失效而白屏。数据库的增删改查全部自己写 SQL没有用 ORM 框架把 SQLiteOpenHelper 的用法暴露出来正好对应课程设计里「学会操作 SQLite」的考核点。和联网点餐系统相比这里省掉了 Retrofit、OkHttp、JSON 解析那一套核心逻辑反而更聚焦。有一种常见做法是把购物车放在 Application 里作为全局变量这样 Activity 之间就不用传序列化对象。但期末答辩时老师一定会问「要是 Activity 被系统回收了怎么办」。用 Intent 传参会被回收存 Application 里也可能因为进程被杀而丢。所以我干脆让购物车只活在 MainActivity 的内存里跳转时用 Bundle 带上一个 ArrayList Food 实现了 Serializable确保订单页面能独立展示。这也解释了为什么订单明细在下单后要立刻落库而不是回到上一个页面再从内存里捞。2.2 工程目录与模块划分打开工程后主模块的目录长这样restaurant/ ├── app/ │ ├── src/main/ │ │ ├── java/com/example/restaurant/ │ │ │ ├── MainActivity.java # 选桌号 菜品列表 加入购物车 │ │ │ ├── OrderActivity.java # 购物车确认 下单 结算 │ │ │ ├── db/ │ │ │ │ ├── DBHelper.java # SQLiteOpenHelper建库建表 │ │ │ │ ├── FoodDao.java # 菜品查询 │ │ │ │ └── OrderDao.java # 订单插入与查询 │ │ │ └── model/ │ │ │ ├── Food.java │ │ │ ├── Order.java │ │ │ └── OrderDetail.java │ │ ├── res/ │ │ │ ├── layout/ │ │ │ │ ├── activity_main.xml │ │ │ │ ├── activity_order.xml │ │ │ │ └── item_food.xml │ │ │ └── drawable/ # 菜品图片 │ │ └── AndroidManifest.xml │ └── build.gradle这个目录没有做复杂的 MVVM 分层因为单机点餐的数据量级小逻辑全在 Activity 里也完全跑得开。我把数据库访问单独抽成 dao 包是为了让下一个开发者一进来就知道哪些类是干数据活的。model 包里的 Food、Order、OrderDetail 只是承载字段的普通 JavaBean对应三张表。文件职责可以看这张表文件 / 类职责DBHelper.java继承 SQLiteOpenHelper负责建库、建表、升级FoodDao.java查询菜品列表按分类筛选OrderDao.java插入订单主表 明细表查询历史订单MainActivity.java展示菜品维护选中的购物车OrderActivity.java展示购物车下单并结算2.3 单机架构下的数据流当服务员在界面点菜时数据的流向是这样的MainActivity 先从 FoodDao 读菜品列表并展示到 ListView用户点某道菜时系统把菜名、单价、数量放进一个购物车集合点「去结算」跳到 OrderActivity 时Intent 把购物车对象序列化后传过去OrderActivity 拿到数据后调用 OrderDao 的 insertOrderWithDetails 插入一条订单同时插入该订单下的所有明细并且用事务保证两步都成功。因为单机不需要考虑并发SQLite 的读写锁在这个场景下完全够用。服务端点餐系统要考虑的订单丢失、数据同步、断网补偿在这里可以直接省略。这个架构的好处就是让课程设计的重点落在 SQL 和界面跳转上对新手友好。如果你之后要改成多人点餐只要把 FoodDao 和 OrderDao 的数据库实现替换成网络请求Activity 层的购物车逻辑基本可以原封不动地复用。3. 数据库设计与核心模块拆解从建表到订单落库的完整实现这一章是整个工程的心脏我把建表、菜品查询、下单写入三块代码都拎出来过一遍。每一段代码后面都会说清楚它做了什么、参数怎么改。3.1 SQLite 表结构菜品表、订单表、订单明细表数据库入口是 DBHelper它负责创建数据库和升级版本。下面是完整的建表代码public class DBHelper extends SQLiteOpenHelper { private static final String DB_NAME restaurant.db; private static final int DB_VERSION 1; public DBHelper(Context context) { super(context, DB_NAME, null, DB_VERSION); } Override public void onCreate(SQLiteDatabase db) { db.execSQL(CREATE TABLE food( _id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, price REAL NOT NULL, // 单价单位元 category TEXT)); // 分类如热菜、主食、饮料 db.execSQL(CREATE TABLE orders( _id INTEGER PRIMARY KEY AUTOINCREMENT, table_no INTEGER NOT NULL, // 桌号 total_price REAL NOT NULL, create_time TEXT NOT NULL)); // 下单时间存 yyyy-MM-dd HH:mm:ss db.execSQL(CREATE TABLE order_detail( _id INTEGER PRIMARY KEY AUTOINCREMENT, order_id INTEGER NOT NULL, food_name TEXT NOT NULL, unit_price REAL NOT NULL, count INTEGER NOT NULL)); // 插入演示菜品数据第一次启动就有菜可点 db.execSQL(INSERT INTO food(name, price, category) VALUES(宫保鸡丁, 28.0, 热菜)); db.execSQL(INSERT INTO food(name, price, category) VALUES(酸辣土豆丝, 12.0, 热菜)); db.execSQL(INSERT INTO food(name, price, category) VALUES(米饭, 2.0, 主食)); db.execSQL(INSERT INTO food(name, price, category) VALUES(可乐, 5.0, 饮料)); } Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } }DBHelper 是数据库的门面。DB_NAME 定义库文件名DB_VERSION 是版本号之后只要改这个数字让手机上旧库调用 onUpgrade。onCreate 在三张表建好后插入演示菜品这样第一次启动就有数据可以点菜。orders 和 order_detail 之间通过 order_id 建立关联靠这个字段能反查某个订单点了哪些菜。价格字段这里用了 REAL 存浮点演示没问题如果要商用建议改成 INTEGER 存「分」避免精度问题这点在避坑章节会细说。3.2 菜品查询与点餐列表加载菜品查询被封装在 FoodDao 里MainActivity 只需要调用一行就能拿到列表。先看 FoodDaopublic class FoodDao { private DBHelper helper; public FoodDao(Context context) { helper new DBHelper(context); } public ListFood getAllFoods() { ListFood list new ArrayList(); SQLiteDatabase db helper.getReadableDatabase(); Cursor cursor db.rawQuery(SELECT * FROM food ORDER BY category, _id, null); while (cursor.moveToNext()) { Food food new Food(); food.setId(cursor.getInt(cursor.getColumnIndexOrThrow(_id))); food.setName(cursor.getString(cursor.getColumnIndexOrThrow(name))); food.setPrice(cursor.getDouble(cursor.getColumnIndexOrThrow(price))); food.setCategory(cursor.getString(cursor.getColumnIndexOrThrow(category))); list.add(food); } cursor.close(); return list; } }getAllFoods 用 rawQuery 执行原生 SQLCursor 是结果集moveToNext 每次移动一行。getColumnIndexOrThrow 拿到列索引而不是写死下标以后改表字段顺序代码也不会炸。注意用完后要 close() 释放 Cursor否则反复打开 Activity 会造成内存泄漏。getReadableDatabase 和 getWritableDatabase 在这个场景下没差别查数据用前者语义更清晰。然后是 MainActivity 里的加载代码ListView listView findViewById(R.id.lv_food); FoodDao dao new FoodDao(this); ListFood foods dao.getAllFoods(); FoodAdapter adapter new FoodAdapter(this, foods); listView.setAdapter(adapter);写完 Adapter 后setAdapter 就会把数据渲染出来。FoodAdapter 的 getView 里 inflate item_food.xml把菜名和价格填到两个 TextView。这个项目用 ListView 而不是 RecyclerView一是期末作业没引入复杂依赖二是 ListView 的 Item 点击事件用 setOnItemClickListener 处理对新手更友好。如果你想改成 RecyclerView后面二次开发章节会给你方向。3.3 下单逻辑购物车明细写入订单下单是整个系统最关键的一步要同时写订单主表和订单明细表。我在 OrderDao 里用事务保证原子性public long insertOrderWithDetails(int tableNo, ListFood cart, double totalPrice) { SQLiteDatabase db helper.getWritableDatabase(); db.beginTransaction(); try { ContentValues orderValues new ContentValues(); orderValues.put(table_no, tableNo); orderValues.put(total_price, totalPrice); orderValues.put(create_time, new SimpleDateFormat(yyyy-MM-dd HH:mm:ss) .format(new Date())); long orderId db.insert(orders, null, orderValues); for (Food food : cart) { ContentValues detailValues new ContentValues(); detailValues.put(order_id, orderId); detailValues.put(food_name, food.getName()); detailValues.put(unit_price, food.getPrice()); detailValues.put(count, food.getCount()); db.insert(order_detail, null, detailValues); } db.setTransactionSuccessful(); return orderId; } finally { db.endTransaction(); } }这个方法用了事务。先插入订单主表拿到自增 orderId再循环把购物车里的每道菜写入明细表。beginTransaction 开始事务setTransactionSuccessful 标记成功endTransaction 在 finally 里确保无论成没成功都结束事务。只要中间任何一条 insert 失败整单都会回滚不会出现订单主表有数据、明细丢失的半截订单。参数 tableNo 是服务员选的桌号cart 是购物车列表totalPrice 提前算好传入。明细表里存了 food_name 和 unit_price 的快照这样以后菜品涨价历史订单仍然显示下单时的价格。计算总价的逻辑我放在 MainActivity遍历购物车对每道菜的 price * count 累加。因为浮点计算会有精度偏差我在结账入口用 BigDecimal 四舍五入到两位小数再跳转到 OrderActivity。跳转代码如下double total 0; for (Food f : cart) { total f.getPrice() * f.getCount(); } BigDecimal bd new BigDecimal(total).setScale(2, RoundingMode.HALF_UP); total bd.doubleValue(); Intent intent new Intent(MainActivity.this, OrderActivity.class); intent.putExtra(table_no, tableNo); intent.putExtra(total_price, total); intent.putExtra(cart, (ArrayListFood) cart); startActivity(intent);putExtra 的第一个参数是键第二个是值。table_no 是整数total_price 是 doublecart 是 ArrayList 。因为 Food 实现了 Serializable整个 ArrayList 可以直接序列化进 IntentBundle 的容量也足够放下一个餐厅的菜品数量。OrderActivity 拿到这些数据后展示在购物车界面等服务员点「确认下单」就调用刚才的 insertOrderWithDetails下单成功 Toast 提示并 finish 返回主界面清空购物车。4. 项目运行与打包从导入 Android Studio 到安装到手机代码拆完了得让它先跑起来。这一章我按自己平时带新人跑项目的顺序写跟着做基本能一次成功。4.1 导入项目与 Gradle 配置解压工程包后打开 Android Studio 选择 File - Open定位到工程根目录等 Gradle 同步完成。如果电脑上没装对应版本的 SDK同步会报错根据提示点安装缺失的 SDK 即可。这个工程用的 Gradle 配置是常见的配置块android { compileSdk 33 defaultConfig { applicationId com.example.restaurant minSdk 21 targetSdk 33 versionCode 1 versionName 1.0 } }compileSdk 决定能用哪个版本的 APIminSdk 21 表示 Android 5.0 以上的设备都能跑targetSdk 33 对应 Android 13 的运行行为。如果你的机器没有装 SDK 33把 compileSdk 改成你本机已装的版本同时 targetSdk 保持一致代码里没有用到高版本专属 API降级完全没问题。4.2 模拟器运行与真机调试推荐先用模拟器跑通逻辑。在 Device Manager 里建一台 Pixel 模拟器系统镜像选 API 30 左右就行启动后直接点 Run 按钮App 会自动安装并打开。第一次启动后应该能看到菜品列表点几个菜选桌号结算后去 data/data/com.example.restaurant/databases 下查一下 restaurant.db确认数据真的落库了。真机调试也一样简单手机开启开发者选项里的 USB 调试插上电脑adb 能看到设备adb devices adb install -r app-debug.apk第一次插上手机会弹授权提示记得点允许。如果 adb devices 看不到设备多半是驱动问题Windows 下装一下厂商 USB 驱动Mac 和 Linux 一般免驱。真实设备上跑数据库读写比模拟器更贴近演示场景。4.3 生成 APK 与期末演示如果只是课堂演示debug 包直接连电脑跑就行。但交作业或发给别人装需要生成独立 APK。流程是 Build - Generate Signed Bundle / APK - 选 APK - Create new keystore填上密码和别名签名后生成 release 包。生成 release 包时有个小坑签名文件的密码和别名一定要截图存好后面二次修改再打包要用同一个签名否则装不上旧设备。演示前先在模拟器上完整走一遍点餐流程确认数据正常。老师如果问数据库在哪用 Android Studio 的 Device File Explorer 找到 databases 目录导出 restaurant.db用 SQLite 工具打开给他看表结构和数据这一手很容易加分。5. 常见问题与避坑这几个坑我写期末作业时全踩过跑通是一回事从跑通到能演示、能答辩又是另一回事。这一章列了五个真实踩过的坑每一条都按现象、原因、解决写清楚遇到问题直接对号入座。5.1 安装后闪退数据库升级忘加旧表字段现象第一次运行正常改了表结构后再次安装启动直接闪退日志里报 table orders has no column named remark。原因数据库文件已经存在但 DB_VERSION 没改onUpgrade 空实现新字段根本没有加进去。解决每次改动表结构都要把 DB_VERSION 加一并在 onUpgrade 里写清楚迁移逻辑Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { if (oldVersion 2) { db.execSQL(ALTER TABLE orders ADD COLUMN remark TEXT); } }这样老用户升级时只会执行增量 SQL而不是把整库重建。开发期图省事可以卸载重装但演示和交付前一定要把升级路径测一遍。5.2 ListView 不刷新Adapter 还是旧数据现象下单成功后回到主页面菜品列表没变化购物车角标还是原来的数量。原因数据库里的数据变了但 ListView 的 Adapter 持有的 List 还是旧对象没有通知刷新。解决在数据变更后主动调用 adapter.notifyDataSetChanged()。MainActivity 在 onActivityResult 里收到下单成功的标志后清空购物车集合重新 setAdapter保证每次回到主界面都读到最新状态。新手经常忘了这一行结果界面看起来像「坏掉了」其实就是缺一个刷新通知。5.3 总价对不上double 精度误差现象点了一份 28 元的宫保鸡丁和一份 2 元的米饭总价不是 30而是 29.999999999999996。原因二进制浮点精度问题double 在计算和存储尾数时会丢失精度。解决金额计算统一走 BigDecimal展示统一走 DecimalFormat。更彻底的做法是数据库价格字段改成 INTEGER 存「分」订单明细里的 unit_price 也存分只在界面转换成元。我从那以后只要是跟钱有关的字段一律按分存储算完后除以 100彻底绕开浮点坑。5.4 模拟器打字崩溃输入法兼容问题现象在模拟器的 EditText 里输入中文菜名瞬间崩掉log 里满屏 Unable to start activity ComponentInfo。原因部分模拟器镜像自带的键盘和中文输入法有兼容问题输入过程触发资源加载失败。解决这个项目本来不需要手动输入桌号用下拉框选菜品用 ListView 点选能把 EditText 整个省掉。如果一定要输入备注建议在真机上调试或给模拟器装第三方中文输入法。在答辩现场我吃过这个亏之后凡是演示用的模拟器我都提前把输入环节全部改成选择交互。5.5 真机安装失败签名冲突现象adb install 报 INSTALL_FAILED_UPDATE_INCOMPATIBLE手机上显示「未安装」。原因设备上已经装了一个同包名但签名不同的版本比如之前用调试签名装过现在又用另一个 debug 签名装。解决先卸载旧的再装adb uninstall com.example.restaurant adb install app-debug.apk如果你接的是别人的项目还要检查 applicationId 是否冲突改包名要连带改 AndroidManifest 里所有 activity 的完整路径。这个坑在交作业前的最后一天特别容易遇到因为那会大家都会拿别人的 APK 互相装。6. 二次开发把这套系统改造成能交差的完整功能基础版本能跑通但离「优秀作业」和真实餐厅需求还差一点。这一章给你三个改造方向按优先级来改完就是一套拿得出手的完整系统。6.1 加入数量加减与购物车持久化现在的购物车是点一次加一道菜一个菜想加三份得点三次体验很差。我把 FoodAdapter 改成维护一个 Map餐品Id, 数量每次点击加减按钮都更新这个 Map同时刷新 Item 上的数量 TextView。结算时直接遍历 Map 生成购物车列表统一传给 OrderDao。这样改动只涉及 Adapter 和 MainActivity数据库层完全不用动。6.2 用 RecyclerView 替代 ListView答辩时老师可能会问「为什么还用 ListView」。你可以提前把菜品列表换成 RecyclerViewFoodAdapter 改成 RecyclerView.Adapter 结构ViewHolder 用标准的 itemView 模式。RecyclerView 对点击事件的处理更灵活后面要加长按删除、滑动修改数量都很顺手。用 GridLayoutManager 还能把菜品变成九宫格样式视觉上更像个点餐平板。6.3 订单历史查询与状态管理加一个订单历史页面从 orders 表按 create_time 倒序查出来点击某条订单跳到详情页用 order_id 去 order_detail 表查明细。如果想让流程更完整给 orders 表加一个 status 字段区分「已下单」和「已完成」在 OrderActivity 里加一个状态更新按钮就成了一个带订单流转的点餐闭环。这个改造有一个隐藏考点让老师看到你已经会处理表间关联和状态枚举比堆一堆 UI 特效有用得多。从那以后我每次拿到数据库相关的安卓项目都强制先画一遍字段关系表再动手写 DAO最后才碰 Activity。数据层稳了上层怎么改都不慌。希望帮到你。本文还有配套的精品资源点击获取