Categoría: Android

java.lang.OutOfMemoryError: Java heap space en Visual Studio con Xamarin

Al desarrollar una app en Visual Studio, aprovechando las ventajas de la nueva y gratuita integración con Xamarin, el compilador nos puede arrojar eventualmente un error del tipo

java.lang.OutOfMemoryError: Java heap space .

Esto ocurre básicamente porque se excede el máximo de memoria alojada (allocated memory) de la pila de instrucciones (heap memory), lo que se puede resolver de dos formas: la primera es colocar un parámetro en tiempo de compilación que aumente ese espacio a 1 Gb o más, y la segunda es cambiar el JDK con el que se esta compilando, ya que por defecto se toma la versión de 32 bits del mismo, y el JDK disponible de 64 bits tiene mayores optimizaciones y licencias que esta versión, por ende, hay que remplazarla.

Leer más →

Descomprimir fichero zip en Android

En ocasiones, necesitaremos descomprimir un archivo zip en Android para evitar la descarga de muchos archivos pequeños (como imágenes, o pequeños ficheros de texto), agrupándolos en uno solo.

Adjunto una clase heredada desde AsyncTask para mostrar un dialogo modal de espera mientras se ejecuta la descompresión. Esta funciona cargando en un buffer de 2MB para evitar una llamada constante al Garbage Collector mientras se escriben bytes individuales, lo que acelera INCREÍBLEMENTE el tiempo de descompresión.

package com.descompresor;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import android.app.ProgressDialog;
import android.content.Context;
import android.os.AsyncTask;
import android.util.Log;

class DescomprimeArchivos extends AsyncTask {

	ProgressDialog mProgresoDescompresion;
	String _Ubicacion_ZIP;
	String _Destino_Descompresion;
	boolean _Mantener_ZIP;

	/**
	 * Descomprime un archivo .ZIP
	 * @param ctx Contexto de la Aplicación Android
	 * @param Ubicacion_ZIP Ruta ABSOLUTA de un archivo .zip
	 * @param Destino_Descompresion Ruta ABSOLUTA del destino de la descompresión. Finalizar con /
	 * @param Mantener_ZIP Indica si se debe mantener el archivo ZIP despues de descomprimir
	 */
	public DescomprimeArchivos(Context ctx, String Ubicacion, String Destino, boolean Mantener)
	{
		mProgresoDescompresion = new ProgressDialog(ctx);
		mProgresoDescompresion.setTitle("Descomprimiendo...");
		mProgresoDescompresion.setMessage("Por favor espere...");
		mProgresoDescompresion.setIndeterminate(false);
		mProgresoDescompresion.setProgress(100);
		mProgresoDescompresion.setCancelable(true);
		mProgresoDescompresion.setProgressStyle(ProgressDialog.STYLE_SPINNER);

		_Ubicacion_ZIP = Ubicacion;
		_Destino_Descompresion = Destino;
		_Mantener_ZIP = Mantener;
	}

	@Override
	protected String doInBackground(String... params) {
		int size;
		byte[] buffer = new byte[2048];

		new File(_Destino_Descompresion).mkdirs(); //Crea la ruta de descompresion si no existe

		try {
			try {
				FileInputStream lector_archivo = new FileInputStream(_Ubicacion_ZIP);
				ZipInputStream lector_zip = new ZipInputStream(lector_archivo);
				ZipEntry item_zip = null;

				while ((item_zip = lector_zip.getNextEntry()) != null) {
					Log.v("Descompresor", "Descomprimiendo " + item_zip.getName());

					if (item_zip.isDirectory()) { //Si el elemento es un directorio, crearlo
						Crea_Carpetas(item_zip.getName(), _Destino_Descompresion);
					} else {
						FileOutputStream outStream = new FileOutputStream(
								_Destino_Descompresion + item_zip.getName());
						BufferedOutputStream bufferOut = new BufferedOutputStream(
								outStream, buffer.length);

						while ((size = lector_zip.read(buffer, 0, buffer.length)) != -1) {
							bufferOut.write(buffer, 0, size);
						}

						bufferOut.flush();
						bufferOut.close();
					}

				}
				lector_zip.close();
				lector_archivo.close();

				if(!_Mantener_ZIP)
					new File(_Ubicacion_ZIP).delete();

			} catch (Exception e) {
				Log.e("Descompresor", "Descomprimir", e);
			}
			mProgresoDescompresion.dismiss();
		} catch (Exception e) {
			Log.e("Error", e.getMessage());
		}
		return null;
	}

	@Override
	protected void onPreExecute() {
		super.onPreExecute();
		mProgresoDescompresion.show();
	}

	@Override
	protected void onProgressUpdate(Integer... progress) {
		super.onProgressUpdate(progress);
		mProgresoDescompresion.setProgress(progress[0]);
	}

	private void Crea_Carpetas(String dir, String location) {
		File f = new File(location + dir);

		if (!f.isDirectory()) {
			f.mkdirs();
		}
	}

}

Instanciamos el objeto. Notar que grabaremos en el cache de la aplicación Android y no en la tarjeta SD o la memoria interna.

final DescomprimeArchivos descompresor = 
new DescomprimeArchivos(this, getApplicationContext().getCacheDir().toString() + 
"/archivo.zip", getApplicationContext().getCacheDir().toString() + "/descomprimir/", false);

Para utilizar esta clase, y basándonos en su derivación de AsyncTask podemos invocar al método público y estático excecute. Este método recibe parámetros de longitud variable (es decir, n argumentos), pero para este caso, no necesitamos ninguno. Como inicializamos con el contexto de Android previamente, podemos llamar a este método directamente desde el evento de un control. Para este ejemplo, invocamos el método directamente desde el evento OnClick de un Button.

Button btn_desc = (Button) findViewById(R.id.btn_desc);
		btn_desc.setOnClickListener(new View.OnClickListener() {
			public void onClick(View v) {
				descompresor.execute();
			}
		});