Android Developer Roadmap: Làm việc với Hệ thống File trong Android – Bộ nhớ Nội bộ và Ngoài

Chào mừng các bạn trở lại với loạt bài Android Developer Roadmap! Sau khi đã làm quen với các thành phần cốt lõi như Activity Lifecycle, Intents, Services, và các phương pháp lưu trữ dữ liệu bền vững như SharedPreferences, DataStore, và Room Database, đã đến lúc chúng ta khám phá một khía cạnh quan trọng khác: làm việc trực tiếp với hệ thống file trên thiết bị Android.

Việc hiểu và sử dụng đúng cách bộ nhớ nội bộ (Internal Storage) và bộ nhớ ngoài (External Storage) là nền tảng để quản lý dữ liệu của ứng dụng một cách hiệu quả, từ việc lưu trữ các file cấu hình nhỏ gọn đến việc xử lý các file media lớn như ảnh, video, hay tài liệu.

Trong bài viết này, chúng ta sẽ đi sâu vào hai loại bộ nhớ chính này, tìm hiểu sự khác biệt, cách thức truy cập, các API liên quan, và đặc biệt là những thay đổi quan trọng từ Android 10 (API level 29) trở đi với khái niệm Scoped Storage. Hãy cùng bắt đầu!

1. Hiểu về Hệ thống File trong Android

Mỗi thiết bị Android đều có một hệ thống file riêng để lưu trữ dữ liệu. Đối với ứng dụng của bạn, có hai khu vực chính để lưu trữ file:

  • Bộ nhớ Nội bộ (Internal Storage): Đây là khu vực lưu trữ riêng tư dành cho dữ liệu của từng ứng dụng. Các file được lưu ở đây chỉ có thể được truy cập bởi chính ứng dụng đó. Khi ứng dụng bị gỡ cài đặt, tất cả dữ liệu trong bộ nhớ nội bộ của nó sẽ bị xóa hoàn toàn.
  • Bộ nhớ Ngoài (External Storage): Đây là khu vực lưu trữ có thể được chia sẻ giữa các ứng dụng và người dùng. Nó có thể là bộ nhớ tích hợp của thiết bị (thường được gọi là “bộ nhớ trong”) hoặc một thẻ SD di động. Dữ liệu ở đây có thể tồn tại ngay cả khi ứng dụng bị gỡ cài đặt (tùy thuộc vào vị trí lưu trữ).

Việc lựa chọn giữa bộ nhớ nội bộ và ngoài phụ thuộc vào loại dữ liệu bạn muốn lưu trữ, mức độ bảo mật cần thiết, và liệu dữ liệu đó có cần được chia sẻ với các ứng dụng khác hoặc người dùng hay không.

2. Bộ nhớ Nội bộ (Internal Storage): Thế giới riêng tư của ứng dụng

Bộ nhớ nội bộ là nơi lý tưởng để lưu trữ các file nhạy cảm hoặc các file mà chỉ ứng dụng của bạn cần truy cập. Dữ liệu ở đây được cách ly khỏi các ứng dụng khác và người dùng, cung cấp một lớp bảo mật mặc định.

2.1 Đặc điểm

  • Riêng tư: Chỉ ứng dụng của bạn mới có thể truy cập các file trong thư mục bộ nhớ nội bộ của nó.
  • Bảo mật: Dữ liệu được mã hóa theo mặc định trên hầu hết các thiết bị hiện đại.
  • Tự động dọn dẹp: Khi người dùng gỡ cài đặt ứng dụng, tất cả các file trong bộ nhớ nội bộ của ứng dụng đó sẽ bị xóa.
  • Không cần quyền: Bạn không cần yêu cầu bất kỳ quyền đặc biệt nào để đọc hoặc ghi file vào bộ nhớ nội bộ của chính ứng dụng mình.

2.2 Sử dụng Bộ nhớ Nội bộ

Để làm việc với bộ nhớ nội bộ, bạn sẽ sử dụng các phương thức được cung cấp bởi `Context`.

Lấy đường dẫn thư mục

Có hai thư mục chính bạn có thể làm việc với:

  • `getFilesDir()`: Trả về đường dẫn đến thư mục chính để lưu trữ các file thường xuyên của ứng dụng.
  • `getCacheDir()`: Trả về đường dẫn đến thư mục để lưu trữ các file cache tạm thời. Hệ thống có thể xóa các file này khi bộ nhớ thấp.
