很多小伙伴比较关心android11如何访问data(安卓11访问data文件夹的方法),本文带大家一起看看android11如何访问data(安卓11访问data文件夹的方法)。

作者:GrayMonkey链接:https://www.jianshu.com/p/d02bc6266198声明:本文已获GrayMonkey授权发表,转发等请联系原作者授权

前言

Android R上分区存储的限制得到进一步加强,无论APP的targetsdkversion是多少,都将无法访问Android/data和Android/obb这二个应用私有目录。这无疑对会部分APP的业务场景及用户体验造成冲击,典型的如下

文件管理类软件:微信、QQ传输的文件无法展示给用户以便捷使用垃圾清理类软件:清理缓存功能受阻

“你有你的张良计,我有我的过墙梯”,现市面上文件管理类软件(如MT管理器)已解决上述系统限制,本文将浅析其实现方案,并主要分析以下2个问题:

SAF是通过何种方式访问文件系统的,MediaStore API ? File API ? Native Code ?SAF为何能访问Android/data目录实现方案

其实现方案很简单,就是通过Intent ACTION_OPEN_DOCUMENT_TREE,启动SAF让用户授权访问Android/data目录,属于官方公开的方法。

前提是APP的targetsdkversion要小于30。

摘自官方文档

摘自官方文档

文档链接:

https://developer.android.com/about/versions/11/privacy/storage#file-directory-restrictionshttps://developer.android.com/training/data-storage/shared/documents-files#grant-access-directory基本使用通过Intent启动SAF授权界面,注意URI的百分号编解码(:和/),别随意替换,否则SAF无法导航到Android/data目录?????@TargetApi(26)????private?void?requestAccessAndroidData(Activity?activity){????????try?{????????????Uri?uri?=?Uri.parse("content://com.android.externalstorage.documents/document/primary:Android/data");????????????Intent?intent?=?new?Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);????????????intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI,?uri);????????????//flag看实际业务需要可再补充????????????intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION????????????????????????????|?Intent.FLAG_GRANT_WRITE_URI_PERMISSION????????????????????????????|?Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);????????????activity.startActivityForResult(intent,?6666);????????}?catch?(Exception?e)?{????????????e.printStackTrace();????????}????}?

授权申请

在用户同意授权后,持久化uri权限(否则关机重启或授权界面finish后,APP就无权限访问了),并只能通过DocumentFile进行业务操作,File API操作是无效的,此授权只是授权uri操作,并未授权文件系统,后续章节有说明。?implementation?"androidx.documentfile:documentfile:1.0.1"??@Override????protected?void?onActivityResult(int?requestCode,?int?resultCode,?@Nullable?Intent?data)?{????????super.onActivityResult(requestCode,?resultCode,?data);????????switch?(requestCode)?{????????????case?6666:????????????????if?(resultCode?==?Activity.RESULT_OK)?{????????????????????//persist?uri?????????????????????getContentResolver().takePersistableUriPermission(data.getData(),????????????????????????????Intent.FLAG_GRANT_READ_URI_PERMISSION????????????????????????????????????|?Intent.FLAG_GRANT_WRITE_URI_PERMISSION);????????????????????//now?use?DocumentFile?to?do?some?file?op????????????????????DocumentFile?documentFile?=?DocumentFile????????????????????????????.fromTreeUri(this,?data.getData());????????????????????DocumentFile[]?files?=?documentFile.listFiles();????????????????????......????????????????}????????????????break;????????????default:????????????????break;????????}????}注意这个授权用户是可以撤回的,通过点击应用信息界面的存储,就会看到撤回界面,所以业务需要去动态判断?public?boolean?isGrantAndroidData(Context?context)?{????????for?(UriPermission?persistedUriPermission?:?context.getContentResolver().getPersistedUriPermissions())?{????????????if?(persistedUriPermission.getUri().toString().????????????????????equals("content://com.android.externalstorage.documents/tree/primary:Android/data"))?{????????????????return?true;????????????}????????}????????return?false;????}

授权撤回

拓展

通过前面二个章节,已经介绍了实现方案的基本使用,下面就该分析本文的亮点内容了

SAF是通过何种方式访问文件系统的,MediaStore API ? File API ? Native Code ?SAF为何能访问Android/data目录

存储访问框架(SAF)简介

为方便后续讲解,先简单回顾下SAF

SAF架构

APP:

com.example.photos就是我们自己的APP

System UI:

com.google.android.documentsui,一般称作DoucmentUI,就是上文中启动的授权界面APP,它只是个UI壳子

DocumentProvider:

DocumentUI中数据的提供者,这个Provider可以有很多 com.android.externalstorage,是本地文件系统的Provider

关于SAF更详细介绍,请参考官方存储访问框架 经过SAF的简单介绍,分析目标很明确,那就是com.android.externalstorage

SAF是通过何种方式访问文件系统的

先安利几个AOSP源码查看网址:

https://cs.android.com/android/platform/superproject/http://aospxref.com/

PS:后文源码链接都用的是XREF,方便国内查看

从DocumentFile#listFile入手,经过源码跟踪会发现最终会调用 DocumentsProvider#queryChildDocuments方法

public?abstract?class?DocumentsProvider?extends?ContentProvider?{?.......?@Override????public?final?Cursor?query(????????????Uri?uri,?String[]?projection,?Bundle?queryArgs,?CancellationSignal?cancellationSignal)?{???????switch?(mMatcher.match(uri))?{????????????????......????????????????case?MATCH_CHILDREN:????????????????case?MATCH_CHILDREN_TREE:????????????????????????.......????????????????????????return?queryChildDocuments(getDocumentId(uri),?projection,?queryArgs);????????????????????????......????????????????default:????????????????????throw?new?UnsupportedOperationException("Unsupported?Uri?"? ?uri);????????????}????????}?catch?(FileNotFoundException?e)?{????????????Log.w(TAG,?"Failed?during?query",?e);????????????return?null;????????}?????????}?......}

接下来看看com.android.externalstorage中DocumentProvider的实现类 ExternalStorageProvider:frameworks/base/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java

import?com.android.internal.content.FileSystemProvider;public?class?ExternalStorageProvider?extends?FileSystemProvider?

queryChildDocuments的实现位于其父类 FileSystemProvider

public?abstract?class?FileSystemProvider?extends?DocumentsProvider?{??......??private?Cursor?queryChildDocuments(????????????String?parentDocumentId,?String[]?projection,?String?sortOrder,????????????@NonNull?Predicate?filter)?throws?FileNotFoundException?{????????final?File?parent?=?getFileForDocId(parentDocumentId);????????final?MatrixCursor?result?=?new?DirectoryCursor(????????????????resolveProjection(projection),?parentDocumentId,?parent);????????if?(parent.isDirectory())?{????????????//重点是这行????????????for?(File?file?:?FileUtils.listFilesOrEmpty(parent))?{????????????????if?(filter.test(file))?{????????????????????includeFile(result,?null,?file);????????????????}????????????}????????}?else?{????????????Log.w(TAG,?"parentDocumentId?'"? ?parentDocumentId? ?"'?is?not?Directory");????????}????????return?result;????}?......}

FileUtils#listFilesOrEmpty

????/**?{@hide}?*/????public?static?@NonNull?File[]?listFilesOrEmpty(@Nullable?File?dir)?{????????return?(dir?!=?null)???ArrayUtils.defeatNullable(dir.listFiles())????????????????:?ArrayUtils.EMPTY_FILE;????}

至此,第一个问题,已经理清:SAF的ExternalStorageProvider最终也是通过File API来访问文件系统的

那么第二个问题,就很自然地来了,都是File API操作,为何我们的APP就不能访问呢?

SAF为何能访问Android/data目录

既然,SAF和我们的APP都是File API操作,那我们就去看看com.android.externalstorage属于哪些用户组。adb shell 查查com.android.externalstorage进程的用户组

#查进程ID generic_x86_arm:/ $ ps -A|grep com.android.external u0_a64 16233 296 1256792 85960 0 0 S com.android.externalstorage #查进程所属的用户组

generic_x86_arm:/?$?cat?/proc/16233/statusName:???externalstorageUmask:??0077State:??S?(sleeping)Tgid:???16233Ngid:???0Pid:????16233PPid:???296TracerPid:??????0Uid:????10064???10064???10064???10064Gid:????10064???10064???10064???10064FDSize:?64#重点关注这行输出Groups:?1015?1077?1078?1079?9997?20064?50064

拿着这些神秘的GID在前面介绍的网址中一搜,就会很容易地发现GID的定义类 android_filesystem_config.h

#define?AID_SDCARD_RW?1015???????/*?external?storage?write?access?*/#define?AID_EXTERNAL_STORAGE?1077?/*?Full?external?storage?access?including?USB?OTG?volumes?*/#define?AID_EXT_DATA_RW?1078??????/*?GID?for?app-private?data?directories?on?external?storage?*/#define?AID_EXT_OBB_RW?1079???????/*?GID?for?OBB?directories?on?external?storage?*/#define?AID_EVERYBODY?9997????????/*?shared?between?all?apps?in?the?same?profile?*/

其中1078和1079分别对应Android/data和Android/obb的访问权限 如果我们APP能通过某种方式获取到1078和1079的用户组权限,岂不妙哉?遗憾的是,对于三方APP这是不可能的,除非是手机厂商的预置的系统APP

总结Android R上可通过SAF获得访问Android/data和Android/obb目录的权限,前提是APP targetsdkversion 小于30SAF的底层实现ExternalStorageProvider也是通过File API来访问文件系统的SAF之所以能访问Android/data和Android/obb是因为ExternalStorageProvider

进程具有GID 1078 和1079,三方APP是不可能拥有这些GID的

更多android11如何访问data(安卓11访问data文件夹的方法)请持续关注本站。