androidQ sd卡权限使用详解
默认情况下,如果应用以 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" />
阅读排行
- 1Java Swing组件BoxLayout布局用法示例
- 2java中-jar 与nohup的对比
- 3Java邮件发送程序(可以同时发给多个地址、可以带附件)
- 4Caused by: java.lang.ClassNotFoundException: org.objectweb.asm.Type异常
- 5Java中自定义异常详解及实例代码
- 6深入理解Java中的克隆
- 7java读取excel文件的两种方法
- 8解析SpringSecurity+JWT认证流程实现
- 9spring boot里增加表单验证hibernate-validator并在freemarker模板里显示错误信息(推荐)
- 10深入解析java虚拟机