我有一个应用程序,我可以使用设备的相机拍照。我想要做的是在不指定EXTRA_OUTPUT的情况下启动ACTION_IMAGE_CAPTURE意图,然后使用file.renameTo将在默认位置创建的文件移动到我自己的自定义位置。我的代码是这样的:
/* Start camera activity without EXTRA_OUTPUT */
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(intent, _REQUESTCODE_ATTACH_CAMERA);
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == RESULT_OK) {
switch(requestCode) {
case _REQUESTCODE_ATTACH_CAMERA:
/* Get path to most recently added image */
final String[] imageColumns = { MediaStore.Images.Media._ID, MediaStore.Images.Media.DATA };
final String imageOrderBy = MediaStore.Images.Media._ID + " DESC";
Cursor imageCursor = managedQuery(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, imageColumns, null, null, imageOrderBy);
String fullPath = "";
if(imageCursor.moveToFirst()){
fullPath = imageCursor.getString(imageCursor.getColumnIndex(MediaStore.Images.Media.DATA));
imageCursor.close();
}
File f = Environment.getExternalStorageDirectory();
f = new File(f.getAbsolutePath() + File.separator + "DCIM" + File.separator + MY_APP_NAME;
if(!f.exists()) {
f.mkdirs();
}
/* Create new file based on name of most recently created image */
File oldFile = new File(fullPath);
String newPath = f.getAbsolutePath() + File.separator + oldFile.getName() ;
/* Move file with renameTo */
oldFile.renameTo(new File(newPath));
break;
...
}
}
}
所有这些都很有效,但是有一件奇怪的事情正在发生。在我的应用程序中,我有另一个按钮,允许从手机的图库中选择现有图像。该代码如下所示:
Intent galleryIntent = new Intent(Intent.ACTION_GET_CONTENT);
galleryIntent.setType("image/*");
activity.startActivityForResult(galleryIntent, _REQUESTCODE_ATTACH_GALLERY);
这也有效,但是如果我使用上面发布的代码用相机拍照,然后尝试从图库中选择另一张图片,那么图库中会出现空白的“断开链接”类型的项目,其中不包含任何内容并且是不可选择的。这些似乎与使用renameTo拍摄和移动的照片相对应;如果我在onActivityResult中放入代码以将文件名发布到LogCat,则记录的名称与它对应的先前移动的文件的名称相同。尝试创建File对象或以任何方式访问该文件名,导致null对象并强制关闭。
奇怪的是,Eclipse DDMS中没有这些“断链”文件的证据,如果我使用Root Browser,也没有手机本身的证据,如果我重新安装SD卡,它们就会消失。
我用相机捕捉图像后移动图像的全部原因是为了避免用不必要的图像填满手机的图库存储。虽然这些空的“断链”类型文件似乎没有占用任何存储空间,但对于试图浏览其库的最终用户来说,它们仍然会非常烦人。有没有人对这里发生的事情或如何解决这个问题有任何想法?
修改
这是一张照片,显示了图库显示的“断开链接”类型图像。其中一张将出现在使用我的应用程序拍摄的每张照片中,如果我重新装入SD卡,它们都会消失。
答案 0 :(得分:2)
部分感谢this SO thread,我发现了一个解决方案。它实际上是有意义的,因为有一个表保留了媒体内容,所以删除一些东西而不告诉表肯定会创建一个“断链”类型的场景。
最终的解决方案是使用contentResolver.delete删除内容解析器中对文件的引用,但我发现有两种不同的方法可以使用。
/* Moving with renameTo */
//Use the same exact code as I had before (shortened for brevity) to move the file
oldFile.renameTo(newFile);
//Get URI from contentResolver using file Id from cursor
Uri oldUri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, imageCursor.getString(imageCursor.getColumnIndex(MediaStore.Images.Media._ID)));
//Delete old file
getContentResolver().delete(oldUri, null, null);
以这种方式获取URI是必要的,因为它需要引用contentResolver中的图像而不是存储中其位置的路径。这种方式可能会让一些人感到肮脏,因为您正在移动文件,然后在该文件上调用删除函数,以便欺骗内容解析器删除指向该文件的链接。如果您愿意,可以不使用renameTo来执行此操作,以便对delete(...)的调用实际上会删除图像。
/* Moving with streams */
//Get streams
InputStream in = new FileInputStream(oldFile);
OutputStream out = new FileOutputStream(newFile);
byte[] buffer = new byte[1024];
int bytesRead = 0;
//Read old file into new file
while((bytesRead = in.read(buffer)) > 0) {
out.write(buffer, 0, bytesRead);
}
//Get URI from contentResolver using file Id from cursor
Uri oldUri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, imageCursor.getString(imageCursor.getColumnIndex(MediaStore.Images.Media._ID)));
//Delete old file
getContentResolver().delete(oldUri, null, null);
对contentResolver.delete的调用是相同的,我只是想指出,如果图像已被删除,它仍然可以工作。
在此期间,我发现了一个问题的解决方案,我甚至没有意识到我会在这里发布,以防万一有这个问题的人将来遇到这个问题。为了使图像从新位置保持在设备库中可选,您需要让媒体扫描仪知道已进行了更改。我发现有两种方法可以做到这一点:
/* This is the only way that I know of to handle multiple new files at once. I
really would use this sparingly, however, since it will rescan the entire
SD Card. Not only could this take a long time if the user has a lot of files
on their card, it will also show a notification so it is not exactly a
transparent operation. */
sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://" + Environment.getExternalStorageDirectory())));
/* You *could* do multiple files with this by passing in the path for each one
in the array of Strings, however an instance of this will get called for each
one rather than it doing them all at once. Likewise, your onScanCompleted
(if you choose to include one) will get called once for each file in the list.
So really, while this is much better for a small number of files, if you plan
on scanning a very large amount then the full rescan above would probably be
a better option. */
MediaScannerConnection.scanFile(context, new String[]{ newFilePathAsString }, null,
new MediaScannerConnection.OnScanCompletedListener() {
public void onScanCompleted(String path, Uri uri) {
//This executes when scanning is completed
}
}
);