Android ACTION_IMAGE_CAPTURE意向


163

我们正在尝试使用本机相机应用程序让用户拍摄新照片。如果我们省略EXTRA_OUTPUT extra并返回小的Bitmap图片,则效果很好。但是,如果我们putExtra(EXTRA_OUTPUT,...)在启动意图之前一直如此,那么一切都会起作用,直到您尝试单击相机应用程序中的“确定”按钮为止。“确定”按钮什么也不做。相机应用保持打开状态,没有任何锁定。我们可以取消它,但是文件永远不会被写入。ACTION_IMAGE_CAPTURE为了将拍摄的照片写入文件,我们到底需要做什么?

编辑:这是通过MediaStore.ACTION_IMAGE_CAPTURE意图完成的,只是要清楚

Answers:


144

在某些版本的android中,这是一个有据可查的错误。也就是说,在android的google体验版本中,图像捕获无法按文档所述进行。我通常使用的是Utility类中的类似内容。

public boolean hasImageCaptureBug() {

    // list of known devices that have the bug
    ArrayList<String> devices = new ArrayList<String>();
    devices.add("android-devphone1/dream_devphone/dream");
    devices.add("generic/sdk/generic");
    devices.add("vodafone/vfpioneer/sapphire");
    devices.add("tmobile/kila/dream");
    devices.add("verizon/voles/sholes");
    devices.add("google_ion/google_ion/sapphire");

    return devices.contains(android.os.Build.BRAND + "/" + android.os.Build.PRODUCT + "/"
            + android.os.Build.DEVICE);

}

然后,当我启动图像捕获时,我会创建一个检查错误的意图。

Intent i = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
if (hasImageCaptureBug()) {
    i.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, Uri.fromFile(new File("/sdcard/tmp")));
} else {
    i.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
}
startActivityForResult(i, mRequestCode);

然后在我返回的活动中,我会根据设备执行不同的操作。

protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
     switch (requestCode) {
         case GlobalConstants.IMAGE_CAPTURE:
             Uri u;
             if (hasImageCaptureBug()) {
                 File fi = new File("/sdcard/tmp");
                 try {
                     u = Uri.parse(android.provider.MediaStore.Images.Media.insertImage(getContentResolver(), fi.getAbsolutePath(), null, null));
                     if (!fi.delete()) {
                         Log.i("logMarker", "Failed to delete " + fi);
                     }
                 } catch (FileNotFoundException e) {
                     e.printStackTrace();
                 }
             } else {
                u = intent.getData();
            }
    }

这省去了编写新相机应用的麻烦,但是这段代码也不是一件好事。最大的问题是

  1. 您永远不会从带有错误的设备上获取完整尺寸的图像。您将获得512px宽的图片,并将其插入到图像内容提供程序中。在没有错误的设备上,一切都可以作为文档工作,您会看到一张正常的图片。

  2. 您必须维护列表。按照书面规定,可以使用已修复错误的android版本(例如cyanogenmod的builds)来刷新设备。如果发生这种情况,您的代码将崩溃。解决方法是使用整个设备的指纹。


21
在Galaxy S上:intent.getData()返回null,此外,Galaxy s上的照相机应用程序自行将新照片插入图库,但未返回uri。因此,如果将文件中的照片插入图库,则会有重复的照片。
Arseniy

1
嘿,我的设备未在此处列出,我已经通过ur way。实施了,但是我的应用程序仍然崩溃,我不知道原因。请帮助我
Geetanjali 2011年

在droid-x和Sony xpheria设备上使用此代码。两个设备都将Intent值返回为null。droidx还返回结果代码为0,而xpheria返回结果代码为-1。任何人都知道为什么会这样。
rOrlig 2011年

5
yanokwa回答开头的“记录良好的错误”链接不正确。该错误是关于调用不带putExtra(EXTRA_OUTPUT,...)
弗兰克·哈珀


50

我知道以前已经回答过这个问题,但我知道很多人对此感到迷惑,所以我要添加评论。

我在Nexus One上发生了同样的问题。这是因为相机应用程序启动之前磁盘上不存在的文件。因此,在启动相机应用程序之前,请确保文件已存在。这是我使用的一些示例代码:

