Scoped Storage — nó là gì, quản lý tệp và truy cập bộ nhớ

Tác giả: IT Sectr Đã đăng: 2026-07-10 Thời gian đọc: 10 phút

Scoped Storage là một mô hình truy cập hệ thống tệp được giới thiệu trong Android 10 (API 29), hạn chế quyền truy cập tùy tiện của ứng dụng vào bộ nhớ dùng chung của thiết bị. Theo Tài liệu dành cho nhà phát triển Android của Google (2024), Scoped Storage thay thế mô hình quyền cũ READ_EXTERNAL_STORAGE và WRITE_EXTERNAL_STORAGE, chỉ cấp cho ứng dụng quyền truy cập vào các tệp riêng của chúng trong hộp cát cách ly và vào các bộ sưu tập dùng chung thông qua MediaStore. Mô hình này tăng cường quyền riêng tư dữ liệu người dùng và ngăn chặn việc thu thập thông tin trái phép bởi các ứng dụng.

Những điểm chính

  • Scoped Storage là mô hình truy cập tệp trong Android 10+ hạn chế việc đọc tùy tiện bộ nhớ dùng chung.
  • Ứng dụng có hộp cát riêng với quyền truy cập đầy đủ mà không cần thêm quyền.
  • Để truy cập tệp dùng chung (ảnh, video, âm thanh), sử dụng API MediaStore.
  • Để truy cập thư mục và tệp tùy ý, sử dụng Storage Access Framework (SAF).
  • Các quyền READ_EXTERNAL_STORAGE và WRITE_EXTERNAL_STORAGE đã mất hiệu lực đối với hầu hết các tình huống.

Scoped Storage trong Android là gì?

Scoped Storage là một thay đổi kiến trúc trong Android 10 (API 29) thay đổi cơ bản cách ứng dụng truy cập hệ thống tệp của thiết bị. Thay vì truy cập đầy đủ vào bộ nhớ dùng chung, ứng dụng chỉ được truy cập vào hộp cát của riêng mình và vào các bộ sưu tập phương tiện cụ thể thông qua MediaStore.

Trước Scoped Storage, bất kỳ ứng dụng nào có quyền READ_EXTERNAL_STORAGE đều có thể đọc tất cả tệp trong bộ nhớ dùng chung — ảnh, tài liệu, tệp tải xuống, dữ liệu của ứng dụng khác. Điều này tạo ra rủi ro về quyền riêng tư: các ứng dụng không đáng tin cậy có thể thu thập thông tin người dùng mà họ không biết.

Theo Google I/O 2019, hơn 65% người dùng Android coi quyền riêng tư dữ liệu là yếu tố quan trọng khi chọn ứng dụng. Scoped Storage là phản ứng trực tiếp cho nhu cầu này: mỗi ứng dụng hoạt động trong một môi trường cách ly và chỉ truy cập dữ liệu của ứng dụng khác khi có sự đồng ý rõ ràng của người dùng.

Quan trọng: Scoped Storage trong Android 10 là tùy chọn (ứng dụng có thể tắt nó qua requestLegacyExternalStorage). Bắt đầu từ Android 11 (API 30), Scoped Storage trở thành bắt buộc đối với tất cả ứng dụng, bất kể target SDK.

Tại sao Scoped Storage lại cần thiết

Google đã giới thiệu Scoped Storage để giải quyết ba vấn đề cơ bản của mô hình truy cập tệp cũ: quyền riêng tư, quản lý quyền và dọn dẹp tệp còn sót lại.

Vấn đề về quyền riêng tư

Trong mô hình cũ, ứng dụng có READ_EXTERNAL_STORAGE có thể quét toàn bộ bộ nhớ dùng chung và thu thập siêu dữ liệu tệp của người dùng — tọa độ địa lý của ảnh, tên tài liệu, cấu trúc thư mục. Scoped Storage loại bỏ khả năng này: ngay cả khi có quyền, ứng dụng chỉ thấy các tệp do chính nó tạo ra và các tệp được người dùng chọn rõ ràng thông qua SAF.

Vấn đề dọn dẹp khi gỡ cài đặt ứng dụng

