Khi sử dụng ứng dụng nhắn tin phổ biến, người dùng thường nhận thấy rằng ảnh đại diện bạn bè được hiển thị nhanh chóng ngay cả sau lần truy cập thứ hai — điều này cho thấy hệ thống đã lưu trữ cục bộ ảnh, đồng thời vẫn đảm bảo tính nhất quán khi người dùng thay đổi ảnh. Để đạt được hành vi tương tự, cần kết hợp cơ chế lưu đệm hiệu quả với chiến lược đồng bộ hóa thông minh.
Cơ chế lưu đệm ảnh trên thiết bị
Ảnh đại diện là tài nguyên dạng bitmap có kích thước không nhỏ, do đó việc lưu trực tiếp dưới dạng đối tượng Bitmap vào SharedPreferences hoặc bộ nhớ RAM không khả thi — dễ gây OutOfMemoryError. Thay vào đó, giải pháp tối ưu là sử dụng bộ nhớ đệm đa cấp: lưu tạm trong bộ nhớ đệm RAM (memory cache) để truy xuất nhanh, đồng thời sao lưu bền vững trên bộ nhớ ngoài (disk cache) để duy trì giữa các phiên.
Các thư viện quản lý ảnh như Glide, Fresco, hay Coil đã xử lý sẵn toàn bộ luồng này. Dưới đây là ví dụ triển khai bằng Glide — thư viện được ưa chuộng nhờ hiệu năng cao và tích hợp sẵn chính sách quản lý bộ nhớ:
// Khởi tạo Glide với cấu hình tùy chỉnh
Glide.get(context).setMemoryCategory(MemoryCategory.HIGH);
// Cấu hình disk cache (tùy chọn, nếu cần kiểm soát chi tiết)
val glideModule = object : AppGlideModule() {
override fun applyOptions(context: Context, builder: GlideBuilder) {
builder.setDiskCache {
InternalCacheDiskCacheFactory(
context,
"avatar_cache",
250 * 1024 * 1024 // 250MB
)
}
}
}Để tải và hiển thị ảnh với tự động đệm:
// Tải ảnh từ URL, tự động kiểm tra cache trước khi request mạng
Glide.with(fragment)
.load("https://api.example.com/avatar/123.jpg")
.placeholder(R.drawable.ic_avatar_placeholder)
.error(R.drawable.ic_avatar_error)
.transform(RoundedCorners(12))
.into(imageView)Glide sẽ tự động:
- Kiểm tra bộ nhớ đệm RAM trước → nếu có, trả về ngay lập tức;
- Nếu không có trong RAM, kiểm tra file trên ổ đĩa → nếu tồn tại, giải mã và trả về;
- Nếu cả hai đều không có, tiến hành tải từ mạng, lưu vào cả hai lớp cache.
Đồng bộ hóa ảnh khi có thay đổi
Vấn đề then chốt không nằm ở việc lưu đệm, mà ở việc xác định khi nào nên bỏ qua cache. Một cách tiếp cận hiệu quả là gắn thẻ phiên bản cho mỗi ảnh đại diện. Khi server trả về danh sách bạn bè, kèm theo một trường metadata như avatar_version hoặc last_modified_timestamp:
data class Friend(
val id: Long,
val name: String,
val avatarUrl: String,
val avatarVersion: Long // hoặc timestamp: Long
)Trên client, lưu kèm phiên bản này cùng ảnh trong cache — ví dụ bằng cách mở rộng tên file:
// Tạo key cache dựa trên URL + phiên bản
fun buildCacheKey(url: String, version: Long): String {
return "${url.hashCode()}_v$version"
}
// Khi tải ảnh:
val cacheKey = buildCacheKey(friend.avatarUrl, friend.avatarVersion)
Glide.with(context)
.load(friend.avatarUrl)
.signature(ObjectKey(cacheKey)) // buộc Glide coi đây là ảnh mới nếu key thay đổi
.into(imageView)Thư viện Glide hỗ trợ ObjectKey hoặc MediaStoreSignature để đánh dấu sự thay đổi nội dung — giúp tránh việc dùng lại ảnh cũ dù URL không đổi.
Một phương án thay thế là sử dụng ETag hoặc Last-Modified từ header HTTP. Khi gọi API, server gửi kèm các header này; client truyền lại trong request kế tiếp (If-None-Match, If-Modified-Since). Nếu server trả về 304 Not Modified, client giữ nguyên cache hiện tại — ngược lại, tải ảnh mới và cập nhật.
Lưu ý: Không cần thiết phải thiết lập kết nối socket riêng lẻ chỉ để cập nhật ảnh đại diện — điều này gây tốn tài nguyên và phức tạp hóa kiến trúc. Việc kiểm tra phiên bản tại thời điểm khởi tạo màn hình (ví dụ trong onCreate() của Activity hoặc onViewCreated() của Fragment) là đủ để đảm bảo trải nghiệm mượt mà và nhất quán.