Android毕业设计:本地POI检索+区域筛选+MVP架构实战
发布时间:2026/9/16 19:03:06 锦皓数字建站

简介这是一份面向计算机专业本科生的Android毕业设计参考源码聚焦本地生活类App开发实践适用于移动应用开发课程设计、毕设选题与技术复现学习。资源完整实现仿大众点评核心功能模块包含商户展示、区域筛选、POI列表、快速操作栏等典型UI交互逻辑代码结构清晰便于理解MVC架构在Android项目中的落地方式。压缩包共245个文件以67个XML布局文件构建界面、101张PNG资源图支撑视觉呈现、50个Class字节码与18个Java源文件构成业务逻辑主体辅以APK安装包、ProGuard混淆配置及SQLite数据库文件整体体积仅1.01MB轻量易部署。已有204人下载学习读者可直接运行dianping.apk体验效果结合ServerProxy、ResultActivity、QuickActionWidget等关键类深入分析网络请求封装、结果页跳转与自定义控件实现思路快速掌握毕业设计级Android项目开发全流程。1. 这不是个“仿大众点评”的UI套壳而是一套完整跑通本地POI检索区域筛选列表交互的Android MVP结构毕业设计源码很多同学拿到这个压缩包第一反应是“又一个带圆角卡片和红底按钮的仿写项目”——但解压后看到ServerProxy.class、AreaAdapter.class、PoiListItem.class和ResultActivity.class这几个关键类名就能确认它没停留在界面模仿层面而是实现了本地模拟服务端响应 区域维度数据组织 列表项状态管理 快速操作浮层QuickActionBar四层真实业务逻辑。整个结构采用早期但清晰的 MVP 模式View 层为 Activity/AdapterPresenter 隐含在 ServerProxy 与 Activity 通信中没有引入 Dagger 或 RxJava 等高阶依赖对毕业设计场景极其友好既能展示分层思想又不会因框架复杂度掩盖核心逻辑。适合 Android 开发入门 6 个月以上、已完成《第一行代码》级训练、正卡在“如何把多个 Activity 串成有状态流程”这一关的同学——你不需要部署真实后端ServerProxy已用assets/mock_pois.json或硬编码模拟返回也不需要接入高德地图 SDKAreaAdapter直接绑定省市区三级字符串数组完成区域选择闭环。2. 从 assets/mock_pois.json 到 PoiListItem本地 POI 数据建模与懒加载解析链路2.1 为什么不用网络请求而坚持 assets JSON毕业设计评审最看重可复现性当前主流毕业设计答辩中评委最常质疑的是“你的 App 能离线运行吗”、“断网时功能是否降级”、“数据来源是否可控”。本项目用assets/mock_pois.json替代 Retrofit OkHttp 的典型方案本质是用确定性对抗不确定性JSON 文件内容固定、路径明确、解析逻辑封闭避免因网络超时、Mock Server 崩溃、HTTPS 证书问题导致演示翻车。查看ServerProxy.class反编译代码可用 jadx-gui 打开可见其核心方法getPoiListByArea(String area)并未调用HttpURLConnection而是通过AssetManager.open(mock_pois.json)获取输入流再交由org.json.JSONObject解析。这种设计不是技术退化而是针对答辩场景的精准取舍——它把“数据获取”环节压缩为单次 IO 操作把复杂度转移到更易调试的PoiListItem对象映射上。提示不要试图在ServerProxy中强行添加网络请求。毕业设计中“能稳定展示完整流程”比“用了最新网络库”得分更高。若需扩展应在getPoiListByArea()内部加一层判断优先读 assets失败时 fallback 到预置的 ArrayList 。2.2 PoiListItem 类的字段设计暴露了真实业务约束反编译PoiListItem.class可得其字段声明public class PoiListItem { public String name; // 商户名称非空 public String address; // 地址允许为空 public double lat; // 纬度double 类型用于后续扩展地图定位 public double lng; // 经度同上 public int avgPrice; // 人均价格单位元int 类型便于排序 public String category; // 分类如火锅、咖啡厅 public String area; // 所属区域如朝阳区、徐汇区 public boolean isCollected; // 是否收藏影响 QuickActionBar 图标状态 }注意三个关键点lat/lng字段存在但未在 UI 层使用说明作者预留了地图集成接口这是加分项avgPrice用int而非String意味着支持按价格区间筛选如avgPrice 50 avgPrice 150后续ResultActivity中的筛选按钮即基于此isCollected是布尔值而非服务器返回的collected: 1/0说明收藏状态完全本地维护SharedPreferences 存储符合毕业设计对“轻量级持久化”的要求。2.3 解析流程实操用原生 org.json 逐层提取并容错处理在ServerProxy.getPoiListByArea(String area)方法中JSON 解析逻辑如下已还原为可读 Java 代码public ListPoiListItem getPoiListByArea(String targetArea) { ListPoiListItem result new ArrayList(); try { InputStream is context.getAssets().open(mock_pois.json); String jsonStr readInputStreamAsString(is); // 自定义工具方法将流转 String JSONObject root new JSONObject(jsonStr); JSONArray pois root.getJSONArray(data); // 注意键名为 data非 poi_list for (int i 0; i pois.length(); i) { JSONObject item pois.getJSONObject(i); // 容错字段缺失时设默认值避免 JSONException String name item.optString(name, 未知商户); String address item.optString(address, ); double lat item.optDouble(lat, 0.0); double lng item.optDouble(lng, 0.0); int price item.optInt(avgPrice, 0); String category item.optString(category, 其他); String area item.optString(area, 未知区域); // 区域匹配支持模糊匹配如传入朝阳可命中朝阳区、朝阳门 if (area.contains(targetArea) || targetArea.contains(area)) { PoiListItem poi new PoiListItem(); poi.name name; poi.address address; poi.lat lat; poi.lng lng; poi.avgPrice price; poi.category category; poi.area area; poi.isCollected false; // 初始未收藏 result.add(poi); } } } catch (IOException | JSONException e) { Log.e(ServerProxy, 解析 mock_pois.json 失败, e); // 此处应返回空列表而非抛异常保证 UI 不崩溃 return new ArrayList(); } return result; }逻辑说明optString()/optInt()系列方法是关键它们在字段不存在时返回默认值避免getXXX()抛出JSONException区域匹配采用contains()而非严格相等适配用户可能输入“海淀”而非“海淀区”提升鲁棒性readInputStreamAsString()需自行实现可用BufferedReaderStringBuilder不可直接is.toString()—— 后者返回的是InputStream对象地址非内容。3. AreaAdapter 与 QuickActionBar两级联动交互的 Adapter 设计模式3.1 AreaAdapter 不是普通 Spinner 适配器而是支持三级联动的嵌套数据结构查看AreaAdapter.class反编译代码发现其构造函数接收String[][][] areas参数public AreaAdapter(Context context, String[][][] areas) { this.context context; this.areas areas; // 三维数组[省份][城市][区县] }这意味着数据结构为String[][][] areas { { // 北京市 {东城区, 西城区, 朝阳区, 海淀区, 丰台区, 石景山区}, {通州区, 顺义区, 昌平区, 大兴区, 房山区, 门头沟区} }, { // 上海市 {黄浦区, 徐汇区, 长宁区, 静安区, 普陀区}, {虹口区, 杨浦区, 闵行区, 宝山区, 嘉定区} } };AreaAdapter重写了getView()方法根据position计算当前处于第几级省/市/区并动态设置TextView的缩进与字体大小。例如一级省份用18sp加粗二级城市用16sp常规三级区县用14sp灰色——这种视觉层级直接对应数据维度无需额外标记。注意AreaAdapter本身不处理点击事件它只负责渲染。真正的联动逻辑在ResultActivity中当用户在第一个 Spinner 选中“北京市”第二个 Spinner 的数据源被设为areas[0][0]即北京市下辖的区县数组第三个 Spinner 则根据第二个的选择进一步过滤。这种“Adapter 只管画Activity 管逻辑”的分工正是 MVP 中 View 层的典型实践。3.2 QuickActionBar 的悬浮动效与状态同步机制QuickActionBar.class实现了一个底部悬浮的操作栏包含“电话”、“导航”、“收藏”三个图标。其核心在于setPoiItem(PoiListItem poi)方法public void setPoiItem(PoiListItem poi) { this.currentPoi poi; // 根据 poi.isCollected 动态切换收藏图标 collectIcon.setImageResource(poi.isCollected ? R.drawable.ic_collected : R.drawable.ic_collect); // 电话图标仅在 poi.phone 不为空时启用 callIcon.setEnabled(!TextUtils.isEmpty(poi.phone)); // 导航图标始终启用假设 lat/lng 有效 navigateIcon.setEnabled(poi.lat ! 0.0 poi.lng ! 0.0); }参数说明collectIcon.setImageResource()使用R.drawable.ic_collected实心星和R.drawable.ic_collect空心星两个资源需确保res/drawable/下存在对应文件callIcon.setEnabled()的判断条件是!TextUtils.isEmpty(poi.phone)而非poi.phone ! null因为 JSON 解析后optString(phone)可能返回空字符串null检查会漏判navigateIcon.setEnabled()依赖lat/lng是否为初始值0.0这是常见地理坐标校验方式避免用(0,0)坐标发起无效导航。3.3 在 ResultActivity 中串联 AreaAdapter 与 QuickActionBar 的完整生命周期ResultActivity.class是整个流程的中枢其onCreate()中的关键初始化如下Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_result); // 初始化三级 Spinner Spinner provinceSpinner findViewById(R.id.spinner_province); Spinner citySpinner findViewById(R.id.spinner_city); Spinner districtSpinner findViewById(R.id.spinner_district); // 构造 AreaAdapter 并绑定到一级 Spinner AreaAdapter provinceAdapter new AreaAdapter(this, areas); provinceSpinner.setAdapter(provinceAdapter); // 设置一级 Spinner 监听选择后更新二级数据源 provinceSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { Override public void onItemSelected(AdapterView? parent, View view, int position, long id) { // position 对应省份索引据此获取该省所有城市 String[] cities getCityListByProvince(position); ArrayAdapterString cityAdapter new ArrayAdapter(ResultActivity.this, android.R.layout.simple_spinner_item, cities); cityAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); citySpinner.setAdapter(cityAdapter); } // ... onNothingSelected() }); // 初始化 QuickActionBar 并绑定到当前选中 POI QuickActionBar quickActionBar findViewById(R.id.quick_action_bar); quickActionBar.setPoiItem(currentPoi); // currentPoi 来自 getIntent().getParcelableExtra() // 收藏按钮点击事件本地状态切换 UI 更新 quickActionBar.setCollectClickListener(v - { currentPoi.isCollected !currentPoi.isCollected; quickActionBar.setPoiItem(currentPoi); // 触发图标刷新 saveToSharedPreferences(); // 持久化 }); }这段代码揭示了毕业设计中最易被忽略的细节状态必须双向同步。quickActionBar.setPoiItem()不仅更新图标还持有currentPoi引用点击收藏后先改内存对象状态再调用setPoiItem()刷新 UI最后saveToSharedPreferences()持久化——三步缺一不可。若只改SharedPreferences而不更新内存对象下次setPoiItem()仍显示旧状态若只改内存不持久化App 重启后状态丢失。4. R$drawable 与 resources.ap_APK 资源编译原理及毕业设计中的资源管理规范4.1 R$drawable.class 的存在说明资源 ID 已固化禁止手动修改 res/drawable 名称R$drawable.class是 Android 编译系统生成的资源索引类其内容类似public final class R$drawable { public static final int ic_collected 0x7f080001; public static final int ic_collect 0x7f080002; public static final int bg_card 0x7f080003; // ... 其他资源 ID }每个public static final int对应res/drawable/下一个文件。关键点在于这些 ID 是编译期生成的常量一旦 APK 打包完成就不可变。因此在QuickActionBar中写R.drawable.ic_collected是安全的但若你删除res/drawable/ic_collected.png后又新建ic_star_filled.png即使内容相同R.drawable.ic_collected仍指向原 ID此时会报Resources$NotFoundException。毕业设计中务必遵守资源文件名与代码中引用名严格一致如需新增图标先在res/drawable/放入文件再在代码中用R.drawable.xxx引用切勿反向操作。4.2 resources.ap_ 是 aapt2 编译中间产物验证资源完整性必查项resources.ap_文件是 Android Gradle Plugin 3.0 使用 aapt2 编译后生成的二进制资源包它包含了所有res/目录下的资源编译结果布局、图片、字符串等但不包含assets/和AndroidManifest.xml。在毕业设计打包阶段若出现“图片不显示”、“字符串乱码”第一步应检查resources.ap_是否存在且非空# 在项目根目录执行需安装 aapt2 aapt2 dump resources app/build/intermediates/merged_res/debug/resources.ap_ | head -20正常输出应包含类似Package Group 0 id0x7f packageId0x7f namecom.example.dianping Type 0 configCount1 entryCount12 spec resource 0x7f020000 com.example.dianping:drawable/ic_collected: flags0x00000000 spec resource 0x7f020001 com.example.dianping:drawable/ic_collect: flags0x00000000若命令报错或无输出说明resources.ap_损坏或未生成需执行./gradlew clean assembleDebug重建。这是比看R.java更底层的验证方式能绕过 IDE 缓存干扰。4.3 毕业设计资源命名规范表避免评审时被质疑工程素养资源类型推荐命名规则反例说明Drawable 图标ic_功能_状态如ic_collect,ic_collectedstar1,button_redic_前缀表明是图标collect/collected表明语义状态后缀区分不同形态Layout 布局activity_页面名,item_列表项名如activity_result,item_poimain.xml,list.xml前缀明确作用域避免全局冲突String 字符串action_动作,hint_提示,error_错误如action_search,hint_area_selectstr1,text2语义化命名便于多语言适配hint_表明是 EditText 提示文本Color 颜色color_用途_状态如color_primary,color_text_disabledred,gray_light避免硬编码颜色值primary关联主题色disabled表明禁用态提示在strings.xml中为每个字符串添加tools:ignoreUnusedResources注释需引入 tools 命名空间可防止 Lint 误报“未使用资源”——毕业设计中部分字符串可能仅用于未来扩展暂未调用。5. proguard.cfg 与反编译防护毕业设计代码安全的最低成本实践5.1 proguard.cfg 的四条核心规则如何平衡混淆强度与功能可用性本项目proguard.cfg文件虽小但每行都直指毕业设计痛点-keep public class * extends android.app.Activity -keep public class * extends android.app.Application -keep public class * extends android.app.Service -keep public class * extends android.content.BroadcastReceiver这四行的作用是保留所有 Activity/Application/Service/BroadcastReceiver 的类名和构造方法不被混淆。原因在于 Android 系统通过AndroidManifest.xml中的android:name属性反射创建这些组件若类名被混淆如ResultActivity→a.b.c系统将找不到对应类直接 Crash。毕业设计中无需混淆全部代码只需守住这四个入口点即可。其余如PoiListItem、ServerProxy等普通类可被混淆既满足“代码不可读”要求又不影响运行。5.2 验证混淆效果用 jadx-gui 对比混淆前后 class 差异要确认proguard.cfg生效可执行以下步骤在app/build.gradle中开启混淆buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile(proguard-android-optimize.txt), proguard-rules.pro // 注意此处应引用本项目的 proguard.cfg而非默认文件 } }执行./gradlew assembleRelease生成app/build/outputs/apk/release/app-release.apk用 jadx-gui 打开该 APK对比ResultActivity.class与PoiListItem.classResultActivity类名、onCreate()方法名应保持原样PoiListItem类名可能变为a其字段name、address可能变为a、b但getPoiListByArea()方法仍可被识别因ServerProxy未被-keep但方法逻辑未破坏。若ResultActivity也被混淆则说明proguard.cfg未被正确加载需检查proguardFiles路径是否指向项目根目录下的proguard.cfg。5.3 毕业设计答辩前必做的三项代码安全检查检查项操作命令期望结果风险说明敏感信息硬编码grep -r password|key|secret|api.*key app/src/无输出若存在API_KEYxxx需改为BuildConfig.API_KEY或从local.properties读取Log 调试残留grep -r Log\.e|Log\.d|Log\.i app/src/main/仅Log.e出现在catch块中无Log.dLog.d会泄露调试信息发布版应移除或用BuildConfig.DEBUG包裹资源文件权限ls -l app/src/main/res/drawable/所有 PNG 文件权限为-rw-r--r--644若为777说明开发机配置错误可能引发构建异常执行完这三项检查你的毕业设计代码包就具备了基本的安全基线——它不能阻止专业逆向但足以让评委相信你理解了移动应用开发中的基础防护意识。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。