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

这篇具有很好参考价值的文章主要介绍了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 扩展方法:文章来源地址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模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Android保存图片到系统图库并通知系统相册刷新

      在android开发中保存应用的图片并插入到系统图库同时通知相册刷新的功能,做完后发现在部分手机上出现虽然图片保存成功了,但是相册却找不到图片的问题,查找文件夹图片也已经存在,可就是在相册里刷新不出来。   以上代码便是将Bitmap保存图片到指定的路径/

    2024年01月24日
    浏览(39)
  • Android实现保存res下(drawable/mipmap)的图片到手机相册

    项目小需求需要保存二维码名片到手机相册的需求,该图片在res/mipmap或者res/drawable文件夹下,实现方法如下。 代码如下(示例): 代码如下(示例): 以上就是今天要讲的内容,本文仅仅简单介绍了在res/mipmap或者res/drawable文件夹下图片保存到手机相册的方法。

    2024年02月12日
    浏览(56)
  • Android10以上版本调用相机拍照

    1、拍照功能 界面 逻辑处理 存放路径 2、从相册中选择图片的功能 在上面基本代码的基础之上添加

    2024年04月10日
    浏览(41)
  • android studio开发——android11版本以上权限动态申请问题,包括文件读写、图片、相机的调用

    用于android手机的升级,现在已经是android13版本了,对于权限问题可能更加敏感了,前段时间开发发现之前的方法已经不再适用于android11以后的版本了 读写权限申请最好是跳转到设置中进行才是最好了,下面我们开始进行 首先是AndroidManifest.xml文件的权限 然后这里讲解一下权

    2024年02月10日
    浏览(56)
  • uniapp 微信小程序保存图片到系统相册( 获取用户是否开启 授权保存图片到相册。)

    当用户点击拒绝按钮后的截图:   用户点击不授权  则关闭弹窗 单独给用户点击授权后, 跳转到授权页面  开启授权后:

    2024年02月08日
    浏览(51)
  • 微信小程序保存图片到相册 微信小程序实现将图片保存到手机相册(方案一)

    目录 微信小程序实现将图片保存到手机相册(方案一) 微信小程序实现将图片保存到手机相册(方案二) 微信小程序之点击复制文本到剪贴板 微信小程序---判断是IOS还是安卓 微信小程序分享图片给微信好友 首先我们需要调用wx.downloadFile方法下载文件资源到本地,然后利用

    2023年04月20日
    浏览(59)
  • 微信小程序保存图片到相册 微信小程序实现将图片保存到手机相册(方案一)

    目录 微信小程序实现将图片保存到手机相册(方案一) 微信小程序实现将图片保存到手机相册(方案二) 微信小程序之点击复制文本到剪贴板 微信小程序---判断是IOS还是安卓 微信小程序分享图片给微信好友 首先我们需要调用wx.downloadFile方法下载文件资源到本地,然后利用

    2023年04月20日
    浏览(41)
  • [小程序实现保存图片到相册]

    实现逻辑: 首先查看用户申请过的权限中是否有”保存图片到相册“,如果没有这个权限,则需要先申请权限(弹窗授权),如果用户同意授权则保存图片,如果用户不同意,则跳转到设置页,重新授权,然后再保存图片。 查看用户申请的全县有哪些 通过微信的API 获取用户

    2024年02月16日
    浏览(34)
  • flutter开发实战-图片保存到相册

    flutter开发实战-图片保存到相册。保存相册使用的是image_gallery_saver插件 在pubspec.yaml中引入插件 使用image_gallery_saver将图片保存到相册 flutter开发实战-图片保存到相册。保存相册使用的是image_gallery_saver插件。 学习记录,每天不停进步。

    2024年02月15日
    浏览(58)
  • uniapp-小程序保存图片到相册

    一. 将图片保存到手机相册涉及的 api 有以下几个 1. uni.getSetting (获取用户的当前设置) 2. uni.authorize (提前向用户发起授权请求。调用后会立刻弹窗询问用户是否同意授权小程序使用某项功能或获取用户的某些数据,但不会实际调用对应接口。如果用户之前已经同意授权,则不

    2024年04月22日
    浏览(37)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包