⚠️ Quan trọng: Assets là READ-ONLY tại runtime
Thư mục assets/ được đóng gói vào APK/AAB tại **build time**. **Không thể ghi/sửa/xóa file trong assets lúc runtime** (read-only).
Nếu cần ghi file tại runtime, hãy dùng:
- Internal Storage (
context.filesDir) - Private, xóa khi uninstall. - Cache Dir (
context.cacheDir) - Temporary, hệ thống có thể xóa khi thiếu dung lượng. - External Storage (App-specific) (
context.getExternalFilesDir()) - Lớn hơn, không cần permission API 19+.
Đọc file từ Assets (Chỉ đọc - Read Only)
Bước 1: Tạo file trong assets (Build time)
- Android Studio: Chuột phải module
app→ New → Folder → Assets Folder. - Chuột phải thư mục
assets→ New → File → Đặt tênquotes.txt. - Nhập nội dung:
"Hãy sống như thể bạn sẽ chết ngày mai.
Hãy học như thể bạn sẽ sống mãi mãi."
— Mahatma Gandhi
Bước 2: Layout (res/layout/activity_main.xml)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
xmlns:tools="https://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="24dp"
android:gravity="center"
tools:context=".MainActivity">
<Button
android:id="@+id/btnReadAsset"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Đọc file từ Assets" />
<TextView
android:id="@+id/tvContent"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_marginTop="16dp"
android:padding="16dp"
android:background="@android:color/darker_gray"
android:textSize="16sp"
android:gravity="center"
tools:text="Nội dung file assets..." />
</LinearLayout>
Bước 3: Code đọc assets (MainActivity.kt)
package com.example.readassets
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.example.readassets.databinding.ActivityMainBinding
import java.io.BufferedReader
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.btnReadAsset.setOnClickListener {
readFromAssets()
}
}
private fun readFromAssets() {
try {
// Mở file từ assets
val inputStream = assets.open("quotes.txt")
// Đọc toàn bộ nội dung (Kotlin extension)
val content = inputStream.bufferedReader().use { it.readText() }
binding.tvContent.text = content.trim()
} catch (e: Exception) {
binding.tvContent.text = "Lỗi: ${e.message}"
Toast.makeText(this, "Không tìm thấy file trong assets", Toast.LENGTH_SHORT).show()
}
}
}
Copy file từ Assets sang Internal Storage (Để có thể ghi/sửa)
Nếu cần file từ assets làm template, copy sang internal storage lần đầu chạy:
private fun copyAssetToInternalStorage(assetFileName: String, targetFileName: String): File {
val targetFile = File(filesDir, targetFileName)
if (targetFile.exists()) return targetFile // Đã copy rồi
try {
assets.open(assetFileName).use { input ->
targetFile.outputStream().use { output ->
input.copyTo(output)
}
}
return targetFile
} catch (e: Exception) {
e.printStackTrace()
throw RuntimeException("Copy asset failed: $assetFileName", e)
}
}
// Sử dụng
val myFile = copyAssetToInternalStorage("quotes.txt", "my_quotes.txt")
// Giờ có thể ghi/sửa myFile
myFile.writeText("Nội dung mới")
So sánh: Assets vs Internal Storage vs Raw Resource
| Đặc điểm | assets/ | res/raw/ | Internal Storage (filesDir) |
|---|---|---|---|
| Ghi tại runtime | ❌ Không thể | ❌ Không thể | ✅ Có thể |
| Đọc tại runtime | ✅ AssetManager.open() | ✅ Resources.openRawResource() | ✅ File API |
| Cấu trúc thư mục | Giữ nguyên (subfolder OK) | Phẳng (không subfolder) | Tự quản lý |
| Truy cập | Stream (không có Resource ID) | Resource ID (R.raw.xxx) | File path/Stream |
| Kích thước APK | Tăng size APK | Tăng size APK | Không (tạo lúc runtime) |
| Dùng cho | Config, JSON, HTML, font, DB template | Media ngắn, sound, raw data | User data, cache, download, DB |
Best Practices
- File config/template lớn: Đặt trong assets → Copy sang internal storage lần đầu → Đọc/ghi từ internal storage.
- File nhỏ, read-only: Đọc trực tiếp từ assets hoặc res/raw.
- Database (Room): Ship pre-populated DB trong assets → Copy sang filesDir → Mở bằng Room.
- Font: Đặt trong
res/font/(API 26+) hoặc assets (Calligraphy library cũ). - Đừng hardcode path assets - dùng
assets.open("filename").
Tóm tắt
Không thể ghi file vào assets lúc runtime. Assets là read-only, đóng gói tại build time. Hãy copy sang
filesDirhoặccacheDirnếu cần modify.