Scoped storage, introduced in Android 10 and enforced for apps targeting Android 11 (API 30) or higher, is a pivotal privacy and security enhancement that fundamentally changes how apps access files on external storage. Its core principle is to give apps access only to the files they explicitly create or that are designated for their use, thereby preventing widespread, unregulated access to a user's entire external storage. This move aims to reduce data exposure, improve app uninstall cleanliness, and give users greater control over their data, replacing the broad READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE permissions for most common use cases.
Practically, Scoped Storage funnels file access into three main categories. First, apps get a dedicated, private, app-specific directory on external storage (e.g., /Android/data/your.package.name/files). Files stored here are private to the app, automatically deleted upon uninstallation, and require no special storage permissions. This is the simplest and preferred method for app-owned data. Second, for shared media files like images, videos, and audio, apps must use the Media Store API. This API provides a content provider-based interface to access and contribute to the system's media collection, using focused permissions like READ_MEDIA_IMAGES or READ_MEDIA_VIDEO on newer Android versions, or READ_EXTERNAL_STORAGE for older media types.
Finally, for non-media files that an app doesn't own but needs to access or create in a user-chosen location (e.g., a PDF document or a custom data file), the Storage Access Framework (SAF) is the recommended approach. SAF allows the user to explicitly grant an app access to specific files or directories via a system picker, without granting broad, persistent storage permissions. A special case, MANAGE_EXTERNAL_STORAGE, provides broad access to all external storage but is highly restricted, requiring explicit user consent and is generally reserved for app categories like file managers or backup utilities that genuinely need such extensive access, undergoing stricter Google Play Store review.
Key Takeaways
- Scoped Storage restricts apps to their own files or specific APIs for shared data, improving privacy.
- Use app-specific directories for private app data; these require no special storage permissions.
- Access shared media (images, videos, audio) via the Media Store API, not broad
WRITE_EXTERNAL_STORAGE. - For user-selected non-media files, use the Storage Access Framework (SAF) with a system file picker.
MANAGE_EXTERNAL_STORAGEis a highly restricted permission for specific app types requiring broad file access.
Code Example
fun saveToAppSpecificStorage(context: Context, fileName: String, content: String) {
// getExternalFilesDir(null) points to Android/data/your.package.name/files
// This directory is private to your app and deleted on uninstall.
// No special permissions are required for this method with Scoped Storage.
val file = File(context.getExternalFilesDir(null), fileName)
try {
file.writeText(content)
Log.d("ScopedStorage", "Saved to: ${file.absolutePath}")
} catch (e: IOException) {
Log.e("ScopedStorage", "Error saving file: ${e.message}")
}
}How this code works
This saveToAppSpecificStorage function demonstrates how an Android application saves text data to its own private directory on external storage. It takes the app's Context, a fileName, and the content to be saved. A crucial aspect is that this method of storage, adhering to Scoped Storage principles, requires no special user permissions. Data stored this way is private to the app and automatically removed when the app is uninstalled, making it ideal for temporary or app-specific files that don't need to be shared or persist beyond the app's lifecycle.
To achieve this, the code first locates the correct storage path using context.getExternalFilesDir(null). The null argument is important; it tells the system to return the root directory for the app's private files on external storage, rather than a specific subdirectory. A File object is then constructed, combining this base path with the fileName. The file.writeText(content) call performs the actual saving. This entire operation is enclosed in a try-catch block, which robustly handles any potential IOException during file writing. If an error occurs, such as a full disk, the app logs the issue instead of crashing, ensuring a smoother user experience.