val filesDir = applicationContext.filesDir // Ví dụ: /data/data/com.your.app.package/files
val cacheDir = applicationContext.cacheDir // Ví dụ: /data/data/com.your.app.package/cache

Ghi file vào Bộ nhớ Nội bộ

Bạn có thể sử dụng `openFileOutput()` để lấy một `FileOutputStream` để ghi dữ liệu vào một file trong thư mục `filesDir()`.

val fileName = "my_private_data.txt"
val fileContents = "Đây là dữ liệu riêng tư của tôi."

try {
    applicationContext.openFileOutput(fileName, Context.MODE_PRIVATE).use {
        it.write(fileContents.toByteArray())
        println("Đã ghi file $fileName vào bộ nhớ nội bộ thành công!")
    }
} catch (e: Exception) {
    e.printStackTrace()
    println("Lỗi khi ghi file: ${e.message}")
}

Trong ví dụ trên, `Context.MODE_PRIVATE` là chế độ mặc định, đảm bảo file chỉ được đọc/ghi bởi ứng dụng gọi nó. Các chế độ khác như `MODE_APPEND` cho phép thêm dữ liệu vào cuối file.

Đọc file từ Bộ nhớ Nội bộ

Sử dụng `openFileInput()` để lấy một `FileInputStream` để đọc dữ liệu từ một file trong thư mục `filesDir()`.

val fileName = "my_private_data.txt"
var fileContent: String? = null

try {
    applicationContext.openFileInput(fileName).bufferedReader().use {
        fileContent = it.readText()
        println("Đã đọc file $fileName từ bộ nhớ nội bộ: $fileContent")
    }
} catch (e: FileNotFoundException) {
    println("File $fileName không tồn tại.")
} catch (e: Exception) {
    e.printStackTrace()
    println("Lỗi khi đọc file: ${e.message}")
}

Xóa file từ Bộ nhớ Nội bộ

Bạn có thể sử dụng phương thức `deleteFile()` hoặc đối tượng `File` thông thường.

val fileName = "my_private_data.txt"
val isDeleted = applicationContext.deleteFile(fileName)

if (isDeleted) {
    println("Đã xóa file $fileName thành công.")
} else {
    println("Không thể xóa file $fileName (có thể không tồn tại).")
}

// Hoặc sử dụng đối tượng File
val fileToDelete = File(applicationContext.filesDir, "another_file.txt")
if (fileToDelete.exists()) {
    val isDeletedUsingFile = fileToDelete.delete()
    if (isDeletedUsingFile) {
        println("Đã xóa file ${fileToDelete.name} bằng đối tượng File thành công.")
    }
}

3. Bộ nhớ Ngoài (External Storage): Không gian chia sẻ và công cộng

Bộ nhớ ngoài là nơi lưu trữ dữ liệu mà bạn muốn chia sẻ với các ứng dụng khác, người dùng, hoặc các file có kích thước lớn. Nó có thể là bộ nhớ flash tích hợp của thiết bị hoặc thẻ SD (nếu có).

Làm việc với bộ nhớ ngoài phức tạp hơn bộ nhớ nội bộ, đặc biệt là từ Android 10 trở đi do sự ra đời của Scoped Storage.

3.1 Các loại thư mục trên Bộ nhớ Ngoài

Có hai loại thư mục chính trên bộ nhớ ngoài mà ứng dụng của bạn có thể truy cập:

  1. Thư mục riêng tư của ứng dụng trên bộ nhớ ngoài (App-Specific External Storage): Đây là các thư mục nằm trong `/Android/data/your.package.name/` trên bộ nhớ ngoài. Dữ liệu ở đây cũng được coi là “riêng tư” theo nghĩa thuộc về ứng dụng của bạn, nhưng không được bảo mật như bộ nhớ nội bộ và có thể bị truy cập bởi các ứng dụng khác nếu chúng có quyền phù hợp (trước Android 10) hoặc bởi người dùng thông qua các ứng dụng quản lý file. Quan trọng nhất là: **Khi ứng dụng bị gỡ cài đặt, các thư mục này và nội dung của chúng sẽ bị xóa.**
  2. Bộ nhớ chia sẻ (Shared Storage): Đây là các thư mục tiêu chuẩn như `Downloads`, `Documents`, `Pictures`, `Movies`, `Music`. Dữ liệu ở đây được dành cho các loại file cụ thể và được chia sẻ giữa các ứng dụng và người dùng. **Dữ liệu ở đây vẫn tồn tại sau khi ứng dụng bị gỡ cài đặt.**

3.2 Lịch sử Quyền truy cập (Trước và Sau Android 10)

Trước Android 10 (API 29), việc truy cập bộ nhớ ngoài thường đòi hỏi các quyền `READ_EXTERNAL_STORAGE` và `WRITE_EXTERNAL_STORAGE`. Với các quyền này, ứng dụng có thể đọc và ghi vào bất kỳ đâu trên bộ nhớ ngoài (ngoại trừ các thư mục riêng tư của ứng dụng khác). Điều này đặt ra vấn đề về bảo mật và sự lộn xộn file.

Từ Android 10, Google giới thiệu Scoped Storage (Bộ nhớ có phạm vi). Đây là một sự thay đổi lớn trong cách các ứng dụng truy cập bộ nhớ ngoài.

4. Scoped Storage – Cuộc Cách Mạng về Bảo mật và Tổ chức File

Scoped Storage giới hạn khả năng truy cập bộ nhớ ngoài của ứng dụng vào các phạm vi cụ thể:

  • App-specific directories: Ứng dụng có quyền truy cập đầy đủ vào các thư mục riêng tư của nó trên bộ nhớ ngoài (`getExternalFilesDir()`, `getExternalCacheDir()`).
  • Shared collections: Ứng dụng chỉ có thể truy cập các file trong các thư mục chia sẻ (như Pictures, Movies, Downloads) thông qua các API được định nghĩa rõ ràng như `MediaStore` hoặc Storage Access Framework (SAF).
  • Không còn truy cập “raw file path” toàn bộ bộ nhớ ngoài một cách mặc định.

Điều này mang lại nhiều lợi ích:

  • Tăng cường bảo mật: Ứng dụng ít có khả năng truy cập dữ liệu nhạy cảm của người dùng hoặc các ứng dụng khác một cách trái phép.
  • Cải thiện sự tổ chức: Giúp hệ thống dễ dàng quản lý file và dọn dẹp khi ứng dụng bị gỡ cài đặt.
  • Giảm sự lộn xộn: Ngăn các ứng dụng rải file của chúng khắp nơi trên bộ nhớ ngoài.

4.1 Làm việc với Bộ nhớ Ngoài theo Scoped Storage

Thư mục riêng tư của ứng dụng trên bộ nhớ ngoài

Bạn sử dụng các phương thức tương tự `getFilesDir()` và `getCacheDir()`, nhưng với tiền tố `External`:

  • `getExternalFilesDir(type: String?)`: Trả về đường dẫn đến thư mục chính để lưu trữ các file thường xuyên của ứng dụng trên bộ nhớ ngoài. Bạn có thể cung cấp một loại thư mục chuẩn (ví dụ: `Environment.DIRECTORY_PICTURES`, `Environment.DIRECTORY_DOCUMENTS`) để hệ thống tự động tạo thư mục con tương ứng. Nếu truyền `null`, nó sẽ trả về thư mục gốc của ứng dụng trên bộ nhớ ngoài.
  • `getExternalCacheDir()`: Trả về đường dẫn đến thư mục cache tạm thời của ứng dụng trên bộ nhớ ngoài.

Lưu ý: Dữ liệu trong các thư mục này bị xóa khi ứng dụng bị gỡ cài đặt.

// Lấy thư mục files/ của ứng dụng trên bộ nhớ ngoài
val externalFilesDir = applicationContext.getExternalFilesDir(null)

// Lấy thư mục Pictures/files/ của ứng dụng trên bộ nhớ ngoài
val externalPicturesDir = applicationContext.getExternalFilesDir(Environment.DIRECTORY_PICTURES)

// Lấy thư mục cache/ của ứng dụng trên bộ nhớ ngoài
val externalCacheDir = applicationContext.getExternalCacheDir()

println("External files dir: $externalFilesDir")
println("External pictures dir: $externalPicturesDir")
println("External cache dir: $externalCacheDir")

// Ghi file vào thư mục files/ của ứng dụng trên bộ nhớ ngoài
val fileInExternalPrivate = File(externalFilesDir, "external_private_data.txt")
try {
    fileInExternalPrivate.writeText("Dữ liệu ở đây sẽ bị xóa khi gỡ app.")
    println("Đã ghi file vào thư mục riêng tư trên bộ nhớ ngoài: ${fileInExternalPrivate.absolutePath}")
} catch (e: Exception) {
    e.printStackTrace()
}

Để đọc và ghi file trong các thư mục này, bạn chỉ cần sử dụng các API `java.io.File` thông thường, giống như làm việc với bộ nhớ nội bộ, nhưng sử dụng đường dẫn được trả về từ `getExternalFilesDir()` hoặc `getExternalCacheDir()`. Bạn **không cần** các quyền `READ_EXTERNAL_STORAGE` hay `WRITE_EXTERNAL_STORAGE` để truy cập các thư mục này của chính ứng dụng mình từ Android 4.4 (API 19) trở lên.

Bộ nhớ chia sẻ (Shared Storage) – MediaStore và SAF

Để truy cập các file trong các thư mục chia sẻ (Pictures, Movies, Downloads, v.v.) từ Android 10, bạn phải sử dụng:

  • MediaStore API: Dành cho các file media (ảnh, video, âm thanh). API này cung cấp một giao diện có cấu trúc để truy vấn, thêm, sửa, xóa các file media. Nó hiệu quả và được khuyến khích cho các tác vụ phổ biến như hiển thị thư viện ảnh, quay video, ghi âm.
  • Storage Access Framework (SAF): Cung cấp một giao diện chuẩn cho người dùng để chọn file hoặc thư mục, hoặc để ứng dụng tạo file ở vị trí do người dùng chọn. SAF hoạt động thông qua Intents (như `Intent.ACTION_OPEN_DOCUMENT`, `Intent.ACTION_CREATE_DOCUMENT`) và trả về URIs thay vì đường dẫn file trực tiếp. Đây là cách an toàn và hiện đại để làm việc với các loại file không phải media hoặc cho phép người dùng kiểm soát vị trí lưu trữ.

Ví dụ sử dụng MediaStore để lưu một ảnh:

Đây là một ví dụ cơ bản, việc xử lý thực tế (quyền, luồng dữ liệu lớn) phức tạp hơn.

import android.content.ContentValues
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.provider.MediaStore
import java.io.OutputStream

fun saveBitmapToGallery(context: Context, bitmap: Bitmap, displayName: String): Uri? {
    val imageCollection = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
    } else {
        MediaStore.Images.Media.EXTERNAL_CONTENT_URI
    }

    val contentValues = ContentValues().apply {
        put(MediaStore.Images.Media.DISPLAY_NAME, displayName)
        put(MediaStore.Images.Media.MIME_TYPE, "image/png")
        put(MediaStore.Images.Media.WIDTH, bitmap.width)
        put(MediaStore.Images.Media.HEIGHT, bitmap.height)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            put(MediaStore.Images.Media.IS_PENDING, 1)
            // Đặt thư mục con nếu cần, ví dụ Environment.DIRECTORY_PICTURES
            put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES)
        }
    }

    var uri: Uri? = null
    try {
        uri = context.contentResolver.insert(imageCollection, contentValues)
        if (uri != null) {
            context.contentResolver.openOutputStream(uri).use { os: OutputStream? ->
                if (os != null) {
                    bitmap.compress(Bitmap.CompressFormat.PNG, 100, os)
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                        contentValues.clear()
                        contentValues.put(MediaStore.Images.Media.IS_PENDING, 0)
                        context.contentResolver.update(uri, contentValues, null, null)
                    }
                    println("Đã lưu ảnh vào Gallery: $uri")
                    return uri
                }
            }
        }
    } catch (e: Exception) {
        e.printStackTrace()
        // Xóa entry nếu lưu không thành công
        if (uri != null) {
            context.contentResolver.delete(uri, null, null)
        }
        println("Lỗi khi lưu ảnh vào Gallery: ${e.message}")
    }
    return null
}

// Cách gọi (trong Activity/Fragment):
// giả sử bạn có một Bitmap tên 'myBitmap'
// val savedUri = saveBitmapToGallery(this, myBitmap, "my_awesome_photo.png")

Ví dụ sử dụng SAF để cho phép người dùng chọn file:

import android.app.Activity
import android.content.Intent
import android.net.Uri
import androidx.activity.result.contract.ActivityResultContracts

class MyActivity : AppCompatActivity() {

    private val pickFileLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
        if (result.resultCode == Activity.RESULT_OK) {
            val uri: Uri? = result.data?.data
            uri?.let {
                // Làm gì đó với URI của file đã chọn
                println("Người dùng đã chọn file với URI: $it")
                try {
                    contentResolver.openInputStream(it).use { inputStream ->
                        // Đọc dữ liệu từ InputStream
                        val content = inputStream?.bufferedReader()?.readText()
                        println("Nội dung file: $content")
                    }
                } catch (e: Exception) {
                    e.printStackTrace()
                }
            }
        }
    }

    fun openFilePicker() {
        val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
            addCategory(Intent.CATEGORY_OPENABLE) // Chỉ hiển thị các file có thể mở
            type = "*/*" // Chỉ định loại MIME. Ví dụ: "image/*", "text/plain"
            // Bạn có thể thêm EXTRA_MIME_TYPES để chọn nhiều loại
        }
        pickFileLauncher.launch(intent)
    }

    // Gọi openFilePicker() khi người dùng nhấn button hoặc hành động nào đó.
    // ví dụ trong onCreate hoặc một click listener:
    // findViewById<Button>(R.id.button_pick_file).setOnClickListener {
    //     openFilePicker()
    // }
}

Các quyền `READ_EXTERNAL_STORAGE` và `WRITE_EXTERNAL_STORAGE` vẫn tồn tại nhưng cách chúng hoạt động đã thay đổi. Từ Android 10, chúng không còn cấp quyền truy cập rộng rãi vào toàn bộ bộ nhớ ngoài nữa, trừ khi ứng dụng yêu cầu và được cấp quyền `MANAGE_EXTERNAL_STORAGE` (chỉ dành cho một số loại ứng dụng nhất định và cần xem xét kỹ khi đăng lên Google Play). Với Scoped Storage, bạn thường không cần các quyền này cho hầu hết các trường hợp sử dụng phổ biến.

5. So sánh Bộ nhớ Nội bộ và Ngoài

Để củng cố sự hiểu biết, đây là bảng so sánh tóm tắt:

Đặc điểm Bộ nhớ Nội bộ (Internal Storage) Bộ nhớ Ngoài (External Storage – App-Specific) Bộ nhớ Ngoài (External Storage – Shared/Scoped)
Vị trí vật lý Thường là một phân vùng riêng trên bộ nhớ trong Trong thư mục `/Android/data/YOUR_PACKAGE_NAME/` trên bộ nhớ ngoài (trong hoặc thẻ SD) Các thư mục chuẩn như Pictures, Movies, Downloads trên bộ nhớ ngoài
Truy cập bởi ứng dụng khác Không thể (mặc định) Có thể (với quyền thích hợp trước Android 10), nhưng dữ liệu thuộc về ứng dụng của bạn. Có thể đọc và ghi (với API phù hợp như MediaStore/SAF và quyền nếu cần)
Truy cập bởi người dùng Không thể (mặc định) Có thể thông qua trình quản lý file nếu kết nối thiết bị với máy tính hoặc sử dụng các ứng dụng quản lý file đặc biệt. Dễ dàng truy cập thông qua ứng dụng Gallery, Music Player, File Manager, v.v.
Bảo mật Cao (riêng tư, có thể mã hóa) Trung bình (không được mã hóa mặc định, có thể bị truy cập bởi ứng dụng khác/người dùng) Thấp (thiết kế để chia sẻ)
Thời gian tồn tại Bị xóa khi gỡ cài đặt ứng dụng Bị xóa khi gỡ cài đặt ứng dụng Tồn tại sau khi gỡ cài đặt ứng dụng
Quyền truy cập (Android 10+) Không cần quyền đặc biệt Không cần quyền đặc biệt để truy cập thư mục của chính bạn Cần sử dụng MediaStore/SAF. Quyền READ/WRITE_EXTERNAL_STORAGE bị hạn chế hoặc không cần thiết cho các trường hợp phổ biến.
Use Cases File cấu hình, database nhỏ, dữ liệu nhạy cảm, file tạm thời riêng tư File tạm thời không nhạy cảm, file cần bộ nhớ lớn hơn nội bộ nhưng vẫn chỉ thuộc về ứng dụng. Ảnh, video, nhạc, tài liệu, file cần chia sẻ hoặc tồn tại lâu dài.
API chính getFilesDir(), getCacheDir(), openFileInput(), openFileOutput() getExternalFilesDir(), getExternalCacheDir() MediaStore, Storage Access Framework (SAF) – Intent.ACTION_OPEN_DOCUMENT, Intent.ACTION_CREATE_DOCUMENT

6. Các Thực Tiễn Tốt Nhất khi làm việc với File System

  • Luôn ưu tiên Bộ nhớ Nội bộ: Nếu dữ liệu chỉ cần cho ứng dụng của bạn và không cần chia sẻ, hãy lưu vào bộ nhớ nội bộ. Đây là lựa chọn an toàn và đơn giản nhất.
  • Sử dụng Bộ nhớ Ngoài App-Specific cho các file không nhạy cảm và chỉ dùng trong ứng dụng: Nếu bạn cần lưu các file lớn hơn hoặc file không nhạy cảm mà không muốn chiếm không gian bộ nhớ nội bộ, thư mục riêng tư trên bộ nhớ ngoài là lựa chọn tốt, miễn là bạn chấp nhận việc chúng bị xóa khi gỡ app.
  • Sử dụng MediaStore hoặc SAF cho Bộ nhớ Chia sẻ: Tuân thủ Scoped Storage. Đừng cố gắng truy cập bộ nhớ chia sẻ bằng cách xây dựng đường dẫn trực tiếp hoặc dựa vào các quyền cũ nếu không thực sự cần thiết (và phải cân nhắc rủi ro). Học cách sử dụng MediaStore cho media và SAF cho các loại file khác hoặc khi cần tương tác với hệ thống file theo cách có kiểm soát của người dùng.
  • Xử lý Quyền một cách cẩn thận: Yêu cầu quyền (như `READ_EXTERNAL_STORAGE` cho Android < 10 hoặc các quyền liên quan đến MediaStore/SAF nếu cần) chỉ khi thực sự cần và giải thích cho người dùng tại sao ứng dụng cần quyền đó. Xử lý trường hợp người dùng từ chối cấp quyền.
  • Dọn dẹp File Cache: Thường xuyên xóa các file tạm thời trong `getCacheDir()` và `getExternalCacheDir()` khi chúng không còn cần thiết để giải phóng bộ nhớ.
  • Kiểm tra trạng thái Bộ nhớ Ngoài: Trước khi cố gắng truy cập bộ nhớ ngoài, hãy kiểm tra xem nó có sẵn và không bị gắn kết (mounted) ở chế độ chỉ đọc hay không bằng cách sử dụng `Environment.getExternalStorageState()`.
  • Xử lý Ngoại lệ: Các thao tác file có thể gặp lỗi (file không tồn tại, không đủ bộ nhớ, lỗi IO, v.v.). Luôn bao bọc các thao tác file trong khối `try-catch`.
  • Cân nhắc các lựa chọn lưu trữ khác: Đối với dữ liệu có cấu trúc hoặc dữ liệu cần truy vấn phức tạp, hãy cân nhắc sử dụng SharedPreferences, Room Database, hoặc các giải pháp database khác thay vì file văn bản thuần túy.

Kết luận

Làm việc với hệ thống file là một kỹ năng cơ bản nhưng đòi hỏi sự hiểu biết sâu sắc, đặc biệt là với những thay đổi về bảo mật và quyền truy cập trong các phiên bản Android gần đây. Bằng cách nắm vững sự khác biệt giữa bộ nhớ nội bộ và bộ nhớ ngoài (cả app-specific và shared), cùng với việc áp dụng Scoped Storage và sử dụng đúng các API (File, MediaStore, SAF), bạn có thể quản lý dữ liệu của ứng dụng một cách an toàn, hiệu quả và thân thiện với người dùng.

Đây là một bước tiến quan trọng trên con đường trở thành một nhà phát triển Android chuyên nghiệp trong Lộ trình Học Lập trình viên Android 2025. Ở các bài viết tiếp theo, chúng ta sẽ tiếp tục khám phá những khía cạnh khác của phát triển ứng dụng Android. Hãy tiếp tục theo dõi!

Chỉ mục