Android校内二手交易平台开发指南:从表结构到RecyclerView性能优化
简介面向Android课程设计与毕业设计的校内二手交易平台完整工程覆盖用户身份验证、二手物品发布与管理、分类检索、买家留言与卖家回复等核心模块适合应届毕业生或Android入门开发者作为项目蓝本二次开发。压缩包共1522个文件以Java源码、XML布局、class编译文件为主另有可运行APK、JAR依赖库及PNG/JPG界面素材整体约71.89MB模块目录独立清晰。目前已有408人学习下载预览中可见多个不同业务场景的独立APK工程便于对照理解各应用从界面到逻辑的完整实现。开发者可借助全套源码与可执行文件快速跑通流程参考资源中的功能划分与数据设计来扩展交易、消息或管理后台从而缩短毕业设计开发周期。1. 基于android的校内二手交易平台从一道毕设题到一个能演示的App毕业设计选这个题的人多半是从「二手书、自行车、小家电」这几个品类切入的。基于android的校内二手交易平台看起来是个业务题实际上它同时考了四件事移动端UI与列表性能、图片文件处理与系统适配、网络层与数据一致性、还有一套能讲清楚的关系型数据库设计。真正难的不是功能多而是把「发布→检索→联系→成交」这条链路在真机上完整跑通并且答辩演示时能当场不翻车。我按平时实际带项目的思路展开从技术选型、核心表设计、android studio工程搭建、列表与图片的实操参数到最后打包验证不绕弯子。适合正在做毕设的本科生也适合想快速搭一个交易类App骨架、回头能把底层原理讲清楚的工程师。2. 技术选型与核心表结构android客户端怎么配后端最稳2.1 android sdk版本与android studio工程基线装完android studio、拉下工程后第一次sync卡住绝大多数是gradle依赖下载慢而不是代码问题。建项目前先定三个数字compileSdk 34、minSdk 26、targetSdk 34。minSdk 26对应Android 8.0覆盖校园里绝大多数存量机型同时能用上java.time、NotificationChannel这些新特性targetSdk 34意味着你要认真适配分区存储和前台服务限制这些在毕设里反而是可以主动讲的加分点。提示android studio下载完工程后第一次sync去settings.gradle里加阿里云镜像能解决大半超时问题。pluginManagement { repositories { maven { url https://maven.aliyun.com/repository/google } maven { url https://maven.aliyun.com/repository/public } google() mavenCentral() } }这段配置写的是仓库优先级阿里云镜像在前google与mavenCentral兜底依赖解析会先命中国内节点。sync慢还有另一个隐藏原因android sdk平台版本不全。打开sdk manager把Android 34的platform和build-tools勾上比反复点sync重试高效得多。如果是别人移植过来的android studio项目还要先对齐JDK版本android studio 2023之后的版本默认走JDK 17老项目直接打开会报unsupported class file错误。2.2 后端取舍本地Room、Bmob还是自写接口校内二手平台需要多端数据同步纯本地数据库在答辩时会被问「换一台手机数据怎么办」所以不能只做单机。常见做法有三种自建SpringBoot加MySQL、用Bmob这类BaaS、以及Room只做本地缓存。我的建议是优先自建轻量后端把商品、订单、消息定义成RESTful接口android端用OkHttp或Retrofit对接。哪怕只写十个接口对讲清项目架构的帮助也比用BaaS大。方案部署成本演示效果答辩得分适合场景SpringBoot MySQL中完整高有服务器、时间充足Bmob / LeanCloud低完整中想快速出成果Room 本地最低单机低只做前端验收选了自建后端后网络层要统一处理三件事超时重试、token失效跳登录、图片URL拼接。图片我一般不传base64而是直接multipart上传二进制。base64会让JSON体积膨胀三分之一商品列表一次拉20条时体感很明显。列表接口只返回缩略图URL详情页再加载原图这是交易类App最基础的流量控制。如果要做面交地点展示接入高德地图的android SDK能显示校园内的交易位置但对毕设不是必需属于加项。2.3 商品表、订单表与消息表的索引设计交易平台最少四张表用户、商品、订单、消息。下面是SQLite风格的建表语句换到MySQL只需把INTEGER换成BIGINTAUTOINCREMENT换成AUTO_INCREMENT。CREATE TABLE tb_user ( id INTEGER PRIMARY KEY AUTOINCREMENT, nickname TEXT NOT NULL, phone TEXT UNIQUE, password_hash TEXT NOT NULL, campus TEXT, avatar_url TEXT, created_at INTEGER ); CREATE TABLE tb_goods ( id INTEGER PRIMARY KEY AUTOINCREMENT, seller_id INTEGER NOT NULL, title TEXT NOT NULL, price REAL NOT NULL, original_price REAL, category TEXT, cover_url TEXT, status INTEGER DEFAULT 0, created_at INTEGER, FOREIGN KEY (seller_id) REFERENCES tb_user(id) ); CREATE TABLE tb_order ( id INTEGER PRIMARY KEY AUTOINCREMENT, goods_id INTEGER NOT NULL, buyer_id INTEGER NOT NULL, status INTEGER DEFAULT 0, created_at INTEGER ); CREATE TABLE tb_message ( id INTEGER PRIMARY KEY AUTOINCREMENT, goods_id INTEGER, from_id INTEGER NOT NULL, to_id INTEGER NOT NULL, content TEXT, msg_type INTEGER DEFAULT 0, is_read INTEGER DEFAULT 0, created_at INTEGER ); CREATE INDEX idx_goods_status ON tb_goods(status, created_at DESC); CREATE INDEX idx_message_pair ON tb_message(from_id, to_id, created_at);两张索引的重点在查询路径首页是「按状态过滤再按时间倒序」idx_goods_status把status放前、created_at放后消息列表是「查我和某人的会话」idx_message_pair覆盖from_id与to_id的组合。商品表里status用0在售、1下架、2售出比删记录更稳因为订单和消息都要引用它。砍价这个动作不要进订单表留在消息表里完成订单只记录最终成交避开「议价中」这个不好实现的复杂状态机。3. 用android studio实现交易主链路列表、发布、登录3.1 工程分层与依赖版本工程结构我建议用MVVM而不是MVP。MVP要手写大量接口一个商品详情页动辄十个回调毕设写到后面改需求极其痛苦MVVM的ViewModel加LiveData生命周期安全旋转屏幕不丢数据这个细节答辩时也讲得出口。目录按data、repository、ui分层DataBinding按需开启不要全项目强制开。对比项MVPMVVM回调接口每个页面一套LiveData自动通知生命周期手动管理ViewModel自动感知旋转屏幕数据易丢数据保留学习成本低中android { compileSdk 34 defaultConfig { minSdk 26 targetSdk 34 } } dependencies { implementation androidx.core:core-ktx:1.12.0 implementation androidx.appcompat:appcompat:1.6.1 implementation com.google.android.material:material:1.11.0 implementation androidx.constraintlayout:constraintlayout:2.1.4 implementation androidx.recyclerview:recyclerview:1.3.2 implementation androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0 implementation androidx.room:room-runtime:2.6.1 kapt androidx.room:room-compiler:2.6.1 implementation com.github.bumptech.glide:glide:4.16.0 implementation com.squareup.okhttp3:okhttp:4.12.0 }版本号以你在android studio里实际能拉到的最新稳定版为准。这套组合有两个容易踩的版本坑room的kapt处理器和kotlin版本不对应会直接编译失败报错指向GeneratedRoom数据库类找不到glide的注解在release混淆时如果没配上keep规则会出现「图片不显示但也不报错」的怪现象后面章节会说怎么处理。3.2 首页商品列表RecyclerView加DiffUtil列表是平台门面用RecyclerView加ListAdapter是最稳的组合。ListAdapter内部封装了DiffUtil调用submitList时自动计算新旧列表差异只刷新变化的那几行。商品下架、价格变动时列表不会整页闪一下。class GoodsDiff : DiffUtil.ItemCallbackGoods() { override fun areItemsTheSame(oldItem: Goods, newItem: Goods) oldItem.id newItem.id override fun areContentsTheSame(oldItem: Goods, newItem: Goods) oldItem newItem } class GoodsAdapter( private val onItemClick: (Goods) - Unit ) : ListAdapterGoods, GoodsAdapter.VH(GoodsDiff()) { inner class VH(val binding: ItemGoodsBinding) : RecyclerView.ViewHolder(binding.root) override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH VH(ItemGoodsBinding.inflate( LayoutInflater.from(parent.context), parent, false)) override fun onBindViewHolder(holder: VH, position: Int) { val item getItem(position) holder.binding.tvTitle.text item.title holder.binding.tvPrice.text ¥%.2f.format(item.price) Glide.with(holder.itemView) .load(item.coverUrl) .placeholder(R.drawable.ic_placeholder) .error(R.drawable.ic_load_fail) .centerCrop() .into(holder.binding.ivCover) holder.itemView.setOnClickListener { onItemClick(item) } } }areItemsTheSame判断「是不是同一条商品」用主键idareContentsTheSame判断「同一条商品内容变没变」data class的equals就够用价格变化是触发刷新最常见的场景。下拉刷新用SwipeRefreshLayout包RecyclerView网络请求回来后把新列表传给submitList。注意submitList不能在子线程调用虽然ListAdapter内部会处理线程切换但传入的List对象不要复用同一个可变引用否则列表会出现显示错乱。3.3 发布商品的图片选择与FileProvider适配发布页最容易被追问的是图片选择。新版android studio官方推荐Activity Result API替代已经废弃的startActivityForResult。class PublishActivity : AppCompatActivity() { private var coverUrl: String? null private lateinit var progressDialog: ProgressDialog private val coverLauncher registerForActivityResult( ActivityResultContracts.GetContent() ) { uri - uri ?: returnregisterForActivityResult binding.ivCover.setImageURI(uri) uploadCover(uri) } fun onPickCoverClick(view: View) { coverLauncher.launch(image/*) } private fun uploadCover(uri: Uri) { progressDialog ProgressDialog(this) progressDialog.setMessage(图片上传中…) progressDialog.show() lifecycleScope.launch { val bytes withContext(Dispatchers.IO) { contentResolver.openInputStream(uri)?.use { it.readBytes() } } bytes ?: returnlaunch try { coverUrl api.uploadCover(bytes) // suspend请求返回图片URL } catch (e: IOException) { Toast.makeText(thisPublishActivity, 上传失败, Toast.LENGTH_SHORT).show() } finally { progressDialog.dismiss() } } } }这里有个很多人栽过的坑相册返回的uri是content://开头的来源可能是系统相册、文件管理器甚至是微信保存的图片不同来源的FileProvider authority都不一样。你不能拿这个uri直接构造File必须像上面这样用contentResolver.openInputStream统一转字节流。Android 7.0之后跨应用共享文件必须走FileProvider直接传file://路径会抛FileUriExposedException这是系统对文件的硬限制。工程里要注册providerprovider 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?xml version1.0 encodingutf-8? paths external-path nameexternal_storage path. / cache-path namecache path/ / /pathsauthorities里的${applicationId}在构建时替换成你的包名两个应用靠这个authority完成临时读写授权。进度条用ProgressDialog是演示时的最小实现正式一点可以用OkHttp的RequestBody重写writeTo方法回调百分比再驱动android进度条控件更新时间不够就保持对话框版本答辩时补一句「可以替换成带进度的上传」即可。3.4 登录注册的会话保持与软键盘事件处理登录设计要守住一条底线客户端不存明文密码。常见的做法是密码加盐做SHA-256服务端只存哈希值。客户端登录成功后拿token存进SharedPreferences之后每个请求在Header里带Authorization字段后端拦截器校验。退出登录时清token并跳转登录页这个闭环写清楚数据安全部分就能讲三分钟。键盘问题和android的事件分发机制直接相关。登录页通常是ScrollView包EditText在部分机型上键盘弹起会遮挡输入框或者ScrollView抢走输入框的触摸事件导致光标定位失灵。解决办法分两层Manifest里给Activity设android:windowSoftInputModeadjustResize让布局随键盘压缩输入框需要优先响应触摸时在事件分发层面拦截父容器。binding.etPassword.setOnTouchListener { v, event - when (event.actionMasked) { MotionEvent.ACTION_DOWN - v.parent.requestDisallowInterceptTouchEvent(true) MotionEvent.ACTION_UP - v.parent.requestDisallowInterceptTouchEvent(false) } false }requestDisallowInterceptTouchEvent是android事件分发机制里最常用的控制手段调了它父容器在这个事件序列里就不能再拦截事件会先过EditText。注意这个标记只在当前手势序列内有效手指抬起自动重置所以ACTION_UP时要主动放开不然下次手势的判定会受影响。4. 商品列表性能、图片缓存与android真机调试的实操参数4.1 RecyclerView vs ListView三种列表方案的取舍表首页、搜索结果、我的发布都是列表。有同学图省事用ScrollView套LinearLayout来「循环」渲染商品数据量到二十条就开始掉帧原因是ScrollView没有ViewHolder缓存机制所有item一次性全部measure和layout。真机上的直观表现是滑动白屏、掉帧卡顿点在主线程。方案数据量复用缓存刷新粒度推荐度ScrollView LinearLayout10无全量不推荐ListView ViewHolder数百有全量刷新老项目RecyclerView ListAdapter上千有DiffUtil按需推荐RecyclerView的setHasFixedSize(true)可以在item尺寸不变时跳过重新测量但如果商品卡片宽度固定、高度随文字变化就不要开这个开关否则会出现item复用错位的经典问题。分页加载用Paging 3是省心的方案它的PagingSource把「加载更多」「去重」「加载状态」都封装好了不会出现滑动到底部重复请求。首页也可以不加分页一次拉20条到50条用「下拉刷新 上拉加载」两段式就够毕设演示。4.2 Glide加载商品图三个必调参数商品图片是流量和内存的大头Glide有三个参数每次加载都要带上占位图、磁盘缓存策略、显示尺寸。Glide.with(imageView) .load(goods.coverUrl) .placeholder(R.drawable.ic_placeholder) .error(R.drawable.ic_load_fail) .diskCacheStrategy(DiskCacheStrategy.ALL) .override(400, 400) .centerCrop() .into(imageView)placeholder解决网络慢时的白屏error解决URL失效时的破图这两个属于体验兜底。override(400, 400)配合centerCrop是把原图按400x400取样显示内存占用约为原图的十六分之一列表滚动的卡顿大多从这里省出来。diskCacheStrategy.ALL缓存原图和转换后的缩略图二次进页面直接读磁盘不发网络请求。注意Glide的缓存key由URL、override尺寸、变换参数共同决定。改override尺寸而URL没变缓存会新增一条旧的那条不自动清理时间久了缓存目录会膨胀清理逻辑记得写进设置页。如果想做加载进度条用Glide的RequestListener在onResourceReady前显示一个转圈占位图片真正渲染出来再隐藏。这个细节在演示时比任何动画都直观老师能直接看到网络耗时和缓存命中之间的差别。4.3 用android debug bridge做无线调试多机测试是隐藏加分项。android debug bridgeADB不只是装apk的无线调试能让你脱离数据线拿着两台手机在不同楼层走位测网络切换。手机先用USB连上电脑执行两条命令adb devices adb tcpip 5555 adb connect 192.168.1.100:5555adb devices确认设备被识别adb tcpip 5555让设备在5555端口开监听adb connect把电脑连到手机的局域网地址。连接成功后拔掉数据线android studio的运行设备列表里会出现这台手机。前提有两个手机和电脑必须在同一网段校园网开了AP隔离会直接连不通手机重启后5555监听失效需要重新插线执行一次。排查时用adb disconnect清除旧连接再adb connect重连比重启android studio快。5. 打包验证与答辩演示的三个落地技巧毕设演示翻车通常不是功能问题而是环境和流程问题现场网络慢、签名没配好老师手机装不上、演示顺序打乱导致数据对不上。我一般在答辩前一周把验证和演示流程固定下来三个技巧足够。第一个技巧是用命令行打release包绕开android studio界面的构建缓存问题。项目根目录执行cd android ./gradlew clean assembleRelease产物在app/build/outputs/apk/release/下。首次执行会下载gradle发行版慢是正常的二次构建就是增量。release包要提前配好签名签名写在app/build.gradle的signingConfigs里用jks文件加storePassword、keyAlias两个参数并在buildTypes.release里引用。没签名的包装到别的手机上会被提示无法安装这是演示现场最尴尬的情况。第二个技巧是过一遍验证清单冷启动进首页不超过3秒开飞行模式进App有明确的错误提示而不是白屏杀进程再进详情页状态能恢复同一条商品重复进入不重复发网络请求老师手机装上release包能直接跑。这几项逐条测过意外概率大幅下降。图片缓存的重点验证方式是开着抓包工具看日志第二次打开同一商品页应看不到图片URL的网络请求全是磁盘命中。第三个技巧是演示顺序。先演示发布再演示列表浏览发布会生成一条真实数据列表刷新后刚好置顶形成「我发布的我能搜到」的逻辑闭环搜索演示放在列表后面对着刚发布的商品搜关键词命中率最高。联系卖家走消息页现场发一条消息对方端收到后回一条整个链路收在订单确认上。整套演示控制在5分钟内把架构、性能、适配这些内容留到被提问时再讲而不是演示时一口气念完。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →