Android 共享存储空间
访问共享存储空间中的媒体文件
1、MediaStore概述
MediaStore是android系统提供的一个多媒体数据库,专门用于存放多媒体信息的,通过ContentResolver即可对数据库进行操作。
- MediaStore.Files: 共享的文件,包括多媒体和非多媒体信息;
- MediaStore.Audio: 存放音频信息;
- MediaStore.Image: 存放图片信息;
- MediaStore.Vedio: 存放视频信息;
每个内部类中都又包含了Media,Thumbnails和相应的MediaColumns,分别提供了媒体信息,缩略信息和操作字段。
(1)MediaStore.Images.Media的使用——MediaStore.Images.Media.EXTERNAL_CONTENT_URI
(2)MediaStore.Video.Media的使用——MediaStore.Video.Media.EXTERNAL_CONTENT_URI
(3)MediaStore.Audio.Media的使用——MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
(4)MediaStore.Downloads的使用——MediaStore.Downloads.getContentUri(“external”)
(5)MediaStore.Files的使用——MediaStore.Files.getContentUri(“external”)
2、示例
1、Android保存图片到相册,兼容Android10及以上版本:
// 保存bitmap到相册,并兼容AndroidQ
public static boolean saveBitmap(Context context, Bitmap bitmap) {
if (bitmap == null) {
return false;
}
boolean isSaveSuccess;
if (Build.VERSION.SDK_INT < 29) {
isSaveSuccess = saveImageToGallery(context, bitmap);
} else {
isSaveSuccess = saveImageToGalleryQ(context, bitmap);
}
return isSaveSuccess;
}
/**
* android 10 以下版本
*/
private static boolean saveImageToGallery(Context context, Bitmap image) {
// 首先保存图片
String storePath = AssetPathUtil.INSTANCE.getBitmapFileDir();
File appDir = new File(storePath);
if (!appDir.exists()) {
appDir.mkdir();
}
String fileName = getCharacterAndNumber() + ".png";
File file = new File(appDir, fileName);
try {
FileOutputStream fos = new FileOutputStream(file);
// 通过io流的方式来压缩保存图片
boolean isSuccess = image.compress(Bitmap.CompressFormat.JPEG, 100, fos);
fos.flush();
fos.close();
// 保存图片后发送广播通知更新数据库
Uri uri = Uri.fromFile(file);
context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri));
if (isSuccess) {
return true;
} else {
return false;
}
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
/**
* android 10 以上版本
*/
private static boolean saveImageToGalleryQ(Context context, Bitmap image) {
long mImageTime = System.currentTimeMillis();
String mImageFileName = getCharacterAndNumber() + ".png";
final ContentValues values = new ContentValues();
values.put(MediaStore.MediaColumns.RELATIVE_PATH, AssetPathUtil.INSTANCE.getBitmapFileDir());
values.put(MediaStore.MediaColumns.DISPLAY_NAME, mImageFileName);
values.put(MediaStore.MediaColumns.MIME_TYPE, "image/png");
values.put(MediaStore.MediaColumns.DATE_ADDED, mImageTime / 1000);
values.put(MediaStore.MediaColumns.DATE_MODIFIED, mImageTime / 1000);
values.put(MediaStore.MediaColumns.DATE_EXPIRES, (mImageTime + DateUtils.DAY_IN_MILLIS) / 1000);
values.put(MediaStore.MediaColumns.IS_PENDING, 1);
ContentResolver resolver = context.getContentResolver();
final Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
try {
OutputStream out = resolver.openOutputStream(uri);
if (!image.compress(Bitmap.CompressFormat.PNG, 100, out)) {
return false;
}
values.clear();
values.put(MediaStore.MediaColumns.IS_PENDING, 0);
values.putNull(MediaStore.MediaColumns.DATE_EXPIRES);
resolver.update(uri, values, null, null);
} catch (Exception e) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
resolver.delete(uri, null);
}
return false;
}
return true;
}
2、删除相册资源(Kotlin):文章来源:https://www.toymoban.com/news/detail-507844.html
/**
* 保存bitmap到相册,并兼容AndroidQ
*/
fun getBitmapFileDir(): String {
return if (Build.VERSION.SDK_INT < 29) {
// android 10 以下版本
Environment.getExternalStorageDirectory().absolutePath + File.separator + BITMAP_FILE_DIRECTORY
} else {
Environment.DIRECTORY_PICTURES + File.separator + BITMAP_FILE_DIRECTORY
}
}
/**
* 删除保存在【相册指定目录】中的图片
*/
fun deleteBitmapFileDir(context: Context) {
if (Build.VERSION.SDK_INT < 29) {
// android 10 以下版本
val path = getBitmapFileDir()
FileUtils.deleteAllInDir(path)
} else {
deleteImages(context)
}
}
/**
* android 10及以上版本通过ContentResolver删除指定的目录
*/
private fun deleteImages(context: Context) {
var relativePath = getBitmapFileDir()
//判断是否有加斜杠
if (!relativePath.endsWith("/")) {
relativePath += File.separator
}
val projection = arrayOf(MediaStore.Images.Media._ID, MediaStore.Images.Media.RELATIVE_PATH)
val selection = "${MediaStore.Images.Media.RELATIVE_PATH}=?"
val selectionArgs = arrayOf(relativePath)
context.contentResolver.delete(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, selection, arrayOf(relativePath))
}
3、MediaStore 扩展方法:文章来源地址https://www.toymoban.com/news/detail-507844.html
/**
* 和媒体相关的工具类
*/
public class MediaUtils {
/**
* 每页获取的媒体数据量(分页加载解决相册卡顿问题)
* */
private static final int PAGE_SIZE = 1000;
/**
* 添加到媒体数据库
* Add to media database
*/
public static Uri saveVideo2Album(String videoPath, int videoWidth, int videoHeight,
int videoTime) {
File file = new File(videoPath);
if (file.exists()) {
Uri uri = null;
long dateTaken = System.currentTimeMillis();
ContentValues values = new ContentValues(11);
// 路径;
values.put(MediaStore.Video.Media.DATA, videoPath);
// 标题;
values.put(MediaStore.Video.Media.TITLE, file.getName());
// 时长
values.put(MediaStore.Video.Media.DURATION, videoTime * 1000);
// 视频宽
values.put(MediaStore.Video.Media.WIDTH, videoWidth);
// 视频高
values.put(MediaStore.Video.Media.HEIGHT, videoHeight);
// 视频大小;
values.put(MediaStore.Video.Media.SIZE, file.length());
// 插入时间;
values.put(MediaStore.Video.Media.DATE_TAKEN, dateTaken);
// 文件名;
values.put(MediaStore.Video.Media.DISPLAY_NAME, file.getName());
// 修改时间;
values.put(MediaStore.Video.Media.DATE_MODIFIED, dateTaken / 1000);
// 添加时间;
values.put(MediaStore.Video.Media.DATE_ADDED, dateTaken / 1000);
values.put(MediaStore.Video.Media.MIME_TYPE, "video/mp4");
ContentResolver resolver = Utils.getApp().getContentResolver();
if (resolver != null) {
try {
uri = resolver.insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, values);
} catch (Exception e) {
e.printStackTrace();
uri = null;
}
}
if (uri == null) {
MediaScannerConnection.scanFile(Utils.getApp(), new String[]{videoPath}, new String[]{"video/*"}, new MediaScannerConnection.OnScanCompletedListener() {
@Override
public void onScanCompleted(String path, Uri uri) {
}
});
}
return uri;
}
return null;
}
/**
* Add video record by android_Q api.
* AndroidQ以上,增加视频文件记录
*
* @param context 上下文,the context
* @param fileName 视频文件名称 the video file name
* @param fileType 视频文件类型 the video file type
* @param relativePath 相对路径 the relative path
* @param duration 文件时长,单位是毫秒The duration of the file, in milliseconds.
* @return String 类型的Uri. The String of Uri
*/
public static String addVideoRecord_Q(Context context, String fileName, String fileType,
String relativePath, long duration) {
if (!AndroidVersionUtils.isAboveAndroid_Q()) {
return null;
}
if (TextUtils.isEmpty(relativePath)) {
return null;
}
relativePath = "Movies/" + relativePath;
ContentResolver resolver = context.getContentResolver();
//设置文件参数到ContentValues中
ContentValues values = new ContentValues();
//设置文件名
values.put(MediaStore.Video.Media.DISPLAY_NAME, fileName);
//设置文件描述,这里以文件名代替
values.put(MediaStore.Video.Media.DESCRIPTION, fileName);
//设置文件类型为image/*
values.put(MediaStore.Video.Media.MIME_TYPE, "video/" + fileType);
values.put(MediaStore.Video.Media.DATE_ADDED, System.currentTimeMillis() / 1000);
values.put(MediaStore.Video.Media.DATE_MODIFIED, System.currentTimeMillis() / 1000);
values.put(MediaStore.Video.Media.DURATION, duration);
//注意:MediaStore.Images.Media.RELATIVE_PATH需要targetSdkVersion=29,
//故该方法只可在Android10的手机上执行
values.put(MediaStore.Video.Media.RELATIVE_PATH, relativePath);
//EXTERNAL_CONTENT_URI代表外部存储器
Uri external = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
//insertUri表示文件保存的uri路径
Uri insertUri = resolver.insert(external, values);
return String.valueOf(insertUri);
}
/**
* Delete video record by android_Q api
* AndroidQ以上删除视频记录
*
* @param context 上下文,the context
* @param uri 文件的uri the uri of file
* @return 删除的数量,如果是0,代表删除失败 The number of deletions. If it is 0, it means the deletion failed
*/
public static int deleteVideoRecord_Q(Context context, Uri uri) {
context = context.getApplicationContext();
ContentResolver contentResolver = context.getContentResolver();
if (contentResolver == null) {
return 0;
}
Cursor cursor = null;
String column = MediaStore.MediaColumns._ID;
int fileID = -1;
try {
cursor = contentResolver.query(uri, new String[]{column}, null, null,
null);
if (cursor != null && cursor.moveToFirst()) {
int column_index = cursor.getColumnIndexOrThrow(column);
fileID = cursor.getInt(column_index);
}
} catch (Exception e) {
LogUtils.e(e);
} finally {
if (cursor != null) {
cursor.close();
}
}
if (fileID >= 0) {
return contentResolver.delete(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, column + "=?", new String[]{String.valueOf(fileID)});
}
return 0;
}
/**
* 获取媒体数据列表
* Gets a list of media data
*
* @param type TYPE_VIDEO = 0; 视频 TYPE_PHOTO = 1;图片;YPE_ALL = 2;图片和视频
*/
public static void getMediaList(final int type, final String[] filter, final MediaCallback callback, int mCurrentPage) {
ThreadUtils.getCachedPool().execute(new Runnable() {
@Override
public void run() {
final List<MediaData> dataList = new ArrayList<>();
if (type == MediaData.TYPE_ALL) {
Cursor cursor = getMediaCursor(MediaData.TYPE_PHOTO, mCurrentPage);
if (cursor != null) {
createMediaData(cursor, filter, dataList, false);
cursor.close();
}
cursor = getMediaCursor(MediaData.TYPE_VIDEO, mCurrentPage);
if (cursor != null) {
createMediaData(cursor, filter, dataList, true);
cursor.close();
}
} else {
Cursor cursor = getMediaCursor(type, mCurrentPage);
if (cursor != null) {
createMediaData(cursor,filter, dataList, type == MediaData.TYPE_VIDEO);
cursor.close();
}
}
if (callback != null) {
ThreadUtils.runOnUiThread(new Runnable() {
@Override
public void run() {
callback.onResult(dataList);
}
});
}
}
});
}
@SuppressLint("InlinedApi")
private static Cursor getMediaCursor(int type, int mCurrentPage) {
String[] projection = null;
Uri uri = null;
String order = null;
if (type == MediaData.TYPE_VIDEO) {
projection = new String[]{MediaStore.Video.Thumbnails._ID
, MediaStore.Video.Media._ID
, MediaStore.Video.Thumbnails.DATA
, MediaStore.Video.Media.DURATION
, MediaStore.Video.Media.SIZE
, MediaStore.Video.Media.DATE_ADDED
, MediaStore.Video.Media.DISPLAY_NAME
, MediaStore.Video.Media.DATE_MODIFIED};
uri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
order = MediaStore.Video.Media.DATE_ADDED;
} else if (type == MediaData.TYPE_PHOTO) {
projection = new String[]{
MediaStore.Images.Media._ID,
MediaStore.Images.Media.DATA,
MediaStore.Images.Media.DATE_ADDED,
MediaStore.Images.Thumbnails.DATA,
MediaStore.MediaColumns.DISPLAY_NAME
};
uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
order = MediaStore.Images.Media.DATE_ADDED;
}
if (projection == null) {
return null;
}
// 兼容折叠屏,在Android R及以上手机,order中禁止了LIMIT关键字,所以在这里做了适配
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
Bundle bundle = new Bundle();
bundle.putInt(ContentResolver.QUERY_ARG_OFFSET, mCurrentPage * PAGE_SIZE);
bundle.putInt(ContentResolver.QUERY_ARG_LIMIT, PAGE_SIZE);
bundle.putStringArray(ContentResolver.QUERY_ARG_SORT_COLUMNS, new String[]{order + " DESC"});
return Utils.getApp().getContentResolver().query(uri, projection, bundle, null);
} else {
String sortOrder = order + " DESC LIMIT " + PAGE_SIZE + " OFFSET " + mCurrentPage * PAGE_SIZE;
return Utils.getApp().getContentResolver().query(uri, projection, null, null, sortOrder);
}
}
/**
* 创建媒体实体类并添加到集合中
* Create a media entity class and add it to the collection
*/
@SuppressLint("InlinedApi")
private static void createMediaData(Cursor cursor, String[] filter, List<MediaData> list, boolean isVideo) {
if (cursor != null) {
String mediaId;
String mediaDate;
String mediaThumbnails;
String mediaDisplayName;
if (isVideo) {
mediaId = MediaStore.Video.Media._ID;
mediaDate = MediaStore.Video.Media.DATE_ADDED;
mediaThumbnails = MediaStore.Video.Thumbnails.DATA;
mediaDisplayName = MediaStore.Video.Media.DISPLAY_NAME;
} else {
mediaId = MediaStore.Images.Media._ID;
mediaDate = MediaStore.Images.Media.DATE_ADDED;
mediaThumbnails = MediaStore.Images.Thumbnails.DATA;
mediaDisplayName = MediaStore.Images.Media.DISPLAY_NAME;
}
while (cursor.moveToNext()) {
int videoId = cursor.getInt(cursor.getColumnIndex(mediaId));
String absolutePath = cursor.getString(cursor.getColumnIndex(mediaThumbnails));
String path = AndroidVersionUtils.isAboveAndroid_Q()? AndroidVersionUtils.getRealPathAndroid_Q(Uri.parse(absolutePath)): absolutePath;
String displayName = cursor.getString(cursor.getColumnIndex(mediaDisplayName));
int timeIndex = cursor.getColumnIndex(mediaDate);
long date = cursor.getLong(timeIndex) * 1000;
if (fileIsValid(path)) {
if (!TextUtils.isEmpty(absolutePath)) {
int lastDot = absolutePath.lastIndexOf(".");
String type = absolutePath.substring(lastDot + 1);
if (!TextUtils.isEmpty(type)) {
type = type.toLowerCase();
if (type.equals("mpg") || type.equals("mkv")) {
continue;
}
//过滤文件类型
boolean isFilter = false;
if (filter != null && filter.length > 0) {
for (int i = 0; i < filter.length; i++) {
String filterItem = filter[i];
if (StringUtils.equals(filterItem, type)) {
isFilter = true;
break;
}
}
}
if (isFilter) {
continue;
}
}
}
MediaData mediaData = new MediaData()
.setId(videoId)
.setPath(path)
.setDate(date)
.setDisplayName(displayName);
if (isVideo) {
mediaData.setType(MediaData.TYPE_VIDEO)
.setDuration(cursor.getLong(cursor.getColumnIndex(MediaStore.Video.Media.DURATION)));
} else {
mediaData.setType(MediaData.TYPE_PHOTO);
}
if (isGif(path)){
mediaData.setIsGif(true);
mediaData.setType(MediaData.TYPE_VIDEO);
}
list.add(mediaData);
}
}
}
}
/**
* 判断是否是gif图片
* Is gif boolean.
*
* @param path the path
* @return the boolean
*/
public static boolean isGif(String path) {
String fileName = FileUtils.getFileSuffix(path);
if (!TextUtils.isEmpty(fileName) && "GIF".equals(fileName.toUpperCase())) {
return true;
}
return false;
}
private static boolean fileIsValid(String filePath) {
if (FileUtils.isAndroidQUriPath(filePath)) {
return true;
}
File file = FileUtils.getFileByPath(filePath);
return file != null && file.exists();
}
public interface MediaCallback {
void onResult(List<MediaData> dataList);
}
}
到了这里,关于Android保存图片到相册,兼容Android10及以上版本的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!