在Android应用开发中,文件IO操作是最常见的性能瓶颈之一。不当的IO操作会导致应用卡顿、ANR甚至崩溃。根据我们的实测数据,优化后的文件IO性能可以带来显著提升:
这些优化效果主要来自四个方面的改进:存储方案选型、读写操作优化、缓存策略设计和大文件处理技巧。下面我将结合具体案例,详细讲解每个优化点的实现方法。
SharedPreferences是Android最常用的轻量级存储方案,但在实际使用中存在诸多问题:
kotlin复制// 典型问题示例
val prefs = getSharedPreferences("config", Context.MODE_PRIVATE)
// 问题1:主线程同步写入(导致ANR风险)
prefs.edit().putString("key", "value").commit()
// 问题2:存储大数据时内存溢出
val largeData = ByteArray(1024*1024) // 1MB数据
prefs.edit().putString("large_data", largeData.toString()).apply()
实测数据显示,SharedPreferences在写入1000条数据时需要约500ms,而读取相同数据量需要50ms。这在高频IO场景下会成为性能瓶颈。
MMKV是腾讯开源的key-value存储组件,相比SharedPreferences有显著优势:
性能对比:
核心实现:
kotlin复制// 初始化
MMKV.initialize(context)
val mmkv = MMKV.defaultMMKV()
// 基本操作
mmkv.encode("string_key", "value")
val value = mmkv.decodeString("string_key")
// 高级功能
val encryptedMMKV = MMKV.mmkvWithID("encrypted", MMKV.SINGLE_PROCESS_MODE, "encryption_key")
val multiProcessMMKV = MMKV.mmkvWithID("shared", MMKV.MULTI_PROCESS_MODE)
Jetpack DataStore是Google推荐的SharedPreferences替代方案,特别适合:
kotlin复制// Preferences DataStore
val dataStore = context.createDataStore(name = "settings")
// 写入数据
suspend fun saveSettings() {
dataStore.edit { prefs ->
prefs[intPreferencesKey("count")] = 100
}
}
kotlin复制// Proto DataStore
val userDataStore = context.createDataStore(
fileName = "user.pb",
serializer = UserSerializer
)
// 定义Proto结构
syntax = "proto3";
message UserPrefs {
string name = 1;
int32 age = 2;
}
kotlin复制val dataStore = context.createDataStore(
name = "settings",
migrations = listOf(SharedPreferencesMigration(context, "old_prefs"))
)
DataStore适合存储结构化配置数据,但需要注意它目前不支持同步API和多进程访问。
针对不同场景,我们有多种读取优化方案:
基础方案对比:
| 方案 | 适用场景 | 示例代码 | 性能(10MB文件) |
|------|---------|---------|---------------|
| 逐行读取 | 文本日志 | file.bufferedReader().use{ it.lineSequence() } | 120ms |
| 一次性读取 | 小文件 | file.readText() | 80ms |
| 流式读取 | 大文件 | inputStream.use{ it.copyTo(output) } | 60ms |
| 内存映射 | 随机访问 | raf.channel.map(READ_ONLY, 0, size) | 40ms |
内存映射实战:
kotlin复制fun readWithMemoryMap(file: File): String {
RandomAccessFile(file, "r").use { raf ->
val channel = raf.channel
val buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size())
return Charset.defaultCharset().decode(buffer).toString()
}
}
写入操作更需要考虑数据安全和性能平衡:
写入方案对比:
| 方案 | 特点 | 适用场景 | 性能(1万行) |
|------|------|---------|------------|
| 直接写入 | 简单但不安全 | 临时数据 | 500ms |
| 缓冲写入 | 平衡性能与安全 | 一般场景 | 150ms |
| 批量写入 | 性能最优 | 日志记录 | 80ms |
| 原子写入 | 最安全 | 关键数据 | 200ms |
原子写入实现:
kotlin复制fun writeAtomically(file: File, content: String) {
val tempFile = File(file.parent, "${file.name}.tmp")
try {
tempFile.writeText(content)
if (!tempFile.renameTo(file)) {
throw IOException("Rename failed")
}
} finally {
tempFile.delete()
}
}
kotlin复制suspend fun writeAsync(file: File, content: String) =
withContext(Dispatchers.IO) {
file.writeText(content)
}
大文件下载的核心是断点续传功能:
kotlin复制suspend fun downloadWithResume(
url: String,
targetFile: File,
onProgress: (Long, Long) -> Unit
): Boolean {
val downloadedSize = targetFile.length()
val request = Request.Builder()
.url(url)
.header("Range", "bytes=$downloadedSize-")
.build()
val response = client.newCall(request).execute()
response.body?.byteStream()?.use { input ->
FileOutputStream(targetFile, true).use { output ->
val buffer = ByteArray(8192)
var bytesRead: Int
while (input.read(buffer).also { bytesRead = it } != -1) {
output.write(buffer, 0, bytesRead)
onProgress(targetFile.length(), response.header("Content-Length")?.toLong() ?: 0L)
}
}
}
return response.isSuccessful
}
关键点:
对于大文件上传,分片处理是必选方案:
kotlin复制suspend fun uploadWithChunks(
file: File,
uploadUrl: String,
chunkSize: Int = 1024 * 1024, // 1MB
onProgress: (Long, Long) -> Unit
): Boolean {
val totalSize = file.length()
var uploaded = 0L
file.inputStream().use { input ->
val buffer = ByteArray(chunkSize)
var bytesRead: Int
while (input.read(buffer).also { bytesRead = it } > 0) {
val chunk = if (bytesRead == chunkSize) buffer else buffer.copyOf(bytesRead)
if (!uploadChunk(uploadUrl, chunk)) return false
uploaded += bytesRead
onProgress(uploaded, totalSize)
}
}
return true
}
优化技巧:
基于LRU算法的缓存管理器:
kotlin复制class FileCacheManager(
private val cacheDir: File,
private val maxSize: Long = 100 * 1024 * 1024 // 100MB
) {
private val lruCache = object : LinkedHashMap<String, CacheEntry>(16, 0.75f, true) {
override fun removeEldestEntry(eldest: MutableMap.MutableEntry<String, CacheEntry>): Boolean {
return totalSize > maxSize
}
}
fun put(key: String, data: ByteArray) {
val file = File(cacheDir, key.md5())
file.writeBytes(data)
lruCache[key] = CacheEntry(file, System.currentTimeMillis(), data.size)
trimToSize()
}
private fun trimToSize() {
while (totalSize > maxSize * 0.8) { // 清理到80%
lruCache.entries.firstOrNull()?.let { (key, _) ->
lruCache.remove(key)?.file?.delete()
}
}
}
}
带过期时间的缓存管理:
kotlin复制class TimeBasedCacheManager(
private val cacheDir: File,
private val defaultExpire: Long = 7 * 24 * 60 * 60 * 1000 // 7天
) {
fun put(key: String, data: ByteArray, expireMs: Long = defaultExpire) {
val file = File(cacheDir, key.md5())
file.writeBytes(data)
val metadata = CacheMetadata(
key = key,
file = file,
createTime = System.currentTimeMillis(),
expireTime = expireMs
)
saveMetadata(metadata)
}
fun get(key: String): ByteArray? {
val metadata = loadMetadata(key) ?: return null
if (System.currentTimeMillis() > metadata.createTime + metadata.expireTime) {
metadata.file.delete()
return null
}
return metadata.file.readBytes()
}
}
关键监控指标:
kotlin复制data class IOMetrics(
val readCount: Int, // 读取次数
val writeCount: Int, // 写入次数
val totalReadBytes: Long, // 读取总字节数
val totalWriteBytes: Long,// 写入总字节数
val maxReadTime: Long, // 最大读取耗时
val maxWriteTime: Long // 最大写入耗时
)
优化前后关键指标对比:
| 指标 | 优化前 | 优化后 | 提升幅度 |
|---|---|---|---|
| 配置文件读取 | 250ms | 50ms | 80% |
| 日志写入延迟 | 15ms/条 | 2ms/条 | 87% |
| 启动时间 | 3.5s | 1.8s | 49% |
| 存储占用 | 450MB | 225MB | 50% |
存储选型黄金法则:
读写优化要点:
避坑指南:
性能调优技巧: