Bien, hoy vamos a hablar de recursos, sobre cómo incluirlos en nuestro código de manera que queden correctamente agrupados en la aplicación.
Tenemos tres alternativas para hacerlo:
- Meter lo que queramos en la carpeta assets(1)
- Meter los fichero de manera lógica en la carpeta res.
- Si los archivos son creados partiendo de la ejecución de la aplicación crearlos en un directorio asignado para la aplicación específica bajo la ruta /data/app/<paquete>/files (en el caso del post anterior la ruta sería /data/app/mi.paquete.test1/files).
Pero para ambas, lo que nos puede interesar, más que la manera de guardarlas es cómo acceder a ellas y, para eso (acabo de caer en ello y esta es la razón por la que ahora mismo pongo un (I) en el post, para no hacerlo eterno) hace falta que entendais qué es el contexto de la aplicación, es decir Context.
Parece una tontería que lo explique pero el Contexto encapsula todo el entorno de la aplicación para que, en teoría, sólo sea accesible desde dentro de ella.
A primera vista parece muy simple, pero esta clase abstracta (realmente de las instancias de su clase hija, ApplicationContext) es la que nos va a permitir acceder a la información de la aplicación y a sus recursos.
Considerando que existen realmente 5 bloques en toda aplicación android (Application, Activity, IntentReceiver, Service y Provider
(2)) sería lógico que fuera accesible desde cualquiera de ellos, pero esto no es así en la realidad. Sólo es accesible directamente desde Application, Activity y Service, teniendo que recurrir a sucios trucos para poder utilizarla desde IntentReceiver por ejemplo.
Como curiosidad diré que si por ejemplo gestionais el evento de por ejemplo un botón dentro de una Actividad, dicho contexto no será accesible. hay dos alternativas:
- Crear una variable accesible desde cualquier punto en la construcción de la instancia de la actividad.
- Utilizar la variable de clase que incluyen los bloques constructores: <NombreActividad>.this. Suena como que a cualquier purista se le revolverían las tripas con esto, pero yo personalmente me quedo con ésta última.
Bueno, volvamos al código de la actividad, que así se verá todo mejor, en este apartado (el primero de dos) vamos a hablar de los facilones, de assets y de los archivos creado dinámicamente:
- Si hablamos de assets podemos decir realmente que su función es poder meter lo que nos dé la real gana en la aplicación.
- Accedemos a su gestor mediante context.getAssets(), que devuelve la instancia de la clase AssetManager que se corresponde con la aplicación.
- Para listar el contenido de la carpeta context.getAssets(). list( "/" );(3)
- Para extraer su contenido context.getAssets. open("archivoPrueba.txt"); [devuelve un InputStream]
- Si estamos hablando de escritura y lectura sobre archivos, la única manera de hacerlo es mediante el espacio incluido como contexto de la aplicación mediante:
- Context.openFileInput("archivoPrueba2.txt");
- Context.openfileOutput("archivoPrueba3.txt",modo);(4)
Brevemente vamos a hacer una prueba con ambos.
Sobre la aplicación especificada en el post anterior ( es decir, sobre una aplicación vacía) vamos a acceder a los archivos siguiendo los pasos a continuación:
- Utilizaremos droiddraw para crear un layout con un campo de texto y tres botones (por ahora da igual como se disponga)
- Modificamos sus propiedades (pestaña properties):(5)
- id = @+id/btn1, text= abrir asset (extraera el contenido del asset y lo cargara en el campo de texto)
- id= @+id/btn2, text= guardar (guardara el campo de texto en un archivo en el contexto de la aplicacion).
- id=@+id/btn3, text = abrir (abrira el archivo del contexto si existe y los cargara).
- id=@+id/texto, text = vacio.
- Aplicamos los cambios, extraemos el código (generate) y lo reemplazamos por el anterior de res/layout/main.xml (que es el layout que se carga en la actividad)
<?xml version="1.0" encoding="utf-8"?>
<AbsoluteLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
xmlns:android="http://schemas.android.com/apk/res/android"
>
<Button
android:id="@+id/btn1"
android:layout_width="83px"
android:layout_height="wrap_content"
android:text="Abrir asset"
android:layout_x="10px"
android:layout_y="352px"
>
</Button>
<Button
android:id="@+id/btn2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="guardar "
android:layout_x="100px"
android:layout_y="352px"
>
</Button>
<Button
android:id="@+id/btn3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Abrir"
android:layout_x="180px"
android:layout_y="352px"
>
</Button>
<EditText
android:id="@+id/texto"
android:layout_width="197px"
android:layout_height="217px"
android:text=""
android:layout_x="20px"
android:layout_y="22px"
>
</EditText>
</AbsoluteLayout>
- Introducimos el siguiente código en la actividad (Actividad.java):
package mi.paquete.test1;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import android.util.Log;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
public class Actividad extends Activity {
private static final String TAG = "Actividad";
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);
//Accedemos al los objetos creados a través de su identificador
final EditText texto = (EditText) this.findViewById(R.id.texto);
Button btn1 = (Button) this.findViewById(R.id.btn1);
Button btn2 = (Button) this.findViewById(R.id.btn2);
Button btn3 = (Button) this.findViewById(R.id.btn3);
btn1.setOnClickListener(new OnClickListener(){
//Accion a realizar en el click del boton 1
public void onClick(View vista) {
InputStream is;
try {
is = Actividad.this.getAssets().open("archivoprueba.txt");
int tam = is.available();
byte [] buffer = new byte[tam];
is.read(buffer);
texto.setText(new String(buffer));
} catch (IOException e) {
Log.d(TAG,"Error en la lectura",e);
}
}
});
btn2.setOnClickListener(new OnClickListener(){
//Accion a realizar en el click del boton 2
public void onClick(View vista) {
try {
FileOutputStream fos =Actividad.this.openFileOutput("archivoprueba2.txt", MODE_WORLD_READABLEMODE_WORLD_WRITEABLE);
fos.write(texto.getText().toString().getBytes());
fos.close();
} catch (IOException e) {
Log.d(TAG,"Error en la escritura",e);
}
}
});
btn3.setOnClickListener(new OnClickListener(){
//Accion a realizar en el click del boton 3
public void onClick(View vista) {
try {
FileInputStream fis = Actividad.this.openFileInput("archivoprueba2.txt");
int tam = fis.available();
byte [] buffer = new byte[tam];
fis.read(buffer);
texto.setText(new String(buffer));
fis.close();
} catch (IOException e) {
Log.d(TAG,"Error en la lectura",e);
}
}
});
}
}
- Introducimos el archivo archivoprueba.txt en la carpeta assets con el contenido que queramos:
- Ejecutamos et voilá
PD: no sé si alguien me leerá, pero si me estoy yendo por las ramas, o si hay algo que no se entiende, por favor, postead, sólo intento escribir lo que sé sobre esto y que todos aprendamos (iaiaoooo...).
Notas:
(1) : de acuerdo con lo definido por la documentacion de la sdk, se considera un asset como un conjunto de datos asociados con una aplicacion (como blob), que serán organizados en la jerarquia de directorios tal y como están en el apk final, agrupados como un único fichero zip.
(2): aunque parece que me he saltado un paso al no explicar los bloques aún, es porque cada uno tiene una funcionalidad muy específica que requiere haberse adentrado un poquito más en la información y, de momento, con tener una actividad (es decir, una clase que visualice la información en la pantalla que es la que inluye el proyecto por defecto) vamos que chutamos. Pero será lo próximo que caiga como post, palabra.
(3): existe un bug en este punto (a fecha de hoy en revisión) por el que si se realiza list("") sería accesible el asset del framework y se solaparían las referencias si correspondiera al mismo nombre dos archivos, uno en la aplicación y otro en el del framework. (ver
issue 373 y
comentarios)
(4): los modos son MODE_APPEND para concatenar, MODE_WORLD_WRITEABLE para otorgar permisos de escritura, MODE_WORLD_READABLE para otorgar permisos de lectura y MODE_PRIVATE, el más restrictivo y el inlcuido por defecto.
(5): @+id indica que R.java (la clase que asocia a los recursos con identificadores) contendrá dentro del layout de main un índice para ese objeto (para evitar indexarlos todos). Si no lo ponemos no aparecerá aquí:
public final class R {
//...
public static final class id {
public static final int btn1=0x7f050000;
public static final int btn2=0x7f050001;
public static final int btn3=0x7f050002;
public static final int texto=0x7f050003;
}
public static final class layout {
public static final int main=0x7f030000;
}
//...
}
De aquí se deriva la conclusión que no puede duplicarse el id para NINGÚN elemento dentro de la misma aplicación, aunque estén en distintos layouts