Android 存储权限兼容问题全解析
一、引言
在 Android 开发中,存储权限是一个常见且重要的权限。不同版本的 Android 系统对于存储权限的管理和处理方式存在差异,这就导致了开发者在处理存储权限时会遇到各种兼容问题。本文将详细介绍 Android 各版本存储权限的变化,以及如何进行兼容处理,并给出具体的代码示例。
二、Android 各版本存储权限变化
2.1 Android 6.0(API 级别 23)之前
在 Android 6.0 之前,应用的权限在安装时一次性授予。只要用户安装了应用,应用就拥有了在 AndroidManifest.xml
中声明的所有权限,无需在运行时再次请求。例如,要使用存储权限,只需在 AndroidManifest.xml
中添加如下代码:
js
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
2.3 Android 10(API 级别 29)及之后
Android 10 引入了分区存储(Scoped Storage)机制,对应用访问外部存储的方式进行了重大改变。应用默认只能访问自己的专属目录(如 Context.getExternalFilesDir()
返回的目录)和媒体文件,访问其他应用的文件需要特殊处理。同时,WRITE_EXTERNAL_STORAGE
权限不再授予普通应用,除非应用在 AndroidManifest.xml
中声明 android:requestLegacyExternalStorage="true"
以使用旧的存储模式。
2.4 Android 11(API 级别 30)及之后
Android 11 进一步加强了分区存储的限制,android:requestLegacyExternalStorage
属性不再起作用,应用必须适应分区存储。不过,Android 11 提供了一些新的 API 来处理文件访问,如 MediaStore
API 用于访问媒体文件。
三、兼容处理方案及代码示例
3.1 权限检查与请求
以下是一个在 Android 6.0 及以上版本中请求存储权限的代码示例:
js
import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
public class MainActivity extends AppCompatActivity {
private static final int PERMISSION_REQUEST_CODE = 1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 检查是否已经授予存储权限
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
// 如果没有授予权限,则请求权限
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
PERMISSION_REQUEST_CODE);
} else {
// 已经授予权限,执行相关操作
performStorageOperation();
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == PERMISSION_REQUEST_CODE) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 用户授予了权限,执行相关操作
performStorageOperation();
} else {
// 用户拒绝了权限,给出提示
Toast.makeText(this, "存储权限被拒绝,无法执行操作", Toast.LENGTH_SHORT).show();
}
}
}
private void performStorageOperation() {
// 执行存储相关操作
Toast.makeText(this, "开始执行存储操作", Toast.LENGTH_SHORT).show();
}
}
3.2 Android 10 及以上版本的分区存储处理
在 Android 10 及以上版本中,使用分区存储可以通过 Context.getExternalFilesDir()
访问应用的专属目录。以下是一个简单的文件读写示例:
js
import android.content.Context;
import android.os.Bundle;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
public class StorageActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_storage);
// 获取应用的外部存储专属目录
File externalFilesDir = getExternalFilesDir(null);
if (externalFilesDir != null) {
// 创建一个文件
File file = new File(externalFilesDir, "test.txt");
try {
// 写入文件
FileOutputStream fos = new FileOutputStream(file);
String content = "Hello, World!";
fos.write(content.getBytes());
fos.close();
Toast.makeText(this, "文件写入成功", Toast.LENGTH_SHORT).show();
} catch (IOException e) {
e.printStackTrace();
Toast.makeText(this, "文件写入失败", Toast.LENGTH_SHORT).show();
}
}
}
}
3.3 Android 11 及以上版本的媒体文件访问
js
import android.content.ContentResolver;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
public class MediaAccessActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_media_access);
// 获取 ContentResolver
ContentResolver contentResolver = getContentResolver();
// 定义查询的 URI
Uri uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
// 定义查询的列
String[] projection = {MediaStore.Images.Media._ID, MediaStore.Images.Media.DISPLAY_NAME};
// 执行查询
Cursor cursor = contentResolver.query(uri, projection, null, null, null);
if (cursor != null) {
while (cursor.moveToNext()) {
// 获取图片的 ID 和文件名
long id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID));
String displayName = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME));
Toast.makeText(this, "图片 ID: " + id + ", 文件名: " + displayName, Toast.LENGTH_SHORT).show();
}
cursor.close();
}
}
}
四、总结
在 Android 开发中,处理存储权限兼容问题需要根据不同版本的系统特点进行相应的处理。在 Android 6.0 及以上版本中,要使用运行时权限机制;在 Android 10 及以上版本中,要适应分区存储;在 Android 11 及以上版本中,要使用新的 API 进行文件访问。通过合理的权限管理和文件操作,可以确保应用在不同版本的 Android 系统上都能正常运行。