拍照后使用相机意图,如何删除图库中的图片?

67

我知道有很多不同的方法来问这个问题,但我似乎仍然无法从默认文件夹中删除图库图像。我已经正确地将文件保存到SD卡中,并且我可以成功删除该文件,但是显示在Camera文件夹下的默认相册图像文件将无法删除。

由于文件已经存储在/Coupon2下的SD卡中,因此我希望在返回活动时自动删除图像。

有什么建议吗?

public void startCamera() {
    Log.d("ANDRO_CAMERA", "Starting camera on the phone...");

    mManufacturerText = (EditText) findViewById(R.id.manufacturer);
    String ManufacturerText = mManufacturerText.getText().toString();
    String currentDateTimeString = new Date().toString();

    Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    File filedir = new File(Environment.getExternalStorageDirectory()+"/Coupon2");
    filedir.mkdirs();

    File file = new File(Environment.getExternalStorageDirectory()+"/Coupon2", ManufacturerText+"-test.png");
    outputFileUri = Uri.fromFile(file);
    intent.putExtra(MediaStore.EXTRA_OUTPUT, outputFileUri);

    startActivityForResult(intent, CAMERA_PIC_REQUEST);
}

protected void onActivityResult(int requestCode, int resultCode, Intent data) {  
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == CAMERA_PIC_REQUEST && resultCode == -1) {  
        Intent intent = new Intent("com.android.camera.action.CROP");
        intent.putExtra("crop", "true");
        intent.putExtra("scale", "true");

        intent.putExtra("return-data", false);
        intent.setDataAndType(outputFileUri, "image/*");
        intent.putExtra(MediaStore.EXTRA_OUTPUT, outputFileUri);
        startActivityForResult(intent, CAMERA_CROP_REQUEST);
    }else { 
        SetImage();
        saveState();
    }
}

我猜没有真正好的方法可以做到这一点,因为我正在使用MediaStore.ACTION_IMAGE_CAPTURE而不是自定义相机活动。如果有人有一种方法可以将输出文件保存到EXTRA_OUTPUT文件夹而不保存到图库,请告诉我。 - android-overflow
我们也发现了这种行为,但只在一些手机上出现。(尤其是myTouch 3G。)其他手机按预期仅保存到EXTRA_OUTPUT文件中。 - user79758
1
似乎这是同样的问题,而且在LG Ally手机上也会发生。 - user79758
还有哪些具体的设备表现出这种行为?截至今天?或者是模拟器配置?需要测试一下... - Moritz Both
13个回答

71

我的应用程序需要调用一个意图来拍照。照片不能在相册中,而必须在SD卡上的特定目录中。

最初我只是使用了EXTRA_OUTPUT,但很快发现以下问题: - 一些设备完全使用它并跳过相册。 - 一些设备完全忽略它,并且仅使用相册。 - 一些设备真的很差,将完整大小的图像保存到相册中,并只将缩略图保存到我想要的位置。(HTC你知道你是谁...)

所以,我不能盲目地删除相册文件。最后添加的照片可能是我想要删除的,也可能不是。此外,我可能需要复制该文件以替换自己的文件。因为我的活动有2000行,我的公司不希望我们的所有代码都被发布,所以我只发布涉及此操作的方法。希望这能有所帮助。

另外,我要声明,这是我第一个Android应用程序。如果有更好的方法可以做到这一点,我不会感到惊讶,但这是对我有效的方法!

所以,这是我的解决方案:

首先,在我的应用程序上下文中,我定义一个变量如下:

public ArrayList<String> GalleryList = new ArrayList<String>();

接下来,在我的活动中,我定义了一个方法来获取相册中所有照片的列表:
private void FillPhotoList()
{
   // initialize the list!
   app.GalleryList.clear();
   String[] projection = { MediaStore.Images.ImageColumns.DISPLAY_NAME };
   // intialize the Uri and the Cursor, and the current expected size.
   Cursor c = null; 
   Uri u = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
   //
   // Query the Uri to get the data path.  Only if the Uri is valid.
   if (u != null)
   {
      c = managedQuery(u, projection, null, null, null);
   }

   // If we found the cursor and found a record in it (we also have the id).
   if ((c != null) && (c.moveToFirst())) 
   {
      do 
      {
        // Loop each and add to the list.
        app.GalleryList.add(c.getString(0));
      }     
      while (c.moveToNext());
   }
}

这里提供一种方法来返回我新图片的唯一文件名:

private String getTempFileString()
{
   // Only one time will we grab this location.
   final File path = new File(Environment.getExternalStorageDirectory(), 
         getString(getApplicationInfo().labelRes));
   //
   // If this does not exist, we can create it here.
   if (!path.exists())
   {
      path.mkdir();
   }
   //
   return new File(path, String.valueOf(System.currentTimeMillis()) + ".jpg").getPath();
}

我在Activity中有三个变量,用于存储当前文件的信息。一个字符串(路径),一个File变量和该文件的URI:

public static String sFilePath = ""; 
public static File CurrentFile = null;
public static Uri CurrentUri = null;

我从未直接设置这些内容,只是在文件路径上调用了一个 setter:

public void setsFilePath(String value)
{
   // We just updated this value. Set the property first.
   sFilePath = value;
   //
   // initialize these two
   CurrentFile = null;
   CurrentUri = null;
   //
   // If we have something real, setup the file and the Uri.
   if (!sFilePath.equalsIgnoreCase(""))
   {
      CurrentFile = new File(sFilePath);
      CurrentUri = Uri.fromFile(CurrentFile);
   }
}

现在我调用一个Intent来拍照。
public void startCamera()
{
   Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
   // Specify the output. This will be unique.
   setsFilePath(getTempFileString());
   //
   intent.putExtra(MediaStore.EXTRA_OUTPUT, CurrentUri);
   //
   // Keep a list for afterwards
   FillPhotoList();
   //
   // finally start the intent and wait for a result.
   startActivityForResult(intent, IMAGE_CAPTURE);
}

完成此操作后,当活动返回时,以下是我的代码:

protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
   if (requestCode == IMAGE_CAPTURE)
   {
      // based on the result we either set the preview or show a quick toast splash.
      if (resultCode == RESULT_OK)
      {
         // This is ##### ridiculous.  Some versions of Android save
         // to the MediaStore as well.  Not sure why!  We don't know what
         // name Android will give either, so we get to search for this
         // manually and remove it.  
         String[] projection = { MediaStore.Images.ImageColumns.SIZE,
                                 MediaStore.Images.ImageColumns.DISPLAY_NAME,
                                 MediaStore.Images.ImageColumns.DATA,
                                 BaseColumns._ID,};
         //    
         // intialize the Uri and the Cursor, and the current expected size.
         Cursor c = null; 
         Uri u = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
         //
         if (CurrentFile != null)
         {               
            // Query the Uri to get the data path.  Only if the Uri is valid,
            // and we had a valid size to be searching for.
            if ((u != null) && (CurrentFile.length() > 0))
            {
               c = managedQuery(u, projection, null, null, null);
            }
            //   
            // If we found the cursor and found a record in it (we also have the size).
            if ((c != null) && (c.moveToFirst())) 
            {
               do 
               {
                  // Check each area in the gallary we built before.
                  boolean bFound = false;
                  for (String sGallery : app.GalleryList)
                  {
                     if (sGallery.equalsIgnoreCase(c.getString(1)))
                     {
                        bFound = true;
                        break;
                     }
                  }
                  //       
                  // To here we looped the full gallery.
                  if (!bFound)
                  {
                     // This is the NEW image.  If the size is bigger, copy it.
                     // Then delete it!
                     File f = new File(c.getString(2));

                     // Ensure it's there, check size, and delete!
                     if ((f.exists()) && (CurrentFile.length() < c.getLong(0)) && (CurrentFile.delete()))
                     {
                        // Finally we can stop the copy.
                        try
                        {
                           CurrentFile.createNewFile();
                           FileChannel source = null;
                           FileChannel destination = null;
                           try 
                           {
                              source = new FileInputStream(f).getChannel();
                              destination = new FileOutputStream(CurrentFile).getChannel();
                              destination.transferFrom(source, 0, source.size());
                           }
                           finally 
                           {
                              if (source != null) 
                              {
                                 source.close();
                              }
                              if (destination != null) 
                              {
                                 destination.close();
                              }
                           }
                        }
                        catch (IOException e)
                        {
                           // Could not copy the file over.
                           app.CallToast(PhotosActivity.this, getString(R.string.ErrorOccured), 0);
                        }
                     }
                     //       
                     ContentResolver cr = getContentResolver();
                     cr.delete(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, 
                        BaseColumns._ID + "=" + c.getString(3), null);
                     break;                        
                  }
               } 
               while (c.moveToNext());
            }
         }
      }
   }      
}

4
不要使用ArrayList来存储图库项目,考虑使用HashMap。目前您的代码会两次遍历图库中的图像,因此对于N张图片,您会运行n^2个比较操作。这将导致在具有许多图像的设备上出现明显的延迟。如果您使用HashMap,可以使用小写文件名作为键。然后,您可以使用containsKey检查文件的存在性,将其转换为O(n)的操作。 - abscondment
1
@Paul 为什么不使用 HashSet<String> 呢?反正你也没有使用存储在 String 中的整数。 - Plankt
你们不觉得在那些不显示这种行为的设备上,它会先删除其他文件或最后一个从SD卡中获取的文件吗?首先要检查一下。 - user2730944
不会,因为它只能找到在意图调用之后拍摄的照片。它不会盲目地删除文件。 - Paul
1
我发现另一个问题会导致你的代码在LG G2上失败。我的设备将每个拍摄的图像保存到特定文件夹中。如果您调用意图拍照,拍摄一张照片,点击“取消”,再拍摄一张照片,然后点击“确定”,它将保存两张照片。因此,可能会发生您将被丢弃的图像复制到目标文件中的情况。 - Leandroid
显示剩余9条评论

19

这将从图库中删除该文件:

protected void onActivityResult(int requestCode, int resultCode, Intent data) {  
    super.onActivityResult(requestCode, resultCode, data);

    if (requestCode == CAMERA_PIC_REQUEST && resultCode == RESULT_OK) { 

        /* Copy the file to other directory or whatever you want */

        // mContext is the context of the activity
        mContext.getContentResolver().delete(data.getData(), null, null);
    }
 }

关于 EXTRA_OUTPUT 不是标准行为。我认为这个伪算法在任何情况下都应该有效:

1)不要使用 EXTRA_OUTPUT。图像/照片始终会到达图库位置。

2)将文件从图库位置复制到所需位置。

3)从图库中删除该文件(使用上面的代码)。

但当然,它似乎太完美了…在某些设备上(例如带有 Android 2.3 的原始 Galaxy Tab),您必须在 ACTION_IMAGE_CAPTURE 中使用 EXTRA_OUTPUT,否则意图无法正常工作。


文档中没有说明intent.getData()将是图像的URI。根据SO评论的经验,有些手机上它并没有被设置。 - kos

16

如果有人正在寻找一个更简单的解决方案来解决这个问题,这是我解决这个问题的方法。

我有一个捕获按钮,当它被按下时,意图被发送,我所添加的是我还去获取了图像mediastore中的最后一个id并将其存储:

/**
 * Gets the last image id from the media store
 * @return
 */
private int getLastImageId(){
    final String[] imageColumns = { MediaStore.Images.Media._ID };
    final String imageOrderBy = MediaStore.Images.Media._ID+" DESC";
    final String imageWhere = null;
    final String[] imageArguments = null;
    Cursor imageCursor = managedQuery(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, imageColumns, imageWhere, imageArguments, imageOrderBy);
    if(imageCursor.moveToFirst()){
        int id = imageCursor.getInt(imageCursor.getColumnIndex(MediaStore.Images.Media._ID));
        imageCursor.close();
        return id;
    }else{
        return 0;
    }
}

然后当活动返回时,我运行此代码来检查捕获前的最后一个图像ID,然后查询捕获后具有大于记录的ID的图像,如果有多个,则删除位于我指定的位置的记录以供相机保存。

/*
 * Checking for duplicate images
 * This is necessary because some camera implementation not only save where you want them to save but also in their default location.
 */
final String[] imageColumns = { MediaStore.Images.Media.DATA, MediaStore.Images.Media.DATE_TAKEN, MediaStore.Images.Media.SIZE, MediaStore.Images.Media._ID };
final String imageOrderBy = MediaStore.Images.Media._ID+" DESC";
final String imageWhere = MediaStore.Images.Media._ID+">?";
final String[] imageArguments = { Integer.toString(MyActivity.this.captureLastId) };
Cursor imageCursor = managedQuery(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, imageColumns, imageWhere, imageArguments, imageOrderBy);
if(imageCursor.getCount()>1){
    while(imageCursor.moveToNext()){
        int id = imageCursor.getInt(imageCursor.getColumnIndex(MediaStore.Images.Media._ID));
        String path = imageCursor.getString(imageCursor.getColumnIndex(MediaStore.Images.Media.DATA));
        Long takenTimeStamp = imageCursor.getLong(imageCursor.getColumnIndex(MediaStore.Images.Media.DATE_TAKEN));
        Long size = imageCursor.getLong(imageCursor.getColumnIndex(MediaStore.Images.Media.SIZE));
        if(path.contentEquals(MyActivity.this.capturePath)){
            // Remove it
            ContentResolver cr = getContentResolver();
            cr.delete(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, MediaStore.Images.Media._ID + "=?", new String[]{ Long.toString(id) } );
            break;
        }
    }               
}
imageCursor.close();

对我而言,这是一个更简单的解决方案,并且我在遇到此问题的HTC手机上进行了测试。

另外一件事,我最初使用*DATE_TAKEN*而不是*_ID*作为参数,但在模拟器上,通过Intent捕获的一些图像的毫秒数*DATE_TAKEN*被乘以1000,因此我切换到*_ID*,它似乎更加稳定。


2
@Xithias 把这个伪代码当作获取方法要点的方式,你可以实现它,但不要只是复制代码并期望它能工作。 - Emil Davtyan
4
@Emil,你的回答不是伪代码。如果我看到正常的代码,我就期望它能够工作。如果你知道你的回答不完整或需要更多的代码,请在某个地方提醒读者,并告诉他需要做什么才能让它工作。这将使你的回答更加完整和成功。 - Xithias
3
@Xithias,我只是简单地描述了解决这个问题的一个简单方法,代码在那里可以指导你。我不会花时间警告人们,代码可能无法运行或某些方法最终会变得过时,因为我不认为任何有能力的程序员会仅仅复制一些代码片段并抱怨它不能工作。 - Emil Davtyan
1
@Emil 不用担心,我用另一种方式解决了我的问题。但你必须考虑到在StackOverflow这里,有能干的程序员,也有初学者。这就是为什么你在这个答案中没有更多的投票的原因。这种类型的问题是由初学者提出的,是本地相机应用程序的常见问题,而你的答案只适用于“能干的程序员”。 - Xithias
4
@Xithias,我不喜欢你的态度。这与是不是初学者无关,如果你只想复制粘贴代码——请不要混淆你所做的事情和编程。 - Emil Davtyan
显示剩余4条评论

5
我认为整个问题的关键在于你期望另一个应用程序产生某种结果,但事实并非如此。换而言之,问题出在以下几点:
  • 启动可拍照的活动,因为你的应用程序无法捕捉图像。
  • 告诉其他应用程序你想要保存的图片位置。
  • 在你指定的位置使用保存的图像。
  • 但是问题是,其他应用程序还将图像保存在不同的位置,这是你不想看到的。
现在让我们从另一个应用程序的角度来看待它,即捕捉图像的应用程序。让我们考虑两种情况(这是可能的)-
  • 首先,该应用程序以fileName或保存图像的位置选项启动。
  • 其次,如果未提供任何额外信息,例如fileName或保存图像的位置,则该应用程序将被启动。(如果直接从菜单启动捕捉应用程序,则可能会发生这种情况)
理想情况下,如果有fileName或位置等额外信息,则应使用该位置,否则可以使用自己的位置。但是很遗憾,在我们不完美的世界中,当启动相机应用程序时,没有什么是一成不变的。相机应用程序的开发者将有自己的解释。
由于不同的设备具有不同的相机应用程序设置为默认(是的,通常替换了原始相机),因此获得的结果是/将是不同的。
因此,在某些相机中,它可能仅保存在一个位置,而在其他相机中,它可能也保存在相机文件夹中(因为相机应用程序可能始终将捕获的图像保存在相机文件夹中,并且在不同的位置保存它是该应用程序提供的奖励)。
如果你只传递一个fileName给相机应用程序,那么应用程序是否应该在拍摄一张照片后返回呢?我认为不应该。在这种情况下,应该怎么做?这都是非常模糊的或灰色地带。
现在,如果你不希望它保存在相机文件夹中,请找出最近捕获的图像的文件名,然后从你的应用程序中删除它。或者不要告诉相机应用程序需要保存在哪里,只需获取文件名并将其移动到你想要的位置即可。
如果你没有获得文件名,那么就要靠其他应用程序的开发者了。(嘿!这也不是他的错,他只是按照自己的意愿设计了它!)

2

我认为你不能做你想做的事情,这很遗憾,但我找不到另一个答案。

如果你使用谷歌相机实现,它是可以正常工作的。只要没有指定 EXTRA_OUTPUT,就不会将照片存储到图库中。

但是当你面对其他设备时,它们可能会做完全不同的事情。这是因为HTC、LG和其他一些公司有自定义的相机实现,你无法对此做任何处理。你可以保持原样或编写自己的相机,使其完全按照你的需求工作。

