Android: Almacenamiento
Existen varias formas de almacenar información en la aplicación: preferencias compartidas, almacenamiento interno y externo, caché y bases de datos.
1. Preferencias compartidas (SharedPreferences)
Cuando se quiere guardar una pequeña colección de pares de clave-valor, pueden usarse
las SharedPreferences
. Para entender su funcionamiento, vamos a poner un ejemplo en el que
añadimos un valor de clase String
identificado con una clave (que siempre
será String
) en un "archivo" de nombre "grupo1":
SharedPreferences sharedPreferences = getSharedPreferences("grupo1", 0); /* 0 es lo mismo que Context.MODE_PRIVATE que quiere decir que la información sólo es accesible para la app */ SharedPreferences.Editor editor = sharedPreferences.edit(); editor.putString("clave", "valor"); editor.commit();
En el caso de que el valor sea un número entero, usaremos el método putInt()
en vez
de putString()
.
- Para ver la información guardada, tenemos varios métodos. Si queremos recoger el valor de una clave concreta:
SharedPreferences sharedPreferences = getSharedPreferences("grupo1", 0); String valor = sharedPreferences.getString("clave", "valor si no encuentra");
- Para recoger todas las claves y valores guardados en un "archivo" (el ejemplo que crea un TextView por cada par y los añade a un "layout" lineal):
public void viewShPref(LinearLayout layout, String group) { sharedPreferences = getSharedPreferences(group, 0); Map<String, ?> conjunto = sharedPreferences.getAll(); for(Map.Entry<String, ?> entry : conjunto.entrySet()){ textView = new TextView(this); textView.setText(entry.getKey() + ":" + entry.getValue()); textView.setTextSize(16); textView.setGravity(Gravity.LEFT); textView.setPadding(2,0,0,10); textView.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT)); layout.addView(textView); } }
- Para borrar todos los pares de un archivo:
SharedPreferences sharedPreferences = getSharedPreferences("grupo1", 0); SharedPreferences.Editor editor = sharedPreferences.edit(); editor.clear(); editor.commit();
2. Almacenamiento interno
Para cantidades más grandes de información que sólo son accesibles para la app, usamos el almacenamiento interno.
Hay que recordar que siempre que se trabaja con archivos que pueden o no existir, hay que englobar el código en
un bloque try-catch
que recoja el posible error.
- Para añadir información:
public void addIntStorage(String filename, String key, String value) { String sep = ":"; try{ FileOutputStream fos = openFileOutput(filename, Context.MODE_PRIVATE); fos.write(key.getBytes()); fos.write(sep.getBytes()); fos.write(value.getBytes()); fos.close(); }catch(IOException e){ Toast.makeText(getApplicationContext() , e.toString(), Toast.LENGTH_SHORT).show(); } }
- Para ver información:
public void viewIntSto(LinearLayout layout, String filename) { try{ FileInputStream fis = openFileInput(filename); InputStreamReader isr = new InputStreamReader(fis); BufferedReader br = new BufferedReader(isr); String line; while((line = br.readLine()) != null){ textView = new TextView(this); textView.setText(line); textView.setGravity(Gravity.LEFT); textView.setPadding(2,0,0,10); textView.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT)); layout.addView(textView); } br.close(); }catch(IOException e){ textView = new TextView(this); textView.setText(e.toString()); layout.addView(textView); } }
3. Caché
Si queremos usar la caché para guardar información, debemos recordar que hay que controlar el tamaño de ésta aunque Android pueda borrar el caché automáticamente cuando falta espacio. Por la misma razón, debemos asegurarnos de que el archivo de caché existe antes de buscar en él.
- Para añadir información:
public void addCache(String filename, String key, String value) { String sep = ":"; try{ FileOutputStream fos = new FileOutputStream(new File(getCacheDir(), filename)); OutputStreamWriter osr = new OutputStreamWriter(fos); BufferedWriter bw = new BufferedWriter(osr); bw.write(key + sep + value); bw.close(); }catch(IOException e){ Toast.makeText(getApplicationContext() , e.toString(), Toast.LENGTH_SHORT).show(); } }
- Para ver información:
public void viewCache(LinearLayout layout, String filename) { try{ FileInputStream fis = new FileInputStream(new File(getCacheDir(), filename)); InputStreamReader isr = new InputStreamReader(fis); BufferedReader br = new BufferedReader(isr); String line; while((line = br.readLine()) != null){ textView = new TextView(this); textView.setText(line); textView.setGravity(Gravity.LEFT); textView.setPadding(2,0,0,10); textView.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT)); layout.addView(textView); } br.close(); }catch(IOException e){ textView = new TextView(this); textView.setText(e.toString()); layout.addView(textView); } }
4. Almacenamiento externo
Para usar el almacenamiento externo (accesible fuera de la app) primero hay que pedir permiso al usuario con la siguiente etiqueta dentro del manifiesto:
<manifest ...> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> ... </manifest>
También hay que asegurarse de que el almacenamiento es accesible (por ejemplo si el dispositivo está conectado a un ordenador, el almacenamiento externo es inaccesible). La siguiente función lo comprueba, devolviendo "0" si se puede leer y escribir, "1" si sólo se puede leer o "2" si no es accesible:
public static int checkExtSto() { String estado = Environment.getExternalStorageState(); if(Environment.MEDIA_MOUNTED.equals(estado)){ return 0; }else if(Environment.MEDIA_MOUNTED_READ_ONLY.equals(estado)){ return 1; }else{ return 2; } }
- Para añadir información
- En este ejemplo se busca o crea un archivo en la carpeta de descargas del dispositivo, que se
encuentra en la variable
Enviroment.DIRECTORY_DOWNLOADS
. Usa el métodocontext.getExternalFilesDir()
(definiendocontext
como un objetoContext
con valorthis
) para carpetas privadas de la app pero accesibles por el usuario (rutaAndroid/data/<<paquete>>
). El argumento que toma es igual al del método del ejemplo y además se puede personalizar el nombre de la carpeta o que el archivo se añada en la carpeta raíz de la app (con el parámetro "null
"). Por otro lado, en este caso la función que comprueba el almacenamiento está en la claseViewActivity
.
- En este ejemplo se busca o crea un archivo en la carpeta de descargas del dispositivo, que se
encuentra en la variable
public void addExtStorage(String filename, String key, String value) { String sep = ":"; if(ViewActivity.checkExtSto() == 0) { try{ FileOutputStream fos = new FileOutputStream(new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), filename)); OutputStreamWriter osr = new OutputStreamWriter(fos); BufferedWriter bw = new BufferedWriter(osr); bw.write(key + sep + value); bw.close(); }catch(IOException e){ Toast.makeText(getApplicationContext() , e.toString(), Toast.LENGTH_SHORT).show(); } }else{ Toast.makeText(this, "Error con el almacenamiento", Toast.LENGTH_SHORT).show(); } }
- Para leer información:
public void viewExtSto(LinearLayout layout, String filename) { if(checkExtSto() == 0 || checkExtSto() == 1) { try{ FileInputStream fis = new FileInputStream(new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), filename)); InputStreamReader isr = new InputStreamReader(fis); BufferedReader br = new BufferedReader(isr); String line; while((line = br.readLine()) != null) { textView = new TextView(this); textView.setText(line); textView.setGravity(Gravity.LEFT); textView.setPadding(2,0,0,10); textView.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT)); layout.addView(textView); } br.close(); }catch (IOException e) { textView = new TextView(this); textView.setText(e.toString()); layout.addView(textView); } } else{ Toast.makeText(this, "Error con el almacenamiento", Toast.LENGTH_SHORT).show(); } }
5. Bases de datos
Para el uso de bases de datos locales, Android proporciona Room
, que se puede definir como
una capa de abstracción sobre SQLite. Para usar Room se necesita que el proyecto esté basado en AndroidX (es una
mejora de la librería de soporte tradicional de Android. Android Studio permite migrar el proyecto con la
opción Refactor > Migrate to AndroidX
).