Scoped Storage — ay isang modelo ng access sa file system na ipinakilala sa Android 10 (API 29) na naglilimita sa arbitraryong access ng mga app sa shared storage ng device. Ayon sa Google Android Developer Documentation (2024), pinapalitan ng Scoped Storage ang lumang modelo ng mga pahintulot na READ_EXTERNAL_STORAGE at WRITE_EXTERNAL_STORAGE, na nagbibigay sa mga app ng access lamang sa kanilang sariling mga file sa isang isolated na sandbox at sa mga shared collection sa pamamagitan ng MediaStore. Ang modelong ito ay nagpapataas ng privacy ng data ng user at pumipigil sa hindi awtorisadong pangongolekta ng impormasyon ng mga app.
Mga Pangunahing Punto
Scoped Storage — ay isang pagbabago sa arkitektura sa Android 10 (API 29) na pangunahing nagbabago sa paraan ng pag-access ng mga app sa file system ng device. Sa halip na buong access sa shared storage, ang app ay nakakakuha ng access lamang sa sarili nitong sandbox at sa ilang mga koleksyon ng media file sa pamamagitan ng MediaStore.
Bago ang Scoped Storage, sinumang app na may pahintulot na READ_EXTERNAL_STORAGE ay maaaring magbasa ng lahat ng file sa shared storage — mga larawan, dokumento, download, data ng iba pang app. Ito ay lumikha ng mga panganib sa privacy: ang mga hindi tapat na app ay maaaring mangolekta ng impormasyon tungkol sa user nang hindi nito nalalaman.
Ayon sa Google I/O 2019, higit sa 65% ng mga gumagamit ng Android ay itinuturing ang privacy ng data bilang kritikal na kadahilanan sa pagpili ng app. Ang Scoped Storage ay direktang tugon sa kahilingang ito: bawat app ay gumagana sa isang isolated na kapaligiran at nakakakuha ng access sa data ng iba lamang sa malinaw na pahintulot ng user.
Mahalaga: Ang Scoped Storage sa Android 10 ay opsyonal (maaaring i-disable ito ng app sa pamamagitan ng requestLegacyExternalStorage). Simula sa Android 11 (API 30), ang Scoped Storage ay naging mandatory para sa lahat ng app, anuman ang target SDK.
Google ay nagpakilala ng Scoped Storage upang malutas ang tatlong pangunahing problema ng lumang modelo ng access sa file: privacy, pamamahala ng pahintulot, at paglilinis ng natitirang file.
Sa lumang modelo, ang isang app na may READ_EXTERNAL_STORAGE ay maaaring mag-scan ng buong shared storage at mangolekta ng metadata tungkol sa mga file ng user — mga geographic na coordinate ng mga larawan, pangalan ng dokumento, istraktura ng direktoryo. Inaalis ng Scoped Storage ang posibilidad na ito: kahit na may pahintulot, ang app ay nakikita lamang ang mga file na nilikha nito mismo at ang mga file na malinaw na pinili ng user sa pamamagitan ng SAF.
Bago ang Scoped Storage, ang app ay maaaring mag-iwan ng mga file sa shared storage pagkatapos alisin. Sa paglipas ng panahon, ang mga basurang direktoryo ay naipon. Nilulutas ito ng Scoped Storage: lahat ng file sa sandbox ng app ay tinatanggal kasama ng app, at ang mga file sa pamamagitan ng MediaStore ay may may-ari at maaaring linisin ng system.
| Aspekto | Lumang modelo (Legacy) | Scoped Storage |
|---|---|---|
| Access sa shared storage | Buong (may pahintulot) | Sariling sandbox lamang |
| Media file | Direktang file path | Sa pamamagitan ng MediaStore URI |
| File ng iba | Laging available | Sa pamamagitan lamang ng SAF |
| Paglilinis sa pag-alis | Nananatili ang file | Tinatanggal ang sandbox |
| Kontrol ng user | Minimal | Malinaw na pahintulot bawat file |
Ang Scoped Storage ay bahagi ng pangkalahatang estratehiya ng Google para palakasin ang privacy sa Android, na kasama rin ang mga pahintulot na “sa oras lamang ng paggamit”, mga indicator ng access sa camera at mikropono, at Privacy Dashboard.
Bawat app sa Android ay nakakakuha ng sarili nitong pribadong direktoryo, naa-access sa pamamagitan ng Context.getFilesDir() at context.getCacheDir(). Para sa access sa panlabas (shared) na pribadong direktoryo, ginagamit ang Context.getExternalFilesDir().
Sa Scoped Storage, ang app ay may buong access sa sarili nitong panlabas na pribadong direktoryo nang walang anumang pahintulot. Ito ang pangunahing lugar para sa pag-iimbak ng mga file na hindi dapat ma-access ng iba pang app o na nilikha ng app para sa sarili nitong paggamit.
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()
}
}
Path sa panlabas na pribadong direktoryo: /storage/emulated/0/Android/data/{packageName}/files/. Simula sa Android 11, ang direktang file path ay hindi available — sa pamamagitan lamang ng API. Ito ay isa pang pagpapalakas ng seguridad ng Scoped Storage.
MediaStore — ay ang pangunahing API para sa access sa shared media file (mga larawan, video, audio) sa Scoped Storage. Ang app ay hindi gumagana sa mga file path, kundi sa mga content URIs na ibinigay ng MediaStore provider.
Ang MediaStore ay nahahati sa tatlong pangunahing koleksyon: Images, Video at Audio. Bawat koleksyon ay sumusuporta sa CRUD operations sa pamamagitan ng ContentResolver. Para sa pagpasok ng bagong file sa Images collection, ginagamit ang MediaStore.Images.Media, para sa pag-query ng mga umiiral na — query na may kaukulang URI.
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
)
}
}
Mahalaga: Hindi sinusuportahan ng MediaStore ang arbitraryong file path — content URIs lamang. Ang pagtatangkang kumuha ng file path mula sa URI sa pamamagitan ng MediaStore.Files.getContentUri() ay hindi magbibigay ng direktang path sa file sa Android 11+. Sa halip, gamitin ang ContentResolver.openInputStream() at openOutputStream() para sa pagtatrabaho sa nilalaman.
Storage Access Framework (SAF) — ay ang API para sa access sa arbitraryong file at direktoryo sa labas ng sandbox ng app. Ang SAF ay nagbibigay sa user ng interface para pumili ng file o direktoryo, pagkatapos nito ang app ay makakatanggap ng content URI na may pansamantalang access.
Ang SAF ay ginagamit para sa mga senaryo na hindi saklaw ng MediaStore: pagtatrabaho sa arbitraryong dokumento (PDF, ZIP, APK), access sa mga direktoryo sa SD card, pag-import at pag-export ng file mula sa iba pang app. Ang user ay malinaw na pumipili ng file sa pamamagitan ng system file picker — ginagarantiyahan nito ang kanyang pahintulot para sa access.
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
// Kumuha ng persistent URI permission
val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
contentResolver.takePersistableUriPermission(treeUri, flags)
}
}
Ang SAF ay nagbibigay sa app ng access sa direktoryo na pinili ng user hanggang sa i-restart ang device (kapag ginagamit ang takePersistableUriPermission). Ito ang tanging paraan upang makakuha ng access sa arbitraryong file sa shared storage sa Android 11+.
Ang pag-migrate ng umiiral na Android app sa Scoped Storage ay nangangailangan ng mga pagbabago sa ilang pangunahing lugar. Inirerekomenda ng Google ang isang phased approach na may pagsubok sa Android 11+.
Palitan ang lahat ng direktang File operations sa shared storage ng ContentResolver.openInputStream / openOutputStream. Para sa mga file sa sandbox ng app (getExternalFilesDir), patuloy na gumagana ang mga file path.
Alisin ang READ_EXTERNAL_STORAGE at WRITE_EXTERNAL_STORAGE kung hindi kailangan para sa mga tiyak na senaryo (halimbawa, pamamahala ng file — para doon ay may espesyal na pahintulot na MANAGE_EXTERNAL_STORAGE).
| Senaryo | Lumang approach | Scoped Storage approach |
|---|---|---|
| Pag-save ng larawan | File(path).writeBytes() | MediaStore + ContentResolver |
| Pagbasa ng PDF | File(path).inputStream() | SAF ACTION_OPEN_DOCUMENT |
| Sariling file | Environment.getExternalStorageDirectory() | context.getExternalFilesDir() |
| Cache | File(cacheDir).writeBytes() | context.cacheDir (walang pagbabago) |
Pagkatapos ng migrate, subukan ang app sa Android 11+ (API 30) na may targetSdk = 30 o mas mataas sa build.gradle. Tiyakin na ang lahat ng file operations sa shared storage ay gumagana sa pamamagitan ng MediaStore o SAF, hindi sa pamamagitan ng direktang file path.
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()
}
}
Ayon sa Google Play Console (2024), higit sa 78% ng mga aktibong Android device ay tumatakbo sa Android 10+ na may Scoped Storage. Ang pag-migrate ay isang mandatoryong hakbang para sa pag-publish ng mga update sa Google Play: ang mga bagong app ay dapat mag-target ng API 31+, mga update — API 30+.
Mga Madalas Itanong
Hindi, simula sa Android 11 (API 30) ang Scoped Storage ay mandatory para sa lahat ng app. Ang flag na requestLegacyExternalStorage, na available sa Android 10, ay hindi gumagana sa API 30+. Ang tanging paraan upang makakuha ng malawakang access sa file system ay ang pahintulot na MANAGE_EXTERNAL_STORAGE, ngunit ito ay para lamang sa mga file manager at antivirus.
MANAGE_EXTERNAL_STORAGE — isang espesyal na pahintulot para sa mga app na nangangailangan ng buong access sa file system (mga file manager, backup tool). Ang kahilingan ng pahintulot ay nagbubukas ng system screen na may babala para sa user. Kapag nag-publish sa Google Play, kinakailangang punan ang Declaration form tungkol sa pangangailangan ng pahintulot na ito.
Sa Scoped Storage sa Android 11+, ang direktang file path mula sa content URI ay hindi maaaring makuha. Sa halip, gamitin ang ContentResolver.openInputStream() para sa pagbasa at openOutputStream() para sa pagsulat. Kung kailangan mo ng file path para sa compatibility sa third-party na library — gumawa ng kopya ng file sa getCacheDir() at magtrabaho sa kopya.
FileProvider ay patuloy na gumagana nang walang pagbabago — ginagamit ito upang magbigay ng access sa mga file mula sa sandbox ng app sa iba pang app sa pamamagitan ng content URIs. Hindi naaapektuhan ng Scoped Storage ang FileProvider, dahil ang FileProvider ay gumagana sa antas ng content URIs, hindi sa direktang file path.
Suriin ang Build.VERSION.SDK_INT: kung >= Build.VERSION_CODES.Q (29), sinusuportahan ng device ang Scoped Storage. Gayunpaman, sa Android 10, ang Scoped Storage ay maaaring i-disable sa pamamagitan ng requestLegacyExternalStorage. Sa Android 11+, suriin kung ang flag na requestLegacyExternalStorage ay nakatakda sa manifest — kung hindi, ang Scoped Storage ay aktibo.
Buod
Gagawa kami ng mobile application na turnkey
Gumagawa ang IT Sectr ng mga iOS at Android application para sa mga startup at negosyo mula noong 2017. Magpapayo kami sa iyo at magmumungkahi ng pinakamahusay na solusyon.
Basahin din