简体   繁体   English

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

[英]MediaStore contentResolver.insert() creates copies instead of replacing the existing file when taking photos (Android Q: 29)

I am trying to save an image taken from the camera using the following codes:我正在尝试使用以下代码保存从相机拍摄的图像:

@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!!
}

The function works well for the first time.该功能第一次运行良好。 however when the image (house2.jpg) already exists, the system will create another file called "house2 (1).jpg", "house2 (2).jpg, etc (instead of replacing the old file)然而,当图像(house2.jpg)已经存在时,系统将创建另一个名为“house2 (1).jpg”、“house2 (2).jpg”等的文件(而不是替换旧文件)

在此处输入图片说明

is there anything I can set in the contentValues to force the resolver to replace the file rather than create copies of it?我可以在 contentValues 中设置什么来强制解析器替换文件而不是创建它的副本吗?

below is the codes for the take picture intent.下面是拍照意图的代码。

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

@CommonsWare's comment helped. @CommonsWare 的评论有帮助。

The idea is to这个想法是

  1. Query if file already exists with resolver.query()使用 resolver.query() 查询文件是否已存在
  2. If yes, extract the contentUri from the cursor如果是,则从光标中提取 contentUri
  3. Otherwise, use resolver.insert()否则,使用 resolver.insert()

one thing to note when creating the selection for query is that MediaStore.MediaColumns.RELATIVE_PATH requires a terminating "/"创建查询选择时要注意的一件事是 MediaStore.MediaColumns.RELATIVE_PATH 需要终止“/”

ie 'Pictures/OLArt/' << note the slash after OLArt/即 'Pictures/OLArt/' << 注意 OLArt/ 后的斜线

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

The following is the updated codes.以下是更新后的代码。

@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
}

I then add this method into my existing codes然后我将此方法添加到我现有的代码中

@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!!
}

Angel Koh's answer is correct. Angel Koh 的回答是正确的。

I'm just posting it in Java:我只是用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;
}

and usage of method:和使用方法:

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
        }

This is the correct expected behavior.这是正确的预期行为。 The reason why you see different numeration postfixes, is because probably you are saving the files in the same folder, so Android has to create a unique name in order to allow the files to exist in the same location.您看到不同的编号后缀的原因,可能是因为您将文件保存在同一文件夹中,因此 Android 必须创建一个唯一的名称,以允许文件存在于同一位置。

The Insert method is meant to create always new records. Insert方法旨在始终创建新记录。 The Uri that it returns is always a newly inserted record.它返回的 Uri 始终是新插入的记录。 But if the file is saved in a folder where there is already another file with the same name, then as such file name must be different Android will append the numeric value.但是如果文件保存在已经有另一个同名文件的文件夹中,那么这样的文件名必须不同,Android 将附加数值。

If you wish to replace an existing record, then you must first locate its Uri, and then use it by calling instead the ContentResolver update method.如果您希望替换现有记录,那么您必须首先找到它的 Uri,然后通过调用 ContentResolver 更新方法来使用它。

If you are saving photos from a camera app, then you could use instead the current time as the name, including the milliseconds, to ensure is unique.如果您从相机应用程序保存照片,那么您可以使用当前时间作为名称,包括毫秒,以确保唯一。

Have you tried using the method update ?您是否尝试过使用方法 update

Check if it creates a new one when there's nothing yet, if it doesn't work, then use insert or update depending if the file has been created.检查它是否在没有任何内容时创建一个新文件,如果它不起作用,则根据文件是否已创建使用插入或更新。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM