Intención ACTION_IMAGE_CAPTURE de Android

163

Estamos tratando de usar la aplicación de cámara nativa para permitir que el usuario tome una nueva foto. Funciona bien si dejamos de lado EXTRA_OUTPUT extray devuelve la pequeña imagen de mapa de bits. Sin embargo, si tenemos putExtra(EXTRA_OUTPUT,...)la intención antes de comenzar, todo funciona hasta que intente presionar el botón "Aceptar" en la aplicación de la cámara. El botón "Aceptar" simplemente no hace nada. La aplicación de la cámara permanece abierta y nada se bloquea. Podemos cancelarlo, pero el archivo nunca se escribe. ¿Qué tenemos que hacer exactamente ACTION_IMAGE_CAPTUREpara escribir la imagen tomada en un archivo?

Editar: Esto se hace a través de la MediaStore.ACTION_IMAGE_CAPTUREintención, solo para ser claro

Dibujó
fuente

Respuestas:

144

Este es un error bien documentado en algunas versiones de Android. es decir, en las versiones de Android de la experiencia de Google, la captura de imágenes no funciona como se documenta. lo que generalmente he usado es algo como esto en una clase de utilidades.

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

}

luego, cuando inicio la captura de imágenes, creo una intención que comprueba el error.

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

luego, en la actividad a la que regreso, hago diferentes cosas según el dispositivo.

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

esto le ahorra tener que escribir una nueva aplicación de cámara, pero este código tampoco es excelente. los grandes problemas son

  1. nunca obtienes imágenes de tamaño completo de los dispositivos con el error. obtienes imágenes de 512 píxeles de ancho que se insertan en el proveedor de contenido de imágenes. en dispositivos sin el error, todo funciona como documento, obtienes una gran imagen normal.

  2. Tienes que mantener la lista. tal como está escrito, es posible que los dispositivos se actualicen con una versión de Android (digamos las compilaciones de cyanogenmod ) que tiene el error corregido. Si eso sucede, su código se bloqueará. la solución es usar toda la huella digital del dispositivo.

yanokwa
fuente
21
en Galaxy S: intent.getData () devuelve nulo, y además, la aplicación de cámara en Galaxy s inserta una nueva foto en la galería por sí misma, pero no se devuelve ninguna uri. Entonces, si inserta una foto del archivo en la galería, habrá una foto duplicada.
Arseniy
1
oye mi dispositivo no está aquí y he implementado por ur way.but mi solicitud vuelve a colgarse i don knw la ayuda reason.please mí
Geetanjali
usando este código en dispositivos droid-x y sony xpheria. Ambos dispositivos devuelven el valor de intención como nulo. también droidx devuelve el código de resultado como 0, mientras que xpheria devuelve el código de resultado como -1. Alguien sabe por qué ese debería ser el caso.
rOrlig
55
El enlace al "error bien documentado" que está al comienzo de la respuesta de Yanokwa es incorrecto. Ese error se trata de llamar a la aplicación de la cámara sin putExtra (EXTRA_OUTPUT, ...)
Frank Harper
code.google.com/p/android/issues/detail?id=1480 Bueno, ¿cómo explicas esto entonces?
Pencilcheck
50

Sé que esto ha sido respondido antes, pero sé que mucha gente se ha tropezado con esto, así que voy a agregar un comentario.

Tuve exactamente el mismo problema en mi Nexus One. Esto era del archivo que no existía en el disco antes de que comenzara la aplicación de la cámara. Por lo tanto, me aseguré de que el archivo existente antes de iniciar la aplicación de la cámara. Aquí hay un código de muestra que usé:

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