Trước Scoped Storage, ứng dụng có thể để lại tệp trong bộ nhớ dùng chung sau khi gỡ cài đặt. Theo thời gian, các thư mục rác tích tụ lại. Scoped Storage giải quyết điều này: tất cả tệp trong hộp cát của ứng dụng bị xóa cùng với ứng dụng và các tệp qua MediaStore có chủ sở hữu và có thể được hệ thống dọn dẹp.

Khía cạnhMô hình cũ (Legacy)Scoped Storage
Truy cập bộ nhớ dùng chungĐầy đủ (có quyền)Chỉ hộp cát riêng
Tệp đa phương tiệnĐường dẫn tệp trực tiếpQua URI MediaStore
Tệp của ứng dụng khácLuôn có thể truy cậpChỉ qua SAF
Dọn dẹp khi gỡ cài đặtTệp vẫn cònHộp cát bị xóa
Kiểm soát của người dùngTối thiểuĐồng ý rõ ràng cho từng tệp

Scoped Storage là một phần trong chiến lược quyền riêng tư tổng thể của Google trên Android, cũng bao gồm quyền "chỉ khi đang sử dụng", chỉ báo truy cập camera và micrô, và Bảng điều khiển quyền riêng tư.

Thư mục riêng tư của ứng dụng

Mỗi ứng dụng Android nhận được thư mục riêng tư của riêng mình, có thể truy cập qua Context.getFilesDir() và context.getCacheDir(). Để truy cập thư mục riêng tư bên ngoài (dùng chung), sử dụng Context.getExternalFilesDir().

Trong Scoped Storage, ứng dụng có quyền truy cập đầy đủ vào thư mục riêng tư bên ngoài mà không cần bất kỳ quyền nào. Đây là nơi chính để lưu trữ các tệp không được phép truy cập bởi ứng dụng khác hoặc ứng dụng tạo ra cho mục đích sử dụng riêng.

kotlin
import android.os.Environment
import java.io.File
import java.io.IOException

class FileManager {

    fun saveToAppStorage(context: Context, fileName: String, data: ByteArray) {
        val appDir = context.getExternalFilesDir(null)
            ?: return
        val file = File(appDir, fileName)
        file.writeBytes(data)
    }

    fun readFromAppStorage(context: Context, fileName: String): ByteArray? {
        val appDir = context.getExternalFilesDir(null)
            ?: return null
        val file = File(appDir, fileName)
        return file.takeIf { it.exists() }?.readBytes()
    }
}

Đường dẫn đến thư mục riêng tư bên ngoài: /storage/emulated/0/Android/data/{packageName}/files/. Bắt đầu từ Android 11, đường dẫn tệp trực tiếp không khả dụng — chỉ qua API. Đây là một cải tiến bảo mật khác của Scoped Storage.

MediaStore — truy cập tệp đa phương tiện

MediaStore là API chính để truy cập các tệp đa phương tiện dùng chung (hình ảnh, video, âm thanh) trong Scoped Storage. Ứng dụng không làm việc với đường dẫn tệp mà với content URI do nhà cung cấp MediaStore cung cấp.

MediaStore được chia thành ba bộ sưu tập chính: Images, VideoAudio. Mỗi bộ sưu tập hỗ trợ các thao tác CRUD thông qua ContentResolver. Để chèn tệp mới vào bộ sưu tập Images, sử dụng MediaStore.Images.Media; để truy vấn tệp hiện có — query với URI tương ứng.

kotlin
import android.content.ContentValues
import android.provider.MediaStore
import android.os.Environment
import java.io.OutputStream

fun saveImageToGallery(context: Context, bitmap: Bitmap, title: String) {
    val values = ContentValues().apply {
        put(MediaStore.Images.Media.DISPLAY_NAME, "$title.jpg")
        put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
        put(
            MediaStore.Images.Media.RELATIVE_PATH,
            "${Environment.DIRECTORY_PICTURES}/MyApp"
        )
    }

    val uri = context.contentResolver
        .insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
        ?: return

    context.contentResolver.openOutputStream(uri)?.use { output: OutputStream ->
        bitmap.compress(
            Bitmap.CompressFormat.JPEG,
            95,
            output
        )
    }
}

