Context de Android


En Jarroba hemos querido recopilar y explicar con máximo detalle el Context o contexto de Android, a razón de que el Contex es una parte fundamental a la hora de desarrollar aplicaciones Android (Antes de continuar puede que te interese comprender algunos conceptos básicos como Activity en este artículo o Intent en este otro). Si eres programador Android seguro que notarás que entenderlo resulte fundamental, pero la poca información respecto al tema resulte escasa por la breve documentación, o a dar por dadas cosas que no están lo suficientemente explicadas. Puede que tengamos una breve idea sobre Context. Por eso, le hemos querido dedicar un artículo a esta parte apasionante de Android. Así, si nos dirigimos a la documentación oficial podemos leer:

Es el interfaz global de información acerca del entorno de la aplicación. Es una clase abstracta que implementa Android. Permite acceder a los recursos específicos de la aplicación y a sus clases, así como llamar al padre para realizar operaciones a nivel de la aplicación, como lanzar Activities, difundir mensajes por el sistema, recibir Intents, etc.

(Original en http://developer.android.com/reference/android/content/Context.htm, traducción realizada por www.Jarroba.com)

Antes de explicar con detalle esta idea oficial -a la que te recomiendo que acudas como resumen cuando tengas claras las ideas- seguro que te ha ocurrido el mal de muchos programadores Android. Dicho mal ha sido creernos lo del Context y aplicarlo cada vez que se nos pide sin saber muy bien por qué, para qué sirve realmente, cuántos ContextPero no esta la  hay, o de dónde sale. Por norma general sabemos que el contexto es el this, que es el objeto propiamente:

Context contexto = this;

Pero si es el objeto ¿Cómo puede ser a la vez un contexto? ¿Todos los objetos tienen contexto? Responderemos las preguntas a continuación.

Si te dijera que el Context de Android es solo eso un contexto, puede que te quedes igual. Si tampoco está muy clara la idea de la palabra “contexto” y te digo que cumple a la perfección su segundo significado que figura en la RAE (Definición de contexto en la RAE):

2. m. Entorno físico o de situación, ya sea político, histórico, cultural o de cualquier otra índole, en el cual se considera un hecho.

Puedes que me llames hasta loco 😛 Y aunque no te puedo demostrar que esté loco o no, lo que te comento no es ninguna locura y te lo voy a aclarar.

 

Sobre el Context

Para entenderlo tenemos que volver a un pasado lejano, unos cuantos siglos atrás, concretamente a 1492 (Esto no demuestra mi locura, es solo un ejemplo 🙂 ). El año 1492 lo englobaba todo en esa época: casas, barcos, personas, cultura, libros, etc. Si lo extrapolamos a las Apps de Android, diríamos que 1492 es nuestra aplicación –la que engloba todo- y que los barcos, la gente y las casas son las Activities. Todo tiene un contexto propio y estará dentro de otro. Podemos hablar del contexto histórico de 1492 (ojo con la palabra contexto); así, si le pidiera al contexto de 1492 que me dé un barco, no me devolvería un portaviones de motor nuclear, probablemente me devolvería una carabela, una nao, o algún otro barco de la época. He escogido el año de 1492 porque me parecía muy global, al ser el año del descubrimiento de América por Cristobal Colón; y porque es algo histórico, que es donde más se escucha la palabra contexto, más concretamente “contexto histórico”. Como antes mencioné todo tiene un contexto propio, por ejemplo un barco tiene su propio contexto, que será diferente al contexto de otro barco, y muy diferente al de un libro, pero ambos están dentro del mismo contexto del año 1492 .

Vamos a concretar un poco más el ejemplo. Hemos creado una aplicación que hemos llamado 1492 (en la imagen siguiente verás que hemos puesto 1492.apk, el apk es la extensión en todas las aplicaciones de Android). De momento solo nos ha dado tiempo a crear 4 Activities. 3 Activities van a ser los barcos que Cristobal Colón usó para viajar, estos fueron: La Niña, La Pinta y La Santa María. Y añadimos una última Activity que hemos llamado “Cortes”.

Ejemplo de Context o contexto de Android - www.jarroba.com

Si estando en la Activity La_Pinta por ejemplo, le pedimos que nos muestre los cuadros famosos (imagina que lo que aquí llamamos “cuadro famoso” es un recurso drawable de Android, es decir, un imagen) que están en su contexto, nos devolverá pinturas de la época estén o no estén dentro del barco (en Android serían todos las imágenes que estén en la carpeta res). Pero algún cuadro, lo podremos comprar y colgar dentro del camarote del capitán (para Android, “comprar” equivaldría a obtener la imagen por getResources().getDrawable(), y “colgar en el camarote”  por ejemplo al método de una View: setBackground () ), de este modo sí estaría el cuadro dentro del barco aunque en su contexto existan otros.

Para ver este ejemplo en código vamos a tener la siguiente estructura de carpetas (solo usaremos la clase que hereda de Activity llamada “Barco_La_Pinta.java”, el resto están puestas para ejemplificar el mundo de 1492 del ejemplo):

Entender Context en Android - Ejemplo de estructura del proyecto - www.jarroba.com

Tenemos un layout que hemos llamado “pared_del_camarote_del_barco_la_pinta.xml”, que contiene una ImageView (que es el hueco de la pared donde colgar el cuadro) en el siguiente código:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".Barco_La_Pinta" >

	<ImageView
	android:id="@+id/imageView_Barco_La_Pinta_lugarParaCuadro"
	android:layout_width="wrap_content"
	android:layout_height="wrap_content"
	android:layout_alignParentTop="true"
	android:layout_centerHorizontal="true"
	android:layout_marginTop="188dp"
	android:src="@drawable/ic_launcher" />

</RelativeLayout>

Y el código java de “Barco_La_Pinta.java” es:

public class Barco_La_Pinta extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.pared_del_camarote_del_barco_la_pinta);

		ImageView iv = (ImageView) findViewById(R.id.imageView_Barco_La_Pinta_lugarParaCuadro);

		Context contexto = this;

		Drawable cuadroElegido = contexto.getResources().getDrawable(R.drawable.cuadro_torre);

		iv.setBackground(cuadroElegido); //Nota: Si se usa un Api Level anterior a 16 usar setBackgroundDrawable()
	}

}

