Android保存图片到相册,兼容Android10及以上版本

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):

 /**
     * 保存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 扩展方法:

/**
 * 和媒体相关的工具类
 */
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);
    }
}

猜你喜欢

转载自blog.csdn.net/zhijiandedaima/article/details/131435105