ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

基于Android的水果蔬菜销售APP复现与避坑指南

基于Android的水果蔬菜销售APP复现与避坑指南 简介基于安卓的水果蔬菜销售应用程序设计与实现文档是一份面向计算机相关专业学生及移动开发初学者的毕业设计参考材料围绕选题目的、研究现状、可行性分析等方面系统阐述了蔬菜水果销售应用从需求分析到技术选型的完整过程。资源为单个DOCX格式文档大小892KB内容涵盖中英文摘要、目录、引言、可行性分析及后续设计章节结构完整便于直接查阅与修改。已有79人学习下载适合正在开展安卓项目开发或撰写相关论文的读者借鉴。通过该文档可以了解安卓客户端与网络后台配合实现商品展示、拍照上传等功能的思路以及经济、技术、操作层面的可行性评估方法对完成同类毕业设计或课程项目具有实际参考价值。1. 一份能跑的安卓蔬果销售APP它到底值不值得复现做Android开发这些年我拆过不少课程设计和毕业设计像这份基于Android的水果蔬菜销售APP反而是我建议新手和转行者认真复现的一类项目。理由很简单它不炫技但把电商类App最核心的业务闭环完整走通了——从用户登录注册、浏览商品、下单购买、评价反馈到后台商品录入、类别管理、订单列表该有的链路全都有。而且技术栈是Java、SQLite、HTTP、JSON这套经典组合拿到手就能在Android Studio里打开改。它适合两类人一类是马上要交毕业设计、需要完整可演示项目的学生另一类是想弄懂一个安卓App从页面到数据库再到后台请求整体是怎么咬合在一起的入门开发者。2. 业务与功能模块登录、购物车、订单和评价背后的设计取舍拿到这份文档我第一件事不是看代码而是看需求分析和用例图。文档里的功能点其实很清晰用户侧有登录注册、查看水果、购买商品、发表评价管理侧有商品列表管理、用户管理、商品类别管理、订单列表和评价列表。很多人复现课程设计时容易犯的毛病是一上来就写页面写到一半发现订单数据不知道存哪、评价和订单怎么关联也理不清。这份资源的价值就在于它先把业务参与者和数据边界画出来了。2.1 用例图里的参与者和权限边界两张用例图值得先吃透。普通用户的动作是登录、注册、查看水果、购买、评价管理员的动作是维护商品、管订单、管评价、管用户。你会发现两边几乎没有重叠这就是权限边界的意义用户能看到的商品列表来自后台录入用户提交订单后由后台管理员处理发货用户评价也只针对已购买的商品。模块用户端管理端核心数据商品管理查看列表、查看详情录入、修改、上架下架goods商品类别按类别过滤增删类别category订单创建订单、查看状态查看全部订单、更新状态orders评价提交评价查看、可删除违规评价evaluation用户注册、登录、维护地址查看用户列表users我一般会在新建工程时就把这张表画出来它决定了后面Activity怎么分、接口怎么拆。比如评价功能如果用户没有购买记录就让它评价数据上就不成立所以评价表必须带order_id这个外键。权限边界不是一句空话它会具体到代码里控制按钮的显示和接口的校验。2.2 商品、订单、评价三大表的关系设计文档反复提到SQLite这是Android自带的关系型数据库整个销售系统的本地数据都靠它。复现时我会把表设计成下面这个样子。重点说订单表它不会只存一个goods_id去关联商品表而是直接把goods_name和price冗余一份进来做快照。为什么因为后台改价改名称后历史订单必须还保持当时的下单信息这是电商业务的常识。goodsid、name、category、price、stock、image_url、descriptionusersid、username、password、nickname、phone、addressordersid、order_no、user_id、goods_name、price、quantity、status、create_timeevaluationid、order_id、user_id、goods_id、content、rating、create_time数据库设计到位之后你会发现订单状态流转特别自然。文档里写的是“待发货”这类粗粒度状态实际做个status字段用一个int或者TEXT存都行。text的缺点是拼写错误难排查优点是日志里直接可读课程设计用TEXT更直观比如“待支付”“待发货”“已发货”“已完成”。这些状态会写进流程图也会直接体现在订单列表的UI上。2.3 流程图里藏着的状态流转文档里的系统流程图看起来简单用户输入信息、系统添加数据、上传传输、存储读取、查看订单。但把流程图翻译成代码逻辑它就不只是页面跳转了而是一条数据链路。用户下单这个动作发生后数据要同时走两条路一条写在本地SQLite里用于离线也能看到我的订单另一条通过HTTP接口提交到后台让管理员那边能处理发货。食谱里最容易断的地方是“上传”这一步。拍照上传更是如此文档里特别提到了照相、上传、图片处理这背后牵涉相机调起、文件存储、网络传输、后台接收四个环节。任何一个环节断掉用户看到的都是“上传失败”。我在复现时会把这一条完整链路单独画出来从Android端调用相机拍照到图片存到临时目录再到multipart方式POST到后台接口后台返回图片URL最后客户端把这个URL和商品信息一起入库。业务设计阶段把这张图画清楚后面写代码就不会返工。3. Android客户端落地Activity生命周期、SQLite本地存储与拍照上传文档第四章把开发语言、Android框架、SQLite和工程环境讲得很细这些是背景真正动手时我建议按“工程结构→页面流转→本地存储→拍照上传”这个顺序去落地。很多人下载课程设计资源后第一件事是找MainActivity我的习惯是先看AndroidManifest.xml因为它相当于App的地基页面、服务、权限、文件提供者全在这里声明。3.1 工程结构AndroidManifest.xml先注册后使用一个典型的项目工程里所有Activity、Service、ContentProvider都需要在AndroidManifest.xml里注册。四个组件里我尤其提醒Service和ProviderActivity漏注册会在启动时立刻崩但Service漏注册会在调用startService时报错Provider漏注册会在运行时找不到访问入口。Android 12之后还要求显式声明android:exported不然直接安装失败。manifest xmlns:androidhttp://schemas.android.com/apk/res/android packagecom.example.fruitsales uses-permission android:nameandroid.permission.INTERNET / uses-permission android:nameandroid.permission.CAMERA / uses-permission android:nameandroid.permission.WRITE_EXTERNAL_STORAGE / application android:labelstring/app_name activity android:name.ui.MainActivity android:exportedtrue intent-filter action android:nameandroid.intent.action.MAIN / category android:nameandroid.intent.category.LAUNCHER / /intent-filter /activity activity android:name.ui.LoginActivity android:exportedfalse / activity android:name.ui.OrderListActivity android:exportedfalse / service android:name.service.UploadService android:exportedfalse / provider android:nameandroidx.core.content.FileProvider android:authorities${applicationId}.fileprovider android:exportedfalse android:grantUriPermissionstrue meta-data android:nameandroid.support.FILE_PROVIDER_PATHS android:resourcexml/file_paths / /provider /application /manifest逻辑说明INTERNET权限用于访问后台接口CAMERA和WRITE_EXTERNAL_STORAGE用于拍照和存图。需要注意Android 6.0以上摄像机这类危险权限不能只写在Manifest里还要在代码里动态申请否则真机上调用相机时不会弹授权框。FileProvider的authorities要和应用包名对应这里用${applicationId}.fileprovider编译后会替换成真实的包名。3.2 Activity生命周期与页面流转页面流转是Android App的骨架。文档里提到了登录注册、查看商品、购买、评价这些界面需求实际开发时我会用一个BaseActivity统一管理再让LoginActivity、MainActivity、GoodsDetailActivity、OrderListActivity各自继承。页面之间跳转靠Intent这是Android最核心的协作机制。public void openGoodsDetail(View view) { int goodsId Integer.parseInt(view.getTag().toString()); Intent intent new Intent(this, GoodsDetailActivity.class); intent.putExtra(goods_id, goodsId); startActivity(intent); } public void submitOrder() { Intent serviceIntent new Intent(this, UploadService.class); serviceIntent.putExtra(order_no, currentOrderNo); startService(serviceIntent); }逻辑说明first段是把商品id通过Intent传递到下个页面接收方用getIntExtra(goods_id, -1)取回来再按id查数据库或请求接口。第二段是启动上传服务文档里提到“后台运行的复制信息传输”指的就是Service在后台处理耗时上传任务。参数说明putExtra能传基本类型和Serializable对象但不能传太大的数据复杂对象我建议转成JSON字符串或者直接传id再查一次避免事务TooLargeException。3.3 SQLite本地缓存建表、增删改查与onUpgrade后台数据到不了的时候本地SQLite能不能扛住决定了演示会不会翻车。文档里说SQLite在Android上是嵌入式关系型数据库支持自动建表和对象化操作。我复现时会用SQLiteOpenHelper来管理数据库创建和版本升级核心代码是这样。public class DBHelper extends SQLiteOpenHelper { private static final String DB_NAME fruit_sales.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 goods ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, category TEXT, price REAL, stock INTEGER DEFAULT 0, image_url TEXT)); db.execSQL(CREATE TABLE IF NOT EXISTS orders ( id INTEGER PRIMARY KEY AUTOINCREMENT, order_no TEXT UNIQUE, user_id INTEGER, goods_name TEXT, price REAL, quantity INTEGER, status TEXT DEFAULT 待发货, create_time TEXT)); } Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // 常见做法是按版本号逐段迁移不要直接 DROP 重建 } }逻辑说明onCreate在数据库第一次创建时调用里面建两张核心表。DATABASE_VERSION是结构版本号注意它不是数据版本号只要你的建表语句变了这个值就必须加1否则用户安装新版本后表结构还是旧的样子。参数说明price用REAL是因为要存小数价格order_no用UNIQUE约束能防止重复提交onUpgrade里我习惯用switch匹配oldVersion逐版本执行ALTER TABLE而不是简单DROP TABLE重建那会让用户本地数据清零属于典型的“后悔药没得吃”的操作。3.4 拍照上传相机调用、文件路径与权限处理文档里反复提到“照相、上传、图片出来”这是系统里功能性最强也最容易出坑的部分。拍照首先要解决的是文件路径问题Android 7.0之后直接用file:// Uri去调相机会当场抛FileUriExposedException。正确做法是借助FileProvider把文件Uri暴露出去。private void takePhoto() { Intent take new Intent(MediaStore.ACTION_IMAGE_CAPTURE); File photo new File(getExternalCacheDir(), goods_ System.currentTimeMillis() .jpg); Uri photoUri FileProvider.getUriForFile(this, getPackageName() .fileprovider, photo); take.putExtra(MediaStore.EXTRA_OUTPUT, photoUri); startActivityForResult(take, REQUEST_TAKE_PHOTO); } private void uploadImage(String filePath) { HttpURLConnection conn (HttpURLConnection) new URL(BASE_URL /upload) .openConnection(); conn.setRequestMethod(POST); conn.setConnectTimeout(8000); conn.setReadTimeout(8000); // 拼 multipart/form-data把文件流写入请求体 // 后台返回 {code:0,data:{url:...}} }逻辑说明拍照时指定输出文件避免相机返回的缩略图太小getExternalCacheDir()是App专属缓存目录不需要额外申请存储权限也比getExternalStorageDirectory()更符合分区存储规范。uploadImage用HttpURLConnection这是文档那个时代最通用的方案现在换OkHttp逻辑完全一样换的只是请求构造方式。参数说明connectTimeout和readTimeout都设8000毫秒原因是弱网环境下如果无限等待用户会以为App死了超时后要在onFailure里给Toast提示别让用户对着一个转圈的对话框发呆。4. Web后台与数据流转接口约定、JSON解析与网络切换这份文档的后台部分没有展开讲但它明确写了一个关键点客户端和Web后台结合用户下单后数据要传到后台管理员在后台维护商品列表。这个模式决定了客户端和后台之间必须有一套稳定的接口约定。我会在复现时先定接口再写页面顺序反了的话前后端对不上参数排查起来非常痛苦。4.1 客户端与后台的接口约定接口是客户端和后端的“合同”。我一般先定义下面这几个基础接口覆盖主链路登录、注册、商品列表、提交订单、图片上传、提交评价。接口方法参数返回/api/loginPOSTusername, passwordtoken, userInfo/api/registerPOSTusername, password, phoneuserId/api/goodsListGETcategory可选goods数组/api/addOrderPOSTuserId, goodsId, quantityorderNo/api/uploadImagePOSTmultipart/form-dataimageUrl/api/addEvaluationPOSTorderId, userId, content, rating成功标记为什么要在文档没有写全的情况下先补这一层因为客户端页面能不能跑通完全依赖接口返回的字段名。比如登录接口返回的是token还是userId直接影响本地存什么。字段名大小写也要统一JSON里userName和username是两回事后台一旦返回了不一致前端解析就会拿到null。4.2 JSON解析与列表数据绑定JSON是客户端和后台之间最常用的数据格式Android自带的org.json包解析简单的数据结构完全够用。我在这个项目里不会急着上Gson因为数据结构不复杂手写解析反而更直观还能让新手看清JSON和Java对象之间的映射关系。protected void parseGoods(String jsonString) throws JSONException { JSONObject root new JSONObject(jsonString); if (root.getInt(code) ! 0) { throw new IllegalStateException(接口返回错误 root.getString(msg)); } JSONArray list root.getJSONObject(data).getJSONArray(list); ListGoodsBean goodsList new ArrayList(); for (int i 0; i list.length(); i) { JSONObject item list.getJSONObject(i); GoodsBean bean new GoodsBean(); bean.setId(item.getInt(id)); bean.setName(item.getString(name)); bean.setPrice(item.getDouble(price)); bean.setImageUrl(item.getString(imageUrl)); goodsList.add(bean); } adapter.setData(goodsList); adapter.notifyDataSetChanged(); }逻辑说明先判断code字段不等于0直接抛异常这样错误能在日志里一眼看到然后再取data里的list数组逐条转实体类最后交给Adapter刷新列表。参数说明getDouble处理价格如果用getInt会把8.8读成8imageUrl字段名必须和后台对死我踩过把imageUrl写成image_url的坑后台返回下划线前端取驼峰列表图片一整列出不来排查了很久才发现是字段名不一致。4.3 WiFi与数据流量网络切换时的连接策略文档里专门有一节叫“系统wifi连接与数据流量设计”这是很多人容易忽略的点。真实场景是用户可能在WiFi和4G之间切换切换瞬间网络请求会失败App如果没有感知网络变化的能力用户就只能在失败页面里干等。ConnectivityManager cm (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); NetworkRequest request new NetworkRequest.Builder() .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) .build(); cm.registerNetworkCallback(request, new ConnectivityManager.NetworkCallback() { Override public void onAvailable(Network network) { // 重试失败队列里的请求重新拉取商品列表 } });逻辑说明registerNetworkCallback能监听网络恢复的时机网络从断开恢复后立即重试之前的失败任务。参数说明addCapability(NET_CAPABILITY_INTERNET)是筛选出真正能上网的网络避免连上WiFi但没有外网时误判这个回调API在Android 5.0以上可用现在复现时不用再兼容老古董版本。实际使用时我还会加一个超时重试计数器最多重试两次避免反复请求把服务器打挂。5. 避坑指南真机联调、数据库升级与上传崩溃的五个坑课程设计能跑模拟器不等于能跑真机能在自己的手机上跑不等于换了别人的手机还能跑。这一章我集中写复现这份资源时最容易翻车的五个点全是实际项目里的血泪经验每一条都按现象、原因、解决来拆。5.1 数据库版本升级翻车忘记递增DATABASE_VERSION现象App在原版本基础上升级安装打开后闪退日志里报SQLiteException: no such column。原因改了建表语句比如给goods表加了description字段但SQLiteOpenHelper的DATABASE_VERSION还是原来的值。SQLite只有在版本号变化时才会触发onUpgrade版本号没变旧数据库就不会执行迁移新代码却去查新字段自然报错。解决每次表结构发生变化必须把DB_VERSION加1在onUpgrade里用switch匹配旧版本号逐段执行ALTER TABLE。我一般还会把迁移语句包在try-catch里防止中途失败导致整个升级流程异常终止。这里没有后悔药已经发布出去的版本用户数据是不能指望靠卸载重装解决的。注意发布后的App升级数据库不要简单DROP TABLE重建那会清空用户所有本地数据。正确姿势是ALTER TABLE ADD COLUMN或者建临时表再拷贝数据。5.2 真机拍照上传闪退7.0以上FileUriExposedException现象模拟器上拍照上传一切正常换到Android 8.0真机上点拍照按钮直接崩日志抛出FileUriExposedException。原因Android 7.0开始App之间共享file:// Uri是被禁止的。相机的ACTION_IMAGE_CAPTURE通过Intent接收file:// Uri后无法访问该文件系统直接抛异常。模拟器很多默认API版本较低所以发现不了这个问题。解决按前面第三章的做法配置FileProvider并声明file_paths.xml代码里用FileProvider.getUriForFile生成content:// Uri传给相机。另外注意Android 10分区存储后getExternalStorageDirectory访问受限建议统一改用getExternalCacheDir或者getExternalFilesDir。5.3 模拟器能登录、真机连不上后台现象模拟器里登录、商品列表、下单全部正常装到真机上后所有网络请求超时。原因Android模拟器访问宿主机用10.0.2.2这个特殊地址它映射的是开发机的localhost。代码里如果写死了http://10.0.2.2:8080换到真机上自然找不到服务器。解决把接口baseUrl改成开发机的局域网IP比如http://192.168.1.100:8080并确保手机和电脑连同一个WiFi。临时调试还可以用adb reverse tcp:8080 tcp:8080把手机端口映射到电脑这样代码可以继续写localhost。记得别把局域网IP提交到正式环境那东西换个WiFi就失效。5.4 商品图片一多就卡顿直接加载原图扛不住现象商品列表滑起来掉帧图片多的时候直接OOMLogcat里频繁出现OutOfMemoryError。原因ListView或者RecyclerView的Item里直接加载了原始分辨率的图片。手机拍照的图动不动三四千万像素原样加载进列表内存瞬间就被撑爆。这是项目里常见的“为啥我的APP比别人的卡”的根源。解决两步走。一是加载时用BitmapFactory.Options的inSampleSize采样压缩按需把图片缩小到几个像素再进内存二是图片多的时候别手写加载直接换Glide一行代码搞定。手写加载逻辑列表一长就露馅这是踩过的坑。BitmapFactory.Options opts new BitmapFactory.Options(); opts.inSampleSize 4; // 按 1/4 采样宽高各缩到 1/4 Bitmap bitmap BitmapFactory.decodeFile(imagePath, opts);逻辑说明inSampleSize设为4采样后图片的内存占用大约变成原来的十六分之一。参数说明值必须是2的幂2、4、8、16系统内部会向下取接近的2的幂值。商品列表场景用4比较合适再小图会糊。5.5 混淆打包后Activity找不到keep规则没写现象debug包跑得好好的release包一打开就闪退日志报ClassNotFoundException或者NoSuchMethodException点名某个Activity或实体Bean找不到。原因开启混淆后ProGuard把类名、方法名、字段名全部改成了a、b、c之类而Android要求跳转Activity的类名必须保留原样JSON解析库用反射读实体类字段字段名被改了也解析不出来。debug包默认不开混淆所以这个问题只在release包出现。解决在proguard-rules.pro里对ui包和bean包统一加keep规则保结构不被混淆。实体类必须keep住因为Gson、org.json这类库通过反射拿字段名字段改名等于白解析。-keep class com.example.fruitsales.bean.** { *; } -keep class com.example.fruitsales.ui.** { *; }逻辑说明第一行是保留所有实体Bean的类名、字段名和方法名第二行是保留UI层的Activity和Adapter。参数说明**表示包下所有子包也生效{ *; }表示类里所有成员都不混淆。如果用了第三方库还要按各自文档加对应规则这个项目技术栈简单这两行够用。6. 从课程设计到可演示Demo验证路径与一个实用技巧文档能跑通主链路只是第一步汇报演示能不能顺利演完看的其实是准备功夫。我给一个固定的演示路径建议登录注册 → 首页浏览商品 → 打开详情 → 下单 → 查看订单列表 → 提交评价。整个流程控制在十分钟内。演示前把代码里所有写死的测试账号、测试商品确认一遍别在台上才发现登录密码不对。还有一个我强烈安利的技巧让演示不依赖网络。把SQLite数据库提前塞进assets目录App首次启动时拷贝到data/data/包名/databases/这样就算后台服务没起来商品列表和订单数据也能完整展示。准备演示数据时用adb pull把数据库导出来adb shell run-as com.example.fruitsales cat databases/fruit_sales.db seed.db逻辑说明run-as命令能以App身份读取私有目录里的数据库文件导出的seed.db放进项目assetsApp启动时用文件流覆盖到本地数据库目录。参数说明包名必须和Manifest里的一致数据库文件名为空的话打开App会自动用DBHelper建一份新库不会因为assets里没文件崩溃。这招我一直在用它把演示风险从“网络、服务器、后台服务”三个变量降到了“一台有电的手机”一个变量。从那以后我每次拿到课程设计资源都会强制自己先跑通这条最小闭环——登录、列表、下单、查看订单再评估要不要继续深入。你也不用一上来就想着把所有功能写全先把这一条主链路跑顺再往里加评价、上传、后台管理越往后的功能就越是在给骨架填肉。希望帮到你。本文还有配套的精品资源点击获取
返回列表