在上一篇博文 Android 9.0中sdcard 的权限和挂载问题 中分析了Android 9.0 中sdcard 挂载和权限问题,大概知道外置sdcard 读写失败的因素。但是在预置的应用(例如DocumentsUI)中有很多都是可以直接对外置sdcard 读写而并不需要对应的权限,甚至是storage 的两个runtime permission都无需申请,这个是为什么呢?本文会针对这个问题进行分析。
查看DocumentUI代码之后会发现,当创建目录的时候会调用:
protected DocumentInfo doInBackground(Void... params) {
final ContentResolver resolver = mActivity.getContentResolver();
ContentProviderClient client = null;
try {
client = DocumentsApplication.acquireUnstableProviderOrThrow(
resolver, mCwd.derivedUri.getAuthority());
final Uri childUri = DocumentsContract.createDocument(
client, mCwd.derivedUri, Document.MIME_TYPE_DIR, mDisplayName);
DocumentInfo doc = DocumentInfo.fromUri(resolver, childUri);
return doc.isDirectory() ? doc : null;
} catch (Exception e) {
Log.w(TAG, "Failed to create directory", e);
return null;
} finally {
ContentProviderClient.releaseQuietly(client);
}
}
其中createDocument() 就是创建文件所在,而这里的authority 在初始化的时候根据选择情况已经确定:
public static boolean isExternalStorageDocument(Uri uri) {
return "com.android.externalstorage.documents".equals(uri.getAuthority());
}
public static boolean isDownloadsDocument(Uri uri) {
return "com.android.providers.downloads.documents".equals(uri.getAuthority());
}
public static boolean isMediaDocument(Uri uri) {
return "com.android.providers.media.documents".equals(uri.getAuthority());
}
所以,createDocument() 会选择不同的Provider,例如这里的sdcard 最终authority 确定为:
com.android.externalstorage.documents
public class ExternalStorageProvider extends FileSystemProvider {
所以最终的createDocument() 调用函数为:
public String createDocument(String docId, String mimeType, String displayName)
throws FileNotFoundException {
displayName = FileUtils.buildValidFatFilename(displayName);
final File parent = getFileForDocId(docId);
if (!parent.isDirectory()) {
throw new IllegalArgumentException("Parent document isn't a directory");
}
final File file = FileUtils.buildUniqueFile(parent, mimeType, displayName);
final String childId;
if (Document.MIME_TYPE_DIR.equals(mimeType)) {
if (!file.mkdir()) {
throw new IllegalStateException("Failed to mkdir " + file);
}
childId = getDocIdForFile(file);
onDocIdChanged(childId);
addFolderToMediaStore(getFileForDocId(childId, true));
} else {
try {
if (!file.createNewFile()) {
throw new IllegalStateException("Failed to touch " + file);
}
childId = getDocIdForFile(file);
onDocIdChanged(childId);
} catch (IOException e) {
throw new IllegalStateException("Failed to touch " + file + ": " + e);
}
}
return childId;
}
代码中其他的逻辑比较简单,其中parent 为本文讨论的关键所在。
有兴趣的可以跟一下代码,最终确定是在ExternalStorageProvider的 updateVolumesLocked() 初始化出来的:
root.path = volume.getInternalPathForUser(userId);
这里有别与volume.getPath() 接口,得到的结果为 /mnt/media_rw/6344-0FEF,而不是上一篇博文说到的 /storage/6344-0FEF
通过这里就可以知道之所以Android 源生应用DocumentsUI 能够在外置sdcard 中读写文件,那是因为在AndroidManifest.xml 中申请了WRITE_MEDIA_STORAGE权限,获取了group media_rw,从而最后能有权限在该节点中进行读写。
权限申请如下:
这里就有点疑问,如果通过WRITE_MEDIA_STORAGE 就可以对外置sdcard 进行读写,而不通过Storage 的两个Runtime permission,从而使用sdcardfs 文件系统,这样android 的目的是什么呢?是不是跟runtime permission 机制相违背呢?
对于storage 的两个runtime permission进行了确认,最终发现应用并不需要grant 两个权限就可以对sdcard 进行读写。
本人觉得这个应该是android 在9.0 上遗留的bug,欢迎存储的朋友一起来讨论下这个问题。
相关文章:
Android 9.0中sdcard 的权限和挂载问题