android11如何访问data(安卓11访问data文件夹的方法)
很多小伙伴比较关心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文件夹的方法)请持续关注本站。