Primero creo un nombre de archivo único (algo) usando un hash MD5 y lo pongo en la carpeta apropiada. Luego verifico para ver si existe (no debería, pero es una buena práctica verificar de todos modos). Si no existe, obtengo el directorio principal (una carpeta) y creo la jerarquía de carpetas (por lo tanto, si las carpetas que conducen a la ubicación del archivo no existen, aparecerán después de esta línea. Luego, después de eso Creo el archivo. Una vez que se crea el archivo, obtengo el Uri y lo paso a la intención y luego el botón OK funciona como se esperaba y todo está dorado.

Ahora, cuando se presiona el botón Ok en la aplicación de la cámara, el archivo estará presente en la ubicación dada. En este ejemplo, sería /sdcard/Android/data/com.example.myapp/files/234asdioue23498ad.jpg

No es necesario copiar el archivo en "onActivityResult" como se publicó anteriormente.

Donn Felker
fuente
13
Asegúrese de tener <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />en su manifiesto si está utilizando una versión más nueva que Android 1.5: pasé unas horas depurando las cosas de la cámara y esto no estaba incluido, por lo que mis guardados siempre fallarían.
swanson
Esto funciona bien para mí, pero cada 10 imágenes más o menos, el archivo en blanco no se sobrescribe y termino con un archivo de 0 bytes donde debería estar la imagen. Es muy molesto y ha sido difícil de rastrear.
joey_g216
2
en algunos dispositivos, como el nexus 7 con jelly bean, debe cambiar Environment.getExternalStorageDirectory (). getName () para usar .getAbsolutePath () en lugar de .getName ()
Keith
35

He pasado por una serie de estrategias de captura de fotos, y siempre parece haber un caso, una plataforma o ciertos dispositivos, donde algunas o todas las estrategias anteriores fallarán de manera inesperada. Pude encontrar una estrategia que usa el código de generación de URI a continuación que parece funcionar en la mayoría, si no en todos los casos.

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

Para contribuir más a la discusión y ayudar a los recién llegados, he creado una aplicación de muestra / prueba que muestra varias estrategias diferentes para la implementación de captura de fotos. Las contribuciones de otras implementaciones son definitivamente alentadas a agregar a la discusión.

https://github.com/deepwinter/AndroidCameraTester

invierno profundo
fuente
Esto fue genial. Todavía tengo que probar esto en todos mis dispositivos, pero supongo que ya lo has hecho bastante. Hay mucho ruido sobre este tema, y ​​esta respuesta en particular es muy simple y limpia. Funciona en Nexus 5 hasta ahora, que es donde obtuve muchos informes de que ACTION_IMAGE_CAPTURE no funcionaba sin EXTRA_OUTPUT. Espero que esto funcione en todos los ámbitos, pero hasta ahora todo bien. Thx
Rich
1
@deepwinter - Gracias señor. Me estaba sacando el pelo pero su solución de resolución de contenido funciona bien en todos los escenarios problemáticos que tenía.
Billy Coover
Llegué tarde a esta fiesta, pero esta ha sido la única solución para mí. ¡Gracias un montón!
StackJP
¿puedo recuperar de alguna manera el MediaStore.EXTRA_OUTPUT onActivityResulto realmente tengo que recordarlo yo mismo? Tendría que guardarlo de forma persistente para que mi aplicación incluso lo recuerde si se destruye mientras se toma una foto ...
prom85
No funciona en Samsung Galaxy Note 3 con lolipop: ava.lang.NullPointerException: intento de invocar el método virtual 'java.lang.String android.net.Uri.getScheme ()' en una referencia de objeto nulo
Igor Janković
30

Tuve el mismo problema donde el botón OK en la aplicación de la cámara no hizo nada, tanto en el emulador como en el nexus one.

El problema desapareció después de especificar un nombre de archivo seguro que no tiene espacios en blanco, sin caracteres especiales, en MediaStore.EXTRA_OUTPUT Además, si está especificando un archivo que reside en un directorio que aún no se ha creado, primero debe crearlo. La aplicación de la cámara no hace mkdir por ti.

Yenchi
fuente
1
Y asegúrese de usar en mkdirs()lugar de mkdir()si su ruta tiene más de un nivel de directorio y desea que se creen todos ( mkdirsolo crea el último, el directorio 'hoja'). Tuve algunos problemas con eso, y esta respuesta me ayudó.
Diana
¿podemos usar marcas de tiempo simples? ¿O también pueden cometer algunos errores? por
favor, etiquétenme
@ user2247689 No estoy seguro sobre el nombre de archivo que creó con "marcas de tiempo simples", pero siempre que no contengan caracteres especiales no permitidos por el formato FAT en la tarjeta SD, debería estar bien, creo.
Yenchi
28

El flujo de trabajo que describe debe funcionar como lo describió. Puede ser útil que nos muestre el código en torno a la creación de la intención. En general, el siguiente patrón debería permitirle hacer lo que está intentando.

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

Tenga en cuenta que es la Actividad de la cámara la que creará y guardará el archivo, y en realidad no forma parte de su aplicación, por lo que no tendrá permiso de escritura en la carpeta de la aplicación. Para guardar un archivo en la carpeta de la aplicación, cree un archivo temporal en la tarjeta SD y muévalo a la carpeta de la aplicación en el onActivityResultcontrolador.

Reto Meier
fuente
Esto / debería / funciona, pero el botón ok de la aplicación de la cámara simplemente no hace nada. Lo hicimos funcionar escribiendo en la tarjeta SD, por lo que sospecho que este es un problema de permisos de archivos (aunque pensé que una aplicación tenía automáticamente permisos de escritura en su directorio personal). ¡gracias por la ayuda!
Drew
1
Su aplicación tiene permiso de escritura en su directorio personal; la aplicación de la cámara (que está tomando la foto) no. Sin embargo, puede mover el archivo en onActivityResult, ya que está dentro de su aplicación. Alternativamente, podría implementar una actividad de cámara usted mismo, pero eso parece excesivo.
Reto Meier
Rehacer la aplicación de la cámara parece ser un enfoque común pero tedioso ... Cuando usamos getDir ("imágenes", MODE_WORLD_WRITEABLE), ¿ese permiso no se aplica a ningún archivo que le pedimos que cree en ese directorio?
Drew
No necesariamente. El permiso es para la carpeta, no necesariamente para los archivos que contiene.
Reto Meier
Probé el código con dispositivos Android con Android 2.2 y posterior y todos guardaron la imagen en la ruta provista. Así que supongo que este debería ser un comportamiento estándar para obtener imágenes.
Janusz
7

Para seguir el comentario de Yenchi anterior, el botón Aceptar tampoco hará nada si la aplicación de la cámara no puede escribir en el directorio en cuestión.

Eso significa que no puede crear el archivo en un lugar que solo su aplicación pueda escribir (por ejemplo, algo debajo de getCacheDir())Algo debajo getExternalFilesDir()debería funcionar, sin embargo.

Sería bueno si la aplicación de la cámara imprimiera un mensaje de error en los registros si no pudiera escribir en la EXTRA_OUTPUTruta especificada , pero no encontré ninguno.

Joe
fuente
4

para que la cámara escriba en la tarjeta SD pero mantenga un nuevo álbum en la aplicación de la galería, uso esto:

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

Tenga en cuenta que deberá generar un nombre de archivo único cada vez y reemplazar el 1111.jpg que escribí. Esto fue probado con nexus one. la uri se declara en la clase privada, por lo que en el resultado de la actividad puedo cargar la imagen desde la uri a imageView para obtener una vista previa si es necesario.

nabulaer
fuente
¿De dónde viene la columna "_data"? ¿No hay constante para ello?
Matthias
3
ah, es developer.android.com/reference/android/provider/… Realmente no deberías usar cadenas mágicas en el código.
Matthias
1

Tuve el mismo problema y lo solucioné con lo siguiente:

El problema es que cuando especifica un archivo al que solo su aplicación tiene acceso (por ejemplo, llamando getFileStreamPath("file");)

Es por eso que me aseguré de que el archivo dado realmente exista y que TODOS tengan acceso de escritura.

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

De esta manera, la aplicación de la cámara tiene acceso de escritura a la Uri dada y el botón Aceptar funciona bien :)

