Usuario:ManuelRomero/Android/persistencia/Teoria

De WikiEducator
Saltar a: navegación, buscar
Road Works.svg Trabajo en proceso, espera cambios frecuentes. Tu ayuda y retroalimentación son bienvenidos.
Ver página de charlas.
Road Works.svg



Tipos de almacenamiento externo

  • En Android cuando ejecutamos una aplicación, se crea o levanta una máquina virtual para su ejecución.
  • Cuando se acaba o finaliza la ejecución desaparece dicha máquina virtual
  • En general tengo diferentes sitios para ubicar ficheros (res, targeta SD, asset).
  • Vamos a ver difernetes formas de usar tanto para leer como para escribir datos que se puedan mantener después de la ejecución de la app
  • En Android tenemos las siguientes maneras de almacennar datos
  1. Bases de datos SQLLite
  2. Ficheros de preferencias compartidas
  3. Ficheros en memoria externa o sd
  4. Ficheros como recursos del sistema
    1. ficheros en la carpeta /asset
    2. ficheros en la carpeta /res/raw


Almacenamiento interno

  • Desaparecerán del sistema cuando se desinstale la aplicación

recursos directos o no compilados

  • Este tipo de almacenamiento interno hace que los ficheros se compilan con la app, es decir que se instalarán junto con la aplicación.
  • En un proyecto android existe una carpeta llamada /assets que en principio está vacía. Su objetivo es almacenar ficheros externos a la aplicación que serán distribuidos junto con nuestro .apk (formará parte de la compilacón).
  • Igualmente podremos colocar los fichero sen la carpeta /res/raw, en este caso tendremos una serie de restricciones

El nombre del fichero debe ser restrictivamente sencillo como el de cualquier recurso que cuelgue de la carpeta /res

letras minusculas, números y el carácter de subrayado
identificadores de nombre de recursos:[a..z1..9_]*
  • Este tipo de fichero es de solo lectura, no se puede escribir en ellos.

/assets Vs /res

  • Existen muchas situaciones en las que las dos ubicaciones pueden satisfacer nuestras necesidades
  • Sin embargo existen difernecias
  • En /res/raw solo podemos poner ficheros y no hacer ninguna estructura de directorio, mientras que en /assets sí que se puede hacer
  • Para abrir un fichero en assets necesitamos tener un objeto AssetManager, mientras que no necesitamos ningún agente para /res/raw. Supongamos un recurso llamado datos.txt

1.- en /assets <sourece lang=java> .... AssetManager asset = getAssets(); InputStream = asset.open("datos.txt"); .... </source>

2.- en /res/raw

 InputStream fa = getResources().openRawResource(R.raw.datos);

Los ficheros ubicado en /assets se pueden recuperar y navegar por esa carpeta (directorios, ficheros) como si fuera un sistema de ficheros normal de un SO.

Los ficheros que ubiquemos en /res son accesibles como un recurso identificado en la clase R (clase generado de forma autmática para darme una referencia a cada recurso de mi aplicación).  Los ficheros colocados  en el directorio /asset podrán ser accedidos como si fuera en un sistema de ficheros, sin ningún tipo de restricción  Útil por ejemplo para guardar datos de un juego, o la configuración de una aplicación, …)

Otra opción para guardar información es usar una base de datos, por ejemplo  SQLLite, sobre todo indicado cuando la información esté estruturada (pensemos en una tabla de base de datos).

Para gestionar los recursos ubicado en la carpeta /asset procederemos

  1. Físicamente ubicamos los ficheros en la carpeta
  2. En el código necesitamos un objeto que permita gestionar este recurso (objeto de la clase AssetManager)
  3. Estos recursos no generan un ID en la clase R, por lo que debemos especificar el nombre y la ruta para acceder a ellos (usando la clase AssetManager, como ya hemos indicado).
Conclusión

Nosotros podemos almacenar ficheros en recursos y también en asset permite establecer jerarquía de ficheros, cosas que res no permite, en esta línea los ficheros ubicados en la carpeta res son más restrictivos y su acceso es menos libre (hay que acceder a través del Id que se general en la clase R).

Preferencias Compartidas

Bases de datos

Introducción

Android dentro del API tiene funcionalidad para gestionar una base de datos SQLite
Esto hace que no requeramos ningún driver ni instalar un gestor de bases de datos adicional
SQLite es un motor de bases de datos con una características especiales que le hacen muy atractivas para determinados entornos
  1. Maneja archivos de pequeño tamaño.
  2. Requiere poco espacio en tiempo de ejecución (250MB apx)
  3. no necesita un servidor, no permite concurrencia, está basado en funciones no en servicios, lo que le permite presentar tiempos de respuestas rápidos.
  4. cumple el estándar SQL-92
  5. Es Open Source

Características importantes

  • Presenta los siguientes tipos de datos

Si escribes esto al editar... ...este será el resultado.

Tipos de datos en SQLite
Tipo Declaración del tipo
TEXTO "TEXT"
NUMERO "NUM"
ENTERO "INT"
REAL "REAL"
SIN_TIPO ""
  • No soporta otros tipos
  • No valida los valores o sea que podemos insertar cadenas de texto en un campo de enteros y el sistema no se queja

SQLite y Android

  • Acceder a la base de datos implica acceder al sistema de ficheros
  • Los ficheros que creemos por defecto se ubicarán el en siguiente directoiro

DATA/data/APP_NAME/databases/FILENAME.

DATA es el directorio donde ubica los ficheros Enviroment.getDataDirectory()
APP_NAME nombre de la aplicación
FILENAME es el nombre del fichero de la base de datos

El API SQLLite

  • El API para trabajar con base de datos está definido en los siguientes paquetes
android.database
contiene todas las clases para trabajar con bases de datos
android.database.sqlite
Aquí tenemos las clases específicas para trabajar con sqlite
  1. SQLiteClosable
  2. SQLiteCursor
  3. SQLiteDatabase
  4. SQLiteOpenHelper
  5. SQLiteProgram
  6. SQLiteQuery
  7. SQLiteQueryBuilder
  8. SQLiteStatement
  • Y las siguientes interfaces
  1. SQLiteCursorDriver
  2. SQLiteDatabase.CursorFactory
  3. SQLiteTransactionListener

Trabajar con Android y SQLLite

Para trabajar con una base de datos debemos realizar unos pasos clásicos

  1. Crear la base de datos y gestionarla
  2. Por crear la base de datos entendemos
    1. Definir la base de datos
    2. Definir su estructura (tablas y campos)
  3. Por gestionarla aludo al hecho de hacer operaciones básicas
    1. Insertar tuplas
    2. Borrar
    3. Modificar
    4. Consultar
  4. A continuacion vamos a ver como se realizan estas acciones con Android como lenguje y SQLLite como gestor de bases de datos relacional

Crear una base de datos

  • Para administar una base de datos tenemos la clase SQLiteDatabase
  • Tenemos dos métodos para abrir / crear una base de datos
  • La idea es si existe la base de datos la abro, y si no existe la creo
  • Tenemos dos manera de interoperar en esta manera
  1. Usando métodos concretos de la clase SQLiteDatabase concretamente openDatabase u openOrCreateDatabase
  2. Extendiendo la clase abstracta SQLOpenHelper que es la forma mas habitual y cómoda de realizarlo.

SQLiteDatabase

Presenta los siguientes métodos para crear/abrir una base de datos

  1. openDatabase
  2. openOrCreateDatabase'

Los parámetros que se pueden pasar son los siguientes

  1. String path Es el path y nombre de la base de datos
  2. int flag especifica el modo en el que se abre/crea la base de datos. Sus valores se pueden combinar e.j. OPEN_READWRITE|CREATE_IF_NECESSARY (Especificamos los dos modos).
    1. OPEN_READWRITE
    2. OPEN_READONLY
    3. CREATE_IF_NECESSARY
    4. NO_LOCALIZED_COLLATORS.
  3. SQLLiteDatabase.CursorFactory factory
  4. DatabaseErrorHandler errorHandler
  5. File file
  • En realidad el uso de ambos métodos es similar con el hecho de que en el método openOrCreateDatabase(...) asumimos el flag CREATE_IF_NECESSARY
  • Ambos métodos pueden lanzar una excepción SQLiteException En caso de no abrir/crear la base de datos que hay que capturar

Ejemplo

package com.example.agenda;
 
import android.app.Activity;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.widget.Toast;
 
