ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

安卓简易单机点餐系统开发实战:SQLite+RecyclerView全流程解析

安卓简易单机点餐系统开发实战:SQLite+RecyclerView全流程解析 简介这是一套基于安卓平台与SQLite数据库开发的简易单机点餐系统主要面向安卓初学者以及需要完成期末课程设计的在校学生解决离线环境下餐厅点菜、订单记录等常见需求。整个项目由Eclipse工具构建实现了登录验证、菜品选择、订单生成与历史查询等核心流程页面分区清楚配色自然交互符合一般点餐场景的使用习惯。压缩包共包含一百七十六个文件其中以Java源码、XML布局和PNG界面图片为主同时提供SQLite数据库文件、可安装APK以及少量库依赖整体大小为三十三点五三兆字节目录组织清晰方便按功能模块阅读和二次开发。包内附带数据库说明文档并内置测试账号无需额外配置即可直接运行体验能够帮助初学者理解安卓本地存储、列表展示和基础交互逻辑。目前已有三千一百三十二人学习下载作为期末作业或入门练手项目具有不错的参考价值。 期末作业发下来打开题目一看“安卓简易单机点餐系统”很多同学第一反应是去百度找源码找到一份改改名字交上去结果答辩时被老师问两句就露馅了。我在这个题目上折腾了两个星期踩了不少坑也把整个系统重构了一遍最后答辩拿了不错的分数。这篇文章就把我做这个作业的全过程、核心代码、以及答辩时老师真正关心的点整理出来给正在赶这个作业的同学一个可以“抄作业”但又不至于一眼假的路子。实话说这个题目看着简单但“简易”两个字给了你很大的发挥空间也给了老师很大的扣分空间。做得太少显得敷衍做得太深又容易把自己绕进去。这个度怎么拿捏就是这篇文章要解决的核心问题。1. 需求拆解明白期末作业里的“潜台词”1.1 老师嘴上说的和心里想的题目写的是“安卓简易单机点餐系统”关键词拆开看安卓、单机、点餐。三个词各有各的讲究“安卓”要求你用安卓原生技术栈不是网页套壳“单机”意味着数据不需要远程服务器SQLite或者文件存储都行“点餐”则是业务逻辑的核心菜单展示、添加购物车、下单结算这三板斧必须有。但老师不会明说的隐藏要求才是拉开差距的地方。一般来说一个完整的期末作业要有“数据层”也就是数据库建表与操作要有“用户层”最简单的登录注册也得有要有“业务层”菜单列表、购物车、订单生成缺一不可。表现形式上至少得有两个以上界面界面之间能跳转数据能持久化。如果你只做一个页面把菜列出来然后算个总价那叫“网页版的填空题”不是安卓应用。1.2 技术选型对比别在起跑线就输了方案上手难度老师观感期末作业适配度Java SQLite RecyclerView中等正统安卓开发路线最推荐Kotlin Room偏高技术新、加分如果课程教过Kotlin可以选ListView 文件存储低技术太老勉强过关但容易被追问WebView套H5低明显跑偏不推荐毫无安卓含量我选的是Java SQLite RecyclerView的组合。Java是课程教的大家最熟SQLite是安卓内置数据库不需要引入任何外部依赖断网也能跑完美契合“单机”要求RecyclerView是现在列表开发的主流组件用ListView虽然也行但答辩时老师看到RecyclerView至少觉得你课后用了功。1.3 功能清单什么该做什么不该做我最后定下来的功能清单是这样的登录注册用户名唯一校验密码非明文存储至少做个简单的加密哪怕是自己写的异或算法菜品列表图片、菜名、价格、分类用RecyclerView展示购物车数量加减、实时算总价订单提交生成订单号、保存订单到数据库、清空购物车历史订单查看过去下的单包含订单状态和明细像服务端同步、优惠券、菜品搜索这些就都没做。“简易”两个字意味着你要控制边界做多了自己维护不过来答辩反而被问住。2. 数据层设计SQLite建表与预置数据2.1 四张表打天下单机应用的SQLite设计说白了就是在本地模拟一个缩水版的服务端数据库。我设计了四张表用户表、菜品表、订单表、订单明细表。-- 用户表 CREATE TABLE user ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT NOT NULL UNIQUE, password TEXT NOT NULL ); -- 菜品表 CREATE TABLE menu ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, price REAL NOT NULL, image TEXT, category TEXT, description TEXT ); -- 订单表 CREATE TABLE orders ( id INTEGER PRIMARY KEY AUTOINCREMENT, order_no TEXT NOT NULL, user_id INTEGER, total_price REAL NOT NULL, create_time TEXT NOT NULL, FOREIGN KEY (user_id) REFERENCES user(id) ); -- 订单明细表 CREATE TABLE order_item ( id INTEGER PRIMARY KEY AUTOINCREMENT, order_id INTEGER NOT NULL, menu_id INTEGER NOT NULL, menu_name TEXT NOT NULL, price REAL NOT NULL, count INTEGER NOT NULL, FOREIGN KEY (order_id) REFERENCES orders(id) );注意订单明细表里冗余存了一份menu_name和price这是有意为之的。因为菜品将来可能改价或者改名订单属于历史数据必须保留下单那一刻的快照。这个细节我答辩时主动提了一句老师点了点头。2.2 数据库帮助类的正确写法SQLiteOpenHelper是安卓封装好的数据库助手类核心就是onCreate和onUpgrade两个回调。很多同学的代码里只写了onCreateonUpgrade直接空着这其实是个隐患。假如你后期改了表结构App升级时数据库没同步直接崩。public class DBHelper extends SQLiteOpenHelper { private static final String DB_NAME ordering.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 IF NOT EXISTS user (...)); db.execSQL(CREATE TABLE IF NOT EXISTS menu (...)); db.execSQL(CREATE TABLE IF NOT EXISTS orders (...)); db.execSQL(CREATE TABLE IF NOT EXISTS order_item (...)); // 预置菜单数据 initMenuData(db); } Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // 开发期最简单的处理方式删表重建 db.execSQL(DROP TABLE IF EXISTS user); db.execSQL(DROP TABLE IF EXISTS menu); db.execSQL(DROP TABLE IF EXISTS orders); db.execSQL(DROP TABLE IF EXISTS order_item); onCreate(db); } }2.3 菜品数据从哪里来单机应用没有网络接口菜品数据有两个来源一是首次创建数据库时插入二是直接打包一个已经写好的DB文件放到assets目录。我推荐后者因为图片资源和菜品数据可以提前整理好但对学生来说操作复杂度高一些。我用的是第一种方案在onCreate里调用一个initMenuData(db)方法把菜品写死在代码里。菜品图片放在drawable目录数据库里存图片资源名而不是存Bitmap二进制。读取时通过getResources().getIdentifier()拿到资源ID再加载。这个方案的好处是数据库文件小图片交系统管理不会出现大字段导致的内存问题。3. 菜单列表与购物车核心交互的实现思路3.1 界面结构一页还是两页我考虑过两种方案一种是一个Activity里用Fragment切换不同页面另一种是底部导航加多Activity。最后选了前者底部三个Tab菜单、订单、我的用Fragment实现。这样做的好处是底部导航常驻用户在各个页面间切换体验流畅而且代码组织上更接近现在主流App的结构。Fragment事务切换的代码不复杂但有一个坑要提醒切换时用add而不是replace。replace会销毁再创建Fragment状态丢失比如菜单列表滑到一半的位置、购物车里的数据可能就没了。add配合hide和showFragment的实例一直活着数据自然就保住了。这个方法在答辩时也是个加分回答点。3.2 RecyclerView适配器与购物车的联动RecyclerView的Adapter写起来不难难点在于“数量变化怎么通知界面刷新”。我的做法是用一个HashMapInteger, Integer当作购物车容器key是菜品IDvalue是购买数量。public class MenuAdapter extends RecyclerView.AdapterMenuAdapter.ViewHolder { private ListMenu menuList; private MapInteger, Integer cartMap; // menuId - count private OnCartChangeListener listener; Override public void onBindViewHolder(NonNull ViewHolder holder, int position) { Menu menu menuList.get(position); holder.tvName.setText(menu.getName()); holder.tvPrice.setText( menu.getPrice()); holder.tvCount.setText(String.valueOf(cartMap.getOrDefault(menu.getId(), 0))); holder.btnAdd.setOnClickListener(v - { cartMap.put(menu.getId(), cartMap.getOrDefault(menu.getId(), 0) 1); notifyItemChanged(position); if (listener ! null) listener.onCartChanged(getTotalCount(), getTotalPrice()); }); holder.btnMinus.setOnClickListener(v - { int count cartMap.getOrDefault(menu.getId(), 0); if (count 0) return; if (count 1) { cartMap.remove(menu.getId()); } else { cartMap.put(menu.getId(), count - 1); } notifyItemChanged(position); if (listener ! null) listener.onCartChanged(getTotalCount(), getTotalPrice()); }); } public interface OnCartChangeListener { void onCartChanged(int totalCount, double totalPrice); } }这里需要注意notifyItemChanged(position)只刷新当前项比notifyDataSetChanged()高效而且不会让列表滚动位置跳动。这个细节我在开发过程中被坑过一开始用notifyDataSetChanged每次点按钮列表都会跳回顶部体验很差后来改成局部刷新才解决。3.3 购物车数据存在哪里购物车数据我放在Fragment里的一个静态Map中或者直接存在当前Fragment的字段里因为单机应用不需要服务端保存购物车用户杀掉App购物车清空也说得过去。不过如果要从购物车跳转到确认订单页面这个数据要跨界面传递我推荐三种方式通过接口回调、通过Application持有、通过Intent传递序列化对象。这个项目我用的是Application里存一份全局购物车Map简单直接Activity和Fragment都能拿得到。4. 订单结算与历史订单细节才是拿分点4.1 金额计算别用double这个是真坑。Java里double做减法会有精度问题比如0.1 0.2的结果是0.30000000000000004。做点餐系统结算总价算错了哪怕差一分钱演示的时候都极其尴尬。正确姿势是用BigDecimalprivate double calculateTotal(MapInteger, Integer cartMap, ListMenu menuList) { BigDecimal total BigDecimal.ZERO; for (Menu menu : menuList) { int count cartMap.getOrDefault(menu.getId(), 0); if (count 0) { BigDecimal price BigDecimal.valueOf(menu.getPrice()); total total.add(price.multiply(BigDecimal.valueOf(count))); } } return total.setScale(2, RoundingMode.HALF_UP).doubleValue(); }数据库里存REAL类型够用了但界面上显示的格式化用String.format(%.2f, price)或者DecimalFormat保证显示两位小数。4.2 生成订单号与事务处理订单号我用时间戳加随机数生成简单且能保证不重复。下单的核心操作包括插入订单主表记录、插入订单明细表多条记录、清空购物车。这三个操作必须放在一个事务里保证要么全部成功要么全部失败。不然就会出现订单主表有记录明细表空着的情况——我在开发时真的遇到过最后查了半天发现是明细插入失败但主表已经提交了。SQLiteDatabase db dbHelper.getWritableDatabase(); db.beginTransaction(); try { long orderId db.insert(orders, null, orderValues); for (OrderItem item : itemList) { db.insert(order_item, null, itemValues); } db.setTransactionSuccessful(); } finally { db.endTransaction(); }事务处理是数据库操作的高频考点答辩时老师几乎必问“怎么保证数据一致性”能把事务讲明白这个项目就已经成功一大半了。4.3 历史订单页面的两级展示订单记录我用了一个两级的展示方式上半部分是订单卡片显示订单号、下单时间、总金额、状态点击卡片后弹出明细对话框展示这个订单包含哪些菜品、各自什么价格、数量多少。这里有一个小技巧订单列表用orders表的数据不关联查询菜品明细因为明细放在order_item表里数量多的话联表查询会有性能问题。等用户点击某条订单时才去查对应的明细用WHERE order_id ?条件即可。数据量小单机应用压根不用考虑性能优化但代码结构上清晰老师看着舒服。4.4 空购物车与重复下单的防御这些边界情况虽然不起眼但都是期末作业里的“安全网”。我在提交订单的按钮里加了购物车空判断购物车为空时Toast提示不让走订单流程。登录页对空输入做了校验密码错误也有独立提示。这些代码量不大但能明显提升项目的完整度。很多同学只写了顶层流程没写防御逻辑一演示给老师看空购物车也能下单成功当场就被扣分了。5. 真机运行时的坑与界面上容易被忽略的细节5.1 横竖屏切换数据丢失模拟器默认竖屏但老师可能会旋转屏幕检查适配。如果不处理Activity重新创建界面上的状态全没了。最简单的方案是在AndroidManifest.xml的activity标签里加一行android:configChangesorientation|screenSize|keyboardHidden然后在Activity里重写onConfigurationChanged方法什么都不用做。这个方案不是最优雅的但应付期末作业足够了。更优雅的方案是使用onSaveInstanceState保存数据但那个要写一堆序列化代码学生项目没必要性价比太低。5.2 图片加载的两种思路菜品图片我前面提到用drawable资源名存数据库。加载时用getIdentifier方式int resId context.getResources().getIdentifier(imageName, drawable, context.getPackageName()); if (resId ! 0) { holder.ivImage.setImageResource(resId); } else { holder.ivImage.setImageResource(R.drawable.default_food); }这样比在代码里写一堆if-else或者switch-case要简洁得多。还有一种思路是先把图片放进raw或assets目录读取时转Bitmap。但要注意Bitmap不经压缩直接显示内存一高就容易OOM。用Glide加载本地资源也行但期末作业没必要引第三方库老师还会问你为什么用Glide。5.3 图标、应用名、包名这些小门面期末作业最亏的扣分点一个是App名字还是默认的app_name一个是图标还是安卓默认的小机器人。改起来其实很简单strings.xml里改app_name图标在mipmap目录替换。我有个同学功能做得不错就因为这俩没改被老师说“态度不认真”分数直接降了一个档。界面配色也不要全用系统的默认白底黑字自己定一个主色调按钮圆角、卡片阴影这些用drawable自定义一下观感立刻不一样。5.4 模拟器选择与真机调试建议期末演示一般用模拟器。Android Studio自带的模拟器启动慢、占内存大如果电脑配置一般建议用Genymotion或者直接在手机上调试。真机调试时打开“开发人员选项”里的“USB调试”用数据线连电脑就能直接跑。注意不同的手机厂商进入开发者模式的方式不一样一般是连点“版本号”七次。这个环节看似简单但我见过不少人卡在这里还有USB驱动装不上的提前准备好会比较从容。6. 答辩现场老师最常问的问题和应对思路6.1 高频问题Top 5“为什么用SQLite而不用文件存储”这个问题比较好答。SQLite支持结构化查询、事务、索引数据操作更规范而且安卓系统原生支持不需要额外成本。文件存储适合读写的场景简单、没有复杂查询关系的数据而点餐系统的用户、菜品、订单之间是有关系的用数据库更合适。“购物车数据怎么保存杀进程之后还在吗”说实话这个在我的项目里进程杀掉就清空了。可以补一个思路把购物车存到SQLite或者SharedPreferences启动时恢复。如果要加建议存SQLite里的一个cart表或者更精简的做法是SharedPreferences存JSON。“下单时如果插入订单主表成功了但明细插入失败了怎么办”这就把话题引导到你用了事务上把自己写在4.2节的代码讲一遍老师会觉得你考虑得很周全。“菜品数据写死在代码里如果要新增一个菜品怎么操作”老实说最简单的方式是改initMenuData重新装DB或者做一个“菜品管理”的界面。期末作业做到新增菜品界面就有点超纲了你可以说“生产环境下菜品数据应由服务端下发本题因为是单机系统所以采用预置数据方案”。“密码安全吗”如果明文存储肯定不要撒谎。诚实的回答是“这里采用了简单的加密处理并非明文存储”如果你只做了异或就说“对于课程设计这种场景加密强度够用了生产环境会用MD5加盐或BCrypt”。6.2 演示顺序怎么排这一步非常关键。很多人一上去就从头演示结果刚进入菜单列表就卡住了自己手忙脚乱。我建议按这个顺序来先展示App整体界面说明底部有三个Tab快速带过菜单页重点演示点餐流程加几个菜、减一个、看总价变化、提交订单切到历史订单页展示刚下的单出现在列表里最后演示登录注册逻辑退出再登录验证数据的持久性先走主流程再走分支逻辑即使中间出了状况老师已经看到了核心功能印象分已经拿到手了。写在最后的几个小建议做完这个项目最大的感受是期末作业不是功能越多越好而是“核心流程完整、细节处理到位、技术选型讲得出理由”。数据用SQLite做了持久化购物车和订单有完整的状态流转Adapter封装和Fragment管理用的是当前主流写法防御性判断和异常处理覆盖了关键路径这些才是真正让老师给你打高分的点。再提醒一个事代码里的命名规范。虽然期末作业不要求像企业级代码那样讲究但类名用驼峰、变量命名有含义、关键逻辑写注释这种好习惯会贯穿项目答辩时翻代码给老师看也会更自信。如果你现在还在赶工优先保证主流程跑通然后再回头处理这些细节点但千万不要觉得无所谓就完全不处理。我见过一群人项目跑得通但代码乱成一团被老师吐槽“这是给自己挖坑”所以随手收拾一下不亏的。本文还有配套的精品资源点击获取
返回列表