Quan trọng: MediaStore không hỗ trợ đường dẫn tệp tùy ý — chỉ content URI. Cố gắng lấy đường dẫn tệp từ URI qua MediaStore.Files.getContentUri() sẽ không cung cấp đường dẫn trực tiếp đến tệp trên Android 11+. Thay vào đó, hãy sử dụng ContentResolver.openInputStream() và openOutputStream() để làm việc với nội dung.

Storage Access Framework (SAF)

Storage Access Framework (SAF) là API để truy cập các tệp và thư mục tùy ý bên ngoài hộp cát của ứng dụng. SAF cung cấp cho người dùng giao diện chọn tệp hoặc thư mục, sau đó ứng dụng nhận được content URI với quyền truy cập tạm thời.

SAF được sử dụng cho các tình huống không được MediaStore bao phủ: làm việc với tài liệu tùy ý (PDF, ZIP, APK), truy cập thư mục trên thẻ SD, nhập và xuất tệp từ ứng dụng khác. Người dùng chọn tệp một cách rõ ràng thông qua trình chọn tệp hệ thống — điều này đảm bảo sự đồng ý của họ cho việc truy cập.

kotlin
import android.app.Activity
import android.content.Intent
import android.net.Uri
import android.provider.DocumentsContract

const val REQUEST_CODE_PICK_DIR = 1001

fun pickDirectory(activity: Activity) {
    val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
    activity.startActivityForResult(intent, REQUEST_CODE_PICK_DIR)
}

fun handlePickResult(requestCode: Int, data: Intent?) {
    if (requestCode == REQUEST_CODE_PICK_DIR && data != null) {
        val treeUri: Uri = data.data ?: return

        // Take persistent URI permission
        val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
                       Intent.FLAG_GRANT_WRITE_URI_PERMISSION
        contentResolver.takePersistableUriPermission(treeUri, flags)
    }
}

SAF cấp cho ứng dụng quyền truy cập vào thư mục do người dùng chọn cho đến khi khởi động lại thiết bị (khi sử dụng takePersistableUriPermission). Đây là cách duy nhất để có được quyền truy cập vào các tệp tùy ý trong bộ nhớ dùng chung trên Android 11+.

Di chuyển ứng dụng sang Scoped Storage

Di chuyển ứng dụng Android hiện có sang Scoped Storage yêu cầu thay đổi ở một số lĩnh vực chính. Google khuyến nghị cách tiếp cận theo giai đoạn với thử nghiệm trên Android 11+.

Thay thế đường dẫn tệp bằng content URI

Thay thế tất cả thao tác File trực tiếp trong bộ nhớ dùng chung bằng ContentResolver.openInputStream / openOutputStream. Đối với tệp trong hộp cát của ứng dụng (getExternalFilesDir), đường dẫn tệp vẫn hoạt động.

Cập nhật AndroidManifest

Xóa READ_EXTERNAL_STORAGEWRITE_EXTERNAL_STORAGE nếu chúng không cần thiết cho các tình huống cụ thể (ví dụ: quản lý tệp — có quyền đặc biệt MANAGE_EXTERNAL_STORAGE cho việc này).

Tình huốngCách tiếp cận cũCách tiếp cận Scoped Storage
Lưu ảnhFile(path).writeBytes()MediaStore + ContentResolver
Đọc PDFFile(path).inputStream()SAF ACTION_OPEN_DOCUMENT
Tệp riêngEnvironment.getExternalStorageDirectory()context.getExternalFilesDir()
Bộ nhớ đệmFile(cacheDir).writeBytes()context.cacheDir (không thay đổi)

Sau khi di chuyển, hãy thử nghiệm ứng dụng trên Android 11+ (API 30), đặt targetSdk = 30 hoặc cao hơn trong build.gradle. Đảm bảo rằng tất cả thao tác tệp trong bộ nhớ dùng chung hoạt động qua MediaStore hoặc SAF, không phải qua đường dẫn tệp trực tiếp.

kotlin
import android.os.Build

fun isScopedStorage(): Boolean {
    return Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
}

