ARTICLE DETAIL

资讯详情

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

基于Android Studio的失物招领App毕业设计:客户端、数据库与服务端全解析

基于Android Studio的失物招领App毕业设计:客户端、数据库与服务端全解析 简介这是一套基于Android Studio开发的失物招领App完整毕业设计项目包含Android客户端、Oracle数据库脚本与服务器端程序面向计算机相关专业毕业生和移动开发入门者。项目覆盖失物信息发布、关键词检索、认领匹配与账号管理等核心环节提供了从移动端到服务端的完整实现思路。资源共341个文件以Java源码、XML布局、class文件为主辅以jar依赖库、SQL初始化脚本、可直接安装的APK以及图标切图等资源压缩包约11.64MB。其中lafsql.sql负责初始化Oracle数据表服务器端提供Servlet接口和JDBC工具类账号查询、端口域名及数据库口令等配置均可按实际环境调整。目前已吸引137人学习下载目录结构清晰便于毕业设计答辩演示、功能二次开发或迁移改造也能帮助理解Android与Java Web服务端之间的数据交互全流程。1. 毕业设计里的失物招领App到底交什么只靠客户端Demo拿不到高分「毕业设计基于Android Studio开发的失物招领App源代码数据库服务器端程序」这个题目很多学生拿到手第一反应是“把界面做出来就完事”。但真到答辩现场评委打开服务器端程序和数据库表结构的时间往往比看演示视频还长。这个题目交付的其实是一套三层小系统Android客户端负责展示与录入数据库负责存用户、物品和认领记录服务器端程序负责把两端串起来并提供接口。它解决的是校园或社区里“东西丢了找不到、捡到东西没人认领”的典型问题。适合准备Android方向毕业设计、又不想只交单机Demo的人界面只占工作量一小半大头在接口约定和前后端联调而这恰好是评委最乐意追问的部分。2. 把失物招领拆成三个端客户端、数据库、服务器端的分工与选型很多第一次做完整项目的同学习惯先打开Android Studio写界面写到一半才发现数据从哪来、图片存哪、用户怎么验证都是空的。失物招领业务看着简单一旦做成C/S结构就立刻涉及三个端动手前先把各端边界划清楚后面联调能少走一半弯路。2.1 客户端只做三件事信息展示、发布表单、图片上传我一般建议客户端保持「薄」一点只承担三件事展示失物/招领列表和详情提供发布表单让用户填写上传图片并展示图片。业务规则不要堆在客户端比如「同一件物品只能被认领一次」「发布人对认领申请有最终确认权」这类逻辑应该由服务器端校验。客户端做校验只是为了体验更好服务器端不校验的话别人绕开你的App直接调接口规则就形同虚设。这也是答辩时老师最容易问的一个点你的数据能不能被伪造请求刷进去。客户端这边有四个页面跑不掉首页列表、发布页、详情与认领页、个人中心。个人中心里要能看到我发布的物品、我的认领记录所以客户端本地最少要存一个会话标识一般用SharedPreferences存登录接口返回的token。至于列表要不要用SQLite做本地缓存我建议除非你确实时间富余否则别加。本地缓存会引入数据一致性的问题别人认领了你的物品你本地还是旧的刷新不及时反而显得系统不可靠。把这个工作量省下来投到接口和边界处理上性价比高得多。2.2 数据库选型为什么是MySQL而不是SQLite表结构怎么定服务器端数据库的主流选择是MySQL没有太多悬念。理由很实际指导老师环境里大概率装了MySQL可视化工具Navicat或Workbench都有出问题好查MySQL对并发和事务的支持也能应付答辩时的“多设备同时操作”追问。SQLite更适合做客户端本地缓存或纯单机版如果题目明确写了「服务器端程序」你在答辩时说“数据存在手机自带数据库里”基本会被追问到无话可说。表结构建议三张表起步。一张用户表存账号、密码摘要、昵称和联系方式一张物品表字段包含标题、描述、类型、地点、联系人方式、图片路径、状态、发布时间一张认领记录表记录哪个用户对哪件物品发起了认领申请以及当前处理状态。物品表里用type字段区分「失物」和「招领」很多初学做法是分两张表查询时再合并完全不必要一个状态字段解决的事不要拆成两张表。字符集统一用utf8mb4排序规则utf8mb4_general_ci否则中文写入和查询都可能出现问号。2.3 服务器端程序Java Servlet方案与其他方案的取舍服务器端程序是这个题目的隐藏大头。常见做法有三类Java Servlet跑在Tomcat上、Node.js用Express写、Spring Boot全家桶。结合「毕业设计」这个场景我最常推荐的是Java Servlet理由有三个。第一和Android同为Java生态学生不用为了服务器再去学一套语言第二Servlet上手快一个类一个方法就是一个接口适合课程阶段的知识结构第三部署简单打成WAR包丢进Tomcat的webapps目录就能跑答辩演示时出问题的概率最小。方案需要环境适合人群要注意的坑Java Servlet TomcatJDK Tomcat MySQL课程以Java为主的本科毕设Servlet 3.0以上才方便处理文件上传Node.js ExpressNode.js前后端都想用JS的学生需要额外装运行环境演示机如果没有就尴尬Spring BootJDK Maven学过Spring的少数学生依赖下载慢、概念重本科毕设容易跑偏我不建议在这个题目里上Spring Boot除非你已经熟练。它的自动配置和依赖管理对新手是个黑匣子很多情况下「能跑」但「讲不清」答辩时被问到原理很容易露怯。Servlet方案虽然代码啰嗦但每一行都能解释清楚这恰恰是毕设答辩想要的效果。服务器端的核心职责是鉴权、路由、读写数据库和托管图片把这些做扎实比炫技有用得多。3. 用Android Studio搭起客户端骨架网络权限、登录态与列表页选定三端方案后先从Android Studio这边开搭。新建工程时选Empty Views Activity或Empty Activity都可以包名别太随意我一般用com.example.lostfound这类结构方便后面讲「分层」。如果你刚装好Android Studio界面语言是英文不习惯去Settings里的Appearance可以切换中文显示这属于编辑器层面不影响工程编译但能让你翻菜单快一点。3.1 工程配置三处必须提前改好的设置第一处是网络权限。新建工程默认没有联网权限Manifest里不加uses-permission后面所有请求都会秒失败。第二处是明文流量开关Android 9API 28起默认禁止明文HTTP请求而毕业设计的Tomcat接口几乎都是http://加IP不加开关直接报“Cleartext HTTP traffic not permitted”。第三处是依赖库网络请求和图片加载不要自己造轮子该引的引进来。!-- AndroidManifest.xml 中的关键配置 -- uses-permission android:nameandroid.permission.INTERNET / application android:usesCleartextTraffictrue android:labelstring/app_name /applicationusesCleartextTraffic置true是能最快跑通联调的方案它允许整个App走明文HTTP。如果你想让答辩加分可以换成networkSecurityConfig按域名配置白名单但那个工作量对毕设没有明显收益我通常建议先把true打开跑通后再考虑收紧。注意这个属性只对targetSdk 28及以上才有强制意义但加上无害。// app/build.gradle 中的依赖 dependencies { implementation androidx.appcompat:appcompat:1.6.1 implementation androidx.recyclerview:recyclerview:1.3.1 implementation com.google.android.material:material:1.9.0 implementation com.github.bumptech.glide:glide:4.15.1 implementation com.squareup.okhttp3:okhttp:4.11.0 implementation com.google.code.gson:gson:2.10.1 }依赖库的版本以你自己工程能编译为准网上的博客常常带旧版本号复制下来经常和最新的compileSdk不兼容。Glide用来加载服务器返回的图片OkHttp负责网络请求Gson负责解析JSON这三个组合是Android Studio后端接口联调最常见的配置。Material库主要用它的控件样式也可以不加。同步Gradle时会下载大量依赖建议提前把国内镜像仓库配好否则卡在下载流程也是常事。3.2 登录注册模块接口约定与会话保持登录注册是整套系统的入口也是前后端第一次联调的试金石。端上的逻辑不复杂用户输入用户名密码App把这些参数POST到服务器端/login服务器端校验通过后返回一个tokenApp把token存进SharedPreferences。后面发布物品、提交认领时带着这个token去请求服务器端就知道是谁在操作。这里有个新手必踩的坑网络请求绝不能写在主线程。Android不允许在主线程访问网络一旦写了直接抛NetworkOnMainThreadException。常见做法是开一个子线程执行请求拿到结果后再切回主线程更新UI。// 登录请求核心代码在线程中执行 private String doLogin(String username, String password) throws IOException { URL url new URL(BASE_URL /login); HttpURLConnection conn (HttpURLConnection) url.openConnection(); conn.setRequestMethod(POST); conn.setDoOutput(true); conn.setConnectTimeout(5000); conn.setReadTimeout(5000); // 表单参数需要URL编码中文和特殊字符才不会乱 String body username URLEncoder.encode(username, UTF-8) password URLEncoder.encode(password, UTF-8); conn.getOutputStream().write(body.getBytes(UTF-8)); if (conn.getResponseCode() 200) { InputStream is conn.getInputStream(); return readStream(is); // 把输入流转成字符串再用Gson解析 } return null; }BASE_URL的写法是联调里最容易懵的地方模拟器里访问宿主机用http://10.0.2.2:8080真机用电脑的局域网IP比如http://192.168.1.101:8080。BASE_URL末尾不要带斜杠后面拼接口路径时统一用“/login”这种写法。connectTimeout设为5000毫秒比较合适太短在真机弱网条件下容易误报失败太长演示卡住时很难看。服务器端的接口路径要和App里拼出来的一致这一环不一致其他全是白搭。3.3 列表页与分页加载RecyclerView的正确打开方式列表页用RecyclerView实现这是Android Studio开发App项目的标准姿势。RecyclerView比ListView省事的地方在于它强制你写ViewHolder数据量大时滑动更流畅。数据来源是GET请求服务器端/item/list接口返回JSON数组后通过Gson解析成对象列表再交给Adapter渲染。// 分页加载示意代码 private void loadPage(int page) { new Thread(() - { String json HttpUtil.get(BASE_URL /item/list ?type currentType page page pageSize10); runOnUiThread(() - { ListItemBean list GsonUtil.parseList(json); if (page 1) { adapter.setData(list); // 下拉刷新时替换整页 } else { adapter.addData(list); // 上拉加载时追加 } }); }).start(); }这里的currentType是列表页当前的筛选类型失物还是招领。分页参数page从1开始pageSize固定10服务器端返回数据时同时返回total总数客户端据此判断还有没有下一页。我特别提醒一句空列表状态一定要做。很多人的列表页只有有数据时正常数据清空后页面一片白答辩时老师删掉一条记录再刷新界面就成了白板这种细节特别掉分。加载失败也要有提示最简单的做法是列表底部放一个TextView显示“加载失败点击重试”这比静默失败强得多。4. 建库建表与接口定义六条API撑起失物招领全部流程服务器端程序设计里数据库和接口是绑在一起交的。先建表再定义接口接口的返回格式决定了Android端解析代码怎么写。这一步如果前后端分开两个人做一定要先定接口文档如果一个人包办至少把接口路径和返回字段写进代码注释不然过两周你自己都忘了。4.1 建库建表SQL用户表、物品表、认领记录表SQL脚本是整个系统的地基。数据库名称用lostfound字符集务必用utf8mb4。三张表的主键都让MySQL自增外键逻辑放在应用层判断而不是数据库强制约束这样对新手来说反而简单不会出现外键冲突导致数据写不进去的情况。-- 初始化失物招领系统数据库 CREATE DATABASE lostfound DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE lostfound; -- 用户表账号密码和联系方式 CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL COMMENT SHA-256摘要禁止明文, nickname VARCHAR(50), phone VARCHAR(20), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB; -- 物品表同时存放失物和招领 CREATE TABLE item ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL COMMENT 发布人ID, title VARCHAR(100) NOT NULL, description TEXT, type TINYINT NOT NULL COMMENT 1失物 2招领, location VARCHAR(100), contact VARCHAR(100), image_url VARCHAR(255), status TINYINT DEFAULT 0 COMMENT 0待处理 1已被认领, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_type_time (type, create_time) ) ENGINEInnoDB; -- 认领记录表谁申请认领哪件物品 CREATE TABLE claim ( id INT PRIMARY KEY AUTO_INCREMENT, item_id INT NOT NULL, user_id INT NOT NULL, message VARCHAR(255), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB;密码列我特意用SHA-256摘要而不是明文答辩时老师几乎必问“密码安全怎么处理”你至少要能说出“不能明文存”这个结论。物品表里type和status是两个不同的概念type表示这条记录是丢了东西还是捡到东西status表示这条记录目前的状态。很多同学把这两个字段混成一个导致查询逻辑绕圈子。索引只加了type和create_time的联合索引因为首页查询条件基本就是“按类型按时间倒序”这个索引够用多余的索引反而是写入负担。4.2 六个接口的约定路径、参数与统一返回JSON服务器端接口按功能拆成六条就足够覆盖全部流程。所有接口统一返回一个JSON结构code表示业务状态码200是成功400是参数错误500是服务器异常msg是给人看的提示文案data放具体数据。这个约定一定要先定死Android端解析代码才能一次写对。接口方法路径用途核心参数注册POST/register创建账号username, password登录POST/login获取tokenusername, password上传图片POST/upload返回图片URLfile发布物品POST/publish新增失物/招领title, type, location, contact, imageUrl列表查询GET/item/list分页列表或详情type, page, pageSize, id提交认领POST/claim登记认领申请itemId, message列表查询这里偷了个懒不单独做详情接口客户端点击列表项时调用/item/list?id5服务器端判断如果传了id就返回单条详情。这样少一个接口也少一次联调。数据库里查不到记录时返回的data为nullmsg写成“物品不存在”客户端拿到code不是200时弹Toast提示msg内容就行。{ code: 200, msg: ok, data: { id: 12, title: 黑色钱包, type: 1, location: 图书馆三楼, contact: 13800000000, status: 0 } }服务器端Servlet实现时推荐用Gson把Java对象直接转成这段JSON不要用字符串拼接。拼接容易漏引号、漏逗号而且用户输入里如果包含引号拼接出来的JSON是坏的解析直接报错。用Gson的toJson方法一个对象一个方法就完事。返回前记得设置response.setContentType(application/json;charsetUTF-8)这行不写中文会乱。4.3 图片上传Base64方案还是Multipart方案图片上传是失物招领系统里让不少同学卡壳的环节。两种方案都可行标准做法是Multipart/form-data格式上传文件Servlet 3.0以上用MultipartConfig注解配合request.getPart就能读到文件省事做法是客户端把图片压缩后转成Base64字符串当作普通表单字段提交服务器端解码后存成文件。我建议毕业设计走Base64方案。理由不是Multipart不好而是Base64把文件传输问题降级成了普通字符串传输问题你不需要掌握Multipart的边界、临时文件清理这些细节Android端和服务端两边代码都短出问题好排查。代价是Base64会让数据体积膨胀约三分之一但失物招领的图片大多是手机拍的一张照片压到70%质量后一般在几百KB以内完全可接受。// Android端图片转Base64示意 ByteArrayOutputStream baos new ByteArrayOutputStream(); bitmap.compress(Bitmap.CompressFormat.JPEG, 70, baos); String imgBase64 Base64.encodeToString(baos.toByteArray(), Base64.NO_WRAP);压缩质量70是我常用的经验值肉眼基本看不出损失体积能砍掉一半以上。Base64.NO_WRAP这个参数很关键默认的编码会插入换行符整段塞进表单参数时换行会导致服务器端解析截断。服务器端收到后用Base64的decoder解码文件命名用UUID加时间戳避免两个人上传重名文件互相覆盖。数据库里只存相对路径比如/upload/20240601/xxxx.jpg访问时拼上服务器IP和端口就行。5. 联调避坑模拟器连不上服务器、中文乱码和图片上传前后端分开开发时联调阶段才是一堆玄学问题的爆发期。这些问题单独看每一个都不难但排起来非常消耗时间。这一章把我这些年见过最多的坑按现象、原因、解决三段写清楚照着排查能省下好几个通宵。5.1 模拟器与真机的网络地址10.0.2.2还是局域网IP现象App在模拟器里启动后点击登录转圈几秒后提示“连接失败”但服务器端Tomcat明明已经启动浏览器访问接口也正常。原因Android模拟器把宿主机电脑看作是特殊地址10.0.2.2你在模拟器里写localhost指到的是模拟器自己。很多教程写的127.0.0.1在模拟器里根本不通。真机调试时10.0.2.2也不通必须用电脑在局域网里的IP同时保证手机和电脑连的是同一个WiFi。解决把启动入口提供一个可配置的BASE_URL或者写一个简单的设置页填服务器地址。真机调试时先用电脑命令行查IPWindows用ipconfigmacOS用ifconfig找到类似192.168.x.x的地址填进去。模拟器里直接用http://10.0.2.2:8080。另外记得关掉电脑防火墙对Tomcat的拦截或者弹出提示时选择允许否则真机依然连不上这个和代码没有关系。5.2 中文乱码UTF-8在两端都要显式声明现象注册的用户名是中文服务器端收到的变成一串问号或者服务器端返回的“操作成功”在App里显示成乱码。原因字符编码在四个环节里只要有一环不是UTF-8就会乱。Android端发送时没指定编码Tomcat接收时用了默认的ISO-8859-1Servlet返回时没设置ContentType的charsetMySQL表字符集不是utf8mb4。只改其中一处不起作用。解决四端全堵上。Android端发送内容用body.getBytes(UTF-8)Servlet里所有接口入口先调request.setCharacterEncoding(UTF-8)再调用getParameter响应统一response.setContentType(application/json;charsetUTF-8)JDBC连接串带上useUnicodetruecharacterEncodingUTF-8。Tomcat 8以上URIEncoding默认就是UTF-8GET请求一般不用管主要是POST和响应两头容易漏。5.3 图片上传失败多数不是代码问题是请求格式问题现象发布带图片的物品时接口返回400去掉图片字段就正常。原因常见两类。一类是用了Multipart格式但服务器端Servlet没有加MultipartConfig注解request.getPart直接抛异常另一类是Base64字符串里带了换行或加号被解析破坏。加号在URL编码里会被当成空格Base64的/组合在表单传输时经常因为编码问题被篡改。解决如果走MultipartServlet类上加MultipartConfig(maxFileSize510241024, maxRequestSize1010241024)文件大小和请求大小上限都做限制。如果走Base64先对Base64字符串做URLEncoder编码再拼到表单里服务器端先URLDecoder再解码Base64。图片压缩要控制在70%质量原始照片直接上传会超过限制。服务器端保存目录要提前创建很多情况下代码没错是upload目录不存在导致写入失败。5.4 数据库连接池耗尽每次请求都new connection的代价现象连续点击发布了十几次物品服务器端偶尔报“Too many connections”Tomcat控制台刷出红色异常。原因很多初学代码里每个Servlet方法开头都写一行DriverManager.getConnection用完就close。MySQL默认最大连接数是151快速连续请求下连接来不及释放连接数被打满后新的请求全部报错。这个坑在你演示时尤其致命因为演示时你会频繁操作触发概率极高。解决不要每次请求现连数据库用连接池。Servlet容器最简单的方式是配JNDI数据源在Tomcat的context.xml里声明代码里通过Context查找DataSource取连接时用ds.getConnection()。参数上maxTotal设20对毕设足够maxWaitMillis设3000拿不到连接时快速失败而不是无限等。!-- Tomcat context.xml 中的数据源配置 -- Resource namejdbc/lostfound authContainer typejavax.sql.DataSource driverClassNamecom.mysql.cj.jdbc.Driver urljdbc:mysql://localhost:3306/lostfound?useUnicodetrueamp;characterEncodingUTF-8amp;serverTimezoneAsia/Shanghai usernameroot password你的密码 maxTotal20 maxIdle10 maxWaitMillis3000/XML里符号要写成这是很多照着配但起不来Tomcat的原因。serverTimezoneAsia/Shanghai是MySQL 8的时区参数不加会报时区错误。如果你不想碰JNDI用Apache Commons DBCP在代码里初始化一个静态的DataSource也可以核心思想是一样的连接复用来代替每次新建。5.5 交付源代码时翻车的三处Gradle版本、SDK路径与SQL脚本现象把整个Android工程压缩发给指导老师或同学对方Android Studio打开后一直卡在Gradle sync或者报各种找不到SDK的错误。老师还没看到你的代码印象分先掉一半。原因Android Studio新建工程时Gradle版本和Android Gradle Plugin版本是绑定的你的工程里如果依赖了本地SDK的绝对路径换台电脑路径不存在构建直接失败。还有一种情况是只发了客户端代码忘了带SQL脚本接收方建不了库系统根本跑不起来。解决工程务必用Gradle Wrapper方式交付。项目根目录下的gradle/wrapper/gradle-wrapper.properties和gradlew脚本、gradlew.bat都要保留这样对方打开时会自动下载对应版本不会因为本机Gradle版本不一致而失败。交付前在干净环境验证一次构建输入./gradlew clean assembleDebug能出APK再发。打包时别发build目录和.idea目录这两个是本地产物垃圾又大。SQL脚本单独放一个lostfound.sql放在项目根目录的db文件夹里README里写清楚MySQL版本和Tomcat版本。6. 答辩演示怎么演先跑通四条主流程再演示边界功能做完了最后一步是把这套系统在答辩现场稳稳当当地演出来。演示顺序和验证方法是有讲究的我建议提前一天按下面这套流程过一遍比临时临场发挥稳妥得多。6.1 用Postman把接口先过一遍先别急着开模拟器。把服务端跑起来用Postman按接口表的顺序逐条验证注册、登录、上传图片、发布、列表、认领。这一步的作用是隔离问题如果接口在Postman里都通App又报错那问题在客户端如果Postman就不通那问题在服务器端不要混在一起猜。# 也可以直接用curl验证登录接口 curl -X POST http://localhost:8080/lostfound/login \ -d usernametestpassword123456返回JSON里code是200说明接口链路通。这里顺手验证一下中文参数和超长参数比如username传一个带空格的中文看服务器端会不会乱码。这些验证结果记下来答辩时老师问“你怎么保证接口是对的”你把这套验证流程讲出来就是加分的实操经验。6.2 演示时的顺序与话术要点现场演示我先跑四条主流程注册新账号、用这个账号发布一条失物、回到首页看到这条记录、用另一个账号提交认领申请。这四条串起来覆盖了用户、物品、认领三张表和六个接口是整套系统的核心链路。跑完主流程再演示边界断网时点列表App能提示“网络异常”而不是白屏发布时不上传图片接口能正常接收重复提交认领服务器端能给出“已认领过”的提示。这几个边界场景是最能显示你考虑过真实使用条件的证据。图片加载这个环节容易翻车我习惯在演示前先确认服务器端的图片URL在模拟器里能直接打开模拟器访问宿主机静态资源没问题App里的显示才能顺利。还有一个小细节发布物品的输入框键盘弹出后页面会被顶得乱七八糟提前在AndroidManifest里给Activity配windowSoftInputModeadjustPan演示时观感差很多。这些细节都是我自己带过的项目里一次次踩出来的功能大家都写得出来拉开差距的正是边界处理和演示稳定性。希望帮到你。本文还有配套的精品资源点击获取
返回列表