Opciones de almacenamiento

Android ofrece varias formas de guardar datos en una aplicación, todo dependerá de la necesidad que tengamos a la hora de guardarlos. Es decir, si los datos van a ser privados de la propia aplicación o van a ser públicos accesibles desde otras aplicaciones, la cantidad de datos que queremos guardar, ...

En el caso de hacer los datos públicos a otras aplicaciones deberemos usar un Content Provider, que simplemente es un componente opcional que nos proporciona lectura/escritura de datos en una aplicación sin ningún tipo de restricción. (Puedes obtener mas información en Content Providers)

En este post nos vamos a centrar en las siguientes formas de almacenamiento:
Shared Preferences (muy útil a la hora de pasar datos entre actividades)
Internal Storage (almacenamiento interno)
External Storage (almacenamiento externo)
Memoria cahe 



1. Shared Preferences

Esta clase nos permite almacenar y recuperar tipos de datos primitivos asociados a una clave, como pueden ser: booleans, floats, ints, longs y strings. Aunque nuestra aplicación este cerrada, estos datos van a estar guardados en todo momento en un fichero xml. Este fichero esta ubicado en:
"data/data/'NOMBREdelPAQUETE'/shared_prefs/..."

Para hacer uso de esta clase deberemos crear una instancia "SharedPreferences" y usar uno de estos dos métodos:
getSharedPreferences(name, mode): utilizaremos este método cuando necesitemos utilizar varios ficheros para guardar los datos, como primer parámetro "name" indicaremos el nombre del fichero.
getPreferences(mode): este método lo usaremos cuando necesitemos un solo fichero de preferencias.
Como parametro "mode" indicaremos el modo de acceso a esos datos, los mas utilizados son:
MODE_PRIVATE: es el modo por defecto y quiere decir que el archivo creado solo sera accesible por la aplicación que lo creo. También lo podemos indicar con un 0.
MODE_WORLD_READABLE: En este caso todas las aplicaciones pueden leer los datos guardados en el fichero, pero solo la aplicación que lo creo puede modificarlos.
MODE_WORLD_WRITABLE: todas las aplicaciones pueden consultar y modificar los datos. 
Para escribir datos tendremos que crear una instancia "SharedPrefecences.Editor" y llamar al método "edit()". Una vez hecho esto podremos escribir datos con los siguientes métodos:
putBoolean(key, value)
putFloat(key, value)
putInt(key, value)
putLong(key, value)
putString(key, value)
Como primer parámetro "key" indicaremos la clave/palabra que asociaremos a ese dato guardado. Y como segundo parámetro "value" el valor que queremos guardar.

Para terminar deberemos llamar al método "commit()" para confirmar que estamos guardando datos.

Si por otro lado queremos leer esos datos guardados, usaremos la instancia "SharedPreferences" y uno de los siguientes métodos:
getBoolean(key, defValue)
getFloat(key, defValue)
getInt(key, defValue)
getLong(key, defValue)
getString(key, defValue)
Como primer parámetro "key" indicaremos la clave/palabra que queremos recuperar. Y como segundo parámetro "defValue" indicaremos un valor por defecto a devolver en caso de que el dato que queremos recuperar no exista. 

Vamos a ver todo esto en un ejemplo:
public class MiClase extends Activity {

    protected void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);

        // RECUPERAR DATOS
        // Creamos la instancia de "SharedPreferences" en MODE_PRIVATE
        SharedPreferences settings = getSharedPreferences("PREFERENCES", 0);
        boolean bol = settings.getBoolean("VALOR1", false);
        int entero = settings.getInt("VALOR2", 0);
        String str = settings.getString("VALOR3", "");
    }

    // GUARDAR DATOS
    // Lo podemos hacer en cualquier método de la Activity
    protected void onStop(){
        super.onStop();

        // Creamos la instancia de "SharedPreferences"
        // Y también la "SharedPreferences.Editor"
        SharedPreferences settings = getSharedPreferences("PREFERENCES", 0);
        SharedPreferences.Editor editor = settings.edit();
        editor.putBoolean("VALOR1", true);
        editor.putInt("VALOR2", 25);
        editor.putString("VALOR3", "string");
        editor.commit();
    }
}




2. Internal Storage

