MediaStore的contentResolver.insert()在拍照时创建副本而不是替换现有文件(Android Q:29)

6
我正在尝试使用以下代码保存从相机拍摄的图像:
@RequiresApi(Build.VERSION_CODES.Q)
private fun setImageUri(): Uri {
    val resolver = contentResolver
    val contentValues = ContentValues().apply {
        put(MediaStore.MediaColumns.DISPLAY_NAME, "house2.jpg")
        put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
        put(MediaStore.MediaColumns.RELATIVE_PATH, "Pictures/OLArt")
    }

    imageUri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)

    return imageUri!!
}

该函数第一次运行良好。但是当图像(house2.jpg)已经存在时,系统将创建另一个名为“house2(1)。jpg”,“house2(2)。jpg”等的文件(而不是替换旧文件)。

enter image description here

有没有什么我可以在contentValues中设置的东西,来强制解析器替换文件而不是创建副本?

下面是拍照意图的代码。

 Intent(MediaStore.ACTION_IMAGE_CAPTURE).also { takePictureIntent ->

     takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, setImageUri()) //<- i paste in the imageUri here

     // Ensure that there's a camera activity to handle the intent
     takePictureIntent.resolveActivity(packageManager)?.also {

         startActivityForResult(takePictureIntent, 102)
     }
  }

另请参阅:https://stackoverflow.com/a/60599437/115145 - CommonsWare
4个回答

5
@CommonsWare的评论很有帮助。
思路如下:
  1. 使用resolver.query()查询文件是否已经存在。
  2. 如果是,则从游标中提取contentUri。
  3. 否则,使用resolver.insert()插入数据。
需要注意的一点是,在为查询创建选择条件时,MediaStore.MediaColumns.RELATIVE_PATH需要一个终止符“/”。
例如,“Pictures/OLArt/” <<请注意OLArt之后的斜杠。
    val selection = "${MediaStore.MediaColumns.RELATIVE_PATH}='Pictures/OLArt/' AND " 
                   + "${MediaStore.MediaColumns.DISPLAY_NAME}='house2.jpg' "

以下是更新后的代码。
@RequiresApi(Build.VERSION_CODES.Q)
private fun getExistingImageUriOrNullQ(): Uri? {
    val projection = arrayOf(
        MediaStore.MediaColumns._ID,
        MediaStore.MediaColumns.DISPLAY_NAME,   // unused (for verification use only)
        MediaStore.MediaColumns.RELATIVE_PATH,  // unused (for verification use only)
        MediaStore.MediaColumns.DATE_MODIFIED   //used to set signature for Glide
    )

    // take note of the / after OLArt
    val selection = "${MediaStore.MediaColumns.RELATIVE_PATH}='Pictures/OLArt/' AND " 
                  + "${MediaStore.MediaColumns.DISPLAY_NAME}='house2.jpg' "

    contentResolver.query( MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
        projection, selection, null, null ).use { c ->
        if (c != null && c.count >= 1) {

            print("has cursor result")
            c.moveToFirst().let {

                val id = c.getLong(c.getColumnIndexOrThrow(MediaStore.MediaColumns._ID) )
                val displayName = c.getString(c.getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME) )
                val relativePath = c.getString(c.getColumnIndexOrThrow(MediaStore.MediaColumns.RELATIVE_PATH) )
                lastModifiedDate = c.getLong(c.getColumnIndexOrThrow(MediaStore.MediaColumns.DATE_MODIFIED) )

                imageUri = ContentUris.withAppendedId(   
                             MediaStore.Images.Media.EXTERNAL_CONTENT_URI,  id)

                print("image uri update $displayName $relativePath $imageUri ($lastModifiedDate)")

                return imageUri
            }
        }
    }
    print("image not created yet")
    return null
}

然后我把这个方法添加到我的现有代码中

@RequiresApi(Build.VERSION_CODES.Q)
private fun setImageUriQ(): Uri {

    val resolver = contentResolver

    imageUri = getExistingImageUriOrNullQ() //try to retrieve existing uri (if any)
    if (imageUri == null) {

       //=========================
       // existing codes for resolver.insert
       //(SNIPPED)
       //=========================
    }
    return imageUri!!
}

4

Angel Koh 的答案是正确的。

我只是用 Java 发布它:

@RequiresApi(Build.VERSION_CODES.Q)
public static Uri CheckIfUriExistOnPublicDirectory(Context c ,String[] projection, String selection){

    ContentResolver resolver = c.getContentResolver();
    Cursor cur = resolver.query(MediaStore.Downloads.EXTERNAL_CONTENT_URI, projection, selection , null, null);

    if (cur != null) {

        if(cur.getCount()>0){

            if (cur.moveToFirst()) {
                String filePath = cur.getString(0);
                long id = cur.getLong(cur.getColumnIndexOrThrow(MediaStore.MediaColumns._ID));
                String displayName = cur.getString(cur.getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME) );
                String relativePath = cur.getString(cur.getColumnIndexOrThrow(MediaStore.MediaColumns.RELATIVE_PATH) );
                long z = cur.getLong(cur.getColumnIndexOrThrow(MediaStore.MediaColumns.DATE_MODIFIED) );

                return imageUri = ContentUris.withAppendedId(
                        MediaStore.Images.Media.EXTERNAL_CONTENT_URI,  id);

            } else {
                // Uri was ok but no entry found.
            }

        }else{
            // content Uri was invalid or some other error occurred
        }
        cur.close();
    } else {
        // content Uri was invalid or some other error occurred
    }

    return null;
}

方法的使用:

String[] projection = {MediaStore.MediaColumns._ID,
                MediaStore.MediaColumns.DISPLAY_NAME,
                MediaStore.MediaColumns.RELATIVE_PATH,
                MediaStore.MediaColumns.DATE_MODIFIED
        };

        String selection = MediaStore.MediaColumns.RELATIVE_PATH + "='" + Environment.DIRECTORY_DOWNLOADS + File.separator + folderName + File.separator + "' AND "
                + MediaStore.MediaColumns.DISPLAY_NAME+"='" + fileName + "'";

        uri = CheckIfUriExistOnPublicDirectory(context,projection,selection);
        if(uri != null){
            // file already exist
        }else{
            // file not exist, insert
        }

1
这是正确的预期行为。你看到不同的编号后缀是因为可能你正在将文件保存在同一个文件夹中,所以Android必须创建一个唯一的名称,以允许文件存在于同一个位置。 Insert 方法旨在始终创建新记录。它返回的Uri始终是新插入的记录。但如果文件保存在已经有相同名称的文件的文件夹中,则由于此类文件名必须不同,Android将附加数字值。
如果您想替换现有记录,则必须首先找到其Uri,然后通过调用ContentResolver update方法来使用它。
如果您正在从相机应用程序保存照片,则可以改为使用当前时间作为名称,包括毫秒,以确保唯一性。

-1

你尝试过使用更新方法吗?

检查一下,如果还没有创建文件,它是否会创建一个新的。如果不行,那么根据文件是否已经创建,使用插入或更新。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接