String storageState = Environment.getExternalStorageState();
        if(storageState.equals(Environment.MEDIA_MOUNTED)) {

            String path = Environment.getExternalStorageDirectory().getName() + File.separatorChar + "Android/data/" + MainActivity.this.getPackageName() + "/files/" + md5(upc) + ".jpg";
            _photoFile = new File(path);
            try {
                if(_photoFile.exists() == false) {
                    _photoFile.getParentFile().mkdirs();
                    _photoFile.createNewFile();
                }

            } catch (IOException e) {
                Log.e(TAG, "Could not create file.", e);
            }
            Log.i(TAG, path);

            _fileUri = Uri.fromFile(_photoFile);
            Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE );
            intent.putExtra( MediaStore.EXTRA_OUTPUT, _fileUri);
            startActivityForResult(intent, TAKE_PICTURE);
        }   else {
            new AlertDialog.Builder(MainActivity.this)
            .setMessage("External Storeage (SD Card) is required.\n\nCurrent state: " + storageState)
            .setCancelable(true).create().show();
        }

我首先使用MD5哈希创建一个唯一的(某种程度上)文件名,并将其放入适当的文件夹中。然后,我检查它是否存在(不应该,但是无论如何还是要检查它的好习惯)。如果不存在,则获取父目录(一个文件夹)并创建直至其的文件夹层次结构(因此,如果导致该文件位置的文件夹不存在,它们将在此行之后。然后)我创建了文件,一旦创建文件,我得到Uri并将其传递给意图,然后OK按钮按预期工作,并且一切正常。

现在,在相机应用程序上按“确定”按钮时,文件将出现在给定位置。在此示例中,它是/sdcard/Android/data/com.example.myapp/files/234asdioue23498ad.jpg

如上所述,无需在“ onActivityResult”中复制文件。


13
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />如果您使用的是比Android 1.5更高的版本,请确保已出现在清单中-花费了几个小时来调试摄像头,但这并未包括在内,因此我的保存将永远失败。
swanson

这对我很有用,但是每隔大约10张图像,空白文件不会被覆盖,而我最终得到一个0字节的文件,该图像应位于该位置。这很烦人,很难追踪。
joey_g216 2011年

2
在某些设备上,例如带有果冻豆的nexus 7,您需要将Environment.getExternalStorageDirectory()。getName()更改为使用.getAbsolutePath()而不是.getName()
Keith

35

我经历过许多照片捕获策略,并且似乎总是有一种情况,平台或某些设备,其中上述某些或全部策略会以意想不到的方式失败。我能够找到一种使用以下URI生成代码的策略,该策略似乎在大多数情况下(即使不是所有情况下)都可以使用。

mPhotoUri = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, 
            new ContentValues());
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT, mPhotoUri);
startActivityForResult(intent,CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE_CONTENT_RESOLVER);

为了进一步促进讨论并帮助新手,我创建了一个示例/测试应用程序,其中显示了几种不同的照片捕获实施策略。绝对鼓励其他实现的贡献来增加讨论。

https://github.com/deepwinter/AndroidCameraTester


太好了 我尚未在所有设备上对此进行测试,但我假设您已经做了很多。这个主题上有太多杂音,这个特定的答案是如此简单和干净。到目前为止,该版本可在Nexus 5上运行,这是我第一次收到很多关于ACTION_IMAGE_CAPTURE在没有EXTRA_OUTPUT的情况下无法正常工作的报告。我确实希望这一切都能奏效,但到目前为止一切顺利。Thx
Rich

1
@deepwinter-谢谢您,先生。我正在拔头发,但是您的内容解析器解决方案在我遇到的所有有问题的场景中都能很好地工作。
比利·库弗

这次聚会有点晚,但这是为我工作的唯一解决方案。谢谢一群!
2014年

我可以以某种方式检索MediaStore.EXTRA_OUTPUT onActivityResult还是必须自己记住?我必须坚持保存它,以便我的应用程序甚至可以记住它,如果它在拍照时被毁了……
prom85

它不适用于带有lolipop的三星银河笔记3:ava.lang.NullPointerException:尝试在空对象引用上调用虚拟方法'java.lang.String android.net.Uri.getScheme()'
IgorJanković

30

我遇到了同样的问题,即相机应用程序中的“确定”按钮在模拟器和连结上均无作用。

在还指定了没有空格,没有特殊字符的安全文件名之后,该问题消失了。MediaStore.EXTRA_OUTPUT 此外,如果您指定的文件位于尚未创建的目录中,则必须首先创建它。相机应用不会为您执行mkdir。


1
并确保使用mkdirs()而不是mkdir()您的路径是否具有多个目录级别,并且您要创建所有目录(mkdir仅创建最后一个目录,即“叶”目录)。我对此有一些问题,这个答案对我有所帮助。
戴安娜(Diana)

我们可以使用简单的时间戳吗?还是可以使一些错误?请在回复时给我
加上

@ user2247689我不确定您使用“简单时间戳记”创建的文件名,但只要它们不包含SD卡上FAT格式不允许的特殊字符,我认为应该没问题。
Yenchi 2013年

28

您描述的工作流程应按您描述的那样工作。如果您可以向我们展示有关Intent创建的代码,则可能会有所帮助。通常,以下模式应该可以让您执行正在尝试的操作。

private void saveFullImage() {
  Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
  File file = new File(Environment.getExternalStorageDirectory(), "test.jpg");
  outputFileUri = Uri.fromFile(file);
  intent.putExtra(MediaStore.EXTRA_OUTPUT, outputFileUri);
  startActivityForResult(intent, TAKE_PICTURE);
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  if ((requestCode == TAKE_PICTURE) && (resultCode == Activity.RESULT_OK)) {
    // Check if the result includes a thumbnail Bitmap
    if (data == null) {    
      // TODO Do something with the full image stored
      // in outputFileUri. Perhaps copying it to the app folder
    }
  }
}

请注意,将由Camera Activity创建并保存文件,它实际上不是您的应用程序的一部分,因此它对您的应用程序文件夹没有写权限。要将文件保存到您的应用程序文件夹中,请在SD卡上创建一个临时文件,然后将其移动到onActivityResult处理程序中的您的应用程序文件夹中。


/应该/可以,但是相机应用的“确定”按钮什么也没做。我们确实可以将其写入SD卡,因此我怀疑这是文件权限问题(尽管我认为应用程序自动对其个人目录具有写权限)。谢谢您的帮助!
提请2009年

1
您的应用对其个人目录具有写权限-相机应用(正在拍照)没有此权限。不过,您可以在onActivityResult中移动文件,因为它位于您的应用程序内。另外,您也可以自己实施“相机活动”,但这似乎有些过分。
Reto Meier

重做相机应用程序似乎是一种常见但繁琐的方法...当我们使用getDir(“ images”,MODE_WORLD_WRITEABLE)时,该权限是否不适用于我们告诉它在该目录中创建的任何文件?
提请2009年

不必要。该权限是针对文件夹的,不一定是针对其中的文件的。
Reto Meier

我在装有Android 2.2及更高版本的Android设备上测试了代码,所有代码均将图像保存到提供的路径中。因此,我想这应该是获取图像的标准行为。
Janusz 2013年

7

为了跟进Yenchi的上述评论,如果相机应用无法写入相关目录,则“确定”按钮也不会执行任何操作。

这意味着您不能在只能由应用程序编写的位置中创建文件(例如,getCacheDir())Something下的某些内容getExternalFilesDir()应该可以工作)。

如果照相机应用程序无法写入指定的EXTRA_OUTPUT路径,但我没有找到它,那么如果照相机应用程序将错误消息打印到日志中会很好。


4

让相机将数据写入sdcard,但将其保存在图库应用的新相册中,我使用以下方法:

 File imageDirectory = new File("/sdcard/signifio");
          String path = imageDirectory.toString().toLowerCase();
           String name = imageDirectory.getName().toLowerCase();


            ContentValues values = new ContentValues(); 
            values.put(Media.TITLE, "Image"); 
            values.put(Images.Media.BUCKET_ID, path.hashCode());
            values.put(Images.Media.BUCKET_DISPLAY_NAME,name);

            values.put(Images.Media.MIME_TYPE, "image/jpeg");
            values.put(Media.DESCRIPTION, "Image capture by camera");
           values.put("_data", "/sdcard/signifio/1111.jpg");
         uri = getContentResolver().insert( Media.EXTERNAL_CONTENT_URI , values);
            Intent i = new Intent("android.media.action.IMAGE_CAPTURE"); 

            i.putExtra(MediaStore.EXTRA_OUTPUT, uri);

            startActivityForResult(i, 0); 

请注意,您每次都需要生成一个唯一的文件名,并替换我编写的1111.jpg。这已与连结一进行了测试。uri是在私有类中声明的,因此在活动结果上,我能够将uri中的图像加载到imageView进行预览。


“ _data”列从何而来?没有常数吗?
马赛厄斯

3
嗯,是developer.android.com/reference/android/provider / ...您实际上不应该在代码中使用魔术字符串。
马赛厄斯

1

我遇到了同样的问题,并通过以下方法进行了修复:

问题在于,当您指定仅您的应用有权访问的文件时(例如,通过调用getFileStreamPath("file");

这就是为什么我只是确保给定文件确实存在,并且每个人都对此文件具有写权限。

Intent intent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
File outFile = getFileStreamPath(Config.IMAGE_FILENAME);
outFile.createNewFile();
outFile.setWritable(true, false);
intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT,Uri.fromFile(outFile));
startActivityForResult(intent, 2);

这样,相机应用程序就可以对给定的Uri进行写访问,并且“确定”按钮可以正常工作:)


AndroidRuntime(2.1仿真器)引发NoSuchMethodError:java.io.File.setWritable
jwadsa​​ck,2011年

1

我建议您按照android培训文章中的说明拍摄照片。他们以一个示例展示了如何拍摄大大小小的照片。您也可以从此处下载源代码


0

我创建了一个简单的库,该库将管理从不同来源(画廊,相机)中选择图像,也许将其保存到某个位置(SD卡或内存)并返回图像,所以请自由使用并加以改进-Android-ImageChooser


您的链接不再起作用!为什么,我在2周前看到了它,我想使用它:(
Mohammad Ersan 2012年

0

正如Praveen指出的那样,该文件必须可由相机写入。

在我的用法中,我想将文件存储在内部存储中。我这样做是:

Intent i = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (i.resolveActivity(getPackageManager()!=null)){
    try{
        cacheFile = createTempFile("img",".jpg",getCacheDir());
        cacheFile.setWritavle(true,false);
    }catch(IOException e){}
    if(cacheFile != null){
        i.putExtra(MediaStore.EXTRA_OUTPUT,Uri.fromFile(cacheFile));
        startActivityForResult(i,REQUEST_IMAGE_CAPTURE);
    }
}

cacheFile是一个全局文件,用于引用已写入的文件。然后,在result方法中,返回的意图为null。然后,用于处理意图的方法如下所示:

protected void onActivityResult(int requestCode,int resultCode,Intent data){
    if(requestCode != RESULT_OK){
        return;
    }
    if(requestCode == REQUEST_IMAGE_CAPTURE){
        try{
            File output = getImageFile();
            if(output != null && cacheFile != null){
                copyFile(cacheFile,output);

                //Process image file stored at output

                cacheFile.delete();
                cacheFile=null;
            }
        }catch(IOException e){}
    }
}

getImageFile()是用于命名和创建应在其中存储图像的文件的实用程序方法,copyFile()也是一种复制文件的方法。


0

您可以使用此代码

private Intent getCameraIntent() {

    PackageManager packageManager = mContext.getPackageManager();
    List<ApplicationInfo> list = packageManager.getInstalledApplications(PackageManager.GET_UNINSTALLED_PACKAGES);
    Intent main = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
    List<ResolveInfo> launchables = packageManager.queryIntentActivities(main, 0);
    if (launchables.size() == 1)
        return packageManager.getLaunchIntentForPackage(launchables.get(0).activityInfo.packageName);
    else
        for (int n = 0; n < list.size(); n++) {
            if ((list.get(n).flags & ApplicationInfo.FLAG_SYSTEM) == 1) {
                Log.d("TAG", "Installed Applications  : " + list.get(n).loadLabel(packageManager).toString());
                Log.d("TAG", "package name  : " + list.get(n).packageName);
                String defaultCameraPackage = list.get(n).packageName;


                if (launchables.size() > 1)
                    for (int i = 0; i < launchables.size(); i++) {
                        if (defaultCameraPackage.equals(launchables.get(i).activityInfo.packageName)) {
                            return packageManager.getLaunchIntentForPackage(defaultCameraPackage);
                        }
                    }
            }
        }
    return null;
}

0

使用“活动结果代码”解决此问题非常简单,请尝试使用此方法

if (reqCode == RECORD_VIDEO) {
   if(resCode == RESULT_OK) {
       if (uri != null) {
           compress();
       }
    } else if(resCode == RESULT_CANCELED && data!=null){
       Toast.makeText(MainActivity.this,"No Video Recorded",Toast.LENGTH_SHORT).show();
   }
}

上面不完整的代码片段不具有它要求的简单性。当然,对于不熟悉的旅行者来说,这里隐藏着复杂的事物。例如扫描文件的存在与否。例如枯萎,它对于应用程序是公开可用的或私有的。例如需要提供者或仅获取缩略图大小。这些是虚弱的旅行者需要注意的信息空白。
truthadjustr

0
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (resultCode == RESULT_CANCELED)
    {
        //do not process data, I use return; to resume activity calling camera intent
        enter code here
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.