Por defecto los archivos guardados en la memoria interna del móvil son privados y el resto de aplicaciones no podrán acceder a ellos. Al borrar la aplicación estos archivos se eliminan. Y los ficheros se guardan en:
"data/data/'NOMBREdelPAQUETE'/files/..."

Para crear el archivo donde vamos a almacenar los datos usaremos la clase "FileOutputStream" y llamando a su método "openFileOutput(name, mode)". Una vez creada la instancia podremos escribir en el archivo con el método "write()".

Por el contrario si queremos leer un archivo usaremos la clase "FileInputStream" llamando a su método "openFileIntput(name)". Leeremos el archivo con el método "read()".

Como primer parámetro "name" indicaremos el nombre del archivo a crear/modificar. Y como segundo parámetro "mode" el modo de acceso para manejar esos datos. Los modos mas usados en este caso son:
MODE_PRIVATE
MODE_WORLD_READABLE
MODE_WORLD_WRITABLE
MODE_APPEND
Los tres primeros modos nos crearan o reemplazaran el archivo a escribir, es decir, nos eliminara el antiguo y creara uno nuevo. En cambio el ultimo modo nos permite seguir escribiendo el mismo archivo.

Por ultimo para finalizar de leer o escribir en el archivo deberemos llamar al método "close()" para cerrar el archivo.

En ambos casos de lectura o escritura podremos usar los filtros de Java como pueden ser: buffers, readers, writers, ...


NOTA: Podremos añadir un archivo al proyecto en tiempo de compilación creando la carpeta "res/raw" y posteriormente tendremos que crear una instancia "InputStream" y hacer uso del método "openRawResource()" para acceder a el.

Veamos unos ejemplos:
// CREACION DEL ARCHIVO Y ESCRITURA
try {
    OutputStreamWriter OSW = newOutputStreamWriter(
            openFileOutput("pruebas.txt", 0));
    OSW.write("TEXTO PRUEBA");
    OSW.flush();
    OSW.close();
} catch (FileNotFoundException e) {
    Log.e("ERROR", "No ha sido posible crear el archivo" + e.toString());
} catch (IOException e) {
    Log.e("ERROR", "No ha sido posible escribir en el archivo" +e.toString());
}

// LECTURA DEL ARCHIVO
try {
    BufferedReader BR = new BufferedReader(new InputStreamReader(
            openFileInput("pruebas.txt")));
    String str = BR.readLine();
    BR.close();
} catch (FileNotFoundException e) {
    Log.e("ERROR", "No a sido posible acceder al archivo" + e.toString());
} catch (IOException e) {
    Log.e("ERROR", "No ha sido posible leer el archivo" + e.toString());
}
        
// LECTURA DEL ARCHIVO GUARDADO EN RES/RAW
try {
    InputStream IS = getResources().openRawResource(R.raw.blog);
    ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
    byte[] bytes = new byte [4096];
    int len = 0;
    while ((len=IS.read(bytes))>0) {
        byteStream.write(bytes, 0, len);
    }
    try {
        String str = new String (byteStream.toByteArray(), "UTF8");
    } catch (UnsupportedEncodingException e) {
        Log.e("ERROR", "Fallo la codificacion del archivo" + e.toString());
    }
    byteStream.flush();
    byteStream.close();
    IS.close();
} catch (IOException e) {
    Log.e("ERROR", "No ha sido posible leer el archivo" + e.toString());
}
En el primer caso hemos hecho uso del filtro Java "OutputStreamWriter" que se encarga de convertir una secuencia de caracteres en un flujo de bytes ya que el método "write()" nos pide como parámetro un flujo de bytes. Una vez escrito en el archivo llamamos al método "flush()" para vaciar el OutputStreamWriter. Destacar también las excepciones que manejamos, "FileNotFoundException" en caso de que la ejecución del programa tenga problemas a la hora de crear el archivo e "IOException" para manejar las excepciones de escritura en el archivo.

En el caso dos hacemos uso de 2 filtros, "InputStreamReader" (que hace lo contrario a OutputStreamReader) convierte un flujo de bytes a una secuencia de caracteres y "BufferedReader" que se encarga de almacenar en memoria todos los caracteres que recibe y una vez esta lleno manda el buffer a donde se le haya indicado. Decir que con el método "readLine()" leeremos una linea del contenido del archivo.