public class Contactos extends Activity {
	private static final String DATABASE_NAME = "contactos.db";
	private String nombreBD;
	private SQLiteDatabase baseDatos;
 
	private String crearTablaContacto= "create table if not exists "  
		  + " contacto (codigo integer primary key autoincrement, "  
		  + " nombre text not null, telefono text not null unique);"; 
 
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        /**
           Construimos el path para crear la base de datos asegurandonos de la ubicación de los ficheros
           por defecto será /data/data/com.example.agenda/...
        */ 
        nombreBD=  Environment.getDataDirectory()+"/data/com.example.agenda/"+DATABASE_NAME;
        setContentView(R.layout.layout_contactos);
        if (abrirBaseDatos()){
           Toast.makeText(this,"Base de datos Abierta ok!! en "+nombreBD,Toast.LENGTH_LONG).show();
        }else{
             Toast.makeText(this,"ERROR no se ha podido abrir la base de datos!! "+nombreBD,Toast.LENGTH_LONG).show();
        }
    }
 
    /**
     * Método para crear o abrir la base de datos
     * Retorna un booleano indicando el éxito o fracaso de la acción
    */
    private boolean abrirBaseDatos(){   
         try{   
	     baseDatos = SQLiteDatabase.openOrCreateDatabase(nombreBD, null);   
	     // baseDatos = openDatabase(nombreBD,null,SQLiteDatabase.CREATE_IF_NECESSARY);
	     baseDatos.execSQL(crearTablaContacto);   
	     return true;
	  }    
	  catch (SQLiteException e){   
	     Log.i("BASE DATOS","Error al abrir o crear la base de datos" + e);
	     return false;
	  }   
   }  
}//Fin de la clase Contactos
  • En el caso anterior alternativamente podemos usar el método openDatabase(...) que vemos en la línea comentada

Trabajar con la clase abstracta SQLiteOpenHelper

  • Esta clase nos va a permitir abrir/crear o actualizar una base de datos de forma sencilla
  • Presenta una serie de métodos que nos permite realizar operaciones con la base de datos
  • Un objeto SQLiteHelperOpen, no es una referencia directa a la base de datos, si no una referencia a un objeto que nos va a ayudar a interactuar con la base de datos (abrirla, crearla, interactuar con ella).
  • Los métodos onCreate y onUpdate son abstractos, así que necesariamente los tenemos que implementar

De modo que cuando creemos una clase que va a derivar de la clase SQLiteOpenHelper debemos tener la siguiente estructura

package com.example.contactos1;
 
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper;
 
public class BaseDatos extends SQLiteOpenHelper{
	public BaseDatos(Context contexto, String nombreBD, CursorFactory factory,
			int version) {
		super(contexto, nameBD, factory, version);
		// TODO Auto-generated constructor stub
	}
 
	@Override
	public void onCreate(SQLiteDatabase baseDatos) {
		// TODO Auto-generated method stub
	}
 
	@Override
	public void onUpgrade(SQLiteDatabase baseDatos, int versionAntigua, int versioNueva) {
		// TODO Auto-generated method stub
	}
 
 
}
  • A continuación explicamos cada método
El constructor
SQLiteOpenHelper(Context contexto, String nonbreBD, SQLiteDatabase.CursorFactory factory, int version)
SQLiteOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version, DatabaseErrorHandler errorHandler)
  • En el constructor lo que debemos hacer es invocar a super(...)
  • Este método es muy rápido en ejecución. Aquí no se crea o abre la base de datos. Para tener una referencia a la base de datos abierta, hay que invocar a métodos onRedableDatabase o onWritableDatabase().
  • Los parámetros
  1. Context context Especifica el contexto en el que se va a abrir la base de datos
  2. String name Identifica el nombre de la base de datos
  3. SQLiteDatabase.CursorFactory factory
  4. int version Entero que especificará la versión de la base de datos, útil para indicar al sistema si queremos cambiar de versión, en cuyo caso de invocará al método onUpgrade()
  5. DatabaseErrorHandler errorHandler Opcional este objeto será utilizado cuando se detecte una corrupción en la base de datos. Las acciones a realizar se implemntarán en el método abstracto onCorruption(SQLiteDatabase bd) de la clase DataBaseErrorHandler