fun compatReadFile(context: Context, uri: Uri): ByteArray? {
    return if (isScopedStorage()) {
        context.contentResolver
            .openInputStream(uri)?.readBytes()
    } else {
        File(uri.path ?: return null)
            .takeIf { it.exists() }
            ?.readBytes()
    }
}

Theo Google Play Console (2024), hơn 78% thiết bị Android đang hoạt động chạy Android 10+ với Scoped Storage. Di chuyển là bước bắt buộc để xuất bản cập nhật trên Google Play: ứng dụng mới phải nhắm mục tiêu API 31+, bản cập nhật — API 30+.

Câu hỏi thường gặp

Có thể tắt Scoped Storage trong Android 11 không?

Không, bắt đầu từ Android 11 (API 30) Scoped Storage là bắt buộc đối với tất cả ứng dụng. Cờ requestLegacyExternalStorage, có sẵn trong Android 10, không hoạt động trên API 30+. Cách duy nhất để có quyền truy cập rộng rãi vào hệ thống tệp là quyền MANAGE_EXTERNAL_STORAGE, nhưng nó chỉ dành cho trình quản lý tệp và phần mềm chống vi-rút.

MANAGE_EXTERNAL_STORAGE là gì và làm thế nào để có được nó?

MANAGE_EXTERNAL_STORAGE là quyền đặc biệt dành cho ứng dụng cần quyền truy cập đầy đủ vào hệ thống tệp (trình quản lý tệp, công cụ sao lưu). Yêu cầu quyền sẽ mở màn hình hệ thống với cảnh báo cho người dùng. Khi xuất bản trên Google Play, cần điền biểu mẫu Tuyên bố về sự cần thiết của quyền này.

Làm thế nào để lấy đường dẫn tệp từ content URI trong Scoped Storage?

Trong Scoped Storage trên Android 11+, không thể lấy đường dẫn tệp trực tiếp từ content URI. Thay vào đó, hãy sử dụng ContentResolver.openInputStream() để đọc và openOutputStream() để ghi. Nếu bạn cần đường dẫn tệp để tương thích với thư viện bên thứ ba, hãy tạo bản sao của tệp trong getCacheDir() và làm việc với bản sao.

Scoped Storage ảnh hưởng đến FileProvider như thế nào?

FileProvider tiếp tục hoạt động mà không thay đổi — nó được sử dụng để cung cấp quyền truy cập vào tệp từ hộp cát của ứng dụng cho các ứng dụng khác thông qua content URI. Scoped Storage không ảnh hưởng đến FileProvider, vì FileProvider hoạt động ở cấp độ content URI, không phải đường dẫn tệp trực tiếp.

Làm thế nào để kiểm tra thiết bị có hỗ trợ Scoped Storage không?

Kiểm tra Build.VERSION.SDK_INT: nếu >= Build.VERSION_CODES.Q (29), thì thiết bị hỗ trợ Scoped Storage. Tuy nhiên, trên Android 10, Scoped Storage có thể bị tắt qua requestLegacyExternalStorage. Trên Android 11+, kiểm tra xem cờ kê khai requestLegacyExternalStorage có được đặt không — nếu không, Scoped Storage đang hoạt động.

Tổng kết

  • Scoped Storage là mô hình truy cập tệp trong Android 10+ hạn chế việc đọc tùy tiện bộ nhớ dùng chung.
  • Ứng dụng có quyền truy cập đầy đủ vào hộp cát riêng tư mà không cần thêm quyền.
  • Để truy cập tệp đa phương tiện dùng chung, sử dụng API MediaStore với content URI.
  • Để truy cập tệp và thư mục tùy ý, sử dụng Storage Access Framework (SAF).
  • Bắt đầu từ Android 11, Scoped Storage là bắt buộc đối với tất cả ứng dụng.
  • Các quyền READ_EXTERNAL_STORAGE và WRITE_EXTERNAL_STORAGE không hoạt động trong hầu hết tình huống trên Android 11+.
  • Khi di chuyển, hãy thay thế đường dẫn tệp trực tiếp bằng ContentResolver.openInputStream / openOutputStream.

Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay

IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.

Thảo luận dự án

Đọc thêm