Y en el ultimo caso leeremos un archivo "blog.txt" ubicado en res/raw. Para ello hemos usado un "ByteArrayOutputStream" que se encarga de almacenar el contenido del archivo en bytes. Para leer hacemos uso del método "read()" que nos pide como parámetro un array byte. Una vez leído el contenido y almacenado en nuestro ByteArrayOutputStream lo podemos convertir a String con los métodos: "toByteArray()" o "toString()". En este caso lo hemos convertido a String usando un formato de codificación de caracteres "UTF8" manejando la excepción "UnsupportedEncodingException" en caso de que la codificación de caracteres no sea compatible.






3. External Storage

Antes de usar el almacenamiento externo o sdcard (no confundir con tarjeta SD) debemos hacer un par de comprobaciones: comprobar que esta montada (ya que podría estar inaccesible al tener conectado el móvil como almacenamiento masivo en el pc) y tener permiso de escritura en ella. Para comprobar que esta montada lo podemos hacer de la siguiente manera usando la clase "Environment" y su método "getExternalStorageState()":
    boolean mExternalStorageAvailable = false;
    boolean mExternalStorageWriteable = false;
    String state = Environment.getExternalStorageState();

    // COMPROBACION DEL ALMACENAMIENTO EXTERNO
    if (Environment.MEDIA_MOUNTED.equals(state)) {
        // Podremos leer y escribir en ella
        mExternalStorageAvailable = mExternalStorageWriteable = true;
    } else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
        // En este caso solo podremos leer los datos
        mExternalStorageAvailable = true;
        mExternalStorageWriteable = false;
    } else {
        // No podremos leer ni escribir en ella
        mExternalStorageAvailable = mExternalStorageWriteable = false;
    }

Tenemos varios medios de comprobación en la clase Environment:
MEDIA_BAD_REMOVAL: el medio se retiro antes de que fuera desmontado
MEDIA_CHECKING: el medio esta presente y siendo revisado
MEDIA_MOUNTED: el medio esta presente, montado y preparado para lectura o escritura
MEDIA_MOUNTED_READ_ONLY: el medio esta presente, montado pero solo preparado para lectura
MEDIA_NOFS: el medio esta presente pero esta en blanco o si tiene un sistema de archivos no soportado
MEDIA_REMOVED: el medio no esta presente
MEDIA_SHARED: el medio no esta presente pero esta compartido a través del almacenamiento masivo USB
MEDIA_UNMOUNTABLE: el medio esta presente pero no se puede montar
MEDIA_UNMOUNTED : el medio esta presente pero sin montar
Por otro lado para tener permisos de escritura deberemos añadir a nuestro archivo Manifest del proyecto el siguiente permiso:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
Y si queremos tener acceso de lectura:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
Una vez comprobado que tenemos acceso al almacenamiento externo y permiso para escribir, debemos usar la clase "File" para la creación de archivos y tenemos dos categorías de archivos:
Archivos públicos: los archivos están disponibles para otras aplicaciones y para el usuario. Si se desinstala la aplicación estos archivos permanecen en sus respectivos directorios. Esto nos viene bien cuando queremos compartir con el sistema imágenes, mp3, documentos, ... Nos aparecerán en la Galería, Reproductor de música, ...
Archivos privados: por derecho pertenecen a la aplicación y solo serán accesibles por ella. Se eliminaran automáticamente cuando el usuario desinstale la aplicación. El directorio privado de la aplicación se encuentra en:
"Android/data/'NOMBREdelPAQUETE'/files/..."
Para crear archivos públicos debemos usar el método "getExternalStoragePublicDirectory()" que nos pide como parámetro el directorio publico que necesitemos usar. Tenemos los siguientes:
DIRECTORY_ALARMS 
DIRECTORY_DCIM
DIRECTORY_DOWNLOADS 
DIRECTORY_MOVIES
DIRECTORY_MUSIC 
DIRECTORY_NOTIFICATIONS
DIRECTORY_PICTURES
DIRECTORY_PODCAST
DIRECTORY_RINGTONES
Siguiendo el ejemplo de comprobación de SD, vamos a ver un ejemplo:
// CREACION DE ARCHIVOS PUBLICOS
if (mExternalStorageWriteable == true) {
      
    // Creamos una carpeta "MisImagenes" dentro del directorio "Pictures"
    // Con el método "mkdirs()" creamos el directorio si es necesario
    File path = new File(Environment.getExternalStoragePublicDirectory(
            Environment.DIRECTORY_PICTURES), "MisImagenes");
    path.mkdirs();
      
    // Creamos un archivo dentro de la carpeta que hemos creado "prueba.jpg"
    File file = new File(path, "prueba.jpg");
         
    // Comprobamos si el archivo que estamos creando ya existe
    if (file.exists()) {
        Log.d("ERROR", "El archivo ya existe");
    } 
         
    // Copiamos una imagen "demo.jpg" del directorio drawable
    // al archivo que hemos creado
    InputStream is = getResources().openRawResource(R.drawable.demo);
    try {
        OutputStream os = new FileOutputStream(file);
        byte[] data = new byte[is.available()];
        is.read(data);
        os.write(data);
        is.close();
        os.close();
    } catch (FileNotFoundException e) {
        Log.e("ERROR", "No a sido posible acceder al archivo" + e.toString());
    } catch (IOException e) {
        Log.e("ERROR", "No ha sido posible ercribir el archivo" + e.toString());
    }
            
    // Borramos el archivo
    file.delete();
         
    // O lo borramos cuando termine la ejecución del programa
    file.deleteOnExit();
         
    // Al borrar el archivo el directorio queda vacío
    // y para borrarlo podríamos usar el siguiente código
    new File("/sdcard/Pictures/MisImagenes").delete();
      
} else {
    Log.d("ERROR", "No ha sido posible crear archivos/carpetas");
}

