Android中解析后端VPS上的大型动态JSON数据并实现过滤
处理Android中双大型JSON数据的解析与过滤方案
嘿,针对你在Android里处理VPS上两个大型持续更新JSON的需求,我整理了一套实用的方案,兼顾性能和灵活性,咱们一步步来:
一、先搞定网络请求(获取VPS上的JSON)
首先得把数据从VPS拿下来对吧?因为是大型且持续更新的文件,推荐用OkHttp或者Retrofit,这俩都支持缓存和断点续传。要是后端能配合加个ETag或者Last-Modified头,还能只拉取变化的部分,省流量又快。
举个OkHttp的简单示例(记得在后台线程执行,别卡UI):
val client = OkHttpClient.Builder() .cache(Cache(context.cacheDir, 10 * 1024 * 1024)) // 10MB缓存 .build() val request = Request.Builder() .url("https://your-vps-url/movie-releases.json") .build() client.newCall(request).enqueue(object : Callback { override fun onFailure(call: Call, e: IOException) { // 处理请求失败 } override fun onResponse(call: Call, response: Response) { response.body?.let { body -> // 拿到输入流,直接流式解析,别转成String! val inputStream = body.byteStream() // 这里调用解析方法 } } })
二、JSON解析:优先用流式解析(避免OOM)
因为是大型JSON,绝对不能把整个文件加载到内存里解析,否则很容易OOM。推荐这几种流式解析方案:
1. Gson流式解析(Java/Kotlin都友好)
先定义对应的数据类,把JSON字段映射成实体:
// 第一个JSON的实体类 data class MovieRelease( val id: Int, val release_date: String ) // 第二个JSON的实体类 data class MovieDetail( val id: Int, val name: String, val description: String, val genres: List<Int> )
然后用Gson的JsonReader流式解析,边读边处理,还能提前过滤不需要的数据:
fun parseMovieReleases(stream: InputStream): List<MovieRelease> { val gson = Gson() val reader = JsonReader(InputStreamReader(stream, Charsets.UTF_8)) val releases = mutableListOf<MovieRelease>() reader.beginArray() while (reader.hasNext()) { val release = gson.fromJson(reader, MovieRelease::class.java) // 这里可以提前过滤,比如只存2018年后的电影,减少内存占用 if (release.release_date >= "2018-01-01") { releases.add(release) } } reader.endArray() reader.close() return releases }
2. Moshi流式解析(Kotlin更适配)
Moshi对Kotlin数据类支持更友好,不用额外注解,代码更简洁:
val moshi = Moshi.Builder().build() val detailAdapter = moshi.adapter(MovieDetail::class.java) fun parseMovieDetails(stream: InputStream): List<MovieDetail> { val reader = JsonReader(InputStreamReader(stream, Charsets.UTF_8)) val details = mutableListOf<MovieDetail>() reader.beginArray() while (reader.hasNext()) { detailAdapter.fromJson(reader)?.let { details.add(it) } } reader.endArray() return details }
3. 原生JsonReader(无依赖)
如果不想加第三方库,用Android原生的JsonReader也能搞定,就是代码繁琐点:
fun parseReleasesNative(stream: InputStream): List<MovieRelease> { val reader = JsonReader(InputStreamReader(stream, Charsets.UTF_8)) val releases = mutableListOf<MovieRelease>() reader.beginArray() while (reader.hasNext()) { reader.beginObject() var id = 0 var releaseDate = "" while (reader.hasNext()) { when (reader.nextName()) { "id" -> id = reader.nextInt() "release_date" -> releaseDate = reader.nextString() else -> reader.skipValue() // 跳过不需要的字段 } } releases.add(MovieRelease(id, releaseDate)) reader.endObject() } reader.endArray() return releases }
三、数据关联与过滤逻辑
拿到两个数据集后,要通过id关联,再做过滤,分两种场景:
1. 内存中关联(数据量适中时)
把其中一个数据集转成Map(用id当key),这样查询速度会快很多,然后遍历另一个数据集做匹配和过滤:
// 先把上映日期数据转成Map:id -> release_date val releaseDateMap = movieReleases.associate { it.id to it.release_date } // 关联详情数据,过滤出2018年后、包含科幻类型(假设genre 12是科幻)的电影 val filteredMovies = movieDetails.filter { detail -> val releaseDate = releaseDateMap[detail.id] releaseDate != null && releaseDate >= "2018-01-01" && 12 in detail.genres }.map { detail -> // 合并成完整的Movie对象 FullMovie( id = detail.id, name = detail.name, description = detail.description, releaseDate = releaseDateMap[detail.id]!!, genres = detail.genres ) }
2. 边解析边关联(超大型数据)
如果数据大到没法同时放内存,就用本地数据库(比如Room)来中转:
- 先解析第一个JSON,把
id和release_date存到Room,给id加索引提高查询速度 - 解析第二个JSON时,边读边查数据库的上映日期,符合条件的再存到数据库或者直接展示
示例代码(假设已经配置好Room):
// 先插入上映日期数据 db.movieDao().insertAllReleases(movieReleases) // 解析详情数据时,边解析边过滤 fun parseAndFilterDetails(stream: InputStream) { val reader = JsonReader(InputStreamReader(stream, Charsets.UTF_8)) val gson = Gson() reader.beginArray() while (reader.hasNext()) { val detail = gson.fromJson(reader, MovieDetail::class.java) // 查数据库拿上映日期 val releaseDate = db.movieDao().getReleaseDateById(detail.id) releaseDate?.let { if (it >= "2018-01-01" && 12 in detail.genres) { // 符合条件,存到数据库或者通知UI层 db.movieDao().insertDetail(detail) } } } reader.endArray() }
四、持续更新的处理策略
因为数据是持续更新的,得优化更新逻辑:
- 增量更新:让后端提供增量接口,比如返回上次更新时间之后的变化数据;或者用
ETag/Last-Modified头,客户端请求时带上,后端只返回变化部分 - 本地缓存:用Room缓存已有的数据,每次更新只同步变化的内容,不用全量下载
- 后台同步:用WorkManager实现定期自动同步,或者让后端通过FCM推送更新通知,客户端收到后再拉取新数据
五、性能优化小技巧
- 所有网络请求和解析操作必须放在后台线程(推荐用Kotlin协程,简单易用)
- 流式解析时,跳过不需要的字段(比如用
reader.skipValue()),减少内存占用 - 给数据库的
id字段加索引,加快关联查询速度 - 超大型数据可以分批解析、分批存储,避免一次性处理太多数据导致内存压力
内容的提问来源于stack exchange,提问作者user7409907
相关产品推荐
相关产品推荐

