Java 在android 11中如何将图像保存在外部SD卡的DCIM文件夹中?

Java 在android 11中如何将图像保存在外部SD卡的DCIM文件夹中?,java,android,android-studio,Java,Android,Android Studio,我正在使用以下代码将图像保存在内部存储器的DCIM文件夹中: public Uri saveBitmap(@NonNull final Context context, @NonNull final Bitmap bitmap, @NonNull final Bitmap.CompressFormat format, @NonNull final String mimeType,

我正在使用以下代码将图像保存在内部存储器的DCIM文件夹中:

    public Uri saveBitmap(@NonNull final Context context, @NonNull final Bitmap bitmap,
                      @NonNull final Bitmap.CompressFormat format,
                      @NonNull final String mimeType,
                      @NonNull final String displayName) throws IOException {

    final ContentValues values = new ContentValues();
    values.put(MediaStore.MediaColumns.DISPLAY_NAME, displayName);
    values.put(MediaStore.MediaColumns.MIME_TYPE, mimeType);
    values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM);

    final ContentResolver resolver = context.getContentResolver();
    Uri uri = null;

    try {
        final Uri contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
        uri = resolver.insert(contentUri, values);

        if (uri == null)
            throw new IOException("Failed to create new MediaStore record.");

        try (final OutputStream stream = resolver.openOutputStream(uri)) {
            if (stream == null)
                throw new IOException("Failed to open output stream.");
         
            if (!bitmap.compress(format, 95, stream))
                throw new IOException("Failed to save bitmap.");
        }

        return uri;
    }
    catch (IOException e) {

        if (uri != null) {
            // Don't leave an orphan entry in the MediaStore
            resolver.delete(uri, null, null);
        }

        throw e;
    }
}
现在我想把图像保存在外部SD卡的DCIM文件夹中,但我不知道怎么做

我知道我必须对这一行进行更改:

values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM);

但是
环境。目录\u DCIM
始终返回内部存储的DCIM文件夹。

您需要使用API 29中引入的新API方法获取现有卷

下一个示例获取卷列表,并尝试所有卷,首先对任何连接的SD卡进行优先级排序,最后在未找到SD卡或无法保存时使用内部存储

您不需要对相对路径内容值进行任何更改,因为它是相对于选定卷的

还要注意的是,我更改了saveBitmap,返回了null,而不是在失败时抛出异常。根据您的要求进行更改

@NonNull
private List<Uri> getContentUris(@NonNull final Context context) {

    final List<String> allVolumes = new ArrayList<>();

    // Add the internal storage volumes as last resort.
    // These will be kept at the bottom of the list if
    // any SD-card volumes are found
    allVolumes.add(MediaStore.VOLUME_EXTERNAL_PRIMARY);
    allVolumes.add(MediaStore.VOLUME_EXTERNAL);

    // Obtain the list of volume name candidates

    final Set<String> externalVolumeNames = MediaStore.getExternalVolumeNames(context);

    for (final String entry : externalVolumeNames) {
        // If the volume is "not" already cached in the list,
        // then is an SD-card, so prioritize it by adding it 
        // at the top of the list
        if (!allVolumes.contains(entry))
            allVolumes.add(0, entry);
    }

    // Finally resolve the target Image content Uris

    final List<Uri> output = new ArrayList<>();

    for (final String entry : allVolumes) {
        output.add(MediaStore.Images.Media.getContentUri(entry));
    }

    return output;
}

@Nullable
public Uri saveBitmap(@NonNull final Context context, @NonNull final Bitmap bitmap,
                      @NonNull final Bitmap.CompressFormat format,
                      @NonNull final String mimeType,
                      @NonNull final String displayName) {

    final ContentValues values = new ContentValues();
    values.put(MediaStore.MediaColumns.DISPLAY_NAME, displayName);
    values.put(MediaStore.MediaColumns.MIME_TYPE, mimeType);
    values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM);

    final ContentResolver resolver = context.getContentResolver();
    final List<Uri> contentUriList = getContentUris(context);

    for (final Uri contentUri : contentUriList) {
       
        Uri uri = null;

        try {
            uri = resolver.insert(contentUri, values);

            if (uri == null)
                throw new IOException("Failed to create new MediaStore record.");

            try (final OutputStream stream = resolver.openOutputStream(uri)) {
                if (stream == null)
                    throw new IOException("Failed to open output stream.");

                if (!bitmap.compress(format, 95, stream))
                    throw new IOException("Failed to save bitmap.");
            }

            return uri;
        }
        catch (IOException e) {
            Log.w(TAG, "Failed to save in volume: " + contentUri);

            if (uri != null) {
                // Don't leave an orphan entry in the MediaStore
                resolver.delete(uri, null, null);
            }

            // Do not throw, and try the next volume
        }
    }

    return null;
}
@NonNull
私有列表getContentURI(@NonNull final Context){
最终列表allVolumes=new ArrayList();
//添加内部存储卷作为最后手段。
//如果需要,这些将保留在列表的底部
//找到任何SD卡卷
添加(MediaStore.VOLUME\u EXTERNAL\u PRIMARY);
添加(MediaStore.VOLUME\u外部);
//获取卷名候选列表
最终设置externalVolumeNames=MediaStore.getExternalVolumeNames(上下文);
for(最终字符串条目:externalVolumeNames){
//如果卷“未”已缓存在列表中,
//然后是SD卡,所以通过添加它来确定它的优先级
//排名第一
如果(!allVolumes.contains(条目))
添加(0,条目);
}
//最后解析目标图像内容URI
最终列表输出=新的ArrayList();
for(最终字符串条目:allVolumes){
add(MediaStore.Images.Media.getContentUri(条目));
}
返回输出;
}
@可空
公共Uri保存位图(@NonNull最终上下文,@NonNull最终位图,
@非空最终位图.CompressFormat格式,
@非空的最终字符串mimeType,
@非空的最终字符串(显示名称){
最终ContentValues=新ContentValues();
value.put(MediaStore.MediaColumns.DISPLAY\u NAME,displayName);
value.put(MediaStore.MediaColumns.MIME_类型,mimeType);
value.put(MediaStore.MediaColumns.RELATIVE_PATH,Environment.DIRECTORY_DCIM);
final ContentResolver resolver=context.getContentResolver();
最终列表contentUriList=getContentUris(上下文);
for(最终Uri contentUri:contentUriList){
Uri=null;
试一试{
uri=resolver.insert(contentUri,值);
if(uri==null)
抛出新IOException(“未能创建新的MediaStore记录”);
try(final OutputStream=resolver.openOutputStream(uri)){
if(流==null)
抛出新IOException(“打开输出流失败”);
if(!bitmap.compress(格式,95,流))
抛出新IOException(“保存位图失败”);
}
返回uri;
}
捕获(IOE异常){
Log.w(标记“未能在卷中保存:“+contentUri”);
if(uri!=null){
//不要在MediaStore中留下孤立条目
delete(uri,null,null);
}
//不要扔,试试下一卷
}
}
返回null;
}

最终Uri contentUri=MediaStore.Images.Media.EXTERNAL\u CONTENT\u Uri您只需更改该集合
MediaStore.Images.Media.getContentUri(名称)
带有名称,例如从
MediaStore.getExternalVolumeNames(上下文)获取的1CD3-23ED(或1CD3-23ED)等值
知道我必须在这一行进行更改:
否。因为这都是相对的。
Environment.DIRECTORY\u DCIM始终返回内部存储的DCIM文件夹。
否。它正好等于“DCIM”。