尧图精选

社区居家养老APP源码拆解:Android订单流转与高德地图定位实战

🕒 发布时间:2026/10/2 4:24:44 📁 来源:尧图网络
简介基于Android的社区居家养老服务APP源码工程面向Android初中级开发者和毕业设计选题学生覆盖社区养老场景下的完整业务闭环。项目包含注册登录、上门看病与康复护理预约、送药配送、营养餐推荐及配送、身体指标记录、星级医护聊天以及个人信息与密码修改等模块可作为移动应用开发课程设计或实际产品原型的重要参考。压缩包共2000个文件约89.43MB以535个xml界面布局、304个java业务代码、268个json数据配置为主另含png/jpg图片素材、so动态库及少量jsp/sql等辅助文件目录结构按功能拆分较为清晰便于局部查阅与二次开发。资源已有151人学习浏览代码中保留了完整业务逻辑与资源文件可帮助理解Android项目组织方式、网络数据交互与界面搭建思路适合需要快速入手整套APP源码的开发者参考借鉴。1. 社区居家养老APP不止是“老人点餐”这份源码到底给了你什么最近在整理Android项目文件夹的时候发现这套社区居家养老服务APP的源码很有意思。第一眼看去它不是一个只有登录注册和几个空页面的教学demo而是把“老人/家属下单、服务商接单、服务人员上门、服务评价闭环”的完整业务线做了出来并且工程里还带着设计说明文档和数据库脚本。这对准备做智慧养老、社区服务类课题的在校生或者刚接手外包项目想快速套业务骨架的Android开发来说是个能省不少事的底包。我打算从源码结构、数据模型、订单流转、地图定位这几个核心点往深里拆再讲编译和运行时最容易翻车的几个坑。这套源码用的是原生Android Java网络层是Retrofit OkHttp地图是高德SDK数据库用的是SQLite配合模拟数据接口。整体难度中等适合有半年以上基础的人去改造成自己的项目。如果你正卡在“不知道怎么把业务逻辑和数据表设计出来”这份资源能给你一套可复现的思路。2. 先理清系统边界角色、服务流程与数据模型做这类社区服务APP很多人一上来就写代码结果做到订单流转那一步就乱套。这套源码首先值得看的就是它的角色划分和数据表设计。源码里把用户分成了三类老年用户端、服务人员端、运营管理端三个角色共用同一个APP工程通过登录后返回的角色字段切换导航菜单和功能入口。这种单工程多角色的做法比拆三个APP更省成本也适合现阶段很多社区服务项目多端合一的需求。2.1 三种角色的权限划分与界面入口登录接口返回的roleType字段决定了一个人能干什么。老人端roleType1能看到服务分类、服务项目详情、我要下单、我的订单列表、服务评价服务人员端roleType2能看到待接单、已接单、工单详情、上门打卡、收款码管理端roleType3在APP里主要是服务工单监督和数据统计真正的订单管理一般放在后台Web端APP里只做待办提醒和紧急求助处理。实际代码里主界面MainActivity会根据登录返回的roleType动态切换BottomNavigationView的item和对应的Fragment。我在改造这个工程时一般还会在后端返回的用户信息里加一个menuList字段前端根据菜单集合渲染这样后续增加新功能免不了还要改Android端判断逻辑。这套源码的做法是写死在代码里的好处是简单坏处是加角色就要改代码。你拿到了可以先不动等业务稳定后再做动态菜单。权限控制不在APP层而是在接口层。源码的ApiService里每个接口都带Header(Authorization)后台解析token后判断角色权限。这里有张表我习惯放在设计文档第一页角色典型操作主界面入口老人/家属浏览服务、下单、取消、评价首页服务分类、我的订单服务人员抢单、接单、查看工单、打卡、结算工作台、我的工单运营管理工单审核、服务监督、数据上报待办列表、监控看板2.2 数据库表设计服务订单、工单、评价表怎么关联这套源码的设计文档里有完整的SQLite建表脚本核心是四张表user、service_item、service_order、work_order。我精简一下订单和工单的字段结构你就能看出来订单状态是怎么流转的。CREATE TABLE service_order ( id INTEGER PRIMARY KEY AUTOINCREMENT, order_no TEXT NOT NULL, user_id INTEGER NOT NULL, service_item_id INTEGER NOT NULL, worker_id INTEGER, status INTEGER DEFAULT 0, service_address TEXT, service_time TEXT, remark TEXT, evaluate_status INTEGER DEFAULT 0, create_time TEXT ); CREATE TABLE work_order ( id INTEGER PRIMARY KEY AUTOINCREMENT, order_id INTEGER NOT NULL, worker_id INTEGER NOT NULL, actual_start_time TEXT, actual_end_time TEXT, signin_location TEXT, signout_location TEXT, work_remark TEXT, status INTEGER DEFAULT 0 );这里的关键设计是service_order只管业务层状态比如待支付、待接单、服务中、待评价、已完成而work_order是服务人员层面的执行明细记录实际上下门时间和打卡位置。两张表通过order_id关联这样的好处是老人下单后如果有退款或者改期service_order状态可以变但work_order一旦生成执行记录不能被篡改。我在实际项目里踩过个坑一开始把时间字段放在了service_order里导致用户下单时间和预约服务时间混在一起SQL查询一复杂就乱。后来学这套源码的做法把“预约时间”留在订单表“实际开始/结束时间”放工单表逻辑就清晰了。evaluate_status这个字段也值得学它单独放在订单表而不是建一张评价表是因为一个订单只有一次评价用字段更简单。当然如果你想做追评那就要拆表了。service_item表的核心字段只有id、name、type、price、unit、cover_url。这里有个设计取舍为什么服务项目要单独建表而不是在下单页面写死价格因为服务价格小区可以自己定物业改价时只要后台更新数据库APP端不用发版。之前我见过一个项目把价格写在枚举类里结果每次调价都要等应用商店审核非常痛苦。2.3 从原型图到Android工程目录结构与模块划分设计文档和源码是对应得上的。设计文档里画了完整的功能结构图、用例图和时序图源码里的包结构基本按照业务模块划分。主工程是单Module包名有点像com.xxx.eldercare主要区分了ui、api、model、db、utils这几个包。ui下面又按home、order、worker、mine拆Fragment和Activity。我一般拿到这种源码第一件事不是运行而是先看app/src/main/java下面的包结构确认模块划分。这套源码的model包里实体类是跟着数据库表走的User.java、ServiceOrder.java、WorkOrder.java这些再配合Room或者SQLiteOpenHelper做本地缓存。文档目录中有db_init.sql脚本你在Android Studio里用SQLite插件跑一遍就能把表结构导入导出了。工程里有一个mock包里面写了几个工具类生成模拟数据比如MockOrderData、MockWorkerData方便你在没有后台的情况下把页面跑起来。这个设计很实用因为做前端或者UI联调时可以等后台接口。但我建议你注意mock数据走的是静态返回不是真正的HTTP请求如果直接跑整个APP有些页面会显示“模拟数据”的Toast提示正式联调时要记得把ApiClient.BASE_URL切回真实环境。用Android Studio打开这个工程时你需要注意几点。第一确认你本地JDK版本是11因为Gradle插件版本如果是7.0以上JDK8会直接拒绝编译。第二第一次Sync会下载很多依赖jcenter()已经废弃了如果源码里还配置了这个仓库要替换成mavenCentral()不然会卡在下载阶段。第三源码里自带的local.properties不会提交到版本库你需要根据自己的Android SDK路径生成一份或者让Android Studio自动创建。3. 把核心功能跑起来网络层、登录态与订单流转这章我们开始看能运行的代码。这套源码的网络层和订单流转是整个工程里最有参考价值的部分因为大部分社区服务类APP都绕不开这几个问题接口怎么封装、token怎么统一带、订单状态怎么维护。3.1 网络框架选择与接口封装RetrofitOkHttp这套源码网络层用了Retrofit 2.9.0 OkHttp 4.9这是Android开发里比较经典的一套组合。Retrofit负责把接口注解转成HTTP请求OkHttp负责底层连接和拦截器。依赖配置在app/build.gradle里大致是implementation com.squareup.retrofit2:retrofit:2.9.0 implementation com.squareup.retrofit2:converter-gson:2.9.0 implementation com.squareup.okhttp3:okhttp:4.9.0 implementation com.squareup.okhttp3:logging-interceptor:4.9.0网络层核心是一个RetrofitClient单例。我简化了源码里的写法保留关键逻辑public class RetrofitClient { private static final String BASE_URL https://api.example.com/; private static Retrofit retrofit null; public static Retrofit getInstance() { if (retrofit null) { OkHttpClient client new OkHttpClient.Builder() .connectTimeout(15, TimeUnit.SECONDS) .readTimeout(15, TimeUnit.SECONDS) .addInterceptor(new HttpLoggingInterceptor() .setLevel(HttpLoggingInterceptor.Level.BODY)) .addInterceptor(new TokenInterceptor()) .build(); retrofit new Retrofit.Builder() .baseUrl(BASE_URL) .client(client) .addConverterFactory(GsonConverterFactory.create()) .build(); } return retrofit; } }参数说明连接超时15秒比较保守如果你们跑的是局域网模拟后台超时时间可以调到5秒这样失败重试更快。HttpLoggingInterceptor的Level设成BODY会打印请求和响应全文调试时方便但如果发布到生产环境记得要改成NONE或BASIC不然日志里会泄露手机号和地址信息。TokenInterceptor是源码里一个比较关键的自定义拦截器作用是统一给每个请求头带上Authorization: Bearer token。这也是一种常见做法而不是每个接口单独去传token。3.2 登录与Token过期处理的实现细节登录接口是login(String phone, String password)返回的LoginResponse里包含token、roleType、userInfo。源码把token存在了SharedPreferences里我的习惯是建议你用自己的加密存储或者第三方库但直接用SP在demo里够用。登录代码的关键路径public void login(String phone, String pwd) { ApiService api RetrofitClient.getInstance().create(ApiService.class); api.login(phone, pwd).enqueue(new CallbackLoginResponse() { Override public void onResponse(CallLoginResponse call, ResponseLoginResponse response) { LoginResponse body response.body(); if (body ! null response.isSuccessful()) { PrefsUtil.putString(token, body.getToken()); PrefsUtil.putInt(roleType, body.getRoleType()); startActivity(new Intent(MainActivity.this, HomeActivity.class)); } else { Toast.makeText(MainActivity.this, 登录失败 response.code(), Toast.LENGTH_SHORT).show(); } } Override public void onFailure(CallLoginResponse call, Throwable t) { Toast.makeText(MainActivity.this, 网络异常 t.getMessage(), Toast.LENGTH_SHORT).show(); } }); }这里有个细节手动判断了response.isSuccessful()而不是只看body是否为空。因为Retrofit把2xx之外的状态码也会走onResponse如果后台返回了401或者500body可能也是空或者是一个统一的错误结构体。如果只判断body就会导致拿到一个空对象去读字段空指针闪退。Token过期处理是这套源码里做得比较粗的部分。它没有全局的401自动刷新逻辑只是在TokenInterceptor里判断如果请求返回401就从SP里清掉token并跳转登录页。我实际改造时会把这段逻辑抽成一个回调放到onResponse里统一拦截。但如果你只是跑通流程源码的做法也够用。3.3 服务下单与状态机从“待受理”到“已完成”的代码路径订单状态是这套源码里最值得细看的部分。它用一个OrderStatus的常量类把状态码管理起来具体值分别是状态码含义可操作0待受理用户取消1待服务服务人员接单2服务中上门签到后进入3待评价签退后进入4已完成用户评价后在ServiceOrder实体类里有一个getStatusText()方法做状态到文字的映射。状态流转的逻辑放在了OrderPresenter里我这里只摘出核心的下单方法public void submitOrder(ServiceOrder order) { if (order.getServiceTime() null || order.getServiceAddress() null) { view.showError(请完善服务时间和地址); return; } order.setStatus(0); order.setCreateTime(DateUtils.getNowTime()); ApiService api RetrofitClient.getInstance().create(ApiService.class); api.createOrder(order).enqueue(new CallbackServiceOrder() { Override public void onResponse(CallServiceOrder call, ResponseServiceOrder response) { if (response.isSuccessful() response.body() ! null) { view.onOrderCreated(response.body()); } else { view.showError(下单失败); } } Override public void onFailure(CallServiceOrder call, Throwable t) { view.showError(t.getMessage()); } }); }注意点状态0是“待受理”这算是状态机的初态。取消订单只在状态为0时允许状态一旦进入1取消就要走售后流程。服务人员端接单操作会直接把状态从0改为1同时给work_order插入一条记录。这就是前面说的两张表联动的关系。源码里还写了一个OrderStateMachine类来校验状态转移是否合法核心是一个switch判断public boolean transition(ServiceOrder order, int targetStatus) { switch (order.getStatus()) { case 0: return targetStatus 1 || targetStatus 4; case 1: return targetStatus 2; case 2: return targetStatus 3; case 3: return targetStatus 4; default: return false; } }这里有个隐患状态0待受理直接跳到状态4已完成这是为了照顾“用户取消”但取消单和完成单混在一起会导致订单完成率统计失真。你接手后应该增加一个“已取消”状态码比如5把取消单独走一条分支。另外订单列表的加载也是常见问题。源码里只做了第一页加载和下拉刷新没有上拉加载更多。如果单个用户订单超过20条后面的数据就看不到。我改这个功能时会在OrderFragment里维护一个pageNum每次加载完成后累加并加一个isLoading布尔锁防止重复请求。点击订单进详情源码传的是orderId而不是整个对象这个习惯很好——详情页重新从网络拉最新状态不会出现列表和服务端状态不一致的情况。4. 地图定位与服务人员派单高德地图接入实战社区养老APP里服务人员上门必须要打卡这就用到地图定位。这套源码用的是高德地图Android SDK版本3.x。这一章我们把它接入和定位的代码拆开。4.1 地图SDK初始化与权限配置高德SDK首先要申请Key然后在AndroidManifest.xml里声明权限和应用Key。源码里已经配置好了你换自己的包名后必须同步换Key不然地图黑屏。关键配置uses-permission android:nameandroid.permission.INTERNET / uses-permission android:nameandroid.permission.ACCESS_COARSE_LOCATION / uses-permission android:nameandroid.permission.ACCESS_FINE_LOCATION / uses-permission android:nameandroid.permission.ACCESS_BACKGROUND_LOCATION / application meta-data android:namecom.amap.api.v2.apikey android:value你的高德Key / /application在Application的onCreate里做SDK初始化public class App extends Application { Override public void onCreate() { super.onCreate(); MapsInitializer.initialize(this); } }这里有两个容易踩的细节。第一个高德的Key是和包名、SHA1签名绑定的你换电脑签名或改包名后地图会黑屏但不报错日志里会有一句INVALID_USER_SCODE。遇到这种问题不要怀疑代码先去高德开放平台把新的包名和SHA1加进Key。第二个背景定位权限在Android 10以后需要额外在设置中开启而且很多手机厂商的权限管理会把后台定位默认关掉代码里要引导用户手动开启。4.2 定位精度与省电策略高德SDK定位有两种模式高精度和省电。源码里做的是高精度定位AMapLocationClientOption设置了如下参数AMapLocationClientOption option new AMapLocationClientOption(); option.setLocationMode(AMapLocationClientOption.AMapLocationMode.Hight_Accuracy); option.setInterval(2000); option.setOnceLocation(false); option.setNeedAddress(true);参数说明Hight_Accuracy会同时使用GPS和网络定位冷启动定位速度最快2秒左右但耗电明显。setInterval(2000)表示每2秒回调一次位置这个频率在打卡场景下没有必要反而会持续打电话到GPS模块。我一般会把连续定位的间隔改成10秒或者干脆用一次定位模式只在点击“签到”按钮时才调startLocation()。如果你需要的是服务人员上门时自动定位打卡我建议改成setOnceLocation(true)这样定位完成就自动停止省电效果立竿见影。这套源码虽然默认用连续定位但它也留了一个LocationReceiver可以在服务人员进入工单详情页时开始定位离开页面或成功后停止。另外注意setNeedAddress(true)会额外做逆地理编码拿到具体的街道和门牌号这个接口有次数限制上线时要注意配额能撑住多少日活。4.3 基于地理围栏的自动签到/签退地理围栏是高德SDK里的一个成熟功能代码里用一个AMapGeoFenceManager来监听服务人员是否进入了老人地址周围某个半径区域。源码在服务人员上门场景里预设了100米围栏进入围栏后自动弹窗确认签到。核心代码如下AMapGeoFenceManager fenceManager AMapGeoFenceManager.newInstance(); fenceManager.setActivateAction(AMapGeoFenceManager.GEOFENCE_ENTER); fenceManager.createGeoFence(new LatLng(39.90403, 116.407526), 100, 0, onGeoFenceCreateListener); fenceManager.addGeoFenceListener((geoFence, fenceId, location, code) - { if (code GeoFenceServiceConstants.GEOFENCE_ENTER) { // 进入围栏调用签到接口 orderPresenter.signIn(orderId, location.getLatitude(), location.getLongitude()); } });这段代码的逻辑是以老人家的经纬度为中心半径100米生成一个圆形的围栏当服务人员的GPS位置进入这个围栏时SDK会触发GEOFENCE_ENTER事件。注意createGeoFence的最后一个参数是customId可以用来标记这个围栏对应的订单号方便回调里识别。关于围栏回调源码是在主线程触发的你直接在监听器里更新UI没问题。但有个性能点如果同时创建了多个围栏所有订单的回调都在同一个监听器里触发你要先判断fenceId或者customId是不是当前订单否则可能会出现“签到了别的订单”的问题。我的解决办法是在进入工单详情时只创建一个围栏退出时调用fenceManager.removeGeoFence(fenceId)。地理围栏还有一个精度问题。它依赖定位如果服务人员站在围栏边缘或在室内GPS信号弱可能一直不触发。我遇到过一次服务人员明明到楼门口了围栏没触发最后只能手动加一个“点击按钮签到”的兜底。所以设计时一定要同时保留自动打卡和手动打卡并在后端比较两次打卡的时间和位置避免纠纷。5. 避坑手册编译、混淆、适配与后台模拟的5个常见问题这套源码我前前后后跑过几遍也帮人解决过问题整理出最常翻车的5个地方。每一个都是先现象、再原因、给解决办法你照着排查能省不少时间。5.1 问题一高德地图Key没配置导致黑屏现象地图页面打开后是一片灰色或者黑底没有网格也不报崩溃只有Log里出现AMapException: INVALID_USER_SCODE。原因高德Key绑定的是创建工程时填写的包名和签名SHA1值。你下载源码后如果改了包名或者本地使用的签名文件和当初申请Key时不是同一个高德服务端校验就会失败。解决到高德开放平台把新的包名和debug/release签名SHA1添加进去。在Android Studio里可以通过gradle signingReport命令查看签名信息。如果只是本地调试直接生成一个新的Key替换Manifest里的com.amap.api.v2.apikey再把build.gradle里的signingConfigs配上一致即可。5.2 问题二混淆规则漏掉Gson和OkHttp导致运行崩溃现象release包安装后一进订单列表就直接闪退logcat报ClassCastException或者IllegalArgumentException: Expected BEGIN_ARRAY but was BEGIN_OBJECT。debug包正常。原因release开启了minifyEnabled但没有针对Gson的实体系加混淆keep规则。混淆后实体类的字段名被改成a、b、c而Gson反序列化是按照JSON字符串里字段名反射赋值的字段对不上就会抛错。OkHttp报文解析同理。解决在proguard-rules.pro里加下面这段并重新打包-keep class com.xxx.eldercare.model.** { *; } -keepattributes Signature -keepattributes *Annotation* -keep class com.squareup.okhttp3.** { *; }这段规则的意思是保留model包下所有实体类的字段名同时保留OkHttp的内部结构。注意com.xxx.eldercare.model要改成你实际包名。还有一个血泪经验即使加了混淆规则也一定要用release包做一遍全流程回归不要只测debug包。5.3 问题三Android 12分区存储导致图片上传失败现象在Android 12手机上上传服务现场照片时Uri传给后台后报FileNotFoundException或者拍照后返回的Uri为空。原因Android 10开始强制分区存储Android 13进一步收紧。源码里如果还沿用Environment.getExternalStorageDirectory()写文件然后通过file://协议分享给其他组件已经不被允许。拍照使用MediaStore后用FileProvider.getUriForFile生成content://协议才能被其他模块读取。解决在AndroidManifest.xml里配置FileProvider然后拍照时使用FileProvider.getUriForFile而不是直接用绝对路径。代码里把临时图片的Uri作为EXTRA_OUTPUT传给系统相机再从onActivityResult回调里通过content://Uri去读文件。相关代码片段就不贴了重点是不要再用getExternalStoragePublicDirectory。5.4 问题四模拟后台数据时出现中文乱码现象把mock包里返回的字符串直接显示在TextView或ListView上中文变成乱码或者弹Toast直接消失。原因字符串在Java文件中硬编码时如果IDE默认编码没有设置为UTF-8编译后生成的class文件里汉字就是乱码。源码里Mock数据包含中文地址和服务名称如果你的Android Studio里File Encoding不是UTF-8一打开就会变乱码。解决将Settings Editor File Encodings里的Global Encoding、Project Encoding和Properties Files都设置成UTF-8重新同步或者重新打开工程。如果已经乱码了就用Android Studio的File Reload from Disk恢复原始字节。另外mock包里的MockServiceItem返回的地址字符串我建议不要自己手敲直接复制原文件内容避免编码二次转换。5.5 问题五多Module工程构建卡死与资源冲突现象导入工程后Gradle sync很慢甚至长时间卡在Executing...。构建时提示Resource merge failed或者图片资源找不到。原因源码工程虽然有多个module但各自compileSdk和buildToolsVersion可能不一致。尤其是主工程用了高德3D地图地图库需要的minSdkVersion和你设置的不一致时资源合并就会冲突。另外某些Module依赖的support库和AndroidX库混用也会导致第三方库的res目录合并失败。解决统一所有app和library模块的compileSdk为31、minSdk为24、targetSdk为31。在根目录build.gradle里将整个项目的依赖仓库统一为mavenCentral()和google()并在gradle.properties里加上android.enableJetifiertrue android.useAndroidXtrue如果那个引用的库还是支持库Jetifier会自动转换依赖。构建卡住的话可以执行gradlew clean后重新同步如果还是卡八成是网络问题换成国内依赖镜像就行。6. 给项目做加法从demo到可上架的养老APP还需这几步验证源码跑通只是第一步离上线还差三件事自动化测试、性能摸底、安全检查。6.1 用Robolectric做一次登录流程的单元测试登录页逻辑独立用Robolectric在JVM上就能跑不需要模拟器。重点是验证“手机号为空时不走网络请求”以及“登录失败后正确反馈”。代码大致这样RunWith(RobolectricTestRunner.class) public class LoginPresenterTest { Test public void should_show_error_when_phone_empty() { LoginActivity activity Robolectric.buildActivity(LoginActivity.class).create().get(); EditText phone activity.findViewById(R.id.edit_phone); phone.setText(); activity.findViewById(R.id.btn_login).performClick(); assertEquals(请输入手机号, ShadowToast.getTextOfLatestToast()); } }这个测试的价值在于后续改造登录界面时不会不小心删掉必填校验。网络层的测试还需要MockWebServer这里不展开。6.2 性能摸底启动耗时与内存泄漏的检查方法养老用户手里的手机配置普遍偏低启动速度很关键。用Android Studio的Profiler跑一遍冷启动观察CPU和内存曲线。重点检查OrderPresenter是否在页面销毁后还持有Activity引用。我习惯在build.gradle里加debugImplementation com.squareup.leakcanary:leakcanary-android:2.9.1跑一遍从登录到下单、地图打卡、退出登录的流程看Logcat里有没有Memory Leak。6.3 上线前的安全检查清单项目检查点建议网络层日志拦截器是否关闭发布时把Level改NONE混淆实体类、Retrofit、OkHttp是否keep跑release包回归权限后台定位权限测评是否真的需要后台定位数据存储token和用户信息是否明文换EncryptedSharedPreferences高德Keyrelease签名对应的Key别用debug的Key上架这个清单每次发版前都过一遍。我当年第一次发布定位类APP时忘了关HttpLoggingInterceptor线上用户抓包直接看到别人的token和家庭住址差点出事故。从那以后每次发版我都强制走一遍这个清单确认日志级别、混淆配置和权限列表才敢点发布。希望这份拆解能帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →