Keywords: Android | Permission Management | External Storage | Runtime Permissions | Music Player
Abstract: This article provides an in-depth exploration of common issues with READ_EXTERNAL_STORAGE permissions in Android applications. Through analysis of a real-world music player case study, it explains the distinction between permission declaration and runtime requests, offers code examples compatible with different Android versions, and discusses the evolution of permission models and best practices.
Introduction
Accessing media files on external storage devices is a common requirement in Android application development, particularly when building music players, gallery apps, or file managers. However, many developers encounter various issues when using the READ_EXTERNAL_STORAGE permission, leading to application crashes or failure to read files properly. This article analyzes the root causes of these problems through a practical case study and provides effective solutions.
Problem Analysis
In the provided Q&A data, a developer attempted to build a simple music player application but encountered a SecurityException when accessing audio files from external storage. The error message clearly stated: reading com.android.providers.media.MediaProvider uri content://media/external/audio/media from pid=27696, uid=10059 requires android.permission.READ_EXTERNAL_STORAGE, or grantUriPermission(). This indicates that although the application declared the permission in AndroidManifest.xml, it did not obtain authorization at runtime.
The developer tried multiple approaches, including adjusting the permission declaration location, adding maxSdkVersion attributes, setting the android:exported flag, and using grantUriPermission, but none succeeded. These attempts reflect a misunderstanding of Android's permission model, particularly the runtime permission mechanism.
Evolution of Android Permission Model
Android's permission management has undergone several significant changes. Before Android 6.0 (API level 23), applications only needed to declare permissions in the manifest file, with users granting all permissions at installation time. This model posed privacy and security risks, prompting Google to introduce the runtime permission model in Android 6.0.
Runtime permissions categorize permissions into two types: normal permissions and dangerous permissions. Normal permissions (such as INTERNET) are automatically granted at installation, while dangerous permissions (such as READ_EXTERNAL_STORAGE) must be explicitly granted by the user at runtime. This change requires developers to actively request permissions in code and handle user authorization results.
Solutions
Method 1: Lower targetSdkVersion
This is a temporary solution that bypasses runtime permission requirements by setting the application's targetSdkVersion to 22 or lower. The system will use the older permission model, granting all declared permissions at installation. However, this approach is not recommended for production environments as it cannot leverage new system security features and may become ineffective in future versions.
Method 2: Implement Runtime Permission Requests
This is the correct and recommended solution. Developers need to check permission status and request authorization appropriately before accessing protected resources. Below is a code example based on the best answer:
private static final int MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE = 1;
public void initialize() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
if (ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.READ_EXTERNAL_STORAGE)) {
// Explain to the user why the permission is needed
showExplanationDialog();
} else {
// Request the permission directly
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE);
}
} else {
// Permission has been granted, perform the operation
loadMediaFiles();
}
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// Permission granted
loadMediaFiles();
} else {
// Permission denied
handlePermissionDenied();
}
}
}
private void loadMediaFiles() {
ContentResolver contentResolver = getContentResolver();
Uri uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
String[] projection = {
MediaStore.Audio.Media._ID,
MediaStore.Audio.Media.TITLE,
MediaStore.Audio.Media.ARTIST,
MediaStore.Audio.Media.DATA
};
Cursor cursor = contentResolver.query(uri, projection, null, null, null);
if (cursor != null) {
while (cursor.moveToNext()) {
String title = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE));
String artist = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST));
String path = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DATA));
// Process audio file information
}
cursor.close();
}
}Best Practices for Permission Handling
When implementing runtime permissions, follow these best practices:
- Request Permissions Appropriately: Request permissions when users perform actions that require them, not at application startup.
- Provide Explanations: Use
shouldShowRequestPermissionRationaleto determine if an explanation is needed when users have previously denied the permission. - Handle Denials Gracefully: When users deny permissions, implement graceful degradation rather than allowing the application to crash.
- Test Different Scenarios: Ensure the application works correctly when permissions are granted, denied, or permanently denied.
Storage Permission Changes in Android 10+
Starting with Android 10 (API level 29), Google introduced Scoped Storage, further restricting application access to external storage. Under Scoped Storage, applications can only access their private directories and specific types of media files by default, without requiring the READ_EXTERNAL_STORAGE permission.
For cases requiring access to media files created by other applications, use the new media permissions: READ_MEDIA_AUDIO, READ_MEDIA_IMAGES, and READ_MEDIA_VIDEO. These permissions, introduced in Android 13 (API level 33), replace READ_EXTERNAL_STORAGE for media file access control.
Compatibility Considerations
To ensure applications work correctly across different Android versions, developers must carefully handle permission compatibility:
private void checkStoragePermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
// Android 13+ uses new media permissions
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_MEDIA_AUDIO)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.READ_MEDIA_AUDIO},
MY_PERMISSIONS_REQUEST_READ_MEDIA_AUDIO);
} else {
loadMediaFiles();
}
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// Android 6.0-12 uses READ_EXTERNAL_STORAGE
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE);
} else {
loadMediaFiles();
}
} else {
// Android 5.1 and below, permissions are automatically granted at installation
loadMediaFiles();
}
}Conclusion
Properly handling READ_EXTERNAL_STORAGE and other storage-related permissions is crucial in Android application development. By understanding the runtime permission model, following best practices, and considering version compatibility, developers can build secure and user-friendly applications. As the Android system continues to evolve, storage permission management may undergo further changes, making it essential to stay informed about the latest development practices.