Goddchen
fuente
AndroidRuntime (emulador 2.1) lanza NoSuchMethodError: java.io.File.setWritable
jwadsack
1

Te recomiendo que sigas la publicación de entrenamiento de Android para capturar una foto. Muestran en un ejemplo cómo tomar fotografías pequeñas y grandes. También puedes descargar el código fuente desde aquí

JuanVC
fuente
0

Creé una biblioteca simple que administrará la elección de imágenes de diferentes fuentes (Galería, Cámara), tal vez la guarde en algún lugar (tarjeta SD o memoria interna) y devuelva la imagen, así que puede usarla y mejorarla libremente - Android-ImageChooser .

svenkapudija
fuente
tu enlace ya no funciona !! porque, lo vi hace 2 semanas, y quería usarlo :(
Mohammad Ersan
0

El archivo debe ser editable por la cámara, como señaló Praveen .

En mi uso, quería almacenar el archivo en almacenamiento interno. Hice esto con:

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

Aquí cacheFilehay un archivo global utilizado para referirse al archivo que está escrito. Luego, en el método de resultado, la intención devuelta es nula. Entonces el método para procesar la intención se ve así:

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

Aquí getImageFile()hay un método de utilidad para nombrar y crear el archivo en el que se debe almacenar la imagen, y copyFile()es un método para copiar un archivo.

Duncan
fuente
0

Puedes usar este código

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;
}
Ngông Cuồng
fuente
0

Es muy sencillo resolver este problema con el Código de resultado de actividad. Pruebe este método

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();
   }
}
Savita
fuente
El fragmento incompleto anterior no merece la simplicidad que afirmó. Seguramente, hay complejidades ocultas para el viajero desconocido. Tal como escanear en busca de presencia o ausencia del archivo. Como wither está disponible públicamente o en privado para la aplicación. Como la necesidad de un proveedor u obtener un tamaño de miniatura solamente. Estas son brechas de información que el viajero desprevenido debe tener en cuenta.
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
    }
}
Ajesh Gupta
fuente