拓冰建站拓冰建站
首页 / 资讯中心 / 正文

基于Android的小商品拍卖APP:倒计时、并发控制与订单状态机

简介基于Android平台的小商品拍卖APP项目源码面向安卓开发学习者、毕业设计学生及拍卖电商方向的技术人员。项目完整实现用户注册登录、商品发布名称、描述、图片、起拍价与时限设置、竞拍出价支付起拍价20%作为保证金、搜索分类、订单管理、商品管理、用户信息设置等模块并明确拍卖结果规则截止后最高价获得购买权竞价失败退回保证金成功后须24小时内付尾款逾期扣除保证金。资源共355个文件以Java源代码与XML资源文件为主Java承载业务逻辑和Activity交互XML负责界面布局与数据配置同时包含PNG图片、Gradle构建脚本等压缩包约4.05MB目录结构清晰便于导入Android Studio运行与二次开发。目前已有507人学习下载适合作为毕业设计参考或拍卖类APP入门项目可快速理解从用户端到管理端的完整竞拍闭环。1. 基于Android的小商品拍卖APP真正要解决的不只是出价页面校园二手、社区闲置、档口尾货这类客单价在几十到几百元的小商品拍卖和传统古董字画拍卖完全是两回事。用户不会守着电脑更不会装一个PC客户端大部分人拿起手机看一眼价格、点两下能中就中不中就走。所以基于Android的小商品拍卖APP核心体验是“打开快、看得懂、出价不卡”而不是功能越多越好。这类APP通常由商品发布、出价竞拍、倒计时、订单支付、个人中心几个模块组成。服务端负责商品和价签客户端负责把倒计时和出价做顺数据一致性才是真正的难点。开发方式上可以选择Android原生、Flutter或UniApp但从后台服务和硬件兼容角度看用Android Studio配原生Java/Kotlin开发仍然是个人开发者和中小团队最稳妥的路径第三方SDK齐全、抓包调试方便、系统权限可控。下面这整套方案按“先定接口、再写客户端、最后做状态机”的顺序来推进适合有Java基础但没做过完整项目的开发者直接照着落地。2. 技术选型与工程骨架Android Studio、SDK版本和网络层规划2.1 为什么小商品拍卖场景优先选Android原生跨端框架这两年很热Flutter和UniApp在列表页、表单页这类常规界面上的开发效率确实高。但拍卖APP有一个特点倒计时需要精确到秒出价请求需要快速返回而且页面会频繁刷新价格。跨端框架在列表复用和定时器行为上偶尔会出现不可控的延迟排查问题要同时兼顾Dart层、原生层和WebView对新手并不友好。原生Android用RecyclerView加CountDownTimer逻辑直白出了问题能直接在Android Studio里断点跟进去。另外小商品拍卖通常需要对接支付宝、微信支付和推送SDK这些SDK的原生版本永远第一个更新兼容性问题最少。服务端如果用Java系客户端用Android原生还能复用一套JSON解析思路和异常处理习惯。工程上推荐Java为主、Kotlin渐进引入。翻看大量Android开发招聘需求和小项目源码Java代码的维护门槛更低网上可查的中文资料也更多。2.2 Android SDK与构建环境踩坑点用Android Studio创建项目时SDK版本不要盲目追新。compileSdk 33、targetSdk 33在目前主流应用商店是合理的minSdk 24可以覆盖绝大多数存量设备同时避免处理Android 7.0以下的文件Uri权限问题。构建工具Gradle版本和AGP版本必须匹配否则会出现Unable to find suitable Visual Studio toolchain之类的报错——这多半是把C模块和AGP版本混配了。android { compileSdk 33 defaultConfig { applicationId com.example.auction minSdk 24 targetSdk 33 versionCode 1 versionName 1.0 } }compileSdk负责编译期可用APItargetSdk决定系统兼容行为两个值设置成一样比较稳妥。minSdk 24意味着放弃Android 6.0以下设备换来的是运行时权限代码更简单FileProvider的适配成本也低。Android Studio下载SDK后如果出现无法勾选SDK平台的问题常见原因是镜像源连接不通修改SDK Manager里的代理设置即可。2.3 网络层接口契约先定义好客户端开发前先把服务端接口字段定死能避免后期两头扯皮。小商品拍卖的核心接口不需要多复杂关键是把状态码语义定义清楚。接口方法请求参数返回说明获取商品列表GETpage, size, categoryId商品基本信息及当前价获取商品详情GETgoodsId含起拍价、加价幅度、结束时间出价POSTgoodsId, userId, price成功返回最新价格失败返回错误码生成订单POSTgoodsId, userId, finalPrice拍卖结束后调用校验支付凭证POSTorderId, voucherCode确认转账到账情况出价接口尤其要注意客户端展示的当前价只能是参考真正的价格以服务端返回为准。网络层用OkHttp加Gson足够不需要引入太重的基础框架。Android原生环境里网络请求必须放在子线程回调切回主线程更新UI。3. 竞拍核心链路的实现倒计时、出价栏和并发控制3.1 倒计时用CountDownTimer而不是自定义Handler商品列表页和详情页都需要展示距结束的剩余时间。最直接的方案是在RecyclerView每个item里放一个TextView起一个每秒走一次的任务去刷新。但要避免在Activity里直接开子线程循环否则页面退出后线程还在跑容易内存泄漏。Android原生提供的CountDownTimer天然封装了倒计时的生命周期onTick在UI线程回调可以安全更新视图。CountDownTimer timer new CountDownTimer(endTime - System.currentTimeMillis(), 1000) { Override public void onTick(long millisUntilFinished) { long totalSeconds millisUntilFinished / 1000; tvTimer.setText(formatTime(totalSeconds)); } Override public void onFinish() { tvTimer.setText(已结束); btnBid.setEnabled(false); } }; timer.start();endTime是服务端下发的毫秒级结束时间戳客户端不自己算总时长而是用结束时间减去当前系统时间可以避开用户改系统时间导致倒计时不准的问题。1000毫秒的间隔适合秒级展示如果想要进度条效果把间隔改成200毫秒再对进度条做setProgress更新。这里有一个坑RecyclerView滑动复用item时如果直接在Adapter里start CountDownTimer会产生大量Timer界面会卡顿。常见的做法是让Activity持有唯一的Timer实例用一个Map记录每个item的结束时间和绑定位置onTick里只更新当前可见、且position匹配的item。Android进度条组件ProgressBar在拍卖场景里可以拿来做“距结束还有多少比例”的视觉提示随着onTick更新比单纯的文字刷新更醒目。3.2 出价操作按钮、输入框和Handler串行化出价逻辑不适合在onClick里直接发网络请求后立刻刷新UI。用户手速快会连点两次产生两个并发请求回包顺序不一致时价格会显示错乱。做法是把每次出价封装成一条消息通过Handler放进消息队列按顺序处理。private Handler mHandler new Handler(Looper.getMainLooper()) { Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_BID: doBid((int) msg.obj); break; case MSG_UPDATE_PRICE: tvPrice.setText(msg.obj.toString()); break; } } }; private void doBid(int price) { MapString, Object params new HashMap(); params.put(goodsId, goodsId); params.put(price, price); HttpUtil.postAsync(/api/bid, params, new Callback() { Override public void onSuccess(String response) { JsonObject json JsonParser.parseString(response).getAsJsonObject(); int currentPrice json.get(currentPrice).getAsInt(); Message msg Message.obtain(); msg.what MSG_UPDATE_PRICE; msg.obj currentPrice; mHandler.sendMessage(msg); } }); }Handler天然串行的特性在这里是优势用户连续点击每次点击产生的请求都会按顺序发出、按顺序回包、按顺序更新UI不会出现后发先至的乱序问题。出价按钮在点击后立即置灰等服务器返回后才恢复能避免大部分连点。出价成功后客户端不要自己把当前价加一个加价幅度上去必须用服务端返回的最新价。原因很简单其他用户可能同时出了更高的价本地的“乐观更新”会误导用户以为出价成功。3.3 服务端怎么并发控制一个事务和一条版本号客户端再稳服务端并发控制不到位同一件商品会被两个人以同一个价格拍中。小商品拍卖服务端如果直接用MySQL的update语句更新价格高并发出价时容易超卖。推荐方案是加一个version字段更新的同时带上版本号数据库行锁会保证只有第一个更新请求生效。UPDATE goods SET current_price ?, version version 1, last_bid_user_id ? WHERE id ? AND version ? AND end_time NOW();int rows update(UPDATE goods SET current_price ?, version version 1 WHERE id ? AND version ? AND end_time NOW(), newPrice, goodsId, version); if (rows 0) { // 表示价格已被别人抢先或拍卖结束返回提示让用户重新看价格 }rows 0意味着要么价格已变要么拍卖结束这时把最新的价格和剩余时间拉回到客户端展示即可。此方案在Redis集群还没有普及的小项目里足够可靠加价幅度的校验在事务外做一次真正落库时以上面的update为准。常见的误区是只靠客户端传回的时间戳判断价格是否有效服务端必须用数据库时间作为唯一基准。4. 订单、收藏与消息触达SQLite存储和状态机设计4.1 用户订单和拍卖记录的SQLite表结构客户端本地需要缓存用户的历史出价记录、收藏和订单数据避免每次打开应用都拉一遍服务端。Android原生提供的SQLiteOpenHelper足够用配合Room框架反而增加编译期注解处理的复杂度。CREATE TABLE bid_record ( id INTEGER PRIMARY KEY AUTOINCREMENT, goodsId INTEGER NOT NULL, goodsName TEXT, bidPrice INTEGER, bidTime INTEGER, status INTEGER DEFAULT 0 ); CREATE TABLE favorites ( goodsId INTEGER PRIMARY KEY, goodsName TEXT, coverUrl TEXT, createdTime INTEGER ); CREATE TABLE orders ( orderId TEXT PRIMARY KEY, goodsId INTEGER, finalPrice INTEGER, status INTEGER, voucherCode TEXT, createdTime INTEGER );bid_record.status用于表示出价是否领先、已经出局、还是最终赢家0代表出局1代表领先2代表已成交。SQLiteOpenHelper中onCreate负责建表onUpgrade负责ALTER TABLE加字段注意升级时先判断旧表里是否已有目标列否则重复执行会报duplicate column name。4.2 集合页的展示逻辑与收藏竞态RecyclerView展示商品时收藏按钮的切换要跟服务端同步。本地先把收藏状态写进favorites表界面上立刻变成已收藏同时发起一个异步请求同步到服务端。SQLiteDatabase db dbHelper.getWritableDatabase(); ContentValues values new ContentValues(); values.put(goodsId, goodsId); values.put(goodsName, goodsName); values.put(coverUrl, coverUrl); values.put(createdTime, System.currentTimeMillis()); db.insertWithOnConflict(favorites, null, values, SQLiteDatabase.CONFLICT_REPLACE);insertWithOnConflict的CONFLICT_REPLACE模式能自动覆盖重复主键避免先查再插的竞态窗口。拍卖结束后如果用户是赢家orders表里插入一条status0的待支付订单提示用户去线下转账或扫码支付并填写转账验证码做人工核验。4.3 拍中不付款的兜底状态机加WorkManager自动重试小商品拍卖一个普遍问题是参拍者随便出价、成交后不付钱。客户端和服务端要约定完整的订单状态机0待支付、1已支付待核验、2已完成、3已退款、4逾期关闭。Android原生推荐用WorkManager处理“状态同步”这类需要保证可靠性的任务。比如用户拍中后生成了订单但App被系统杀掉需要在下次启动时检测未完成的订单并重新拉取状态。Constraints constraints new Constraints.Builder() .setRequiredNetworkType(NetworkType.CONNECTED) .build(); OneTimeWorkRequest request new OneTimeWorkRequest.Builder(SyncOrderWorker.class) .setConstraints(constraints) .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 10, TimeUnit.SECONDS) .build(); WorkManager.getInstance(context).enqueue(request);setBackoffCriteria设置了指数退避重试策略服务端返回失败时自动等待10秒、20秒、40秒再试覆盖网络抖动和Android系统杀后台导致的同步失败。WorkManager和Service、Handler的主要区别在于它由系统统一调度应用被杀死后已入队的任务在条件允许时仍会执行。5. 发布前的走势验证出价超时的边界和服务器回退5.1 用Android Studio跑多设备验证倒计时边界拍卖倒计时的最后几秒是最容易出问题的用户在00:00:01点击出价服务端如何判定超时客户端的处理技巧是倒计时小于等于2秒时直接停用出价按钮给请求在网络上留出时间余量。服务端则必须用数据库时间校验end_time即使网络卡顿导致请求在结束后1秒才到达数据库层面也要拒绝并返回AUCTION_ENDED错误码。本地联调时如果app抓包失败优先检查Android 7.0以上系统是否已安装用户证书或者把targetSdk临时降级跑一次测试。网络上大量项目采用把服务端部署在内网、Android模拟器通过10.0.2.2访问本机服务的方式联调注意真机访问开发机要换成局域网IP。5.2 版本升级与接口灰度更新的具体建议服务器端接口如果出现破坏性修改客户端现有的旧版本会直接崩溃。常用做法是保留/v1和/v2两套接口路径客户端根据versionCode调用对应版本。Android应用内升级通过检查升级接口返回新版本号和下载地址用DownloadManager下载APK安装时跳转安装未知应用页面。服务端返回数据时版本号字段和业务字段放同一层不要塞在data里{ code: 0, version: 2024-06-01, data: { currentPrice: 88 } }5.3 拍卖结束后刷新商品列表和清理本地状态拍卖结束的瞬间所有在详情页停留的用户都应当被提示“本场已结束”。如果用户停留在详情页超过1分钟说明这个页面还开在内存里定时器还在运行。处理方式是监听Activity的onPause和onResume页面切到后台时记录时间戳回到前台时用当前时间与结束时间比对如果已超时直接刷新整个页面拉到最终结果。本地数据库中的bid_record状态也要在应用启动时做一次同步避免展示“领先”的旧数据。最终状态以服务端订单接口为准本地SQLite只是缓存层不是权威数据源。竞拍结束后客户端通过轮询订单状态接口获取成交结果轮询间隔设置为5秒配合WorkManager做后台兜底同步整个流程不再依赖任何前台线程Android原生的生命周期管理手段在这个节点上才真正发挥了作用。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门