En Android, cuando la documentación oficial se refiere a “interfaz global de información acerca del entorno de la aplicación”, quiere decir que nos proporciona a los programadores acceso a métodos que nos facilitan la vida; como lanzar nuevas Activities con startActivity() (¿A qué no programas cada línea de código al lanzar una aplicación? Simplemente llamas a este método y se acabó). U obtener recursos de la carpeta res (como a las imágenes, colores, textos, etc) con getResources() o los Strings con getString(), obtener los datos almacenados en local con getSharedPreferences(), entre otros muchos.

Volvamos al ejemplo, ¿Pero si le pregunto al contexto de 1492 por los cuadros famosos, no nos devolvería los mismos? Sí, los mismos. Pero no es esta la causa por lo que se diferencian los contextos, existen cosas comunes.
Al contexto de cualquier barco le puedo preguntar ¿Cómo de ajetreado está el mar? y nos responderá, pero el de la “corte” nunca le voy a poder hacer esa pregunta ¡no está en el mar! (En Android no es lo mismo el contexto de una Activity al que le puedo preguntar por el Layout asociado, que de un Servicio que no puedo preguntarle por el Layout porque no tiene ninguno).
Entonces, en Android nos encontramos con los siguientes tres diferentes tipos de contextos:

  • Aplicación (Como es lógico, al cubrir todo el ciclo de vida de la aplicación desde que la arrancamos hasta que muere, cada aplicación tiene un único contexto de aplicación; además, este contexto engloba a todos los demás): Crea una instancia Singleton que se crea al iniciar el proceso de la aplicación. Se puede acceder desde una Activity o un Service con getApplication(), o desde cualquiera que herede de Context con getApplicationContext().
  • Activity o Service: Heredan de ContextWrapper que a su vez hereda de Context. ContextWrapper hace que se apodere de todas las llamadas y las envíe a una instancia oculta de Context (conocido como “Base Context”)

Los siguientes componentes fundamentales de Android no heredan de Context, pero lo necesitan, por lo que el sistema se los pasa de algún modo:

  • BroadcastReceiver: Pero se le pasa un Context en el onReceive() cada vez que un nuevo evento difundido por el sistema entra.
  • ContentProvider: Puede acceder al Context con getContext() y se le devolverá el Context de la aplicación que lo esté ejecutando (puede ser la misma aplicación u otra diferente).

En el ejemplo, si un barco es destruido, su contexto muere con él. Así, en Android el Context está ligado al ciclo de vida del elemento que cubre, desde el momento que está en ejecución hasta que termina.
Siendo un poco más técnicos. Context es una clase abstracta que implementa Android. Los elementos que tienen Context heredan de la clase Context. Por lo que –y respondiendo a la preguntas del principio- ni todos los objetos tienen Context, y ni el objeto es el propio Context, depende de si heredan de Context o no.

