Android中如何存储JSON数据实现离线访问?解决API离线加载差异
Android中实现JSON数据缓存以支持离线访问的方案
嘿,我来帮你搞定这个离线缓存JSON的问题!首先得搞清楚为什么Pixabay的API能离线显示——大概率是他们的服务端响应里设置了合适的HTTP缓存头(比如Cache-Control),你的网络库(比如OkHttp或者系统默认的HttpURLConnection)自动帮你缓存了响应;而你的自研API没配置这些头,所以默认不会缓存,离线自然加载不了。
下面给你几种靠谱的实现方案,你可以根据自己的需求选:
1. 利用OkHttp的内置缓存机制(推荐)
如果你的项目用OkHttp做网络请求,这是最省心的方案,它自带HTTP缓存支持,只需要简单配置就能实现离线缓存。
步骤:
- 首先创建缓存目录,一般用App的内部缓存目录(不用申请权限)
- 配置OkHttpClient,添加缓存拦截器,设置缓存大小和缓存策略
- 在请求时根据网络状态设置缓存控制逻辑
代码示例(Kotlin):
// 创建缓存目录,大小设为100MB val cacheDir = File(context.cacheDir, "http_cache") val cache = Cache(cacheDir, 100 * 1024 * 1024) // 100MB // 配置OkHttpClient val okHttpClient = OkHttpClient.Builder() .cache(cache) .addInterceptor { chain -> var request = chain.request() // 根据网络状态设置缓存策略 request = if (isNetworkAvailable(context)) { // 在线时,允许读取缓存,但强制请求服务器验证缓存是否新鲜 request.newBuilder().header("Cache-Control", "public, max-age=60").build() } else { // 离线时,允许读取最多7天前的缓存 request.newBuilder().header("Cache-Control", "public, only-if-cached, max-stale=604800").build() } chain.proceed(request) } .build() // 网络状态判断方法(需要权限:ACCESS_NETWORK_STATE) fun isNetworkAvailable(context: Context): Boolean { val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager val networkInfo = connectivityManager.activeNetworkInfo return networkInfo != null && networkInfo.isConnected }
如果你的自研API能修改,也可以在服务端设置Cache-Control头(比如Cache-Control: public, max-age=3600),这样客户端不用写拦截器,OkHttp会自动按照服务端的规则缓存。
2. 手动将JSON存储到本地文件
如果你需要更灵活的缓存逻辑(比如自定义缓存键、过期时间),可以手动把JSON字符串写到本地文件里。
代码示例(Kotlin):
保存JSON到缓存文件:
fun saveJsonToCache(context: Context, cacheKey: String, jsonString: String) { try { val cacheFile = File(context.cacheDir, cacheKey) val outputStream = FileOutputStream(cacheFile) outputStream.write(jsonString.toByteArray()) outputStream.close() } catch (e: IOException) { e.printStackTrace() } }
从缓存文件读取JSON:
fun loadJsonFromCache(context: Context, cacheKey: String): String? { return try { val cacheFile = File(context.cacheDir, cacheKey) if (!cacheFile.exists()) return null // 可以在这里添加过期判断,比如检查文件最后修改时间 val lastModified = cacheFile.lastModified() val currentTime = System.currentTimeMillis() if (currentTime - lastModified > 7 * 24 * 60 * 60 * 1000) { // 超过7天就过期 cacheFile.delete() return null } val inputStream = FileInputStream(cacheFile) val jsonString = inputStream.bufferedReader().use { it.readText() } inputStream.close() jsonString } catch (e: IOException) { e.printStackTrace() null } }
这里的cacheKey可以用请求的URL加上参数拼接,确保每个请求的缓存唯一。
3. 使用Room数据库存储JSON(适合需要结构化查询的场景)
如果你的JSON数据需要后续查询、筛选,或者想更规范地管理缓存,可以用Room数据库存储JSON字符串,同时记录缓存的key和过期时间。
步骤:
- 创建实体类,包含缓存key、JSON字符串、过期时间
- 编写Dao接口,定义插入和查询方法
- 创建Room数据库类
代码示例(Kotlin):
实体类:
@Entity(tableName = "json_cache") data class JsonCache( @PrimaryKey val cacheKey: String, val jsonContent: String, val expireTime: Long // 过期时间戳(毫秒) )
Dao接口:
@Dao interface JsonCacheDao { @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertCache(jsonCache: JsonCache) @Query("SELECT jsonContent FROM json_cache WHERE cacheKey = :key AND expireTime > :currentTime") suspend fun getValidCache(key: String, currentTime: Long): String? }
数据库类:
@Database(entities = [JsonCache::class], version = 1) abstract class AppDatabase : RoomDatabase() { abstract fun jsonCacheDao(): JsonCacheDao companion object { // 单例实现 @Volatile private var INSTANCE: AppDatabase? = null fun getInstance(context: Context): AppDatabase { return INSTANCE ?: synchronized(this) { val instance = Room.databaseBuilder( context.applicationContext, AppDatabase::class.java, "app_database" ).build() INSTANCE = instance instance } } } }
使用示例:
// 保存缓存 suspend fun saveJsonToRoom(context: Context, cacheKey: String, jsonString: String, expireDays: Int = 7) { val expireTime = System.currentTimeMillis() + expireDays * 24 * 60 * 60 * 1000 val jsonCache = JsonCache(cacheKey, jsonString, expireTime) AppDatabase.getInstance(context).jsonCacheDao().insertCache(jsonCache) } // 读取缓存 suspend fun loadJsonFromRoom(context: Context, cacheKey: String): String? { return AppDatabase.getInstance(context).jsonCacheDao().getValidCache(cacheKey, System.currentTimeMillis()) }
一些注意事项
- 缓存过期逻辑:不管用哪种方案,都要记得设置合理的过期时间,避免用户看到过时的数据
- 缓存清理:定期清理过期的缓存文件或数据库记录,防止占用过多存储空间
- 权限问题:如果使用外部存储缓存,需要在AndroidManifest.xml中添加
WRITE_EXTERNAL_STORAGE权限(Android 6.0以上还要动态申请),不过优先用内部缓存目录,不需要权限 - 网络状态判断:离线时优先读取缓存,在线时可以选择更新缓存或者直接用缓存
内容的提问来源于stack exchange,提问作者AnandaLC
相关产品推荐
相关产品推荐

