论Android 9.0 外置sdcard 读写

前言

在上一篇博文 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 的权限和挂载问题

 

 

 

你可能感兴趣的:(android)