Scoped Storage — คืออะไร การจัดการไฟล์และการเข้าถึงพื้นที่จัดเก็บ

ผู้แต่ง: IT Sectr เผยแพร่เมื่อ: 2026-07-10 เวลาอ่าน: 10 นาที

Scoped Storage คือรูปแบบการเข้าถึงระบบไฟล์ที่นำมาใช้ใน Android 10 (API 29) ซึ่งจำกัดการเข้าถึงพื้นที่จัดเก็บร่วมของอุปกรณ์โดยแอปพลิเคชันต่างๆ ตามเอกสารของนักพัฒนา Android ของ Google (2024) Scoped Storage แทนที่รูปแบบสิทธิ์แบบเก่าอย่าง READ_EXTERNAL_STORAGE และ WRITE_EXTERNAL_STORAGE โดยให้แอปพลิเคชันเข้าถึงได้เฉพาะไฟล์ของตนเองในแซนด์บ็อกซ์ที่แยกออกจากกัน และคอลเล็กชันที่ใช้ร่วมกันผ่าน MediaStore รูปแบบนี้ช่วยเพิ่มความเป็นส่วนตัวของข้อมูลผู้ใช้และป้องกันการรวบรวมข้อมูลโดยไม่ได้รับอนุญาตจากแอปพลิเคชัน

ประเด็นสำคัญ

  • Scoped Storage คือรูปแบบการเข้าถึงไฟล์ใน Android 10+ ที่จำกัดการอ่านพื้นที่จัดเก็บร่วมโดยไม่ได้รับอนุญาต
  • แอปมี แซนด์บ็อกซ์ของตนเอง ที่เข้าถึงได้อย่างเต็มที่โดยไม่ต้องมีสิทธิ์เพิ่มเติม
  • การเข้าถึงไฟล์ที่ใช้ร่วมกัน (รูปภาพ วิดีโอ เสียง) ใช้ MediaStore API
  • การเข้าถึงไดเรกทอรีและไฟล์ต่างๆ ใช้ Storage Access Framework (SAF)
  • สิทธิ์ READ_EXTERNAL_STORAGE และ WRITE_EXTERNAL_STORAGE สูญเสียผลบังคับสำหรับสถานการณ์ส่วนใหญ่

Scoped Storage ใน Android คืออะไร

Scoped Storage คือการเปลี่ยนแปลงทางสถาปัตยกรรมใน Android 10 (API 29) ที่เปลี่ยนแปลงวิธีการที่แอปพลิเคชันเข้าถึงระบบไฟล์ของอุปกรณ์อย่างสิ้นเชิง แทนที่จะเข้าถึงพื้นที่จัดเก็บร่วมได้อย่างเต็มที่ แอปจะเข้าถึงได้เฉพาะแซนด์บ็อกซ์ของตนเองและคอลเล็กชันมีเดียเฉพาะผ่าน MediaStore

ก่อน Scoped Storage แอปใดๆ ที่มีสิทธิ์ READ_EXTERNAL_STORAGE สามารถอ่านไฟล์ทั้งหมดในพื้นที่จัดเก็บร่วมได้ — รูปภาพ เอกสาร ดาวน์โหลด ข้อมูลของแอปอื่นๆ สิ่งนี้สร้างความเสี่ยงด้านความเป็นส่วนตัว: แอปที่ไม่น่าเชื่อถือสามารถรวบรวมข้อมูลผู้ใช้โดยที่ผู้ใช้ไม่รู้ตัว

ตาม Google I/O 2019 ผู้ใช้ Android กว่า 65% ถือว่าความเป็นส่วนตัวของข้อมูลเป็นปัจจัยสำคัญในการเลือกแอป Scoped Storage คือการตอบสนองโดยตรงต่อความต้องการนี้: แต่ละแอปทำงานในสภาพแวดล้อมที่แยกออกจากกัน และเข้าถึงข้อมูลของแอปอื่นได้ก็ต่อเมื่อได้รับความยินยอมอย่างชัดแจ้งจากผู้ใช้เท่านั้น

สำคัญ: Scoped Storage ใน Android 10 เป็นทางเลือก (แอปสามารถปิดใช้งานผ่าน requestLegacyExternalStorage) ตั้งแต่ Android 11 (API 30) เป็นต้นไป Scoped Storage กลายเป็นข้อบังคับสำหรับทุกแอป โดยไม่คำนึงถึง target SDK

ทำไมจึงต้องมี Scoped Storage