Para crear archivos privados también podemos usar los directorios públicos o crear unos personalizados (recordar que los archivos serán privados y solo accesibles por la aplicación, no se verán ni en la Galería, ni reproductor, ...). Estarán ubicados siempre en la carpeta "files" de nuestro directorio privado. Por lo tanto para crearlos deberemos llamar a los métodos: "getExternalFilesDir()" si estamos usando un nivel de API 8 o superior, o "getExternalStorageDirectory() si usamos API 7 o inferior. Lo vemos en un ejemplo usando un nivel de API 8:
//CREACION DE ARCHIVOS PRIVADOS
if (mExternalStorageWriteable == true) {
    // Creamos un archivo usando los directorios públicos
    File file1 = new File(getExternalFilesDir(
            Environment.DIRECTORY_PICTURES), "prueba.jpg");
      
    // O creamos un archivo usando un directorio personalizado
    File file2 = new File(getExternalFilesDir("Documentos"), "prueba.txt");
      
    // Escribimos algo en el archivo creado
    try {
        OutputStreamWriter OSW = new OutputStreamWriter(
                new FileOutputStream(file2));
        OSW.write("TEXTO PRUEBA");
        OSW.flush();
        OSW.close();
    } catch (FileNotFoundException e) {
        Log.e("ERROR", "No ha sido posible crear el archivo" + e.toString());
    } catch (IOException e) {
        Log.e("ERROR", "No ha sido posible escribir en el archivo" 
                + e.toString());
    }
      
} else {
    Log.d("ERROR", "No ha sido posible crear archivos/carpetas");
}

Comentar que podemos usar los métodos: "getFreeSpace()" y "getTotalSpace()" para comprobar el espacio disponible que tiene el almacenamiento ya sea interno o externo. No es obligado usarlos ya que podemos manejar la excepción "IOExcepcion" en caso de que ocurra algo.




4. Memoria cache

Tanto en el almacenamiento interno como en el externo podremos usar la memoria cache, esta memoria puede ser borrada si el sistema necesita recursos y por otro lado los ficheros serán eliminados automáticamente cuando el usuario desinstale la aplicación. Hay que tener un control sobre su uso en la memoria interna ya que puede ralentizar el sistema.

Para hacer uso de la cache en memoria interna usaremos el método "getCacheDir()". Por el contrario si queremos usar la cache de la memoria externa (mas recomendado) usaremos los métodos: "getExternalCacheDir()" para un nivel de API 8 o superior, o "getExternalStorageDirectory()" para un nivel de API 7 o inferior.