实际上这与这个问题无关,但总有一天你会发现,在某些设备上,裁剪意图(CROP Intent)无法正常工作(http://groups.google.com/group/android-developers/browse_frm/thread/2dd647523926192c/4b6d087073a39607?tvc=1&pli=1)。所以如果需要,你将不得不自己编写代码。


2

这不是一个容易解决的任务,但可以采用一个巧妙的方法来解决。如果有人需要一个简单可行的解决方案,请尝试以下代码:

  1. 在调用相机意图之前,将当前时间以毫秒为单位保存。
  2. 在OnActivityResult中查询拍摄日期大于步骤1中的毫秒数的图像URI,并删除该文件。就这样。
String[] projection = {MediaStore.Images.ImageColumns.SIZE,
                MediaStore.Images.ImageColumns.DISPLAY_NAME,
                MediaStore.Images.ImageColumns.DATA,
                BaseColumns._ID,MediaStore.Images.ImageColumns.DATE_ADDED};
        final String imageOrderBy = MediaStore.Images.Media._ID + " DESC";
        final String selection = MediaStore.Images.Media.DATE_TAKEN+" > "+mImageTakenTime;
        //// intialize the Uri and the Cursor, and the current expected size.
        Cursor c = null;
        Uri u = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
        c = getActivity().managedQuery(u, projection, selection, null, imageOrderBy);
        if(null != c && c.moveToFirst()){
            ContentResolver cr = getActivity().getContentResolver();
            cr.delete(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                    BaseColumns._ID + "=" + c.getString(3), null);
        }

1
这是我使用的代码,它可以拍照并将其保存在指定的位置。
Uri outputFileUri;

public void takePhoto() {
    File directory = new File(Environment.getExternalStorageDirectory()
            + "/HI-Tech" + "/");

    if (!directory.exists()) {
        directory.mkdir();
    }

    int count = 0;
    if (directory != null) {
        File[] files = directory.listFiles();
        if (files != null) {
            count = files.length;
        }
    }
    count++;
    String imagePath = "IMAGE_" + count + ".jpg";
    File file = new File(directory, imagePath);
    outputFileUri = Uri.fromFile(file);

        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    intent.putExtra(MediaStore.EXTRA_OUTPUT, outputFileUri);
    startActivityForResult(intent, JobActivity.TAKE_PIC);
}

然后我处理响应。

protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    try {
        if (resultCode == RESULT_OK) {
            if (requestCode == JobActivity.TAKE_PIC) {
                Bitmap bitmap = decodeFile(new File(outputFileUri.getPath()), 160, 120);
    } catch (Exception e) {
        Log.e("Error", "Unable to set thumbnail", e);
    }
}

我不得不将outputFileUri声明为全局变量,因为我找不到在onActivityResult中获取保存路径的方法。通过传递outputFileUri,您会注意到图像未保存在相机文件夹中,而是保存在指定位置。我已经在我的Nexus1和一个便宜的三星设备上尝试了这段代码。

希望这可以帮助到您。


6
你的代码与提问者的代码和我的项目中的代码在功能上是完全一致的。它在某些手机上可以运行,但在其他一些手机上无法运行,这些手机会将数据写入到EXTRA_OUTPUT和其他一些位置。 - user79758

1

看这里 - 这是一段程序代码,可以将照片保存到EXTRA_OUTPUT文件夹而不会保存到相册中。在我的应用程序中,我使用这种方式直接从摄像头抓取图片,然后删除拍摄的图片。


2
你的代码在功能上与提问者的代码和我的项目中的代码完全相同。它可以在一些手机上运行,但在写入 EXTRA_OUTPUT 和其他位置的手机上无法正常工作。 - user79758

1
我也曾经遇到过这个问题。顺便说一下,在Android bug tracker中,这个问题被标记为Obsolete。由于某些原因,Android团队不再认为这是一个错误。可能是因为这与制造商有关。
似乎所有的解决方案(来自本主题和其他博客文章等)都形成了以下算法:
  1. 保存上次拍摄照片的唯一标识符;
  2. 使用带有EXTRA_OUTPUTIntent启动相机活动;
  3. 拍照并检查图库中最后拍摄的照片的ID是否已更改,以决定是否应删除它。
接受的答案中提出了类似的方法。
但我发现这种方法不能用于生产代码。例如,让我们想象一个简单的情况:
您在图库中存储了上次拍摄照片的某个ID,并启动相机活动。当用户在某些设备上拍照时,相机活动会显示一个带有CancelOK按钮(或类似的东西)的对话框。它们背后的逻辑如下:
  • 如果用户按下取消,他将返回相机活动并可以继续拍照。
  • 如果他/她(用户)按下确定按钮,相机活动将把结果返回给您的应用程序。

重要提示:事实证明,在某些设备上(我在LG和HTC上进行了测试),当此对话框被呈现时,图像可能已经保存。

现在想象一下,如果用户因某种原因决定点击主页按钮并启动另一个应用程序(例如,用户可以在另一个相机应用程序中拍摄不同的照片!)。然后返回到仍然显示该对话框的应用程序并按下确定。显然,在这种情况下,您的应用程序将删除错误的文件...并使用户失望:(

因此,在大量研究后,我决定克服这个问题的最佳方法是编写自己的相机引擎或只是使用一些第三方库,如:material camera


我认为使用自己或库相机实现是最好的选择,特别是在4.4版本之后,因为不可能删除/移动其他应用程序创建的文件。 - shtolik

0
protected void onActivityResult(int requestCode, int resultCode, Intent data) {  
    if (requestCode == CAMERA_REQUEST && resultCode == RESULT_OK) {

    Bitmap photo = (Bitmap) data.getExtras().get("data");
    Uri tempUri = Utils.getUri(getActivity(), photo);
    File finalFile = new File(getRealPathFromURI(tempUri));
    finalFile.delete();
    }  
}


public String getRealPathFromURI (Uri contentUri) {
    String path = null;
    String[] proj = { MediaStore.MediaColumns.DATA };
    Cursor cursor = getActivity().getContentResolver().query(contentUri, proj, null, null, null);
    if (cursor.moveToFirst()) {
        int column_index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA);
        path = cursor.getString(column_index);
    }
    cursor.close();
    return path;
}

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