Nota sobre la herencia: Si no entiendes muy bien la documentación, te recomiendo que eches un vistazo a las diferentes documentaciones (Por facilitar, hemos puesto los links de las documentaciones en cada uno de los elementos anteriores). Por ejemplo, a la documentación de Activity (ver en la documentación oficial de Activity). Vemos en la cabecera como efectivamente hereda de Context:

Herencia de Context de la Activity de Android - www.jarroba.com

 

 

Suelen pedir el Context

Normalmente piden el Context:

  • Al acceder a los recursos como imágenes, Strings, etc
contexto.getResources().getDrawable(R.drawable.imagen_en_los_recursos);
  • Al crear nuevos Views, Listeners, Adapters, Intents, etc. Por ejemplo:
TextView miTexto = new TextView(contexto);

Intent intento = new Intent(contexto, claseActivityArrancar.class);
  • Al acceder directamente a componentes como ActionBar, Intents, etc
contexto.getIntent();

 

Obtener el Context

Hemos explicado cómo obtener el Context dependiendo del tipo. Aquí dejo una lista de las diferentes maneras de obtener el Context con ejemplos completos:

  • Una clase que hereda de Context (Como desde una Activity o Service): this
public class ObtenerContexto extends Activity  {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		Context contexto = this;
	}

}
  • Una clase que hereda de Context desde una clase anónima: ClaseActivity.this
public class ObtenerContexto extends Activity  {

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

		Button boton = (Button) findViewById(R.id.button_obtenerContext_boton);
		boton.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				Context contexto = ObtenerContexto.this;
			}

		});

}

}
  • Una clase que NO hereda de Context (como desde View): mi_view.getContext()
public class ObtenerContexto extends Activity  {

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

		Button boton = (Button) findViewById(R.id.button_obtenerContext_boton);

		Context contexto = boton.getContext();
	}

}
  • La aplicación (desde cualquier clase que hereda de Context): getApplicationContext()
public class ObtenerContexto extends Activity  {

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

		Context contexto = getApplicationContext();
	}

}
  • Otro Context (desde una clase que hereda de ContextWrapper, como Activity): getBaseContext()
public class ObtenerContexto extends Activity  {

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

		Context contexto = getBaseContext();
	}

}
  • La Activity del Fragment que lo contiene (desde un Fragment): getActivity()
public class obtenerContextoFragment extends Fragment {

	@Override
	public void onAttach (Activity activity) {
		super.onAttach(activity);

		Context contexto = getActivity();
	}

}

 

Evitar problemas con el Context

Ahora solo queda evitar las fugas de memoria que el Context nos puede provocar. Lo primero que tenemos que repasar es que un Context vive tanto como el elemento al que pertenece, por lo que depende del ciclo de vida. Así, un Context de una Activity vivirá el mismo tiempo que esta, y el Context de la aplicación vive hasta que se termina por completo la aplicación (hasta que muere, no hasta que se pausa).

Aunque suene un poco contradictorio, por norma general siempre hay que usar el Context más pequeño que suele ser el del elemento al que pertenece; pero en la mayoría de casos se obtiene pérdida de memoria, siendo altamente recomendable usar el Context de la aplicación. Esto sucede por el tema de la doble referencia, es decir un objeto apunta a otro objeto y el Garbage Collection (recolector de basura de Java, para la liberación automática de memoria) no puede eliminar dichos objetos, lo que implica no poder liberar esa memoria aunque no haya nada más apuntando a dichos objetos más que el uno al otro.

Imaginemos que queremos guardar en una variable estática una imagen de fondo para no tener que estar accediendo todo el rato a la memoria cada vez que giramos el dispositivo (recuerdo rápidamente que al girar el dispositivo se destruye la actividad y se vuelve a crear, si la variable es estática siempre estará en memoria aunque sea destruida), con esto el código iría mucho más rápido:

public class miActivity extends Activity  {

	private static Drawable sFondoDelTexto;

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

		Context contexto = this;

		TextView label = new TextView(contexto);
		label.setText("Texto con fondo");

		if (sFondoDelTexto == null) {
			sFondoDelTexto = getDrawable(R.drawable.mi_imagen);
		}

		label.setBackgroundDrawable(sFondoDelTexto);

		setContentView(label);
	}

}

El problema es que le ponemos el Context como this al TextView, y al TextView le asignamos una imagen que no va a ser destruida al rotar el dispositivo. Como esta imagen apunta al objeto TextView, y la imagen no va a ser destruida, no se destruirá el TextView, así no se destruirá el Context, por lo que no se destruirá la Activity que está ligado al Context. Aquí tenemos el problema de memoria, pues al volverse a crear, la imagen apuntará a otro Context de otra Activity creada; y así en cada giro del móvil, hasta que nos quedemos sin memoria y de error.

