Register now for Android Dev Summit 2019!

使用范围化目录访问功能

应用(例如照片应用)通常只需要访问外部存储设备中的特定目录,例如 Pictures 目录。现有的外部存储访问方法未经专门设计,无法轻松地为这些类型的应用提供专门的目录访问权限。例如:

  • 在清单中请求 READ_EXTERNAL_STORAGEWRITE_EXTERNAL_STORAGE 允许应用访问外部存储设备上的所有公共目录,而获取的访问权限可能会超出应用需要的范围。
  • 使用存储访问框架通常会让用户通过一个系统界面选择目录,如果应用始终访问同一个外部目录,则此操作没有任何必要。

Android 7.0 提供了简化的 API 来访问常见的外部存储目录。

访问外部存储目录

使用 StorageManager 类获取适当的 StorageVolume 实例。然后,通过调用此实例的 StorageVolume.createAccessIntent() 方法创建一个 intent。使用此 intent 访问外部存储目录。要获取所有可用卷的列表,包括可移动介质卷,请使用 StorageManager.getStorageVolumes()

如果您有关于特定文件的信息,请使用 StorageManager.getStorageVolume(File) 获取包含此文件的 StorageVolume。对此 StorageVolume 调用 createAccessIntent() 可访问文件的外部存储目录。

在次要卷(例如外部 SD 卡)上,调用 createAccessIntent() 时传入 null,可请求访问整个卷(而不是特定目录)。 如果您向主要卷传入 null,或传入无效的目录名,则 createAccessIntent() 将返回 null。

以下代码段展示了如何在主要共享存储空间中打开 Pictures 目录:

Kotlin

    val sm = getSystemService(Context.STORAGE_SERVICE) as StorageManager
    val volume: StorageVolume = sm.primaryStorageVolume
    volume.createAccessIntent(Environment.DIRECTORY_PICTURES).also { intent ->
        startActivityForResult(intent, request_code)
    }
    

Java

    StorageManager sm = (StorageManager)getSystemService(Context.STORAGE_SERVICE);
    StorageVolume volume = sm.getPrimaryStorageVolume();
    Intent intent = volume.createAccessIntent(Environment.DIRECTORY_PICTURES);
    startActivityForResult(intent, request_code);
    

系统会尝试授予对外部目录的访问权限,并使用一个简化的界面向用户确认访问权限(如有必要):

图 1. 请求访问 Pictures 目录的应用。

如果用户授予访问权限,则系统调用 onActivityResult() 替换方法后结果代码为 RESULT_OK,并返回包含 URI 的 intent 数据。使用提供的 URI 访问目录信息,与使用存储访问框架返回的 URI 类似。

如果用户不授予访问权限,则系统调用 onActivityResult() 替换方法后结果代码为 RESULT_CANCELED,并返回空 intent 数据。

获得对特定外部目录的访问权限,即会同时获得对此目录中子目录的访问权限。

访问可移动介质上的目录

要使用“范围化目录访问”访问可移动介质上的目录,首先请添加一个用于监听 MEDIA_MOUNTED 通知的 BroadcastReceiver,例如:

    <receiver
        android:name=".MediaMountedReceiver"
        android:enabled="true"
        android:exported="true" >
        <intent-filter>
            <action android:name="android.intent.action.MEDIA_MOUNTED" />
            <data android:scheme="file" />
        </intent-filter>
    </receiver>
    

当用户装载可移动介质(例如 SD 卡)时,系统会发送 MEDIA_MOUNTED 通知。此通知会在 intent 数据中提供 StorageVolume 对象,您可用它访问可移动介质上的目录。以下示例会访问可移动介质上的 Pictures 目录:

Kotlin

    // BroadcastReceiver has already cached the MEDIA_MOUNTED
    // notification Intent in mediaMountedIntent
    val volume =
        mediaMountedIntent.getParcelableExtra(StorageVolume.EXTRA_STORAGE_VOLUME) as StorageVolume
    volume.createAccessIntent(Environment.DIRECTORY_PICTURES).also { intent ->
        startActivityForResult(intent, request_code)
    }
    

Java

    // BroadcastReceiver has already cached the MEDIA_MOUNTED
    // notification Intent in mediaMountedIntent
    StorageVolume volume = (StorageVolume)
        mediaMountedIntent.getParcelableExtra(StorageVolume.EXTRA_STORAGE_VOLUME);
    Intent intent = volume.createAccessIntent(Environment.DIRECTORY_PICTURES);
    startActivityForResult(intent, request_code);
    

最佳做法

如果可能,请保留外部目录访问权限 URI,这样就不必重复要求用户授予访问权限。在用户授予访问权限后,调用 getContentResolver(),并在返回 ContentResolver 后,使用目录访问权限 URI 调用 takePersistableUriPermission()。系统会保留此 URI,并且后续的访问请求会返回 RESULT_OK,而不会向用户显示确认界面。

如果用户拒绝授予对外部目录的访问权限,请勿立即再次请求访问权限。反复请求访问权限会导致糟糕的用户体验。如果用户拒绝了一项请求,而应用再次请求访问权限,界面便会显示一个不再询问复选框:

图 1. 应用第二次请求访问可移动介质。

如果用户选择不再询问并拒绝请求,则系统会自动拒绝您的应用未来针对给定目录提出的所有请求,不会向用户显示请求界面。