Google นำ Scoped Storage มาใช้เพื่อแก้ปัญหาพื้นฐานสามประการของรูปแบบการเข้าถึงไฟล์แบบเก่า: ความเป็นส่วนตัว การจัดการสิทธิ์ และการทำความสะอาดไฟล์ที่เหลืออยู่

ปัญหาความเป็นส่วนตัว

ในรูปแบบเก่า แอปที่มี READ_EXTERNAL_STORAGE สามารถสแกนพื้นที่จัดเก็บร่วมทั้งหมดและรวบรวมข้อมูลเมตาของไฟล์ผู้ใช้ — พิกัดทางภูมิศาสตร์ของรูปภาพ ชื่อเอกสาร โครงสร้างไดเรกทอรี Scoped Storage ขจัดความเป็นไปได้นี้: แม้จะมีสิทธิ์ แอปจะเห็นเฉพาะไฟล์ที่สร้างขึ้นเองและไฟล์ที่ผู้ใช้เลือกอย่างชัดแจ้งผ่าน SAF

ปัญหาการทำความสะอาดเมื่อถอนการติดตั้งแอป

ก่อน Scoped Storage แอปสามารถทิ้งไฟล์ไว้ในพื้นที่จัดเก็บร่วมหลังจากการถอนการติดตั้ง เมื่อเวลาผ่านไป ไดเรกทอรี ขยะ จะสะสมมากขึ้น Scoped Storage แก้ปัญหานี้: ไฟล์ทั้งหมดในแซนด์บ็อกซ์ของแอปจะถูกลบพร้อมกับแอป และไฟล์ผ่าน MediaStore มีเจ้าของและสามารถทำความสะอาดโดยระบบได้

ด้านรูปแบบเก่า (Legacy)Scoped Storage
การเข้าถึง พื้นที่จัดเก็บร่วมเต็มที่ (มีสิทธิ์)เฉพาะแซนด์บ็อกซ์ของตนเอง
ไฟล์มีเดียพาธไฟล์โดยตรงผ่าน MediaStore URI
ไฟล์ของ แอปอื่นเข้าถึงได้ตลอดเวลาผ่าน SAF เท่านั้น
การทำความสะอาด เมื่อถอนการติดตั้งไฟล์ยังคงอยู่แซนด์บ็อกซ์ถูกลบ
การควบคุม ของผู้ใช้น้อยที่สุดความยินยอมอย่างชัดแจ้งต่อไฟล์

Scoped Storage เป็นส่วนหนึ่งของกลยุทธ์ ความเป็นส่วนตัว โดยรวมของ Google ใน Android ซึ่งรวมถึงสิทธิ์ "เฉพาะขณะใช้งาน" ตัวบ่งชี้การเข้าถึงกล้องและไมโครโฟน และแผงควบคุมความเป็นส่วนตัว

ไดเรกทอรีส่วนตัวของแอป

แต่ละ แอปใน Android จะได้รับไดเรกทอรีส่วนตัวของตนเอง ซึ่งเข้าถึงได้ผ่าน Context.getFilesDir() และ context.getCacheDir() สำหรับการเข้าถึงไดเรกทอรีส่วนตัวภายนอก (ที่ใช้ร่วมกัน) จะใช้ Context.getExternalFilesDir()

ใน Scoped Storage แอปมี การเข้าถึงอย่างเต็มที่ ไปยังไดเรกทอรีส่วนตัวภายนอกโดยไม่ต้องมีสิทธิ์ใดๆ นี่คือสถานที่หลักสำหรับจัดเก็บไฟล์ที่ไม่ควรเข้าถึงได้โดยแอปอื่น หรือที่แอปสร้างขึ้นเพื่อการใช้งานของตนเอง

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()
    }
}

พาธไปยังไดเรกทอรีส่วนตัวภายนอก: /storage/emulated/0/Android/data/{packageName}/files/ ตั้งแต่ Android 11 เป็นต้นไป พาธไฟล์โดยตรงไม่สามารถใช้ได้ — ผ่าน API เท่านั้น นี่คือการเสริมความปลอดภัยอีกประการของ Scoped Storage

MediaStore — การเข้าถึงไฟล์มีเดีย

MediaStore คือ API หลักสำหรับการเข้าถึงไฟล์มีเดียที่ใช้ร่วมกัน (รูปภาพ วิดีโอ เสียง) ใน Scoped Storage แอปทำงานไม่ใช่กับพาธไฟล์ แต่กับ content URIs ที่จัดหาโดยผู้ให้บริการ MediaStore

MediaStore แบ่งออกเป็นสามคอลเล็กชันหลัก: Images, Video และ Audio แต่ละคอลเล็กชันรองรับการดำเนินการ CRUD ผ่าน ContentResolver ในการแทรกไฟล์ใหม่ลงในคอลเล็กชัน Images จะใช้ MediaStore.Images.Media ในการค้นหาไฟล์ที่มีอยู่ — query ด้วย URI ที่เกี่ยวข้อง

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
        )
    }
}

สำคัญ: MediaStore ไม่รองรับพาธไฟล์ต่างๆ — เฉพาะ content URIs การพยายามรับพาธไฟล์จาก URI ผ่าน MediaStore.Files.getContentUri() จะไม่ให้พาธโดยตรงไปยังไฟล์บน Android 11+ ให้ใช้ ContentResolver.openInputStream() และ openOutputStream() ในการทำงานกับเนื้อหาแทน

Storage Access Framework (SAF)

Storage Access Framework (SAF) คือ API สำหรับการเข้าถึงไฟล์และไดเรกทอรีต่างๆ นอกแซนด์บ็อกซ์ของแอป SAF จัดเตรียมอินเทอร์เฟซการเลือกไฟล์หรือไดเรกทอรีให้ผู้ใช้ หลังจากนั้นแอปจะได้รับ content URI ที่มีการเข้าชั่วคราว

SAF ใช้สำหรับสถานการณ์ที่ MediaStore ครอบคลุมไม่ถึง: การทำงานกับเอกสาร ต่างๆ (PDF, ZIP, APK) การเข้าถึงไดเรกทอรีบนการ์ด SD การนำเข้าและส่งออกไฟล์จากแอปอื่น ผู้ใช้เลือกไฟล์อย่างชัดแจ้งผ่านตัวเลือกไฟล์ของระบบ — ซึ่งรับประกันความยินยอมในการเข้าถึง

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 ให้แอปเข้าถึงไดเรกทอรีที่ผู้ใช้เลือกจนกว่าจะรีบูตอุปกรณ์ (เมื่อใช้ takePersistableUriPermission) นี่เป็นวิธีเดียวที่จะเข้าถึงไฟล์ต่างๆ ในพื้นที่จัดเก็บร่วมบน Android 11+

การโยกย้ายแอปไปยัง Scoped Storage

การโยกย้ายแอป Android ที่มีอยู่ไปยัง Scoped Storage ต้องมีการเปลี่ยนแปลงในหลายพื้นที่สำคัญ Google แนะนำวิธีการแบบเป็นขั้นตอนพร้อมการทดสอบบน Android 11+

แทนที่พาธไฟล์ด้วย content URIs

แทนที่การดำเนินการ File โดยตรงทั้งหมดในพื้นที่จัดเก็บร่วมด้วย ContentResolver.openInputStream / openOutputStream สำหรับไฟล์ในแซนด์บ็อกซ์ของแอป (getExternalFilesDir) พาธไฟล์ยังคงทำงานได้

อัปเดต AndroidManifest

ลบ READ_EXTERNAL_STORAGE และ WRITE_EXTERNAL_STORAGE หากไม่จำเป็นสำหรับสถานการณ์เฉพาะ (เช่น การจัดการไฟล์ — สำหรับนี้มีสิทธิ์พิเศษ MANAGE_EXTERNAL_STORAGE)

สถานการณ์วิธีการเก่าวิธีการของ Scoped Storage
บันทึก รูปภาพFile(path).writeBytes()MediaStore + ContentResolver
อ่าน PDFFile(path).inputStream()SAF ACTION_OPEN_DOCUMENT
ไฟล์ ของตนเองEnvironment.getExternalStorageDirectory()context.getExternalFilesDir()
แคชFile(cacheDir).writeBytes()context.cacheDir (ไม่เปลี่ยนแปลง)

หลังการโยกย้าย ให้ทดสอบแอปบน Android 11+ (API 30) โดยตั้งค่า targetSdk = 30 หรือสูงกว่าใน build.gradle ตรวจสอบให้แน่ใจว่าการดำเนินการไฟล์ทั้งหมดในพื้นที่จัดเก็บร่วมทำงานผ่าน MediaStore หรือ SAF ไม่ใช่ผ่านพาธไฟล์โดยตรง

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()
    }
}

ตาม Google Play Console (2024) อุปกรณ์ Android ที่ใช้งานอยู่กว่า 78% ทำงานบน Android 10+ ที่มี Scoped Storage การโยกย้ายเป็นขั้นตอนบังคับสำหรับการเผยแพร่อัปเดตบน Google Play: แอปใหม่ต้องกำหนดเป้าหมาย API 31+ อัปเดต — API 30+