Para solucionar este caso lo mejor es usar el contexto de la aplicación (Usando Context.getApplicationContext() o con Activity.getApplication() ) ya que no está asociado al ciclo de vida de la Activity, por lo que al destruirse el Context de la Activity quedará libre cuando tenga que ser destruida y podrá ser recolectada:

public class miActivity extends Activity  {

	private static Drawable sFondoDelTexto;

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

		Context contexto = getApplicationContext();

		TextView label = new TextView(contexto);
		label.setText("Texto con fondo");

		if (sFondoDelTexto == null) {
			sFondoDelTexto = getDrawable(R.drawable.mi_imagen);
		}

		label.setBackgroundDrawable(sFondoDelTexto);

		setContentView(label);
	}

}

También hay que tener cuidado al crear una clase Singleton que guarde nuestro Context (desde Jarroba no recomendamos guardar un Context ni con Singleton ni con variables estáticas, el Context se ha de pasar siempre para no perder su control). Pues si la Activity es destruida nunca se borrará de memoria, ya que como el ejemplo anterior, existirá una doble referencia que no permita al Garbage Collector liberar dicha memoria, por lo que la Activity seguirá ocupando toda la memoria pese a estar destruida:

public class miSingleton {

	private static miSingleton sInstancia;

	private Context mContexto;

	public static miSingleton getInstance(Context contexto) {
		if (sInstancia == null) {
			sInstancia = new miSingleton(contexto);
		}

		return sInstancia;
	}

	private miSingleton(Context contexto) {
		mContexto = contexto;
	}

}

Para corregir esto, se puede usar el Context de la aplicación otra vez (aunque recordemo que el Context de una aplicación está guardado en una clase Singleton a su vez, con lo qué ¿Realmente necesitamos guardarlo de esta manera o con obtenerlo nos basta?):

public class miSingleton {

	private static miSingleton sInstance;

	private Context mContexto;

	public static miSingleton getInstance(Context contexto) {
		if (sInstance == null) {
			sInstancia = new miSingleton(contexto.getApplicationContext());
		}

		return sInstancia;
	}

	private miSingleton(Context contexto) {
		mContexto = contexto;
	}

}

Con esto terminamos con un resumen del Context: sirve para abstraer de muchas cosas que las hace el sistema operativo por nosotros, así se hace la labor del desarrollador mucho más simple. Siempre que entendamos el Context y sus implicaciones.

Esperamos que el artículo haya servido para aclarar muchos conceptos. No ha sido un artículo fácil por la escasa información que existe sobre el tema. Por esta razón lo modificaremos con el tiempo, para que sea todavía más aclaratorio tanto por el feedback que nos vayáis aportando, como por las novedades que vayan saliendo del mundo Android.

Si quieres ver algunos ejemplos más en donde se aplica Context puedes ver el artículo de listado o en el artículo de fragments.

Referencias:

Comparte esta entrada en:
Safe Creative #1401310112503
Context de Android por "www.jarroba.com" esta bajo una licencia Creative Commons
Reconocimiento-NoComercial-CompartirIgual 3.0 Unported License.
Creado a partir de la obra en www.jarroba.com

