Keywords: Android Permissions | WRITE_EXTERNAL_STORAGE | Scoped Storage
Abstract: This article provides an in-depth analysis of the changes to the WRITE_EXTERNAL_STORAGE permission in Android 10 (API 29) and later versions, exploring how the introduction of Scoped Storage impacts file access permissions. It explains the causes of lint warnings and offers compatibility solutions for different Android versions, including the use of maxSdkVersion attribute, requestLegacyExternalStorage flag, and MANAGE_EXTERNAL_STORAGE permission. Through code examples and performance considerations, it helps developers understand how to balance functionality and compatibility in multi-version support, avoiding common permission configuration errors.
Background of Permission Model Evolution
With the release of Android 10 (API 29), Google introduced the Scoped Storage mechanism to enhance user data privacy and security. This change significantly impacted traditional file access permissions, particularly the android.permission.WRITE_EXTERNAL_STORAGE permission. In Android 9 (API 28) and earlier, this permission allowed apps to write anywhere on external storage, but starting from Android 10, its scope was substantially restricted.
The core idea of Scoped Storage is to limit apps' free access to shared storage areas, requiring them to use the MediaStore API or Storage Access Framework (SAF) to access specific types of media files. This means that even if an app declares the WRITE_EXTERNAL_STORAGE permission, on Android 10 and above, it can no longer write to external storage without restrictions as before. Instead, apps can only access their private directories (e.g., Pictures/MY_APP_NAME) or other locations through user authorization.
Detailed Analysis of Lint Warnings
The lint tool in Android Studio issues warnings for the WRITE_EXTERNAL_STORAGE permission, indicating that it will no longer provide full write access when the target API level is 29 or higher. This warning is generated based on the targetSdkVersion attribute in the project configuration, ignoring minSdkVersion. Therefore, for apps supporting multiple versions (e.g., from API 21 to 30), developers may encounter a contradiction: on Android 10 and above, removing the permission still allows writing to specific folders in internal storage, but on lower versions (e.g., Android M, API 23), removing the permission causes saving functionality to fail.
This is not a false positive from lint but a design limitation—it primarily focuses on compatibility with the target API level without fully considering the minimum supported version. In practice, developers need to manually handle these cross-version differences to ensure the app works correctly on all supported Android versions.
Compatibility Solutions
To address this issue, the best practice is to use the android:maxSdkVersion attribute to limit the permission's application scope. For example, if the app does not use the requestLegacyExternalStorage flag in Android 10, set maxSdkVersion to 28, so the permission only takes effect on API 28 and lower. An example configuration in AndroidManifest.xml is as follows:
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28"
tools:ignore="ScopedStorage" />
If the app enables the requestLegacyExternalStorage flag in Android 10, adjust maxSdkVersion to 29, as this flag allows the app to continue using the old storage mode in Android 10, where the WRITE_EXTERNAL_STORAGE permission remains effective. However, note that requestLegacyExternalStorage is completely ignored in Android 11 (API 30) and later, making it only a temporary solution.
For Android 11 and above, the WRITE_EXTERNAL_STORAGE permission has no effect. If the app needs to access shared storage folders (e.g., DCIM, Music) or private directories, consider using the MANAGE_EXTERNAL_STORAGE permission, but this requires manual user authorization in settings and may affect app store reviews. An example configuration is:
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
In code, you can check and request this permission as follows:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && !Environment.isExternalStorageManager()) {
Uri uri = Uri.parse("package:" + BuildConfig.APPLICATION_ID);
startActivity(new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION, uri));
}
Performance and API Selection
In Android 11 and above, the traditional File API has been refactored as a wrapper for MediaStore, leading to performance degradation. According to the Android team's evaluation, using the File API can be 2 to 3 times slower than directly accessing MediaStore. Therefore, developers should prioritize using the MediaStore API for file operations, especially in performance-critical scenarios.
For accessing allowed locations (e.g., public shared storage folders or app private directories), the File API can still be used, but be mindful of its performance overhead. In cross-version development, it is recommended to dynamically select APIs based on the API level: use the File API with the WRITE_EXTERNAL_STORAGE permission on lower versions, use the requestLegacyExternalStorage flag in Android 10 (if applicable), and use the MediaStore API or MANAGE_EXTERNAL_STORAGE permission on Android 11 and above.
Summary and Recommendations
When handling the WRITE_EXTERNAL_STORAGE permission, developers should first clarify the range of Android versions supported by the app and formulate a permission strategy accordingly. Using the maxSdkVersion attribute is an effective way to avoid lint warnings and ensure compatibility. At the same time, avoid over-reliance on the requestLegacyExternalStorage flag, as it only works in Android 10 and may introduce maintenance burdens.
In code implementation, it is advisable to use conditional checks (e.g., Build.VERSION.SDK_INT) to adapt to behavioral differences across API levels and prioritize modern APIs (e.g., MediaStore) to improve performance and security. By integrating these strategies, developers can build high-quality Android apps that are both compatible with older versions and compliant with new standards.