onCreate
 public abstract void onCreate (SQLiteDatabase db)
  • Si la base de datos que especificamos en el constructor nombreBD no existe se invoca al método onCreate()
  • Aquí es donde crearemos la base de datos y las tablas por primera vez
  • Para modificar la estructura de la base de datos (añadir campos o tablas), hay que invocar al método onUpgrade
  • Para conseguir este propósito hay que dar una nueva versión a la base de datos.
onUpgrade
public abstract void onUpgrade (SQLiteDatabase db, int oldVersion, int newVersion)
  • En este método podemos realizar cualquier modificación en la estructura de las tablase de la base de datos
  • ALTER TABLE, DROP son instrucciones SQL para ejecutar aquí
  • Si ocurre un error se produce un ROLLBACK y no se hace nada, todas las acciones indicadas en este método se ejecutan como una única transacción.
  • Después de ejecutar este método la base de datos se queda con la versión especificada en el parámetro newVersion

Abrir la BD para operar con ella

  • Una vez creada la base de datos, debemos interactuar con ella.
  • SQLiteDatabase es la clase para definir un objeto que permita interactuar con la base de datos.
  • Un objeto de esta clase es una referenica a la base de datos.
  • Para abrir la base de datos usamos el métodogetReadableDatabase() o getWriteableDatabase()de la clase SQLiteOpenHelper. Este método retornará un objeto de la clase SQLiteDatabase estando la base de datos abierta en modo de escritura/lectura o de solo lectura respectivamente.
 ...
 SQLiteDatabase bdW = getWritebleDatabase();
 SQLiteDatabase bdR = getReadableDatabase(); 
 ...
  • Posteriormente con el objeto bdW o bdR ya podemos interactuar con la base de datos (Consultas, actualizaciones, inserciones y borrados).
  • A la vez la clase SQLOpenHelper tiene el método close() para cerrar la base de datos.

Operar con la base de datos

  • La clase SQLiteDatabase presenta los siguientes métodos para interactuar con la base de datos
insert()
update()
delete()
  • Además de forma más genérica tiene la posibilidad de ejectuar una setnencia SQL con el método
execSQL()
  • También tenemos la clase ContentValues que permite definir parejas campo/valor permitiendo de forma cómoda realizar inserciones y actualizaciones en la base de datos
  • Para hacer consultas
  • La clase SQLiteQueryBuilder me permite realizar consultas con los métodos rawQuery() y query()'
  • El resultado de una consulta es un objeto de la clase Cursor
/**
La siguiente consulta nos devuelve un listado con todos los contactos 
*/
Cursor listaPersonas = rawQuery("Select * from contactos");
rawQuery(String consulta)
directly accepts an SQL select statement as input.
query() provides a structured interface for specifying the SQL query.

Insertando tuplas

Con el método execSQL
  • Directamente realizamos una instrucción del tipo INSERT INTO TABLE nombre_tabla...
  • Tener cuidado con los valores string que van entre comillas simples y los valores enteros que no lo llevan
  • El siguiente código ilustra como usarlo
  • Este método no retorna nada y puede lanzar una excepción del tipo SQLExpection
  • La sentencia que ejecuta no puede ser del tipo SELECT solo INSERT, DELETE O UPDATE
    void  execSQL(String sql)