14 comentarios en “Context de Android”

  1. Dejo fé de que 3 años después de que lo escribieras sigue ayudando a principiantes de Android como yo, que como comentaba otro "colega" en las opiniones he decidido actualizar mis conocimientos de programación 18 años después (vengo de 15 años de Cobol, aunque en época de estudiante si manejé C, C++)

    Aclara muchos conceptos y lógica de pensamiento este tipo de artículos.

    Muchas gracias por el detalle de la explicación y por hacer artículos como este y, en general, una web como la tuya. Desde luego te he añadido a favoritos después de unas cuantas semanas de buceo por la red para aprender Android de forma autodidacta.

  2. Hola, muchas gracias por toda la info! Genial las aclaraciones y muy util la puntualización que haces en los posibles lugares comunes donde se pueden cometer errores.
    Queria hacer una consulta sobre la clase Aplicacion de contexto que tengo hacer para mi proyeto…Como vos dijiste debe ser singleton pero mi problema surge hay querer integrar ADA Framework…Declaro dos clases (cliente y campo) que van a mapear sus respectivas tablas en la base de datos y despues tengo que declara mi Contexto de aplicacion donde declaro e inicialiazo dichas clases….He leido bastante y llegué a tener estas dos clases:

    public class ApplicationDataContext extends ObjectContext {

    private static final String TAG="ApplicationDataContext";

    public static final int DATABASE_VERSION = 1;
    public static final String DATABASE_NAME = "Agrosol_test.db";

    private ObjectSet<Cliente> clientesSet;
    private ObjectSet<Campo> camposSet;

    public ObjectSet<Cliente> getClientesSet(){
    return clientesSet;
    }

    public ObjectSet<Campo> getCamposSet(){
    return camposSet;
    }

    public ApplicationDataContext (Context pContext) throws AdaFrameworkException {

    super(pContext, DATABASE_NAME, DATABASE_VERSION);

    inicializar();

    }

    private void inicializar() {
    try {
    if (clientesSet == null) {
    clientesSet = new ObjectSet<Cliente>(Cliente.class, this);
    }
    if (camposSet == null) {
    camposSet = new ObjectSet<Campo>(Campo.class, this);
    }
    }catch(Exception e)
    {
    Log.e(TAG, e.getMessage());
    }
    }

    }

    Y:

    public class ApplicationContext
    {
    public static ApplicationDataContext contexto;

    public synchronized static ApplicationDataContext get(Context pContext) throws AdaFrameworkException {
    if(contexto==null){
    contexto= new ApplicationDataContext(pContext);
    }
    return contexto;
    }

     Mi problema es que soy nueva en android y no se si esto tiene sentido o siquiera puede hacer. En caso que se pueda…cuando tenga que llamara a mi Contexto de aplicación…a cual llamo? la que posee el singleton?

    Espero puedan ayudarme.

    Muchas gracias!!

    Saludos!!

    1. Hola Mariel, el Context es mejor que lo obtengas desde el Fragment donde inicializas el Framework y se los pases desde ahí. No he utilizado ADA pero por lo que estado ojeando deberías de pasarle el Context en al crear la nueva instancia del que hereda de ObjectContext. Es decir, que en tu Activity o Fragment has de tener:

      new ApplicationDataContext(context);

  3. Estoy actualizando mis conocimientos en programación, la que empecé el año 1983 y que dejé hace más de 15 años. Ahora respecto a la explicación sobre "Context", a pesar que entiendo muy, pero muy poco de android, de algo me sirvió la aclaración, por la forma que lo describiste, fue muy didáctico, por eso aprendí un poco. Agradezco vuestro esfuerzo, en algo que es muy complicado.

    1. De nada Miguel 🙂

      Te animo a profundizar en Android, donde a medida que aprendas irás aclarando un montón de cosas. Para animarte a iniciarte en Android puedes echar un ojo a nuestro libro gratuito de Android en: http://jarroba.com/libro-android-100-gratis/

  4. Hola. Primero muchas gracias por el esfuerzo, me aclara un montón de conceptos. Pero tengo una duda sobre la recolección de basura. En alguna parte he visto que utiliza el concepto de la “reachability” para eliminar objetos de la memoria, es decir, que no es tan tonto, si ve que dos objetos se referencian respectivamente pero que el sistema no puede llegar hasta ellos entonces son candidatos a ser eliminados. Si no, lo cierto es que sería un recolector muy torpe.
    Estais seguros de que no ocurre así también en Android? Me parece muy raro.
    Aún así, el asunto de los contextos me queda mucho más claro.
    Gracias y seguid así.
    Antonio.
    Ourense. Spain.

    1. Hola Antonio,

      es cierto que por lógica (y porque el recolector de Java las limpia) el recolector de basura de Android debería ser capaz de limpiar una doble referencia; aunque por otro lado están las referencias suaves (SoftReference, WeakReference, PhantomReference) y la referencia normal es dura. No te puedo confirmar al 100% una respuesta a tu pregunta, pues oficialmente viene muy poca información al respecto, Android trabaja sobre otra máquina virtual que no es la de Java, y no he descompilado el código de Android asegurarlo. Solo recomendarte que evites las dobles referencias para facilitar la recolección basura como buena práctica 😉

  5. Hola, muy buena explicación acerca del context, pero tengo una pregunta si yo tengo creada una clase java para métodos que van a ser usados en toda la aplicación y esta clase no es una activity sino sólo una clase java común, de que forma puedo llamar el contexto de la aplicación desde esta???

    Gracias!

    1. Hola,

      El Context solo se puede obtener desde una clase que herede de Context (como Activity). Por lo que te serviría pasar el ApplicationContext la primera vez que se cree (y lo puedes almacenar, por ejemplo, en una clase que siga el patrón Singleton).

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Uso de cookies

Este sitio web utiliza cookies para que usted tenga la mejor experiencia de usuario. Si continúa navegando está dando su consentimiento para la aceptación de las mencionadas cookies y la aceptación de nuestra política de cookies

ACEPTAR