16 comentarios:

  1. ¿cómo leer desde código los archivos guardados en la memoria externa?

    ResponderEliminar
  2. No he probado nunca pero creando un enlace simbolico hasta el archivo supongo que funcionara. Prueba algo asi:

    new File("/mnt/external_sd/")

    new File("/mnt/extSdCard/")

    ResponderEliminar
  3. Hola.. como hago para eliminar los datos almacenados en el archivo de texto creado en la memoria interna?

    ResponderEliminar
    Respuestas
    1. Se me ocurren dos posibilidades: escribir lineas vacias en el archivo o borrar directamente el archivo y crearlo nuevamente.

      Eliminar
  4. Hola.
    yo tengo un arraylist, Entrada lista una clase extends parcelable con las variables que se van a introducir en una listview, y tengo otra clase adaptadora para ese fin. quisiera poder grabar y leer este array list en fichero, pero no encuentro la manera de hacerlo y que me funcione. me podriais echar una manilla.

    Saluods.
    Javier,,

    ResponderEliminar
    Respuestas
    1. Se puede hacer de muchas maneras, pero te dejo un par de ellas:

      -Leer item por item en el arraylist e ir escribiendolos en el archivo. Para recuperarlos desde el archivo se haria lo contrario.

      -Otra posibilidad que he visto en google es escribir directamente el objeto arraylist en un archivo. Para ello se utiliza el objeto "ObjectOutputStream" y el metodo "writeObject".

      Eliminar
  5. hola amigos, tengo un android chino con apenas 156Mb de memoria interna, no me permite instalar casi nada, ni siquiera angrybirds....
    he cambiado el archivo vold.fstab, he usado apks swap y nada me funciona. link2sd algo alivió, pero no me funcionan particiones ext1.2.3.4.solo me funcionó con fat32.
    es posible cambiar el almacenamiento?
    el vold.fstab es:
    #Mounts the first usable partition of specified device
    #dev_mount sdcard / mnt /sdcard auto /block/mmcblk0
    dev_mount sdcard /mnt/sdcard auto/devices/platform/sprd-sdhci.0/mmc_host/mmc0

    GRACIAS SI PUEDEN AYUDARME!
    pipeacelas@gmail.com

    ResponderEliminar
  6. Este comentario ha sido eliminado por el autor.

    ResponderEliminar
  7. Tengo una duda, quisiera saber si es posible buscar entre los archivos contenidos en la carpeta raw y extraer uno sin tener que especificar cual. Por ejemplo, tengo una cadena que indica "Objeto1", ¿Cómo busco entonces el archivo R.raw.Objeto1? Asumiendo que se que ese archivo existe en la carpeta

    ResponderEliminar
  8. Hola, quisiera saber cómo guardar videos y sonidos en el Internal Storage de android. Ya guardo imágenes pero no me funciona con otros archivos multimedias. Muchas gracias

    ResponderEliminar
  9. Hola quisiera que me ayudarás con algo, necesito hacer que el archivo de mi base de datos SQL que creo en mi aplicación se guarde en la memoria externa y pueda ser público (que pueda verlo desde el explorador de archivos de mi android) me podrías ayudar con esto te lo agradezco.

    ResponderEliminar
  10. Hola ¿quisiera saber cómo leer desde código los archivos eliminados en mi android?

    ResponderEliminar
  11. Que pasa cuando el dispositivo no tiene almacenamiento externo, almacena automáticamente el la memoria interna?

    ResponderEliminar
  12. Hola estoy tratando de hacer una aplicacion , pero en el emulador si me funciona , en mi dispositivo si bien copie la base sqlite a la carpeta asset y puse tu codigo , no logro que tome la base , primero que no se a donde se debe poner el codigo si en activity main o en el Openhelper, y despues en donde al principio ? , no se si se puede hacer algo mas para super principiante gracias .

    ResponderEliminar
  13. hola el codigo es genial aunque tengo un pequeño problema no muestra el archivo .txt que he creado en el explorador de archivos... que faltaria para que si sea visible en el explorador de archivos?

    ResponderEliminar
  14. Hola, gracias por el aporte! Estoy intentando guardar un txt en memoria externa, me crea el directorio en la SD y todo, pero está vacío, el archivo nunca se genera, cuál puede ser el problema???

    ResponderEliminar