คำถามที่พบบ่อย

สามารถปิด Scoped Storage ใน Android 11 ได้หรือไม่

ไม่ ตั้งแต่ Android 11 (API 30) เป็นต้นไป Scoped Storage เป็นข้อบังคับสำหรับทุกแอป แฟลก requestLegacyExternalStorage ที่มีใน Android 10 ไม่ทำงานบน API 30+ วิธีเดียวที่จะเข้าถึงระบบไฟล์อย่างกว้างขวางคือสิทธิ์ MANAGE_EXTERNAL_STORAGE แต่มีไว้สำหรับตัวจัดการไฟล์และแอนตี้ไวรัสเท่านั้น

MANAGE_EXTERNAL_STORAGE คืออะไรและจะได้รับอย่างไร

MANAGE_EXTERNAL_STORAGE คือสิทธิ์พิเศษสำหรับแอปที่ต้องการเข้าถึงระบบไฟล์อย่างเต็มที่ (ตัวจัดการไฟล์ เครื่องมือสำรองข้อมูล) การขอสิทธิ์จะเปิดหน้าจอระบบพร้อมคำเตือนสำหรับผู้ใช้ เมื่อเผยแพร่บน Google Play คุณต้องกรอกแบบฟอร์มคำประกาศเกี่ยวกับความจำเป็นของสิทธิ์นี้

วิธีรับพาธไฟล์จาก content URI ใน Scoped Storage

ใน Scoped Storage บน Android 11+ ไม่สามารถรับพาธไฟล์โดยตรงจาก content URI ได้ ให้ใช้ ContentResolver.openInputStream() สำหรับการอ่านและ openOutputStream() สำหรับการเขียนแทน หากคุณต้องการพาธไฟล์เพื่อความเข้ากันได้กับไลบรารีของบุคคลที่สาม ให้สร้างสำเนาของไฟล์ใน getCacheDir() และทำงานกับสำเนานั้น

Scoped Storage ส่งผลต่อ FileProvider อย่างไร

FileProvider ยังคงทำงานได้โดยไม่มีการเปลี่ยนแปลง — ใช้สำหรับให้แอปอื่นเข้าถึงไฟล์จากแซนด์บ็อกซ์ของแอปผ่าน content URIs Scoped Storage ไม่ส่งผลต่อ FileProvider เนื่องจาก FileProvider ทำงานที่ระดับ content URIs ไม่ใช่พาธไฟล์โดยตรง

วิธีตรวจสอบว่าอุปกรณ์รองรับ Scoped Storage หรือไม่

ตรวจสอบ Build.VERSION.SDK_INT: ถ้า >= Build.VERSION_CODES.Q (29) แสดงว่าอุปกรณ์รองรับ Scoped Storage อย่างไรก็ตาม บน Android 10 Scoped Storage อาจถูกปิดใช้งานผ่าน requestLegacyExternalStorage บน Android 11+ ให้ตรวจสอบว่าแฟลกในแมนิเฟสต์ requestLegacyExternalStorage ถูกตั้งค่าหรือไม่ — ถ้าไม่ Scoped Storage จะทำงานอยู่

สรุป

  • Scoped Storage คือรูปแบบการเข้าถึงไฟล์ใน Android 10+ ที่จำกัดการอ่านพื้นที่จัดเก็บร่วมโดยไม่ได้รับอนุญาต
  • แอปมี การเข้าถึงอย่างเต็มที่ ไปยังแซนด์บ็อกซ์ส่วนตัวโดยไม่ต้องมีสิทธิ์เพิ่มเติม
  • การเข้าถึงไฟล์มีเดียที่ใช้ร่วมกันใช้ MediaStore API กับ content URIs
  • การเข้าถึงไฟล์และไดเรกทอรีต่างๆ ใช้ Storage Access Framework (SAF)
  • ตั้งแต่ Android 11 เป็นต้นไป Scoped Storage เป็น ข้อบังคับ สำหรับทุกแอป
  • สิทธิ์ READ_EXTERNAL_STORAGE และ WRITE_EXTERNAL_STORAGE ไม่ทำงานในสถานการณ์ส่วนใหญ่บน Android 11+
  • เมื่อโยกย้าย ให้แทนที่ พาธไฟล์ โดยตรงด้วย ContentResolver.openInputStream / openOutputStream

เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร

IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ

ปรึกษาโครงการ

อ่านเพิ่มเติม