Tomar y guardar fotografías [Android]

Nihon

Hola compadres:

Llevo unos meses desarrollando una aplicación en Java (Android) para una empresa. Una de las cosas que hace esta aplicación es tomar una serie de fotos desde la cámara del teléfono y comprimirlas para que ocupen mucho menos espacio y poder subirlas más rapidamente al servidor de la empresa.

Al poco de distribuir la aplicación muchos usuarios me llamaron comentando que el programa se cerraba de golpe al encontrar un fallo a la hora de guardar las imágenes, por lo que los datos sin guardar que hubiera se perdían lo que hacía que el fallo fuera peor todavía. Además de ser un fallo que a mí nunca me ha pasado y que no pude controlar.

Pues bien, hace unos días pensando que el problema tenía que ver con la compresión de imágenes y tener que trabajar con memoria pregunté por aquí y después de algún cambio me pareció que todo funcionaba bien. El caso es volvió a fallar y me pareció haber encontrado el fallo que no tenía nada que ver con las fotos en sí. Os pongo el código y os explico.

	public void Camara()
	{
		try
		{

//Este código es previo al evento de activar la cámara de fotos.
	
		Intent icamara = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
        Time now = new Time();
        archivo = "";
        now.setToNow();

//Aquí me creo un archivo (file) cuyo nombre será un código + un tipo + hora
	        
archivo = Environment.getExternalStorageDirectory() + "/external_sd/Fotos/"+ "ID" + parte.getID()+ tipoFoto + now.format2445().toString() +".jpg"; file = new File(archivo); //Finalmente ejecuto la actividad cámara pasándole dicho archivo como parámetro Uri outputFileUri = Uri.fromFile(file); icamara.putExtra(MediaStore.EXTRA_OUTPUT, outputFileUri); startActivityForResult(icamara,0); } catch(Exception e) { Toast.makeText(getApplicationContext(), e.getMessage(), Toast.LENGTH_LONG).show(); } }

El código anterior se produce al pulsar el botón correspondiente y digamos que es sólo para guardar un archivo para que yo lo controle. El siguiente código se ejecuta al pulsar "Guardar" al tomar una foto.

	 public void onActivityResult (int requestCode, int resultCode, Intent data)
	 {
		 if(requestCode ==0 && resultCode == RESULT_OK)
		 { 
				 pactual = posicion;
				 posicion ++;
				 
			 try
			 {
/*Convierto el archivo generado en la previa a un bitmap para tratarlo como imagen y las últimas pruebas me han dado a entender que el fallo está en alguna de las siguientes 4 líneas*/

				 Bitmap mybit = BitmapFactory.decodeFile(file.getAbsolutePath());
				 
				 imagen.setImageBitmap(mybit);
				 imagen.setScaleType(ScaleType.FIT_XY);
				 
//Añado la foto a la lista de fotos (array) existente.

				 parte.Fotos.addImagen(file.getAbsolutePath(), tipoFoto);
				 
				 if(parte.Fotos.getNumeroFotos(tipoFoto)>0)
				 {
					 eliminar.setVisibility(View.VISIBLE);
				 }
				 
				 if(parte.Fotos.getNumeroFotos(tipoFoto)>1)
				 {
					 siguiente.setVisibility(View.VISIBLE);
				 }
			 
			 //Aqui cambiamos el tamaño de la imagen y lo comprimimos para ocupar mucho menos espacio y seguir tomando fotos si no se ha cancelado.
				 		
				Comprimir(file.getAbsolutePath(), mybit);
			 	Camara();
			 }
			 
			 catch(Exception ex)
			 {
				 pactual = posicion = 0;
				 Toast.makeText(getApplicationContext(),"Se ha producido un error, vuelva a tomar la foto", Toast.LENGTH_LONG).show();
			 }
}
}

El caso es que he conseguido que la aplicación no se cierre de golpe y muestre el mensaje de error del "catch" del evento onActivityResult. Sin embargo tengo 2 problemas, es algo que me sucede de manera tan aleatoria que no puedo estar seguro de que sea lo que yo creo. Segundo, no puedo hacer una traza o mostrar un log ya que los teléfonos que fallan están repartidos por España y el que tengo yo falló hace unos días y no se conecta en modo depuración USB por lo que no puedo conectarme directamente desde Java y comprobar la ejecución.

Lo que os pido es simplemente si me podéis decir si hay algún fallo claro en mi código que haga que falle la aplicación y que yo no vea y pueda causar cualquier fallo no controlado. Gracias de antemano.

1
JuAn4k4
Bitmap mybit = BitmapFactory.decodeFile(file.getAbsolutePath());

ese metodo tiene un segundo parametro opciones.

La cosa era, que en algunos móviles decodeFile puede llegar a ocupar más memoria de la que tu app dispone.

http://www.mediavida.com/foro/dev/hilo-general-dudas-de-java-435123/9#255

Edit: Si eres el mismo ! Por que no usas las opciones ?

Lo que tienes que hacer es en las opciones poner que no te cargue en memoria la imagen.

1 respuesta
Nihon

#2 Es cierto que eso lo usé para comprimir las imágenes pero creo que ahora el error no está en el bitmap en sí si no en el "file" ya que si al fallar intento mostrar el nombre por un mensaje la aplicación falla y se cierra.

1 respuesta
JuAn4k4

#3 Si file es NULL, en el código no sabemos de donde viene, ni porque no debería ser null.

1 respuesta
Nihon

#4 file se crea siempre en Camara() como new File y en onActivityResult, aunque falle, si compruebo las imágenes veo que se ha guardado correctamente lo que me hace pensar que file no es null. Pero si intento acceder a el es cuando falla. En ese "file.getAbsolutePath()" ya que al principio intentaba borrar ese archivo si había habido un error, pero al intentar borrarlo la aplicación también fallaba.

Voy a meter un if(file!=null) después de crearlo que no creo que haga daño.

1 respuesta
Tig

#5 usa también file.exists(), además de != null

Luego, cualquier Bitmap que crees tienes que hacer

if(bitmap != null) bitmap.recycle()

en onDestroy, o en el momento que no vayas a necesitarlo más

Si el problema es de memoria, te recomendaría que no leyeras toda la imagen sino que la escales a una calidad inferior. Esto se hace pasando opciones al decodificar la imagen. Yo uso un código de este estilo

public static Bitmap getReducedBitmap(Context context, String path, boolean overwriteOriginal) {    
File bitmapFile = new File(path); Uri uri = Uri.fromFile(bitmapFile); InputStream in = null; ContentResolver contentResolver = context.getContentResolver(); try { in = contentResolver.openInputStream(uri); // Decode image size BitmapFactory.Options o = new BitmapFactory.Options(); o.inJustDecodeBounds = true; BitmapFactory.decodeStream(in, null, o); in.close(); int scale = 1; while ((o.outWidth * o.outHeight) * (1 / Math.pow(scale, 2)) > WSConfig.PICTURE_MAX_PIXELS) { scale++; } Log.d("UTILS", "scale = " + scale + ", orig-width: " + o.outWidth + ", orig-height: " + o.outHeight); Bitmap b = null; in = contentResolver.openInputStream(uri); if (scale > 1) { // scale to max possible inSampleSize that still yields an image // larger than target o = new BitmapFactory.Options(); o.inSampleSize = scale; b = BitmapFactory.decodeStream(in, null, o); if(overwriteOriginal) Utils.bitmapToFile(b, bitmapFile); } else { b = BitmapFactory.decodeStream(in); } in.close(); Log.d("UTILS", "bitmap size - width: " + b.getWidth() + ", height: " + b.getHeight()); return b; } catch (IOException e) { Log.e("UTILS", e.getMessage(), e); return null; } }

donde WSConfig.PICTURE_MAX_PIXELS es un parámetro de mi webservice.

Falta pulir algún detalle de este método (comprobar que la ruta existe, etc.), pero funciona.

Ah, y no hagas esto en el hilo de pintado, sobre todo si pasas el 3er parámetro a true.

2
19 días después
Nihon

Llevaba un tiempo pensando que había solucionado el problema y que era algo de la memoria al manejar imágenes grandes pero parece ser que tiene que salir algo más a tocarme las narices. He conseguido que me dejaran una tablet que falla al tomar las imágenes y me ha sorprendido ver el fallo que me sale al hacer el debug:

couldn't save which view has focus because the focused view has no id

Al ver esto también he pensado que quizás mi actividad se destruye y se crea de nuevo en algún punto y eso hace que las referencias a file, mybit y demás se pierdan y que ese fuera el fallo original. Me he tirado toda la mañana con esto sin arreglar nada al final y estoy hasta los coj*nes.

Creo que lo mejor va a ser borrar todo lo que tengo y empezar desde 0.

Usuarios habituales

  • Nihon
  • Tig
  • JuAn4k4