/**
 nombre, apellido, telefono y dirección son atributos de la clase con valores .
 cuidado al construir la instrucción puede ser un poco lioso el tema de las comillas.
*/
....
    try{
        bd.execSQL("INSERT INTO contacto(nombre,apellido,telefono,direccion) " +
				"VALUES('" +
                                 nombre+"','"+
                                 apellido+"','" +
                                 telefono+"','" +
                                 direccion+
                                 "')";
    }catch (SQLException e){
	     Toast.makeText(c,"Error insertando en la babla"+ e.getMessage(),Toast.LENGTH_LONG).show();
     }
    SQLiteDatabase.insert(
....
  • Si queremos tener una referencia que si se ha insertado o no sin lanzar una expeción podemos usar el método insert(...)
insert
public long insert (String table, String nullColumnHack, ContentValues values)

table tabla donde queremos insertar nullColumnHack Puede ser null, especifica el nombre de la columna que permite valores null, y se insertará explícitamente el valor null, en caso de que en el ContentValue no haya valor para dicho campo. value los valores de los campos para el registro o tupla que vamos a insertar.

  • Retorna el ID de fila insertada o -1 en caso de que no se haya podido realizar una inserción
ContentValues
  • Esta clase contiene parejas nombre_columna valor como si se tratara de un vector de registros
  • Los valores son procesados por la clase ContentResolver
  • Para pasar los valores (parejas campo/valor) podemos usar el método put ampliamente sobrecargado
void	 put(String key, Byte value)
void	 put(String key, Integer value)
void	 put(String key, Float value)
void	 put(String key, Short value)
void	 put(String key, byte[] value)
void	 put(String key, String value)
void	 put(String key, Double value)
void	 put(String key, Long value)
void	 put(String key, Boolean value)
void	 putAll(ContentValues other)
void	 putNull(String key)
  • En el caso anterior podríamos tener el siguiente ejemplo
...
   SQLiteDatabase datosUsuario = .....
...
 
  ContentValues registroContacto= new ContentValues();
		 c.put("nombre",nombre);
		 c.put("apellido",apellido);
		 c.put("direccion",direccion);
 		 c.put("telefono",telefono);
  //Aquí hacemos la insercion
  long cod =datosUsuario.insert("contacto",null, registroContacto); 
 
  if (cod==-1)
	  Toast.makeText(this,"No SE HA PODIDO INSERTAR LA TUPLA",Toast.LENGTH_LONG).show();
  else
	  Toast.makeText(this,"tupla insertada con id "+cod,Toast.LENGTH_LONG).show();
...

Actualizando y borrando tuplas

  • Además del método execSQL(String SQLSentencia), podemos utilizar un método específico para cada acción de actualizar y borrar
update
public int update (String table, ContentValues values, String whereClause, String[] whereArgs)
  • Los parámetros
  1. table El nombre de la tabla a actualizar
  2. values es un ContentValue con los nuevos valores a actualizar
  3. whereClause claúsula where del update para especificar la condición que tienen que cumplir las tuplas a actualziar; el valor null especifica que la actualización afecte a todas las tuplas
  • Devuelve un entero que indica el núemro de filas afectadas


delete
 public int delete (String table, String whereClause, String[] whereArgs)
  • Los parámetros

table El nombre de la tabla whereClause igual que en el caso anterior, especifica la condición que deben cumplir las tuplas para ser borradas; el valor null hará que se borren todas.

  • Devuelve el número de filas borradas.

Consultas

  • disponemos de los métodos rawQuery() y query()'
  • El resultado de una consulta es un objeto de la clase Cursor

rawQuery

   public Cursor rawQuery (String sql, String[] selectionArgs)
  • Los parámetros
  1. sql es la consulta sql una sentencia con la claúsual SELECT
  2. Opcionalmente podemos incluir en la consluta sql signos ? cada uno de los cuales será sustituido por los valores de array selectionArgs
   Cursor contactos = new contactos();
   contactos = rawQuery ("Select * from contactos where id >?",{"5"});
   contactos = rawQuery ("Select * from contactos where id >5");


query
/**
La siguiente consulta nos devuelve un listado con todos los contactos 
*/
Cursor listaPersonas = rawQuery("Select * from contactos");
rawQuery(String consulta)
query() provides a structured interface for specifying the SQL query.

Gestión de cursores

  • El resultado de una consulta es un objeto cursor que es una lista de filas y metainformación sobre la misma, así como un puntero en un determinado momento a una de las filas
getCount() , getColumnCount()
método que nos informa sobre el número de filas y columnas respectivamente que tiene el cursor
moveToFirst(),moveToLast() moveToNext(),moveToPrevious(), moveToPosition(int posicion)
Posiciona el cursor según se indica (primero, ultimo, siguiene, anterior, a la posicion)
isAfterLast() isBeforeFirst(), isFirst(), isLast(), isNull(), isClosed()
Reotorna un booleano que indica si se cumple la condición
get*(int columnindex)
donde * puede ser (Short, String, Type, Double, Float,Int, Long) Y devuleve el valor de ese tipo de la columna especificada, en el caso de Type retorna el tipo de esa columno
getColumnIndex(String) Obtenemos el índice la columna para en nombre especificado


close()
Un cursor necesita ser cerrado con este método para liberar el recurso

practica