Scoped Storage — co to jest, zarządzanie plikami i dostęp do pamięci

Autor: IT Sectr Opublikowano: 2026-07-10 Czas czytania: 10 min

Scoped Storage — to model dostępu do systemu plików wprowadzony w Android 10 (API 29), który ogranicza dowolny dostęp aplikacji do współdzielonej pamięci urządzenia. Według Google Android Developer Documentation (2024), Scoped Storage zastępuje stary model uprawnień READ_EXTERNAL_STORAGE i WRITE_EXTERNAL_STORAGE, zapewniając aplikacjom dostęp tylko do ich własnych plików w izolowanej piaskownicy oraz do wspólnych kolekcji przez MediaStore. Model ten zwiększa prywatność danych użytkownika i zapobiega nieautoryzowanemu zbieraniu informacji przez aplikacje.

Najważniejsze

  • Scoped Storage — model dostępu do plików w Android 10+, ograniczający dowolne odczytywanie współdzielonej pamięci.
  • Aplikacja ma własną piaskownicę z pełnym dostępem bez dodatkowych uprawnień.
  • Do dostępu do wspólnych plików (zdjęcia, wideo, audio) używa się MediaStore API.
  • Do dostępu do dowolnych katalogów i plików używa się Storage Access Framework (SAF).
  • Uprawnienia READ_EXTERNAL_STORAGE i WRITE_EXTERNAL_STORAGE straciły moc w większości scenariuszy.

Czym jest Scoped Storage w Androidzie?

Scoped Storage — to zmiana architektoniczna w Android 10 (API 29), która fundamentalnie zmienia sposób dostępu aplikacji do systemu plików urządzenia. Zamiast pełnego dostępu do współdzielonej pamięci, aplikacja otrzymuje dostęp tylko do swojej piaskownicy i do określonych kolekcji plików multimedialnych przez MediaStore.

Przed Scoped Storage każda aplikacja z uprawnieniem READ_EXTERNAL_STORAGE mogła czytać wszystkie pliki we współdzielonej pamięci — zdjęcia, dokumenty, pliki do pobrania, dane innych aplikacji. Stwarzało to ryzyko dla prywatności: nieuczciwe aplikacje mogły zbierać informacje o użytkowniku bez jego wiedzy.

Według Google I/O 2019, ponad 65% użytkowników Androida uważa prywatność danych za krytyczny czynnik przy wyborze aplikacji. Scoped Storage jest bezpośrednią odpowiedzią na to zapotrzebowanie: każda aplikacja działa w izolowanym środowisku i uzyskuje dostęp do danych innych aplikacji tylko za wyraźną zgodą użytkownika.

Ważne: Scoped Storage w Android 10 był opcjonalny (aplikacja mogła go wyłączyć przez requestLegacyExternalStorage). Począwszy od Android 11 (API 30), Scoped Storage stał się obowiązkowy dla wszystkich aplikacji, niezależnie od target SDK.

Dlaczego powstał Scoped Storage

Google wprowadził Scoped Storage, aby rozwiązać trzy fundamentalne problemy starego modelu dostępu do plików: prywatność, zarządzanie uprawnieniami i czyszczenie pozostałych plików.

Problem prywatności

W starym modelu aplikacja z READ_EXTERNAL_STORAGE mogła skanować całą współdzieloną pamięć i zbierać metadane o plikach użytkownika — współrzędne geograficzne zdjęć, nazwy dokumentów, strukturę katalogów. Scoped Storage eliminuje tę możliwość: nawet mając uprawnienie, aplikacja widzi tylko pliki przez siebie utworzone oraz jawnie wybrane przez użytkownika przez SAF.

Problem czyszczenia po usunięciu aplikacji

Przed Scoped Storage aplikacja mogła pozostawiać pliki we współdzielonej pamięci po swoim usunięciu. Z czasem gromadziły się śmieciowe katalogi. Scoped Storage rozwiązuje to: wszystkie pliki w piaskownicy aplikacji są usuwane wraz z aplikacją, a pliki przez MediaStore mają właściciela i mogą być wyczyszczone przez system.

AspektStary model (Legacy)Scoped Storage
Dostęp do współdzielonej pamięciPełny (z uprawnieniem)Tylko własna piaskownica
Pliki multimedialneBezpośrednia ścieżka plikuPrzez MediaStore URI
Cudze plikiZawsze dostępneTylko przez SAF
Czyszczenie przy usuwaniuPliki pozostająPiaskownica jest usuwana
Kontrola użytkownikaMinimalnaWyraźna zgoda na każdy plik

Scoped Storage jest częścią ogólnej strategii Google na rzecz wzmocnienia prywatności w Androidzie, która obejmuje również uprawnienia „tylko podczas używania”, wskaźniki dostępu do kamery i mikrofonu oraz Privacy Dashboard.

Prywatny katalog aplikacji

Każda aplikacja w Androidzie otrzymuje własny prywatny katalog, dostępny przez Context.getFilesDir() i context.getCacheDir(). Do dostępu do zewnętrznego (współdzielonego) prywatnego katalogu używany jest Context.getExternalFilesDir().

W Scoped Storage aplikacja ma pełny dostęp do swojego zewnętrznego prywatnego katalogu bez żadnych uprawnień. Jest to główne miejsce do przechowywania plików, które nie powinny być dostępne dla innych aplikacji lub które aplikacja tworzy na własny użytek.

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

Ścieżka do zewnętrznego prywatnego katalogu: /storage/emulated/0/Android/data/{packageName}/files/. Począwszy od Android 11 bezpośrednia ścieżka pliku jest niedostępna — tylko przez API. To kolejne wzmocnienie bezpieczeństwa Scoped Storage.

MediaStore — dostęp do plików multimedialnych

MediaStore — to podstawowe API dostępu do współdzielonych plików multimedialnych (obrazy, wideo, audio) w Scoped Storage. Aplikacja pracuje nie ze ścieżkami plików, ale z content URIs dostarczanymi przez MediaStore provider.

MediaStore jest podzielony na trzy główne kolekcje: Images, Video i Audio. Każda kolekcja obsługuje operacje CRUD przez ContentResolver. Do wstawienia nowego pliku do kolekcji Images używany jest MediaStore.Images.Media, do zapytania istniejących — query z odpowiednim 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
        )
    }
}

Ważne: MediaStore nie obsługuje dowolnych ścieżek plików — tylko content URIs. Próba uzyskania ścieżki pliku z URI przez MediaStore.Files.getContentUri() nie da bezpośredniej ścieżki do pliku na Android 11+. Zamiast tego używaj ContentResolver.openInputStream() i openOutputStream() do pracy z zawartością.

Storage Access Framework (SAF)

Storage Access Framework (SAF) — to API dostępu do dowolnych plików i katalogów poza piaskownicą aplikacji. SAF udostępnia użytkownikowi interfejs wyboru pliku lub katalogu, po czym aplikacja otrzymuje content URI z tymczasowym dostępem.

SAF jest używany w scenariuszach nieobjętych przez MediaStore: praca z dowolnymi dokumentami (PDF, ZIP, APK), dostęp do katalogów na karcie SD, import i eksport plików z innych aplikacji. Użytkownik jawnie wybiera plik przez systemowy selektor plików — gwarantuje to jego zgodę na dostę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

        // Uzyskaj trwałe uprawnienie URI
        val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
                       Intent.FLAG_GRANT_WRITE_URI_PERMISSION
        contentResolver.takePersistableUriPermission(treeUri, flags)
    }
}

SAF daje aplikacji dostęp do wybranego przez użytkownika katalogu aż do ponownego uruchomienia urządzenia (przy użyciu takePersistableUriPermission). To jedyny sposób uzyskania dostępu do dowolnych plików we współdzielonej pamięci na Android 11+.

Migracja aplikacji na Scoped Storage

Migracja istniejącej aplikacji Androida na Scoped Storage wymaga zmian w kilku kluczowych obszarach. Google zaleca podejście etapowe z testowaniem na Android 11+.

Zastąpienie ścieżek plików content URIs

Zamień wszystkie bezpośrednie operacje na File we współdzielonej pamięci na ContentResolver.openInputStream / openOutputStream. Dla plików w piaskownicy aplikacji (getExternalFilesDir) ścieżki plików nadal działają.

Aktualizacja AndroidManifest

Usuń READ_EXTERNAL_STORAGE i WRITE_EXTERNAL_STORAGE, jeśli nie są potrzebne do konkretnych scenariuszy (np. zarządzanie plikami — do tego jest specjalne uprawnienie MANAGE_EXTERNAL_STORAGE).

ScenariuszStare podejściePodejście Scoped Storage
Zapisywanie zdjęciaFile(path).writeBytes()MediaStore + ContentResolver
Czytanie PDFFile(path).inputStream()SAF ACTION_OPEN_DOCUMENT
Własne plikiEnvironment.getExternalStorageDirectory()context.getExternalFilesDir()
Pamięć podręcznaFile(cacheDir).writeBytes()context.cacheDir (bez zmian)

Po migracji przetestuj aplikację na Android 11+ (API 30), ustawiając w build.gradle targetSdk = 30 lub wyższe. Upewnij się, że wszystkie operacje na plikach we współdzielonej pamięci działają przez MediaStore lub SAF, a nie przez bezpośrednie ścieżki plików.

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

Według Google Play Console (2024), ponad 78% aktywnych urządzeń z Androidem działa na Android 10+ z Scoped Storage. Migracja jest obowiązkowym krokiem do publikacji aktualizacji w Google Play: nowe aplikacje muszą targetować API 31+, aktualizacje — API 30+.

Często zadawane pytania

Czy można wyłączyć Scoped Storage w Android 11?

Nie, począwszy od Android 11 (API 30) Scoped Storage jest obowiązkowy dla wszystkich aplikacji. Flaga requestLegacyExternalStorage, dostępna w Android 10, nie działa na API 30+. Jedynym sposobem uzyskania szerokiego dostępu do systemu plików jest uprawnienie MANAGE_EXTERNAL_STORAGE, ale jest ono przeznaczone tylko dla menedżerów plików i antywirusów.

Czym jest MANAGE_EXTERNAL_STORAGE i jak je uzyskać?

MANAGE_EXTERNAL_STORAGE — specjalne uprawnienie dla aplikacji, które potrzebują pełnego dostępu do systemu plików (menedżery plików, narzędzia do backupu). Żądanie uprawnienia otwiera ekran systemowy z ostrzeżeniem dla użytkownika. Przy publikacji w Google Play wymagane jest wypełnienie formularza Declaration o konieczności tego uprawnienia.

Jak uzyskać ścieżkę pliku z content URI w Scoped Storage?

W Scoped Storage na Android 11+ nie można uzyskać bezpośredniej ścieżki pliku z content URI. Zamiast tego używaj ContentResolver.openInputStream() do odczytu i openOutputStream() do zapisu. Jeśli potrzebujesz ścieżki pliku dla zgodności z biblioteką zewnętrzną — utwórz kopię pliku w getCacheDir() i pracuj na kopii.

Jak Scoped Storage wpływa na FileProvider?

FileProvider działa bez zmian — jest używany do udostępniania plików z piaskownicy aplikacji innym aplikacjom przez content URIs. Scoped Storage nie wpływa na FileProvider, ponieważ FileProvider działa na poziomie content URIs, a nie bezpośrednich ścieżek plików.

Jak sprawdzić, czy urządzenie obsługuje Scoped Storage?

Sprawdź Build.VERSION.SDK_INT: jeśli >= Build.VERSION_CODES.Q (29), urządzenie obsługuje Scoped Storage. Jednak na Android 10 Scoped Storage może być wyłączony przez requestLegacyExternalStorage. Na Android 11+ sprawdź, czy flaga w manifeście requestLegacyExternalStorage jest ustawiona — jeśli nie, Scoped Storage jest aktywny.

Podsumowanie

  • Scoped Storage — model dostępu do plików w Android 10+, ograniczający dowolne odczytywanie współdzielonej pamięci.
  • Aplikacja ma pełny dostęp do swojej prywatnej piaskownicy bez dodatkowych uprawnień.
  • Do dostępu do współdzielonych plików multimedialnych używany jest MediaStore API z content URIs.
  • Do dostępu do dowolnych plików i katalogów używany jest Storage Access Framework (SAF).
  • Począwszy od Android 11 Scoped Storage jest obowiązkowy dla wszystkich aplikacji.
  • Uprawnienia READ_EXTERNAL_STORAGE i WRITE_EXTERNAL_STORAGE nie działają w większości scenariuszy na Android 11+.
  • Podczas migracji zastąp bezpośrednie ścieżki plików na ContentResolver.openInputStream / openOutputStream.

Opracujemy aplikację mobilną pod klucz

IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.

Omów projekt

Przeczytaj również