「Android」存储空间:分享与拜访
Android 存储空间概览
存储地位的类别
- 外部存储空间目录:这些目录既包含用于存储持久性文件的专属地位,也包含用于存储缓存数据的其余地位。零碎会阻止其余利用拜访这些地位,并且在 Android 10(API 级别 29)及更高版本中,零碎会对这些地位进行加密。这些特色使得这些地位非常适合存储只有利用自身能力拜访的敏感数据。
-
内部存储空间目录:这些目录既包含用于存储持久性文件的专属地位,也包含用于存储缓存数据的其余地位。尽管其余利用能够在具备适当权限的状况下拜访这些目录,但存储在这些目录中的文件仅供给用本人应用。如果明确打算创立其余利用可能拜访的文件,利用应改为将这些文件存储在内部存储空间的共享存储空间局部。
- 利用专属存储空间:存储利用公有数据,内部存储利用公有目录对应 Android/data/packagename,外部存储利用公有目录对应 data/data/packagename。利用公有目录拜访通过 File path
- 共享存储:存储其余利用可拜访文件,蕴含媒体文件、文档文件以及其余文件,对应设施 DCIM、Pictures、Alarms、Music、Notifications、Podcasts、Ringtones、Movies、Download 等目录。共享目录拜访通过 MediaStore API 或者 Storage Access Framework
对外部存储空间的拜访和所需权限
Android 为对外部存储空间的读写访问定义了以下权限:READ_EXTERNAL_STORAGE 和 WRITE_EXTERNAL_STORAGE。
在较低版本的 Android 零碎中,利用须要申明这些权限能力拜访位于内部存储空间中利用专属目录之外的任何文件。Android 零碎的版本越新,就越依赖于文件的用处而不是地位来确定利用对文件的拜访能力。这种基于用处的存储模型可加强用户隐衷爱护,因为利用只能拜访其在设施文件系统中理论应用的区域。
分区存储
为了让用户更好地治理本人的文件并缩小凌乱,以 Android 10(API 级别 29)及更高版本为指标平台的利用在默认状况下被赋予了对外部存储空间的分区拜访权限(即分区存储)。此类利用只能拜访内部存储空间上的利用专属目录,以及本利用所创立的特定类型的媒体文件。
除非利用须要拜访存储在 利用专属目录 和 MediaStore API 能够拜访的目录 之外的文件,否则请应用分区存储。如果您将利用专属文件存储在内部存储空间中,则能够将这些文件寄存在内部存储空间中的利用专属目录内,以便更加轻松地采纳分区存储。这样,在启用分区存储后,您的利用将能够持续拜访这些文件。
罕用的 MIME 类型
text/plain
、text/rtf
、text/html
、text/json
,接管方应注册text/*
image/jpg
、image/png
、image/gif
,接管方应注册image/*
video/mp4
、video/3gp
,接管方应注册video/*
application/pdf
,接管方应注册反对的文件扩展名
如何分享文件?
利用通常须要将本人的一个或多个文件提供给其余利用。例如,图库可能须要向图片编辑器提供文件,或者文件治理利用可能须要容许用户在内部存储区域之间复制和粘贴文件。发送方利用能够通过响应来自接管方利用的申请分享文件。
在所有状况下,如需将利用中的文件提供给其余利用,惟一平安的做法就是向接管方利用发送 文件的内容 URI,并授予对该 URI 的 长期拜访权限。具备长期 URI 拜访权限的内容 URI 之所以平安,是因为它们仅供接管该 URI 的利用应用,并且会主动过期。Android FileProvider 组件提供了 getUriForFile() 办法,用于生成文件的内容 URI。
若要平安地将利用中的文件提供给其余利用,须要配置利用,以内容 URI 的模式提供文件的平安句柄。Android FileProvider 组件会依据 XML 中指定的内容生成文件的内容 URI。
若要为利用定义 FileProvider,须要在利用清单中增加一个条目。此条目指定生成内容 URI 时应用的受权以及 XML 文件的名称,该 XML 文件指定利用可共享的目录。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.myapp">
<application
...>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.example.myapp.fileprovider"
android:grantUriPermissions="true"
android:exported="false">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/filepaths" />
</provider>
...
</application>
</manifest>
android:authorities 属性指定心愿用于由 FileProvider 生成的内容 URI 的 URI 受权。示例中的受权为 com.example.myapp.fileprovider。请指定由利用的 android:package 值加上字符串“fileprovider”形成的受权值。
<provider> 的 <meta-data> 子元素指向一个 XML 文件,该文件指定要共享的目录。android:resource 属性是该文件的门路和名称,不蕴含 .xml 扩展名。
将 FileProvider 增加到利用清单后,须要指定蕴含要共享的文件的目录。若要指定目录,首先须要在我的项目的 res/xml/ 子目录中创立 filepaths.xml 文件。在此文件中,通过为每个目录增加 XML 元素以指定目录。以下代码段展现了 res/xml/filepaths.xml 的内容示例。该代码段还展现了如何共享外部存储区域中的 files/ 目录的子目录:
<paths>
<files-path path="images/" name="myimages" />
</paths>
<files-path> 标记共享了利用外部存储空间的 files/ 目录中的目录。path 属性共享了 files/ 的 images/ 子目录。name 属性批示 FileProvider 将门路段 myimages 增加到 files/images/ 子目录中文件的内容 URI 中。
<paths> 元素能够有多个子元素,每个子元素指定一个不同的共享目录。除了 <files-path> 元素之外,还能够应用 <external-path> 元素共享内部存储空间中的目录,应用 <cache-path> 元素共享外部缓存目录中的目录。
曾经残缺地指定了 FileProvider,该提供器可用于为利用外部存储空间中的 files/ 目录中的文件或 files/ 的子目录中的文件生成内容 URI。当利用为文件生成内容 URI 时,会蕴含 <provider> 元素中指定的受权 (com.example.myapp.fileprovider)、门路 myimages/ 以及文件的名称。
如果依据本问中的代码段定义 FileProvider,并申请文件 default_image.jpg 的内容 URI,FileProvider 将返回以下 URI:
content://com.example.myapp.fileprovider/myimages/default_image.jpg
如何拜访文件?
当应用间接文件门路拜访文件时:
Android 9 及以下
Old Way。
Android 10
须要停用分区存储:
<manifest ... >
<!-- This attribute is "false" by default on apps targeting
Android 10 or higher. -->
<application android:requestLegacyExternalStorage="true" ... >
...
</application>
</manifest>
其余则与 Android 9 及以下保持一致即可。
Android 11
- 申请 READ_EXTERNAL_STORAGE 权限
- 为了从媒体库中拜访文件,请应用
ContentResolver
对象:
String[] projection = new String[] {media-database-columns-to-retrieve};
String selection = sql-where-clause-with-placeholder-variables;
String[] selectionArgs = new String[] {values-of-placeholder-variables};
String sortOrder = sql-order-by-clause;
Cursor cursor = getApplicationContext().getContentResolver().query(
MediaStore.media-type.Media.EXTERNAL_CONTENT_URI,
projection,
selection,
selectionArgs,
sortOrder
);
while (cursor.moveToNext()) {
// Use an ID column from the projection to get
// a URI representing the media item itself.
}
零碎会主动扫描内部存储卷,并将媒体文件增加到以下明确定义的汇合中:
- 图片(包含照片和屏幕截图),存储在 DCIM/ 和 Pictures/ 目录中。零碎将这些文件增加到 MediaStore.Images 表格中。
- 视频,存储在 DCIM/、Movies/ 和 Pictures/ 目录中。零碎将这些文件增加到 MediaStore.Video 表格中。
- 音频文件,存储在 Alarms/、Audiobooks/、Music/、Notifications/、Podcasts/ 和 Ringtones/ 目录中,以及位于 Music/ 或 Movies/ 目录中的音频播放列表中。零碎将这些文件增加到 MediaStore.Audio 表格中。
- 下载的文件,存储在 Download/ 目录中。在搭载 Android 10(API 级别 29)及更高版本的设施上,这些文件存储在 MediaStore.Downloads 表格中。此表格在 Android 9(API 级别 28)及更低版本中不可用。
媒体库还蕴含一个名为 MediaStore.Files 的汇合。其内容取决于您的利用是否应用分区存储(实用于以 Android 10 或更高版本为指标平台的利用):
- 如果启用了分区存储,汇合只会显示您的利用创立的照片、视频和音频文件。
- 如果分区存储不可用或未应用,汇合将显示所有类型的媒体文件。
例子
获取最新的图片文件:
public static String getLatestImgPath(Context context) {
String imgPath = "";
// 查问门路和批改工夫
String[] projection = {MediaStore.Images.Media.DATA,
MediaStore.Images.Media.DATE_MODIFIED};
// 查问并排序
Cursor cursor = context.getContentResolver().query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
projection,
null,
null,
MediaStore.Files.FileColumns.DATE_MODIFIED + "DESC");
// 增加进 List
if (cursor.moveToNext()) {long time = cursor.getLong(cursor.getColumnIndex(MediaStore.Images.Media.DATE_MODIFIED));
imgPath = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
}
if (!cursor.isClosed()) {cursor.close();
}
return imgPath;
}
如需查找满足一组特定条件(例如时长为 5 分钟或更长时间)的媒体:
// Need the READ_EXTERNAL_STORAGE permission if accessing video files that your
// app didn't create.
// Container for information about each video.
class Video {
private final Uri uri;
private final String name;
private final int duration;
private final int size;
public Video(Uri uri, String name, int duration, int size) {
this.uri = uri;
this.name = name;
this.duration = duration;
this.size = size;
}
}
List<Video> videoList = new ArrayList<Video>();
String[] projection = new String[] {
MediaStore.Video.Media._ID,
MediaStore.Video.Media.DISPLAY_NAME,
MediaStore.Video.Media.DURATION,
MediaStore.Video.Media.SIZE
};
String selection = MediaStore.Video.Media.DURATION +
">= ?";
String[] selectionArgs = new String[] {String.valueOf(TimeUnit.MILLISECONDS.convert(5, TimeUnit.MINUTES));
};
String sortOrder = MediaStore.Video.Media.DISPLAY_NAME + "ASC";
try (Cursor cursor = getApplicationContext().getContentResolver().query(
MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
projection,
selection,
selectionArgs,
sortOrder
)) {
// Cache column indices.
int idColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID);
int nameColumn =
cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DISPLAY_NAME);
int durationColumn =
cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATION);
int sizeColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.SIZE);
while (cursor.moveToNext()) {
// Get values of columns for a given video.
long id = cursor.getLong(idColumn);
String name = cursor.getString(nameColumn);
int duration = cursor.getInt(durationColumn);
int size = cursor.getInt(sizeColumn);
Uri contentUri = ContentUris.withAppendedId(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, id);
// Stores column values and the contentUri in a local object
// that represents the media file.
videoList.add(new Video(contentUri, name, duration, size));
}
}
参考
https://blog.csdn.net/houdada…
https://blog.csdn.net/jingzz1…
https://www.jianshu.com/p/d0c…
https://www.jianshu.com/p/d55…
https://www.hurryyu.com/2020/…
https://developer.android.com…
https://developer.android.com…
https://developer.android.goo…
https://developer.android.goo…
https://developer.android.goo…
https://blog.csdn.net/guolin_…
https://github.com/guolindev/…
https://guolin.blog.csdn.net/…
https://blog.csdn.net/da_caoy…