前言:
现时同学们对“android显示本地html”都比较关切,我们都需要剖析一些“android显示本地html”的相关文章。那么小编在网络上网罗了一些关于“android显示本地html””的相关资讯,希望看官们能喜欢,看官们一起来学习一下吧!作者:GrayMonkey
链接:
声明:本文已获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。
摘自官方文档
摘自官方文档
文档链接:
基本使用通过Intent启动SAF授权界面,注意URI的百分号编解码(%3A和%2F),别随意替换,否则SAF无法导航到Android/data目录
@TargetApi(26) private void requestAccessAndroidData(Activity activity){ try { Uri uri = Uri.parse("content://com.android.externalstorage.documents/document/primary%3AAndroid%2Fdata"); 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%3AAndroid%2Fdata")) { 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源码查看网址:
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<File> 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的
标签: #android显示本地html