androidQ sd卡权限使用详解
作者:十八砖 发布时间:2021-09-27 17:38:51
默认情况下,如果应用以 Android Q 为目标平台,则在访问外部存储设备中的文件时会进入过滤视图。应用可以使用 Context.getExternalFilesDir() 将专用于自己的文件存储在特定于自己的目录中。
1. 临时停用分区存储行为:
以 Android 9(API 级别 28)或更低版本为目标平台。
如果您以 Android Q 为目标平台,请在应用的清单文件中将 requestLegacyExternalStorage 的值设为 true。
<manifest ... >
<!-- This attribute is "false" by default on apps targeting Android Q. -->
<application android:requestLegacyExternalStorage="true" ... >
...
</application>
</manifest>
2. 如何实现隔离存储:
2.1 ApplicationInfo新增PRIVATE_FLAG_REQUEST_LEGACY_EXTERNAL_STORAGE标记
PackageParser.java:
if (sa.getBoolean(
R.styleable.AndroidManifestApplication_requestLegacyExternalStorage,
owner.applicationInfo.targetSdkVersion < Build.VERSION_CODES.Q)) {
ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_REQUEST_LEGACY_EXTERNAL_STORAGE;
}
ApplicationInfo.java:
public boolean hasRequestedLegacyExternalStorage() {
return (privateFlags & PRIVATE_FLAG_REQUEST_LEGACY_EXTERNAL_STORAGE) != 0;
}
2.2 grantRuntimePermission()重新挂载视图
apk启动时默认挂载runtime/default视图,grantRuntimePermission()时如果是READ_EXTERNAL_STORAGE或WRITE_EXTERNAL_STORAGE,则会获取挂载模式重新挂载对应视图。
PermissionManagerService.java:
private void grantRuntimePermission(String permName, String packageName, boolean overridePolicy,
int callingUid, final int userId, PermissionCallback callback) {
......
if (READ_EXTERNAL_STORAGE.equals(permName)
|| WRITE_EXTERNAL_STORAGE.equals(permName)) {
final long token = Binder.clearCallingIdentity();
try {
if (mUserManagerInt.isUserInitialized(userId)) {
StorageManagerInternal storageManagerInternal = LocalServices.getService(
StorageManagerInternal.class);
storageManagerInternal.onExternalStoragePolicyChanged(uid, packageName);
}
} finally {
Binder.restoreCallingIdentity(token);
}
}
}
获取挂载模式这块android10有修改,没有设置Legacy标志的话,总是获取default挂载模式,没有读写权限。
android 10会设置属性[persist.sys.isolated_storage]: [true],因此走到if(ENABLE_ISOLATED_STORAGE)中的getMountMode()。
public static boolean hasIsolatedStorage() {
//[persist.sys.isolated_storage]: [true]
//[sys.isolated_storage_snapshot]: [true]
return SystemProperties.getBoolean("sys.isolated_storage_snapshot",
SystemProperties.getBoolean("persist.sys.isolated_storage", true));
}
private static final boolean ENABLE_ISOLATED_STORAGE = StorageManager.hasIsolatedStorage();
public int getExternalStorageMountMode(int uid, String packageName) {
+ //android 10新增逻辑
+ if (ENABLE_ISOLATED_STORAGE) {
+ return getMountMode(uid, packageName);
+ }
......
int mountMode = Integer.MAX_VALUE;
for (ExternalStorageMountPolicy policy : mPolicies) {
final int policyMode = policy.getMountMode(uid, packageName);
if (policyMode == Zygote.MOUNT_EXTERNAL_NONE) {
return Zygote.MOUNT_EXTERNAL_NONE;
}
mountMode = Math.min(mountMode, policyMode);
}
if (mountMode == Integer.MAX_VALUE) {
return Zygote.MOUNT_EXTERNAL_NONE;
}
return mountMode;
}
正常模式下hasLegacy=false,走到if判断的DEFAULT分支;legacy模式hasLegacy=true,与之前保持一致,有write权限就走到WRITE模式分支。
private int getMountModeInternal(int uid, String packageName) {
try {
......
final boolean hasRead = StorageManager.checkPermissionAndCheckOp(mContext, false, 0,
uid, packageName, READ_EXTERNAL_STORAGE, OP_READ_EXTERNAL_STORAGE);
final boolean hasWrite = StorageManager.checkPermissionAndCheckOp(mContext, false, 0,
uid, packageName, WRITE_EXTERNAL_STORAGE, OP_WRITE_EXTERNAL_STORAGE);
......
final boolean hasLegacy = mIAppOpsService.checkOperation(OP_LEGACY_STORAGE,
uid, packageName) == MODE_ALLOWED;
if (hasLegacy && hasWrite) {
return Zygote.MOUNT_EXTERNAL_WRITE;
} else if (hasLegacy && hasRead) {
return Zygote.MOUNT_EXTERNAL_READ;
} else {
return Zygote.MOUNT_EXTERNAL_DEFAULT;
}
} catch (RemoteException e) {
// Should not happen
}
return Zygote.MOUNT_EXTERNAL_NONE;
}
2.3 Legacy Storage属性对权限的影响
安装apk时,就会根据requestLegacyExternalStorage属性来对ops state进行设置,修改OP_LEGACY_STORAGE的默认状态。
<manifest ... >
<application android:requestLegacyExternalStorage="true" ... >
</application>
</manifest>
//Q 正常模式
LEGACY_STORAGE: mode=ignore
//Q legacy模式
LEGACY_STORAGE: mode=allow
PermissionPolicyService启动时首先进行权限变化监听:
public void onStart() {
permManagerInternal.addOnRuntimePermissionStateChangedListener(
this::synchronizePackagePermissionsAndAppOpsAsyncForUser);
}
private void synchronizePackagePermissionsAndAppOpsAsyncForUser(@NonNull String packageName,
@UserIdInt int changedUserId) {
if (isStarted(changedUserId)) {
synchronized (mLock) {
if (mIsPackageSyncsScheduled.add(new Pair<>(packageName, changedUserId))) {
FgThread.getHandler().sendMessage(PooledLambda.obtainMessage(
PermissionPolicyService
::synchronizePackagePermissionsAndAppOpsForUser,
this, packageName, changedUserId));
}
......
}
}
}
APK安装时,会根据requestLegacyExternalStorage属性来通知storage权限变化,调用关系如下:
//调用关系:
1.PackageManagerService.java:
installPackagesLI()
commitPackagesLocked()
updateSettingsLI()
updateSettingsInternalLI()
2.PermissionManagerService.java:
mPermissionManager.updatePermissions()
restorePermissionState()
//关键代码:
private void restorePermissionState(@NonNull PackageParser.Package pkg, boolean replace,
@Nullable String packageOfInterest, @Nullable PermissionCallback callback) {
......
//判断requestLegacyExternalStorage属性
updatedUserIds = checkIfLegacyStorageOpsNeedToBeUpdated(pkg, replace, updatedUserIds);
......
for (int userId : updatedUserIds) {
notifyRuntimePermissionStateChanged(pkg.packageName, userId);
}
}
最终调用到PermissionPolicyService的监听函数synchronizePackagePermissionsAndAppOpsForUser(),进行默认权限获取和设置。
当apk安装时,声明了requestLegacyExternalStorage="true"属性,并且声明了READ_EXTERNAL_STORAGE、WRITE_EXTERNAL_STORAGE,那么addOpIfRestricted()就会将LEGACY_STORAGE设置为allow模式。
//调用关系:
synchronizePackagePermissionsAndAppOpsForUser():
synchroniser.addPackage()
addOpIfRestricted()//LEGACY_STORAGE加入到mOpsToAllow
synchroniser.syncPackages()
setUidModeAllowed()
setUidMode()//设置LEGACY_STORAGE为allow
//关键代码:
private void addOpIfRestricted(@NonNull PermissionInfo permissionInfo,
@NonNull PackageInfo pkg) {
......
//forPermission()会根据requestLegacyExternalStorage的值进行返回
final SoftRestrictedPermissionPolicy policy =
SoftRestrictedPermissionPolicy.forPermission(mContext, pkg.applicationInfo,
mContext.getUser(), permission);
final int op = policy.resolveAppOp();
if (op != OP_NONE) {
switch (policy.getDesiredOpMode()) {
case MODE_DEFAULT:
mOpsToDefault.add(new OpToChange(uid, pkg.packageName, op));
break;
case MODE_ALLOWED:
//在声明READ_EXTERNAL_STORAGE权限下,会将LEGACY_STORAGE加入到mOpsToAllow
if (policy.shouldSetAppOpIfNotDefault()) {
mOpsToAllow.add(new OpToChange(uid, pkg.packageName, op));
} else {
mOpsToAllowIfDefault.add(
new OpToChange(uid, pkg.packageName, op));
}
break;
......
}
public static @NonNull SoftRestrictedPermissionPolicy forPermission(@NonNull Context context,
@Nullable ApplicationInfo appInfo, @Nullable UserHandle user,
@NonNull String permission) {
switch (permission) {
case READ_EXTERNAL_STORAGE: {
if (appInfo != null) {
boolean hasAnyRequestedLegacyExternalStorage =
appInfo.hasRequestedLegacyExternalStorage();
hasRequestedLegacyExternalStorage = hasAnyRequestedLegacyExternalStorage;
}
return new SoftRestrictedPermissionPolicy() {
@Override
public int getDesiredOpMode() {
if (applyRestriction) {
return MODE_DEFAULT;
} else if (hasRequestedLegacyExternalStorage) {
//声明了requestLegacyExternalStorage就返回allow
return MODE_ALLOWED;
} else {
return MODE_IGNORED;
}
}
@Override
public boolean shouldSetAppOpIfNotDefault() {
return getDesiredOpMode() != MODE_IGNORED;
}
};
}
3. sdcard路径权限说明:
rwx:421,umask默认为八进制022(----w--w-)
/mnt/runtime/default的gid为1015,也就是sdcard_rw;mask 为6,八进制006,group sdcard_rw可读写,也就是other没有rw权限
/mnt/runtime/read的gid为9997,也就是everybody;mask 为23,八进制027,group everybody可读、不可写,other没有读写执行权限
/mnt/runtime/write的gid为9997,也就是everybody;mask 为7,八进制007,group everybody可读写,other没有读写可执行权限
/data/media on /mnt/runtime/default/emulated type sdcardfs (rw,nosuid,nodev,noexec,noatime,fsuid=1023,fsgid=1023,gid=1015,multiuser,mask=6,derive_gid,default_normal)
/data/media on /mnt/runtime/read/emulated type sdcardfs (rw,nosuid,nodev,noexec,noatime,fsuid=1023,fsgid=1023,gid=9997,multiuser,mask=23,derive_gid,default_normal)
/data/media on /mnt/runtime/write/emulated type sdcardfs (rw,nosuid,nodev,noexec,noatime,fsuid=1023,fsgid=1023,gid=9997,multiuser,mask=7,derive_gid,default_normal)
/mnt/runtime/default:
drwxrwx--x 3 root sdcard_rw 4096 2018-12-18 03:41 Android
drwxrwx--x 3 root sdcard_rw 4096 2018-12-18 06:11 DCIM
/mnt/runtime/read:
drwxr-x--- 3 root everybody 4096 2018-12-18 03:41 Android
drwxr-x--- 3 root everybody 4096 2018-12-18 06:11 DCIM
/mnt/runtime/write:
drwxrwx--- 3 root everybody 4096 2018-12-18 03:41 Android
drwxrwx--- 3 root everybody 4096 2018-12-18 06:11 DCIM
/sdcard/Android/data:
drwxrwx--- 4 u0_a64 everybody 4096 2018-12-18 06:11 com.android.camera2
drwxrwx--- 3 u0_a15 everybody 4096 2018-12-18 03:41 com.google.android.gms
drwxrwx--- 4 u0_a84 everybody 4096 2018-12-18 03:41 com.google.android.youtube
4. sdcard文件存储示例:
4.1 getExternalFilesDir()随卸载而删除
///storage/emulated/0/Android/data/com.xx.xx/files
File file = File(context.getExternalFilesDir(null), "test.txt");
4.2 媒体文件
媒体文件使用MediaStore操作,卸载后不会删除。
访问其他应用生成的照片、视频、音频,需要READ_EXTERNAL_STORAGE权限。
4.3 存储访问框架(SAF)
访问其他应用创建的文件,例如"Download"目录,必须使用存储访问框架,用户通过框架选择特定文件。
4.4 照片中的位置信息
需要ACCESS_MEDIA_LOCATION权限,才能获取元数据中的位置信息。
<permission android:name="android.permission.ACCESS_MEDIA_LOCATION"
android:permissionGroup="android.permission-group.UNDEFINED"
android:label="@string/permlab_mediaLocation"
android:description="@string/permdesc_mediaLocation"
android:protectionLevel="dangerous" />
来源:https://www.jianshu.com/p/418180d7e195
猜你喜欢
- java 接口回调实例详解首先官方对接口回调的定义是这样的,所谓回调:就是A类中调用B类中的某个方法C,然后B类中反过来调用A类中的方法D,
- 命令模式的介绍命令模式是对命令的封装。命令模式把发出命令的责任和执行命令的责任分割开,委派给不同的对象每一个命令都是一个操作:请求的一方发出
- 面试中会经常遇到手撕代码的情况,而求TopK的是经常遇到的题目。下面我就用Java来实现。主要通过两种方法实现,快排思想以及堆排序的思想,两
- 这篇文章主要介绍了java加载property文件配置过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,
- 很多学习Android程序设计的人都会发现每个人对代码的写法都有不同的偏好,比较明显的就是对控件响应事件的写法的不同。因此本文就把这些写法总
- 概述:Flutter中常用的滑动布局 ScrollView 有 SingleChildScrollView、NestedScrollView
- springboot+调用支付宝第三方接口(沙箱环境)大神勿喷!!网址:https://developers.alipay.com/plat
- ViewDragHelper是support.v4下提供的用于处理拖拽滑动的辅助类,查看Android的DrawerLayout源码,可以发
- springboot微服务内置了tomcat,在工程目录下执行:mvn clean package,可以将项目打成jar,通过java -j
- 本文实例讲述了Hibernate实现批量添加数据的方法。分享给大家供大家参考,具体如下:1.Hibernate_016_Ba
- Spring 提供了自动代理机制,可以让容器自动生成代理,从而把开发人员从繁琐的配置中解脱出来 。 具体是使用 BeanPostProces
- 一、输入输出流对象cout:标准输出流cerr:标准出凑 和cout(只是用于如果是错误时要输出的)cin :&nb
- 本文实例为大家分享了java将某个数据库的表全部导出到excel中的方法,供大家参考,具体内容如下第一步:如何用POI操作Excel@Tes
- 问题描述Spring Cache提供的@Cacheable注解不支持配置过期时间,还有缓存的自动刷新。我们可以通过配置CacheManneg
- 1.前提已经配置Sleuth,可参考2.什么是Zipkin?官网:https://zipkin.io/大规模分布式系统的APM工具( App
- jar包运行时提示jar中没有主清单属性解决办法在pom文件中添加<build> &n
- 比较适合新手。逻辑上仍然有点问题。可以用于学习java文件操作下载地址:http://yun.baidu.com/share/link?sh
- Bitmap (android.graphics.Bitmap)Bitmap是Android系统中的图像处理的最重要类之一。用它可以获取图像
- 本文实例讲述了C#创建临时文件的方法。分享给大家供大家参考。具体分析如下:C#可以通过Path.GetTempFileName获得一个临时文
- Spring Boot 的启动原理可以概括为以下几个步骤:加载 Spring Boot 应用程序的启动类根据启动类所在的包路径扫描相关的类根