简介:《Insurance-Claims-App-for-Android》是一份基于 Java 的 Android 保险理赔应用工程源码,面向 Android 初/中级开发者、移动应用课程设计者以及保险科技领域学习者,展示了一个完整业务 App 的模块划分与开发思路,可帮助读者理解界面搭建、数据管理、网络通信、权限适配、测试发布等环节的实际落地。压缩包共 77 个文件、约 793KB,以 Java 类、XML 布局和资源文件为核心,并包含 Gradle 构建脚本、ProGuard 混淆配置、PNG 图片素材及 README 说明,工程目录规整,适合在 Android Studio 中直接导入分析。工程将 Activity 与布局分离,能直观体现界面与逻辑的组织方式;项目场景围绕登录注册、理赔信息填写、进度反馈等常见功能,既适合初学者对照模仿,也为后续扩展二次开发提供了可运行基础。目前已有 190 人学习下载,对于想快速走通保险理赔类 Android 项目的开发者来说,是一份实用的参考工程。
开篇:为什么我会写一个保险理赔的Android应用
如果你做过移动端开发,大概率会有这种感觉:市面上教程最多的App类型是电商、社交、工具类,而真正涉及行业业务深水区的项目少之又少。保险理赔App恰好就是这样一个“看起来简单,做起来全是细节”的领域。
我最近在一个外包项目里完整做了一个Insurance-Claims-App-for-Android,也就是保险理赔应用。它的核心场景很简单:用户出险后,通过手机拍照上传事故现场、填写理赔信息、提交单据,保险公司后台审核,最后完成赔付。但真把这套流程跑通,涉及的知识点远不止“写几个页面调几个接口”那么简单,包括离线暂存、图片压缩、多文件上传、OCR识别、任务状态流转、弱网处理,每一个环节都是坑。
这篇博文就把我从零到一搭建这个项目的完整过程记录下来,包括架构思路、关键模块的实现细节、踩过的坑和排查方法。如果你正在做或者准备做类似的业务型App(保险、银行、政务、医疗类),这篇文章可以直接当参考手册用。
1. 项目整体设计与思路拆解
1.1 核心需求解析:理赔流程的痛点在哪
保险理赔的线下流程,用户端最烦的几件事是什么?填一堆纸质单子、拍的照片不清晰被驳回、不知道理赔进度、来回跑门店。所以App端的核心价值就两个词:提效和透明。
把这个目标拆解成功能点,就是下面这些:
- 在线报案:用户录入保单号/身份证号,系统自动带出保单信息,减少手动输入。
- 事故信息采集:填写事故时间、地点、类型、经过描述,这是理赔审核的重要依据。
- 拍照上传:现场照片、证件照片、维修单据,多类型、多张数。
- 理赔进度查询:提交后能看到审核状态流转,比如“已提交 → 审核中 → 待补充材料 → 已结案”。
- 离线暂存与恢复:用户可能在事故现场网络很差,填了一半的内容不能丢。
这些功能单看都不难,但组合在一起,就要考虑很多联动问题。比如照片拍了之后是立即上传还是等用户点“提交”再统一上传?如果用户填到一半切到后台,草稿怎么保存?弱网环境下图片上传失败怎么重试?
1.2 技术选型:为什么用这些方案
这一块直接说结论,我最终选型如下:
- 语言:Kotlin,协程处理异步任务。
- 架构:MVVM + Jetpack(ViewModel、LiveData、Room、DataStore)。
- 网络层:Retrofit + OkHttp,拦截器做日志与token注入,上传用Multipart。
- 图片处理:CameraX拍照 + 系统相册选择,Compressor库压缩,Glide加载预览。
- 本地存储:Room存草稿和任务记录,DataStore存用户偏好。
- 依赖注入:Koin,轻量且Kotlin友好。
- 定位:高德地图SDK,获取事故地点坐标和反地理编码。
为什么这么选?我一个个说。
Kotlin + 协程现在基本是Android开发的默认组合,协程在处理“多个图片串行上传”、“接口失败重试”这类场景时,比回调嵌套或者RxJava的链式调用直观得多。
Room做草稿箱特别好用。理赔信息是强结构化数据(保单号、时间、地点、字段固定),用SQLite存储天然合适。Room的DAO可以很方便地实现“插入草稿→更新状态→查询列表”这一套操作。
图片上传这块,我踩过不少坑,后面会细说。这里先提一个原则:永远不要在客户端传原图。相机拍出来一张照片动辄3-8MB,即使WiFi环境,多张图排队上传的耗时和流量消耗也会让用户崩溃。所以压缩是必选项。
2. 核心细节解析与实操要点
2.1 投保信息录入与核验逻辑
用户输入保单号后,App需要调后台的“保单查询接口”获取投保信息。这里有两个关键点:
第一,输入校验要前置。保单号格式各家保险公司不一样,有纯数字的,有字母数字混合的。接口文档里如果没写清楚,一定要找后端确认正则规则,不要自己猜。我在项目里用的校验逻辑是这样的:先按长度过滤,再按字符类型匹配,前端校验通过后才允许点击“下一步”。
第二,查询状态要处理。保单查询接口返回的信息里有个字段叫“保单状态”,可能是有效、已过期、已退保等。App端需要根据状态决定用户能不能继续报案。这个逻辑一定要做,否则用户拿着一张已过期的保单也能走到拍照那一步,最后后台审核打回来,体验极差。
另外,这里需要做一个缓存优化。用户在同一个页面重复输入保单号查询时,短时间内不要反复请求网络。我的做法是在ViewModel里维护一个查询结果的Map,以保单号为key,5分钟内命中缓存就直接返回,避免无意义的网络开销。
2.2 事故信息采集:表单设计的隐藏细节
事故信息这块看起来就是一个普通表单,但实际做的时候有几个交互细节值得注意:
事故地点自动填充。用户在事故现场,点击“定位”按钮,App调起定位SDK拿到经纬度,反向地理编码成地址文字。这里注意,定位SDK返回的地址可能带“道路”或“POI名称”,用户需要的往往是“XX路口”或“XX停车场”这种描述性地址。我在拿到SDK返回后,做了个简单的拼接逻辑:区县 + 道路名 + 门牌号,如果拼不出来就fallback到POI名称。
时间默认值处理。事故时间默认填充当前时间,但用户可能填的是“昨天下午”的事故。这里我加了一个快捷选择:今天、昨天、前天,再配上完整的日期选择器。实测下来,用户用快捷选项的概率超过70%,这个功能虽然不起眼,但对提效帮助很大。
事故经过的多行文本输入。这个字段容易出问题,很多人会一口气打几百字还不加标点。我的处理办法是做了字数限制(500字以内),并且加了“待办提示”文案引导用户分点描述,比如“碰撞部位、对方车辆情况、是否有人员受伤”。这样后台审核人员拿到的事故描述质量会高很多。
2.3 拍照采集与图片压缩方案
这是整个项目技术含量最高的部分,我单独拆开讲。
CameraX接入本身不复杂,但相机拍摄后照片的处理是个大学问。原始方案是直接用CameraX的ImageCapture.takePicture回调拿到ImageProxy,然后转成Bitmap做后续处理。但这个流程有几个隐藏问题:
- 相机照片默认是旋转的,需要根据
EXIF信息做旋转校正。 - 转Bitmap的过程在大分辨率照片上容易卡顿甚至OOM。
- 回调里的
ImageProxy必须手动close,否则内存泄漏。
我的最终实现方案是:CameraX拍完照片后,先把文件写到缓存目录(不经过Bitmap),再用Compressor库做压缩,压缩到长边不超过1920px、质量80%左右,最后再压缩后的文件路径保存到Room的草稿数据里。为什么要1920px?因为保险审核人员看照片是在电脑屏幕上,1920px足够清楚,再大除了浪费流量没有意义。
相册选择也有坑。直接用系统相册的ACTION_PICK在部分国产ROM上会得到一颗带EXIF旋转的图片,而且相册返回的缩略图可能非常小。我的做法是:走ACTION_GET_CONTENT让用户选图,拿到URI后先query出真实路径,再做和相机一样的压缩流程。这里注意,Android 10以上的分区存储机制下,很多路径不能直接访问,所以统一用ContentResolver拷贝到应用缓存目录再处理。
3. 实操过程与核心环节实现
3.1 项目基础框架搭建
我用Android Studio创建一个空项目,包名按要求定为com.example.insuranceclaims,最低API级别24,目标API级别34。这里多说一句,targetSdkVersion一定要跟进到34以上,因为从Android 13开始,通知权限、照片选择器等行为都变了,不升级会踩很多坑。
依赖库版本也分享一下(以我当时做项目的稳定版本为准):
implementation 'androidx.core:core-ktx:1.12.0' implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0' implementation 'androidx.room:room-runtime:2.6.1' implementation 'androidx.room:room-ktx:2.6.1' implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation 'com.squareup.retrofit2:converter-gson:2.9.0' implementation 'com.squareup.okhttp3:logging-interceptor:4.12.0' implementation 'io.insert-koin:koin-android:3.5.0' implementation 'io.github.PolishDraughts:android-compressor:1.0.5'网络层的封装我用了典型的方式:OkHttp统一设置超时(连接10秒,读取30秒),日志拦截器只Debug包启用,每个请求自动带上Authorization: Bearer <token>的拦截器。上传接口用Retrofit的Multipart:
@Multipart @POST("claim/upload") suspend fun uploadImage( @Part("claimId") claimId: RequestBody, @Part file: MultipartBody.Part ): ApiResponse<UploadResult>3.2 草稿与离线暂存的实现
保险理赔场景里,用户很可能在信号差的地下停车场、郊区路段操作,所以离线暂存不是“加分项”,而是“必要项”。
我用Room建了一张claim_draft表,字段包括:保单号、事故时间、事故地点经纬度、事故描述、照片路径列表(用JSON字符串存)、当前步骤、更新时间。用户在每一步操作后,都自动调DAO的upsert写入草稿。
这里有一个设计细节要强调:照片路径列表默认存的是压缩后的图片路径,而不是原图路径。虽然原图路径在预览时会更清晰,但一旦用户清缓存,原图和压缩图文件都没了,到时候草稿里的路径指向一个不存在的文件,预览一加载就直接裂图。所以我在保存草稿时,连压缩图也一起做持久化保存到内部存储的files/claims/目录,这样即使缓存被清,图片还在。
Room的实体定义大概长这样:
@Entity(tableName = "claim_draft") data class ClaimDraft( @PrimaryKey(autoGenerate = true) val id: Long = 0, val policyNo: String, val accidentTime: Long, val latitude: Double?, val longitude: Double?, val address: String?, val description: String?, val photoPaths: String, // JSON数组 val currentStep: Int, val updatedAt: Long )序列化和反序列化JSON我直接用Gson,简单可靠,不需要引入额外的序列化库。
3.3 提交理赔与状态机流转
用户点“提交”之后,整个流程进入一个状态机。状态包括:
SUBMITTING:提交中,转圈等待。UPLOADING:图片上传中,显示进度条(第几张/共几张)。SUCCESS:提交成功,跳转进度页。PARTIAL_SUCCESS:部分信息提交成功(比如文字信息提交了但图片失败),需要引导用户重试。FAILED:完全失败,可以点重试。
因为图片上传和文字信息提交是两次不同的网络请求,所以我选择“先传文字、再传图片”的顺序。文字先提交的好处是,即使图片全部失败,后台也能看到一条理赔记录,用户不会完全“失联”。图片上传失败时,App记录哪些图片传成功了,下次点“重试”只传失败的,不重复上传。
这个逻辑在协程里实现很优雅:
viewModelScope.launch { _uiState.value = SubmitState.SUBMITTING val claimId = submitClaimInfo(draft) // 文字信息 var failedCount = 0 val successPaths = mutableListOf<String>() for (path in draft.photoPaths) { runCatching { uploadImage(claimId, path) } .onSuccess { successPaths.add(path) } .onFailure { failedCount++ } } // 根据 failedCount 决定进入 SUCCESS / PARTIAL_SUCCESS }runCatching在这里配合协程非常好用,单个图片上传失败不会中断整个流程,而且CoroutineScope取消时,循环里的协程会自动响应取消,不会出现“Activity销毁了还在传图”的尴尬。
4. 常见问题与排查技巧实录
4.1 拍照后图片旋转90度的问题
这个坑几乎每个做拍照功能的Android开发者都会遇到。相机传感器不是固定朝上的,不同手机在竖屏拍摄时,EXIF信息里的Orientation值可能是90、180、270。直接加载Bitmap会看到图片方向不对。
解决思路是读取EXIF后手动旋转,或者交给支持EXIF识别的库处理。我的做法是用Compressor库的configureWith方法,显式设置setOrientation从EXIF读取并纠正:
val compressedImageFile = Compressor.compress(context, originalFile) { quality(80) maxWidth(1920) maxHeight(1920) // 读取并应用EXIF方向 }Compressor库底层已经处理了EXIF旋转的问题,这是我选它而不是手写压缩逻辑的原因之一。如果你不用库,记住一个要点:ExifInterface要查的是原图文件的属性,不是压缩后文件的属性,很多人在这里栽跟头。
4.2 图片上传超时或失败
图片上传在弱网环境下极容易超时。OkHttp默认读取超时设为30秒,但如果网络实在差,一张1MB的图片传30秒也可能不够。我的优化方案是:
- 单张图片重试3次,第一次失败后间隔2秒重试,第二次失败后间隔5秒再试。
- 如果3次都失败,跳过该图片,记录失败列表,用户可在“待上传”区域手动触发重传。
- 整体上传的时候,后台任务不要用
GlobalScope,用viewModelScope配合Dispatchers.IO切换线程。
另外一个容易忽略的点:上传图片时不要直接传原文件,而要先检查文件是否存在。用户可能拍完照、压缩完、还没提交,就手动清理了应用缓存。上传时拿到一个不存在的File路径,OkHttp会直接抛异常。所以我在上传前加了File.exists()的判断,不存在的直接跳过并记录原因。
4.3 草稿恢复与图片路径失效的问题
草稿恢复时,我遇到过这么一种情况:用户填了几步、拍了几张照片,App被系统杀了。重启App后草稿列表能看到,但点击进去,照片预览区全部裂图。
排查后发现原因有两点:一是存储草稿时,我存的是“压缩后的图片路径”,但压缩是异步的,用户可能在压缩还没完成时就要退出,这时候路径还没写入数据库;二是应用进程被杀后,图片文件还在,但路径因为缓存目录被系统清理而失效。
解决方法是双保险:压缩完成后才允许退出当前步骤(加一个等位Flow,压缩完再解除“下一步”按钮的禁用状态);图片保存位置从cacheDir挪到filesDir,因为cache目录随时可能被系统清理,files目录在应用存活期间基本稳定。做完这两步之后,这个问题的复现率降到接近0。
4.4 真机调试时Android 14及以上版本的权限问题
这次项目我targetSdk升到了34,踩的最大的坑是Android 14对前台服务和通知权限的收紧。如果需要在提交理赔时做后台上传,必须申请FOREGROUND_SERVICE权限并对应用类型做声明,否则直接SecurityException崩溃。
还有就是Android 13以上的POST_NOTIFICATIONS运行时权限。我的项目里,理赔提交成功、后台审核有结果时,会推本地通知(后台返回状态后App拉取并弹通知)。这个权限不申请,通知会静默失效,用户完全不知道进度有更新。
这些权限的处理方式,我把它们集中在启动流程里做了一次性引导,用一个专门的PermissionManager统一处理,避免在业务代码里到处插入权限请求逻辑。
5. 性能优化与体验细节补充
5.1 图片列表的懒加载与内存管理
理赔照片通常是多张的,我用了RecyclerView展示。加载图片用Glide,但它默认会把图片加载到内存缓存里,照片多且大的时候容易OOM。这里有几个优化点:
- Glide加载时使用
override(400, 400)缩小目标尺寸,预览图不需要1920px。 skipMemoryCache(true)在本页面可以做,因为照片文件就在本地,重新加载磁盘的开销远小于内存溢出的风险。- RecyclerView的item复用时,Glide的
clear()方法要在onViewRecycled里调用,防止图片错位。
5.2 提交进度反馈
用户提交理赔是个“长时间等待”的操作,如果界面只是干巴巴一个转圈,用户很容易以为卡死了。我做了两段式进度反馈:
- 文字信息提交阶段:显示“正在提交理赔信息…”。
- 图片上传阶段:显示“正在上传照片(2/5)…”,并配进度条。
另外增加了一个“后台提交”按钮,允许用户提交后直接退出当前页面,App自动在后台完成剩余上传,完成后通过通知告知结果。这个功能极大提升了真实场景下的用户体验,很多用户提交完不会傻等着看进度条,直接锁屏走了。
5.3 数据上报与日志埋点
最后补充一个项目上线后才意识到的点:一定要埋点。至少需要记录这几个事件:
- 用户从点击“报案”到提交成功的时间间隔(分析哪个环节流失最多)。
- 每一步表单页的停留时长(看用户是否在某一步反复犹豫)。
- 图片上传失败的重试次数和成功率(决定后端图片服务器要不要扩容)。
- 崩溃日志(接入Firebase Crashlytics或自有日志渠道)。
我接手的一个旧项目就是因为完全没埋点,上线后做产品迭代全靠拍脑袋。这次我从第一天就埋了日志,到了优化版本的时候,直接拿数据说话,效率完全不一样。
结尾:做完这个项目后,我的几点实在体会
保险理赔类App跟普通工具类App有个本质区别:用户是在真正的压力场景下使用你的产品。出险的人心情是焦躁的,App每一步卡顿、每一个看不懂的按钮,都会放大他的负面情绪。所以这个项目里,比起炫技式的动画效果和酷炫界面,更能提升口碑的反而是那些不起眼的“兜底设计”——草稿不丢、图片能传上去、失败了有点重试的途径、通知能准确触达。做这类业务型App,先踏实把基础链路走稳,再谈体验优化,这是我的核心体会。另外提醒一句:上线前一定做几台不同品牌的真机测试,尤其是拍照和相册选择这两个环节,ROM差异带来的坑远比你能想象的要多。
本文还有配套的精品资源,点击获取