赞
踩
文件访问权限是 Android 系统中一个非常复杂且重要的内容,它决定了应用程序能够访问和操作哪些文件和目录。Android 系统对文件访问权限有严格的限制和管理。随着 Android 系统版本的不断更新,文件访问权限也有一些变化和变更,需要我们及时了解和学习 以便应对需求。这块内容非常复杂庞大且内容很杂 。。本文简单学习 Android 系统的文件访问权限的概念、分类、申请方法、framework层源码位置和关键函数,以及 Android 11、12 和 13 的文件访问权限的变化和适配方法。
Android系统 文件访问权限笔记
Android系统 理解/sys/目录权限和UID和GID?
Android系统 应用存储路径与权限
Android系统 自定义系统和应用权限
Android系统 AppOps默认授予应用相应的权限
Android系统 权限组管理和兼容性
Google Android 数据和文件存储概览
Android 11 system_server 读写 SDCARD
解决Android10读取不到/sdcard/、/storage/emulated/0/文件的问题
论Android 9.0 外置sdcard 读写
Android 9.0中sdcard 的权限和挂载问题
Android系统 理解/sys/目录权限和UID和GID?
在安卓11/12/13下修改android/data和obb内容的经验记录
Android 13 功能和变更列表 | Android 开发者 | Android Developers
android 11 申请允许管理所有文件权限
Android11 处理文件 出现 open failed: EACCES (Permission denied) 问题
从Android 10版本开始,普通应用无法直接读取系统/sdcard/、/mnt/下的sdcard和/mnt/下的u盘。这是因为Android 10引入了分区存储机制,旨在防止应用读取其他应用的数据。每个应用都应有自己的存储空间,不能越权访问公共目录。应用在请求数据时需要经过权限检测。
应用如果修改为系统平台签名或者设置android:sharedUserId="android.uid.system"
,并添加相应的读写权限,就可以读写系统/sdcard/、/mnt/下的sdcard和/mnt/下的u盘。这是因为这样的应用会与system拥有相同的签名,可以使用android:sharedUserId="android.uid.system"
来共享system的UID,从而访问系统的核心功能和数据。
通过修改 android:sharedUserId="android.uid.system"
,可以让应用的 UID 变为系统的 UID ,从而可以访问到一些目录。这是因为系统的 UID 是 1000 ,具有最高的权限。
可以在Android源码的system/core/include/private/android_filesystem_config.h路径下找到这个文件。
#define AID_ROOT 0 /* traditional unix root user */
/* The following are for LTP and should only be used for testing */
#define AID_DAEMON 1 /* traditional unix daemon owner */
#define AID_BIN 2 /* traditional unix binaries owner */
#define AID_SYSTEM 1000 /* system server */
。。。。。。。。。。
文件访问权限是 Android 系统中,它决定了应用程序能够访问和操作哪些文件和目录。Android 系统对文件访问权限有严格的限制和管理,,其中有一些关键的类和方法。
Android 系统的文件访问权限的源码位置和关键函数可以根据不同的功能和模块进行区分,主要有以下几个:
http://aospxref.com/android-13.0.0_r3/xref/frameworks/base/services/core/java/com/android/server/StorageManagerService.java
:这个类负责管理外部存储设备的挂载和卸载,以及分配和回收存储空间。它是一个系统服务,可以通过 Context.getSystemService(Context.STORAGE_SERVICE) 来获取它的实例。它提供了一些关键的方法,如:// 这个方法是 StorageManagerService 类的一个公开方法,用于挂载指定 ID 的外部存储设备卷
2309 @Override
2310 public void mount(String volId) {
2311 // 检查调用者是否有 MOUNT_UNMOUNT_FILESYSTEMS 权限,如果没有,抛出 SecurityException 异常
2312 enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
2313
2314 // 根据卷的 ID 找到对应的 VolumeInfo 对象,如果找不到,抛出 IllegalArgumentException 异常
2315 final VolumeInfo vol = findVolumeByIdOrThrow(volId);
2316 // 判断是否允许挂载该卷,如果不允许,抛出 SecurityException 异常
2317 if (isMountDisallowed(vol)) {
2318 throw new SecurityException("Mounting " + volId + " restricted by policy");
2319 }
2320
2321 // 调用私有的 mount 方法,传入 VolumeInfo 对象,用于执行挂载操作
2322 mount(vol);
2323 }
2324
2325 // 这个方法是 StorageManagerService 类的一个私有方法,用于挂载指定的 VolumeInfo 对象
2326 private void mount(VolumeInfo vol) {
2327 try {
2328 // TODO(b/135341433): Remove cautious logging when FUSE is stable
2329 // 记录日志,表示正在挂载卷
2330 Slog.i(TAG, "Mounting volume " + vol);
2331 // 调用 mVold 的 mount 方法,传入卷的 ID、挂载标志、挂载用户 ID 和一个回调对象
2332 mVold.mount(vol.id, vol.mountFlags, vol.mountUserId, new IVoldMountCallback.Stub() {
2333 // 这个回调对象实现了 IVoldMountCallback 接口,它有一个 onVolumeChecking 方法,用于在挂载前对卷进行检查
2334 @Override
2335 public boolean onVolumeChecking(FileDescriptor fd, String path,
2336 String internalPath) {
2337 // 将卷的路径和内部路径设置为传入的参数值
2338 vol.path = path;
2339 vol.internalPath = internalPath;
2340 // 将文件描述符封装成一个 ParcelFileDescriptor 对象
2341 ParcelFileDescriptor pfd = new ParcelFileDescriptor(fd);
2342 try {
2343 // 调用 mStorageSessionController 的 onVolumeMount 方法,传入该对象和卷的信息
2344 mStorageSessionController.onVolumeMount(pfd, vol);
2345 // 如果 onVolumeMount 方法成功返回,表示挂载成功,返回 true
2346 return true;
2347 } catch (ExternalStorageServiceException e) {
2348 // 如果 onVolumeMount 方法抛出 ExternalStorageServiceException 异常,表示挂载失败,返回 false,并在一定时间后重新尝试挂载
2349 Slog.e(TAG, "Failed to mount volume " + vol, e);
2350
2351 int nextResetSeconds = FAILED_MOUNT_RESET_TIMEOUT_SECONDS;
2352 Slog.i(TAG, "Scheduling reset in " + nextResetSeconds + "s");
2353 mHandler.removeMessages(H_RESET);
2354 mHandler.sendMessageDelayed(mHandler.obtainMessage(H_RESET),
2355 TimeUnit.SECONDS.toMillis(nextResetSeconds));
2356 return false;
2357 } finally {
2358 // 无论成功或失败,都要关闭文件描述符对象
2359 try {
2360 pfd.close();
2361 } catch (Exception e) {
2362 Slog.e(TAG, "Failed to close FUSE device fd", e);
2363 }
2364 }
2365 }
2366 });
2367 // 记录日志,表示已经挂载卷
2368 Slog.i(TAG, "Mounted volume " + vol);
2369 } catch (Exception e) {
2370 // 如果发生其他异常,记录错误日志
2371 Slog.wtf(TAG, e);
2372 }
2373 }
// 这个方法是 StorageManagerService 类的一个公开方法,用于卸载指定 ID 的外部存储设备卷
2377 @Override
2378 public void unmount(String volId) {
2379 // 检查调用者是否有 MOUNT_UNMOUNT_FILESYSTEMS 权限,如果没有,抛出 SecurityException 异常
2380 enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
2381
2382 // 根据卷的 ID 找到对应的 VolumeInfo 对象,如果找不到,抛出 IllegalArgumentException 异常
2383 final VolumeInfo vol = findVolumeByIdOrThrow(volId);
2384 // 调用私有的 unmount 方法,传入 VolumeInfo 对象,用于执行卸载操作
2385 unmount(vol);
2386 }
2387
// 这个方法是 StorageManagerService 类的一个私有方法,用于卸载指定的 VolumeInfo 对象
2389 private void unmount(VolumeInfo vol) {
2390 try {
2391 try {
2392 // 判断卷的类型是否为 VolumeInfo.TYPE_PRIVATE,如果是,调用 mInstaller 的 onPrivateVolumeRemoved 方法,传入卷的 UUID,用于移除私有卷的镜像数据
2393 if (vol.type == VolumeInfo.TYPE_PRIVATE) {
2394 mInstaller.onPrivateVolumeRemoved(vol.getFsUuid());
2395 }
2396 } catch (Installer.InstallerException e) {
2397 // 如果 onPrivateVolumeRemoved 方法抛出 Installer.InstallerException 异常,记录错误日志
2398 Slog.e(TAG, "Failed unmount mirror data", e);
2399 }
2400 // 调用 mVold 的 unmount 方法,传入卷的 ID,用于卸载卷
2401 mVold.unmount(vol.id);
2402 // 调用 mStorageSessionController 的 onVolumeUnmount 方法,传入卷的信息,用于处理卸载后的逻辑
2403 mStorageSessionController.onVolumeUnmount(vol);
2404 } catch (Exception e) {
2405 // 如果发生其他异常,记录错误日志
2406 Slog.wtf(TAG, e);
2407 }
2408 }
// 这个方法是 StorageManagerService 类的一个公开方法,用于获取所有已挂载的外部存储设备卷的列表
4001 @Override
4002 public VolumeInfo[] getVolumes(int flags) {
4003 // 同步锁定 mLock 对象,用于保证线程安全
4004 synchronized (mLock) {
4005 // 创建一个 VolumeInfo 类型的数组,大小为 mVolumes 集合的大小
4006 final VolumeInfo[] res = new VolumeInfo[mVolumes.size()];
4007 // 遍历 mVolumes 集合,将每个元素复制到数组中
4008 for (int i = 0; i < mVolumes.size(); i++) {
4009 res[i] = mVolumes.valueAt(i);
4010 }
4011 // 返回数组
4012 return res;
4013 }
4014 }
// 这个方法是 StorageManagerService 类的一个公开方法,用于获取指定 UUID 的外部存储设备卷上可分配给应用程序的字节数
4082 @Override
4083 public long getAllocatableBytes(String volumeUuid, int flags, String callingPackage) {
4084 // 调整分配标志,根据调用者的 UID 和包名,判断是否需要添加 FLAG_ALLOCATE_AGGRESSIVE 标志
4085 flags = adjustAllocateFlags(flags, Binder.getCallingUid(), callingPackage);
4086
4087 // 获取 StorageManager 和 StorageStatsManager 的实例,用于操作存储相关的数据
4088 final StorageManager storage = mContext.getSystemService(StorageManager.class);
4089 final StorageStatsManager stats = mContext.getSystemService(StorageStatsManager.class);
4090 // 保存当前的调用标识,并清除调用者的身份,用于跨进程调用
4091 final long token = Binder.clearCallingIdentity();
4092 try {
4093 // 一般情况下,应用程序可以分配尽可能多的空间,除非超过了最小缓存空间或低磁盘警告空间的限制。为了避免用户混淆,这个逻辑应该和 getFreeBytes() 方法保持一致
4094 // 根据卷的 UUID 找到对应的文件路径
4095 final File path = storage.findPathForUuid(volumeUuid);
4096
4097 // 初始化可用空间、低保留空间、满保留空间和可清除缓存空间为 0
4098 long usable = 0;
4099 long lowReserved = 0;
4100 long fullReserved = 0;
4101 long cacheClearable = 0;
4102
4103 // 如果没有设置 FLAG_ALLOCATE_CACHE_ONLY 标志,表示可以分配非缓存空间
4104 if ((flags & StorageManager.FLAG_ALLOCATE_CACHE_ONLY) == 0) {
4105 // 获取文件路径上的可用空间大小
4106 usable = path.getUsableSpace();
4107 // 获取文件路径上的低磁盘警告空间大小
4108 lowReserved = storage.getStorageLowBytes(path);
4109 // 获取文件路径上的磁盘满空间大小
4110 fullReserved = storage.getStorageFullBytes(path);
4111 }
4112
4113 // 如果没有设置 FLAG_ALLOCATE_NON_CACHE_ONLY 标志,并且卷支持配额功能,表示可以分配缓存空间
4114 if ((flags & StorageManager.FLAG_ALLOCATE_NON_CACHE_ONLY) == 0
4115 && stats.isQuotaSupported(volumeUuid)) {
4116 // 获取卷上的缓存总大小
4117 final long cacheTotal = stats.getCacheBytes(volumeUuid);
4118 // 获取卷上的缓存保留大小
4119 final long cacheReserved = storage.getStorageCacheBytes(path, flags);
4120 // 计算卷上的可清除缓存大小,即缓存总大小减去缓存保留大小,如果为负数,则取 0
4121 cacheClearable = Math.max(0, cacheTotal - cacheReserved);
4122 }
4123
4124 // 如果设置了 FLAG_ALLOCATE_AGGRESSIVE 标志,表示可以分配更多的空间,但可能会影响系统性能或稳定性
4125 if ((flags & StorageManager.FLAG_ALLOCATE_AGGRESSIVE) != 0) {
4126 // 返回可用空间加上可清除缓存空间再减去磁盘满空间大小,如果为负数,则取 0
4127 return Math.max(0, (usable + cacheClearable) - fullReserved);
4128 } else {
4129 // 否则,返回可用空间加上可清除缓存空间再减去低磁盘警告空间大小,如果为负数,则取 0
4130 return Math.max(0, (usable + cacheClearable) - lowReserved);
4131 }
4132 } catch (IOException e) {
4133 // 如果发生 IOException 异常,将其封装成一个 ParcelableException 对象,并抛出
4134 throw new ParcelableException(e);
4135 } finally {
4136 // 无论成功或失败,都要恢复之前的调用标识
4137 Binder.restoreCallingIdentity(token);
4138 }
4139 }
// 这个方法是 StorageManagerService 类的一个公开方法,用于在指定 UUID 的外部存储设备卷上为应用程序分配指定字节数的空间
4126 @Override
4127 public void allocateBytes(String volumeUuid, long bytes, int flags, String callingPackage) {
4128 // 调整分配标志,根据调用者的 UID 和包名,判断是否需要添加 FLAG_ALLOCATE_AGGRESSIVE 标志
4129 flags = adjustAllocateFlags(flags, Binder.getCallingUid(), callingPackage);
4130
4131 // 获取卷上可分配给应用程序的非缓存空间大小
4132 final long allocatableBytes = getAllocatableBytes(volumeUuid,
4133 flags | StorageManager.FLAG_ALLOCATE_NON_CACHE_ONLY, callingPackage);
4134 // 如果要分配的字节数大于可分配的非缓存空间大小,表示空间不足
4135 if (bytes > allocatableBytes) {
4136 // 如果空间不足,检查卷上可分配给应用程序的缓存空间大小
4137 final long cacheClearable = getAllocatableBytes(volumeUuid,
4138 flags | StorageManager.FLAG_ALLOCATE_CACHE_ONLY, callingPackage);
4139 // 如果要分配的字节数大于可分配的非缓存空间大小加上可分配的缓存空间大小,表示即使清除缓存也无法满足需求,抛出 IOException 异常
4140 if (bytes > allocatableBytes + cacheClearable) {
4141 throw new ParcelableException(new IOException("Failed to allocate " + bytes
4142 + " because only " + (allocatableBytes + cacheClearable) + " allocatable"));
4143 }
4144 }
4145
4146 // 获取 StorageManager 的实例,用于操作存储相关的数据
4147 final StorageManager storage = mContext.getSystemService(StorageManager.class);
4148 // 保存当前的调用标识,并清除调用者的身份,用于跨进程调用
4149 final long token = Binder.clearCallingIdentity();
4150 try {
4151 // 为了满足分配需求和低磁盘警告空间的限制,释放足够的磁盘空间
4152 final File path = storage.findPathForUuid(volumeUuid);
4153 // 如果设置了 FLAG_ALLOCATE_AGGRESSIVE 标志,表示可以分配更多的空间,但可能会影响系统性能或稳定性,需要加上磁盘满空间大小
4154 if ((flags & StorageManager.FLAG_ALLOCATE_AGGRESSIVE) != 0) {
4155 bytes += storage.getStorageFullBytes(path);
4156 } else {
4157 // 否则,只需要加上低磁盘警告空间大小
4158 bytes += storage.getStorageLowBytes(path);
4159 }
4160
4161 // 调用 mPmInternal 的 freeStorage 方法,传入卷的 UUID、要释放的字节数和分配标志,用于清理不必要的数据
4162 mPmInternal.freeStorage(volumeUuid, bytes, flags);
4163 } catch (IOException e) {
4164 // 如果发生 IOException 异常,将其封装成一个 ParcelableException 对象,并抛出
4165 throw new ParcelableException(e);
4166 } finally {
4167 // 无论成功或失败,都要恢复之前的调用标识
4168 Binder.restoreCallingIdentity(token);
4169 }
4170 }
http://aospxref.com/android-13.0.0_r3/xref/frameworks/base/core/java/android/os/storage/VolumeInfo.java
:这个类表示一个外部存储设备的卷信息,包括卷 ID、类型、状态、路径等属性。它是一个 Parcelable 对象,可以通过 Intent 或者 Bundle 传递。它提供了一些关键的属性和方法,如:// 这判断该卷是否已挂载且可读取。
@UnsupportedAppUsage
public boolean isMountedReadable() {
// 这个方法检查存储卷的状态是否是已挂载或只读挂载
return state == STATE_MOUNTED || state == STATE_MOUNTED_READ_ONLY;
}
//判断该卷是否已挂载且可写入。
@UnsupportedAppUsage
public boolean isMountedWritable() {
// 这个方法检查存储卷的状态是否是已挂载
return state == STATE_MOUNTED;
}
http://aospxref.com/android-13.0.0_r3/xref/frameworks/base/core/java/android/os/FileUtils.java
:这个类提供了一些文件操作的工具方法,设置文件权限等。,不需要创建实例。它提供了一些关键的方法,如:151 @UnsupportedAppUsage
152 public static int setPermissions(File path, int mode, int uid, int gid) {
153 // 调用另一个重载的方法,传入文件的绝对路径作为参数
153 return setPermissions(path.getAbsolutePath(), mode, uid, gid);
154 }
155
156 /**
157 * 设置给定路径的所有者和模式。
158 *
159 * @param mode 通过 {@code chmod} 应用的模式
160 * @param uid 通过 {@code chown} 应用的用户ID,或 -1 表示不改变
161 * @param gid 通过 {@code chown} 应用的组ID,或 -1 表示不改变
162 * @return 成功时返回 0,否则返回错误码。
163 * @hide
164 */
165 @UnsupportedAppUsage
166 public static int setPermissions(String path, int mode, int uid, int gid) {
167 try {
168 // 使用 Os 类的 chmod 方法修改文件的权限
168 Os.chmod(path, mode);
169 } catch (ErrnoException e) {
170 // 如果出现异常,打印警告信息,并返回错误码
170 Slog.w(TAG, "Failed to chmod(" + path + "): " + e);
171 return e.errno;
172 }
173
174 // 如果 uid 或 gid 不为 -1,表示需要修改文件的所有者
174 if (uid >= 0 || gid >= 0) {
175 try {
176 // 使用 Os 类的 chown 方法修改文件的所有者
176 Os.chown(path, uid, gid);
177 } catch (ErrnoException e) {
178 // 如果出现异常,打印警告信息,并返回错误码
178 Slog.w(TAG, "Failed to chown(" + path + "): " + e);
179 return e.errno;
180 }
181 }
182
183 // 如果没有异常,返回 0 表示成功
183 return 0;
184 }
185
186 /**
187 * 设置给定 {@link FileDescriptor} 的所有者和模式。
188 *
189 * @param mode 通过 {@code chmod} 应用的模式
190 * @param uid 通过 {@code chown} 应用的用户ID,或 -1 表示不改变
191 * @param gid 通过 {@code chown} 应用的组ID,或 -1 表示不改变
192 * @return 成功时返回 0,否则返回错误码。
193 * @hide
194 */
195 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
196 public static int setPermissions(FileDescriptor fd, int mode, int uid, int gid) {
197 try {
198 // 使用 Os 类的 fchmod 方法修改文件描述符的权限
198 Os.fchmod(fd, mode);
199 } catch (ErrnoException e) {
200 // 如果出现异常,打印警告信息,并返回错误码
200 Slog.w(TAG, "Failed to fchmod(): " + e);
201 return e.errno;
202 }
203
204 // 如果 uid 或 gid 不为 -1,表示需要修改文件描述符的所有者
204 if (uid >= 0 || gid >= 0) {
205 try {
206 // 使用 Os 类的 fchown 方法修改文件描述符的所有者
206 Os.fchown(fd, uid, gid);
207 } catch (ErrnoException e) {
208 // 如果出现异常,打印警告信息,并返回错误码
208 Slog.w(TAG, "Failed to fchown(): " + e);
209 return e.errno;
210 }
211 }
212
213 // 如果没有异常,返回 0 表示成功
213 return 0;
214 }
http://aospxref.com/android-13.0.0_r3/xref/frameworks/base/core/java/android/os/SELinux.java
:这个类提供了一些 SELinux 相关的方法,包括检查文件是否有 SELinux 上下文标签等。SELinux 是一种安全增强型 Linux 系统,它可以对文件和进程进行更细致的访问控制。它是一个静态类,不需要创建实例。它提供了一些关键的方法,如:
51 /**
52 * 判断 SELinux 是否被禁用或启用。
53 * @return 表示 SELinux 是否启用。
54 */
55 @UnsupportedAppUsage
56 // 声明一个本地方法,使用 native 关键字,不需要提供方法体
56 public static final native boolean isSELinuxEnabled();
72 /**
73 * 改变已存在的文件对象的安全上下文。
74 * @param path 表示要重新标记的文件对象的路径。
75 * @param context 新的安全上下文,以字符串形式给出。
76 * @return 表示操作是否成功。
77 */
78 // 声明一个本地方法,使用 native 关键字,不需要提供方法体
78 public static final native boolean setFileContext(String path, String context);
80 /**
81 * 获取文件对象的安全上下文。
82 * @param path 文件对象的路径名。
83 * @return 表示安全上下文。
84 */
85 @UnsupportedAppUsage
86 // 声明一个本地方法,使用 native 关键字,不需要提供方法体
86 public static final native String getFileContext(String path);
95 /**
96 * 获取文件描述符对应文件的安全上下文。
97 * @param fd 文件的文件描述符。
98 * @return 表示文件描述符的安全上下文。
99 */
100 // 声明一个本地方法,使用 native 关键字,不需要提供方法体
100 public static final native String getFileContext(FileDescriptor fd);
判断系统的某个目录,某个文件可以被哪些UID或者组访问,有以下几种方法:
ls -l
命令查看文件或目录的权限和所有者。例如:
-rw-r--r--
,表示该文件可以被其所有者(第一个rw
)读写,可以被其所属组(第二个r
)读取,可以被其他用户(第三个r
)读取。drwxr-x---
,表示该目录可以被其所有者(第一个rwx
)读写执行,可以被其所属组(第二个rx
)读取执行,不能被其他用户(第三个-
)访问。stat
命令查看文件或目录的详细信息。例如:
Access: (0644/-rw-r--r--) Uid: ( 1000/ system) Gid: ( 0/ root)
,表示该文件的权限是 0644
,可以用八进制数表示为 -rw-r--r--
,该文件的所有者是 UID 为 0
的用户 root
,该文件的所属组是 GID 为 0
的组 root
。find
命令根据用户的属主、属组、UID、GID等条件查找文件或目录。例如:
/data/data
目录下属于 UID 为 1000
的用户的所有文件,可以使用 find /data/data -uid 1000
命令。# 查看 /etc/passwd文件的权限和所有者
rk3568_t:/etc # ls -ll passwd
-rw-r--r-- 1 root root 0 2023-07-31 06:49:44.000000000 +0000 passwd
# 查看 /etc/passwd 文件的详细信息
rk3568_t:/etc # stat passwd
File: passwd
Size: 0 Blocks: 0 IO Blocks: 512 regular empty file
Device: fd00h/64768d Inode: 1782 Links: 1 Device type: 0,0
Access: (0644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2023-07-31 06:49:44.000000000 +0000
Modify: 2023-07-31 06:49:44.000000000 +0000
Change: 2023-07-31 06:49:44.000000000 +0000
# 查找 /data/data 目录下属于 system 用户或者 system 组的所有目录
rk3568_t:/data/data # find . -user system -o -group system -type d
.
./com.android.dynsystem
./com.android.dynsystem/cache
./com.android.dynsystem/code_cache
从 Android 11 开始,Google 对应用访问外部存储设备的权限做了一些限制和变化,以保护用户的隐私和数据安全。这些限制和变化可能会影响到一些需要读写文件的应用的功能和兼容性。需要了解这些权限的含义和用法,并根据自己的应用需求进行相应的适配。下表列出了三种与外部存储设备相关的权限,以及它们的说明、级别和适用版本。
权限名称 | 权限说明 | 权限级别 | 适用版本 |
---|---|---|---|
android.permission.READ_EXTERNAL_STORAGE | 允许应用从外部存储设备(如 SD 卡或 U 盘)读取文件,包括图片、音频、视频等媒体文件。 | 危险权限,需要在运行时向用户请求授权。 | Android 4.1(API 级别 16)及以上。 |
android.permission.WRITE_EXTERNAL_STORAGE | 允许应用向外部存储设备(如 SD 卡或 U 盘)写入文件,包括图片、音频、视频等媒体文件。该权限也包含了 READ_EXTERNAL_STORAGE 权限的功能。 | 危险权限,需要在运行时向用户请求授权。 | Android 4.1(API 级别 16)及以上。 |
android.permission.MANAGE_EXTERNAL_STORAGE | 允许应用访问和修改外部存储设备上的所有文件,包括系统和其他应用的文件 。该权限也包含了 WRITE_EXTERNAL_STORAGE 权限的功能 。 | 特殊权限,需要在系统设置中向用户请求授权 。 | Android 11(API 级别 30)及以上 。 |
下面是对这些权限的变化和适配方法的简要介绍:
从 Android 11 开始,应用访问外部存储设备的权限变得更加严格和细化,需要根据自己的应用需求和功能,选择合适的权限和 API 来进行适配。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。