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" />

到此这篇关于androidQ sd卡权限使用详解的文章就介绍到这了,更多相关androidQ sd卡权限内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

时间: 2020-06-22

android检测SD卡读写权限方法

一.解析 做项目遇到了一个棘手的问题,SD卡的读写权限问题. 1.android版本在6.0以上版本时,以下代码才有用: if (Build.VERSION.SDK_INT >= 23) { UiUtils.getInstance().showToast("1"); //减少是否拥有权限checkCallPhonePermission != PackageManager.PERMISSION_GRANTED int checkCallPhonePermission = Conte

快速解决设置Android 23.0以上版本对SD卡的读写权限无效的问题

对Android的SD卡进行读取权限设置时: <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> 结果报错: 依然提示我没有权限,于是百度说是版本问题,23.0版本(笔者的版本是25.0)以上的

解决Android 5.1限制外置SD卡写入权限的问题

在Android开发者网站的 "外部存储技术信息"文档中描述道 : WRITE_EXTERNAL_STORAGE只为设备上的主要外部存储授予写权限,应用程序无法将数据写入二级外部存储设备,除非综合权限指定了应用程序的包目录.这目前只影响双存储设备,如果你的设备有内部存储空间,即通常所说的机身存储,那么你的SD卡就是一个二级外部存储设备. 在Android 4.4中,如果你同时使用了机身存储和SD卡, 那么应用程序将无法在SD卡中创建.修改.删除数据.比如,你无法使用酷我音乐盒下载保存M

android 6.0 写入SD卡的权限申请实例讲解

6.0的手机对于写入手机需要申请权限的 我做了如下处理 下面我贴出代码 package com.example.admin.sdapplication; import android.Manifest; import android.app.Dialog; import android.content.DialogInterface; import android.content.pm.PackageManager; import android.os.Build; import android

Android 数据库SQLite 写入SD卡的方法

如果手机没有root,数据库文件是无法查看到的,不方便调试. 最好的办法是把数据库写进SD卡. 修改的地方有两处: 1.在你的helper类中把数据库文件名称 DATABASE_NAME 由原来的一个文件名,修改成路径的形式. 修改前:DATABASE_NAME = "demo.db" public class MyDBHelper extends SQLiteOpenHelper { public static final int VERSION = 1; //数据库版本号 publ

快速解决安卓7.0系统写入SD卡权限失败的问题

如图所示,在mainfest文件中声明了SD卡的读和写权限,仍旧报错: <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"></uses-permi

Android获取assets文件夹中的数据并写入SD卡示例

本文示例主要实现了Android获取assets文件夹中的数据并将其写入到SD卡中,该程序实现的步骤主要为:首先读取assets文件夹中的数据库,再将其写入到SD存储卡中. 完整示例代码如下: import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import android.content.Context; /*将assets文件

Android编程实现向SD卡写入数据的方法

本文实例讲述了Android编程实现向SD卡写入数据的方法.分享给大家供大家参考,具体如下: 1.代码: /** * 向sdcard中写入文件 * @param filename 文件名 * @param content 文件内容 */ public void saveToSDCard(String filename,String content) throws Exception{ File file=new File("/mnt/sdcard", filename); Output

Android编程之在SD卡上进行文件读写操作实例详解

本文实例讲述了Android编程之在SD卡上进行文件读写操作的方法.分享给大家供大家参考,具体如下: 很多知识只有真正理解掌握之后才能运用自如,举一反三.对Java中的文件操作和android系统SD卡里面的文件操作,你觉得有区别吗,显然没有本质区别,如果勉强说有,那也是不足为道滴,但我们在实际运用中却要注意如下几点,不然问题会缠上你. 1.首先想要对android系统SD卡里文件操作需要添加使用权限: android系统是不会让外来程序随意动自己内存的,如果没有许可证,不好意思,不准你动我地盘

Android编程之判断SD卡状态的方法

本文实例讲述了Android编程之判断SD卡状态的方法.分享给大家供大家参考,具体如下: 首先我们要在AndroidManifest.xml中增加SD卡访问权限: <!-- 在SDCard 的挂载权限 --> <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/> <!-- 往SDCard写入数据权限 --> <uses-permission

Android获取监听SD卡状态

项目简介: 该项目监听SD卡状态 详细介绍: 部署该应用后,当SD卡状态发送改变时,系统会发出吐司通知用户 该应用涉及到的知识有: 1.Android的四大组件之一:广播接收者的使用 2.SD卡状态的广播 注意: 1.只有在 2.x 模拟器上才能对SD卡进行操作,所以该应用应当部署在 2.x 的模拟器上 2.一个广播接收者是可以接受多个广播的,直接添加多个action就可以了 3.SD卡不同的状态有不同的广播,主要有三种: <action android:name="android.int

Android N获取外置SD卡或挂载U盘路径的方法

在Android N上并没有提供直接的方法获取外置SD卡或挂载U盘路径,可以通过下面方法获取内置sd卡路径 Environment.getExternalStorageDirectory().getAbsolutePath(); 通过查看getExternalStorageDirectory源码发现,Android只是没有公开的接口获取而已 public static File getExternalStorageDirectory() { throwIfUserRequired(); retu