Professional Documents
Culture Documents
Tema 9
Actividades
En temas anteriores ya se han implementado actividades que proveen pantallas sobre las que
el usuario puede interactuar, y se han utilizado algunos de sus métodos fundamentales.
Además, se ha mostrado cómo invocar una actividad desde otra por medio de un Intent.
En general, una aplicación incluirá varias actividades, débilmente ligadas entre sí, cada una de
las cuales tendrá asignada una ventana en la cual mostrar su interfaz de usuario. Esta ventana
normalmente ocupará toda la pantalla, aunque también podrá presentarse de forma flotante
sobre otras ventanas. En general, cada aplicación tendrá una actividad principal, cuya ventana
será mostrada cuando se invoque a la aplicación por primera vez.
Cada vez que una actividad inicie otra actividad, la primera será parada, aunque el sistema la
mantendrá en memoria, en una pila de actividades llamada “back stack” y que es similar, por
ejemplo, al historial de páginas visitadas en un navegador web. Cada vez que una nueva
actividad es iniciada, el sistema la añadirá a esta pila como el elemento superior 1 y, cuando el
usuario pulse el botón “atrás”, el sistema destruirá esta actividad y reactivará la nueva
actividad superior de la pila.
Cuando el sistema detiene una actividad porque está iniciando otra, notifica los cambios de
estado de las actividades a través de sus métodos callback. Existen varios métodos callback en
cada actividad que pueden ser invocados cuando la misma cambia de estado, siendo el
principal, y ya conocido, el método onCreate(), invocado cuando la actividad es creada por
primera vez. El sistema invocará a otros métodos concretos cuando la actividad sea parada,
reiniciada o destruida, métodos en los cuales podrá ser necesario realizar operaciones
concretas tales como liberar conexiones o recuperarlas, eliminar de memoria objetos
“pesados”, o grabar datos del usuario en algún contenedor persistente.
1
Esta pila sigue el método de encolamiento LIFO, Last In First Out: la última actividad que entra en la pila será la
primera en salir de la misma.
Todas las actividades de las aplicaciones que se desarrollen deberán extender de la clase
Activity o de una subclase de la misma (como, por ejemplo, ListActivity). Además, es
necesario implementar, como mínimo, el método onCreate(), método que será invocado por
el sistema en el momento en el que se instancie por primera vez la actividad. Es importante
tener en cuenta que este método no volverá a ser invocado a menos que el objeto instanciado
(la actividad) sea borrado de la memoria del sistema.
Para mantener el diseño de la interfaz de usuario separado del código que define el
comportamiento de la actividad, es conveniente definir los layouts en archivos XML en la
carpeta de recursos de la aplicación (carpeta “res/”) y después asociar cada layout a su
actividad a través del método setContentView(), que puede recibir como parámetro el
identificador del layout 2. No obstante, también es posible, y a veces necesario, crear vistas de
forma jerárquica directamente en el código de la actividad (en tiempo de ejecución), asociarlas
a un ViewGroup y utilizarlas para crear la interfaz de la actividad pasando dicho ViewGroup al
método setContentView().
Cada una de las actividades de la aplicación deberá estar definida en el archivo de manifiesto
de la misma. Este archivo, también XML, declara, entre otras cosas, las actividades, como
elementos <activity> anidados en el elemento <application>. Entre los atributos del
elemento <activity> destacan propiedades de la actividad tales como su nombre o el estilo
visual que aplicará sobre el layout de la misma.
Tal y como se verá con más profundidad en el tema 11, el elemento <activity> puede definir
varios filtros de intents, <intent-filter>, para declarar cómo puede ser activada por otros
componentes de otras aplicaciones. En general, la actividad principal contendrá un filtro intent
que declara que la actividad responderá a la acción principal (“main”) y que estará ubicada en
la categoría “launcher”:
2
Este identificador equivale al nombre del archivo XML de dicho layout, quedando así definido en la subclase id de
la clase R.
<activity android:name=…>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
Existen sólo dos formas de iniciar una actividad: o bien se invoca a través de un Intent
explícito, o bien a través de un Intent implícito, que describe qué tipo de acción se quiere
realizar. En este caso, el sistema buscará entre todas las actividades de todas las aplicaciones
aquellas que encajen, a través de los filtros de intents que tengan declarados, con lo descrito
en el Intent.
Para iniciar una actividad , se invocará al método startActivity() pasando como parámetro
el objeto Intent creado. Si se necesita obtener un resultado de la actividad iniciada se
invocará a startActivityForResult(). Para no bloquear la aplicación que invoca a la
segunda actividad, en vez de esperar que el método startActivityForResult() devuelva el
resultado deseado, se implementará el método callback onActivityResult(), que será
invocado cuando la segunda actividad finalice y que recibirá como parámetro un Intent con
la información deseada.
Como ya se ha anticipado al principio del tema, la gestión del ciclo de vida de las actividades es
crucial para desarrollar una buena aplicación, debiéndose implementar los métodos callback
que serán invocados cuando la actividad cambie de estado.
3
Un Intent explícito invoca directamente al nombre de la clase de la actividad tal y como se ha mostrado en el
tema 3.
• En pausa. La actividad está en un segundo plano, visible pero parcialmente oculta por
otra actividad que ha obtenido el foco. La actividad sigue viva, manteniéndose su
información en memoria, y sigue unida al gestor de ventanas, pero podría ser
finalizada en casos de necesidad extrema de memoria por parte del sistema.
• Parada. La actividad está en background, oculta. La actividad ya no está unida al gestor
de ventanas, aunque sigue manteniéndose en memoria hasta que sea necesaria más
memoria por parte de cualquier otra aplicación.
Una vez pausada o parada la actividad, el sistema podrá eliminarla de la memoria bien
invocando a finish() 4 o bien eliminando directamente su proceso. En cualquiera de los
casos, cuando la actividad se vuelva a iniciar, deberá ser creada de nuevo (invocando a su
método callback onCreate()).
4
No se debe invocar explícitamente a los métodos finish() o finishActivity() ya que se pueden producir
comportamientos extraños. Sólo se utilizarán en el caso de que no se desee que el usuario vuelva a la misma
instancia de la actividad.
A continuación, se realiza una descripción exhaustiva de los métodos callback del ciclo de vida.
onResume() Método invocado justo antes de que la actividad interactúe con el usuario.
La actividad es la primera en la pila de actividades.
La actividad es visible.
No se puede eliminar la actividad después de este método.
A este método siempre le sigue onPause().
onDestroy() Método invocado justo antes de que la actividad sea destruida (eliminada).
Es destruida para liberar memoria o porque se ha invocado finish().
Se pueden distinguir estos escenarios mediante isFinishing().
Es importante recordar que, cuando se implementen todos o alguno de estos métodos, será
necesario invocar a la implementación del correspondiente método de la superclase
(super.onMetodo()) para que el sistema pueda gestionar correctamente el ciclo de vida.
Por otro lado, es necesario tener en cuenta que el sistema sólo puede eliminar el proceso que
aloja la actividad después de que se haya ejecutado uno de los tres métodos onPause(),
onStop() u onDestroy(), siendo onPause() el primer método después del cual el sistema,
en casos de extrema necesidad, puede eliminar la actividad. Es por ello muy importante que
sea en este método en donde se grabe toda la información crítica en algún contenedor
persistente. También es necesario remarcar que, puesto que la actividad nunca será eliminada
antes de finalizar la ejecución de este método, las operaciones que se realicen deberán ser las
mínimas posibles, ya que mientras tanto la interfaz de usuario estará “congelada”.
5
No existen garantías de que este método sea invocado siempre, ya que existen casos en los que no se necesita
grabar el estado de la actividad como, por ejemplo, cuando el usuario pulsa el botón “atrás”.
6
Si no se almacena ningún par nombre-valor, el objeto Bundle recibido será nulo.
7
Es útil probar a rotar el dispositivo ya que en este caso el sistema destruirá al actividad para renderizarla de nuevo
correctamente.
8
Es conveniente recordar que toda propiedad de una View definida en el archivo XML, tiene su correspondiente
método accesor (setPropiedad()) vía código.
Cambios de configuración
Ya se han explicado los métodos callback que hay que sobreescribir (sin olvidar invocar al
correspondiente método de la superclase) para grabar correctamente el estado de la
actividad. Si este procedimiento se realiza correctamente, la actividad será mucho más robusta
ante eventuales comportamientos inesperados que sucedan durante su ciclo de vida.
Coordinación de actividades
Cuando una actividad invoca a otra en el mismo hilo, sus ciclos de vida se solapan durante
cierto tiempo. Mientras que la primera actividad se pausará y puede que se pare (si se hace
completamente invisible), la segunda actividad será creada.
Fragmentos
Android 3.0 introduce en la API el concepto de fragmento. Un fragmento es, básicamente, una
sección modular de la interfaz de usuario de una actividad que tiene su propio ciclo de vida,
que acepta sus propios eventos y que puede ser reutilizado en distintas actividades. Se pueden
combinar diferentes fragmentos en una actividad para construir interfaces complejas así como
añadirlos o eliminarlos mientras la actividad está activa. Los fragmentos son útiles para crear
interfaces que se adapten correctamente a distintas resoluciones y orientaciones,
maximizando la experiencia del usuario y la reutilización de código.
Un fragmento sólo puede existir dentro de una actividad y el ciclo de vida de esta afectará al
ciclo de vida de aquel. Si la actividad es pausada, también serán pausados todos los
fragmentos que contenga. Sin embargo, mientras la actividad está activa, se pueden crear y
destruir fragmentos contenidos en la misma de forma independiente. Para poder realizar estas
operaciones, Android provee, para cada actividad, una nueva pila análoga a la back stack de las
actividades, que contendrá las operaciones (transacciones) realizadas con los fragmentos de
dicha actividad permitiendo que se deshagan los cambios realizados en los fragmentos al
pulsar el botón “atrás”.
Los fragmentos pueden tener su propio layout 9 que deberá ser insertado dentro de un
ViewGroup de la actividad, bien utilizando el elemento <fragment>, o bien vía código,
instanciando una subclase de Fragment y añadiéndola a un ViewGroup.
El ciclo de vida de un fragmento es similar al de una actividad, aunque tiene más métodos
callback para gestionar las diferentes etapas de su ciclo de vida.
Si se está rescribiendo una actividad para convertirla en un fragmento, bastará con mover el
código de un método callback de la actividad, al mismo método del fragmento.
9
Pueden declararse fragmentos que no tengan asociado un layout, invisibles, dedicados a realizar tareas concretas
que no requieren interfaz de usuario.
A continuación, se realiza una descripción exhaustiva de los métodos callback del ciclo de vida
de un fragmento.
Al igual que las actividades, un fragmento adoptará, principalmente, uno de estos tres estados:
Se puede también conservar el estado del fragmento usando un objeto Bundle, de nuevo
durante la llamada al método onSaveInstanceState(), y podrá ser recuperado en cualquiera
de los tres métodos iniciales del ciclo de vida, una vez adjuntado el fragmento: onCreate(),
onCreateView() u onActivityCreated().
La mayor diferencia en la gestión del ciclo de vida de un fragmento respecto a la gestión del
ciclo de vida de una actividad es que, mientras que en el de la actividad es el sistema quien
añade la misma a la back stack cuando esta es parada, en el caso de los fragmentos sólo serán
añadidos a su propia pila en caso de que se invoque al método addToBackStack() durante
una transacción. Conceptualmente, esta pila contiene estados de fragmentos, o lo que es lo
mismo, fragmentos en un estado concreto.
Por último, hay que recordar que el ciclo de vida del fragmento está determinado por el ciclo
de vida de la actividad que lo contiene, aunque cuando la actividad esté ejecutándose
(resumed), el ciclo de vida del fragmento podrá evolucionar libremente (pudiéndose eliminar o
añadir distintos fragmentos a través de transacciones).
Para recibir las llamadas a los métodos callback anteriores, el fragmento deberá ser una
subclase de Fragment o bien de alguna de sus subclases ya implementadas en la SDK de
Android como, por ejemplo:
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.reproductor_musica_fragment,
container, false);
}
Código de main.xml
<FrameLayout
android:id="@+id/listaMusicaFrameLayout"
android:layout_width="0dp"
android:layout_height="fill_parent"
android:layout_weight="0.4" >
<fragment
android:id="@+id/listaMusicaFragment"
android:name="com.cursoandroid.ui.ListaMusicaFragment"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
</fragment>
</FrameLayout>
<LinearLayout
android:id="@+id/detalleMusicaLayout"
android:layout_width="0dp"
android:layout_height="fill_parent"
android:layout_weight="0.6"
android:orientation="vertical" >
...
</LinearLayout>
</LinearLayout>
En el código anterior, se observa que el elemento <fragment> hace referencia, a través del
atributo android:name, a la clase que instanciará el layout del fragmento 10. Dicha clase, al
implementar el método onCreateView() proveerá el objeto View que el sistema insertará en
el lugar indicado por el elemento <fragment>. Será necesario al menos proveer el atributo
android:id (con identificador único) para que el sistema sea capaz de recuperar el fragmento
si la actividad es reiniciada y para poder capturar el fragmento en código mediante
findFragmentById() 11.
fragmentTransaction.commit();
El gestor de fragmentos también está encargado de gestionar la pila back stack de los mismos,
y provee un método para registrar un listener que avise de cambios en la pila,
addBackStackChangedListener(). Para extraer estados de fragmentos de la pila, el gestor
utilizará el método popBackStack() que podrá recibir un identificador o nombre como
parámetro, entre otros, casos en los cuales extraerá de la pila todos estados de fragmentos
hasta el indicado.
10
Al hacer referencia a la clase, se deberá incluir su espacio de nombres.
11
También se podrá indicar el atributo android:tag, más usado para localizar fragmentos sin representación visual
mediante findFragmentByTag().
12
Si se declara un fragmento en el archivo XML de layout de la actividad y, simultáneamente, se añade el mismo
fragmento en tiempo de ejecución en el mismo ViewGroup, se duplicará el fragmento, superponiéndose uno a otro.
Solo uno de los fragmentos capturará eventos y, por ejemplo, en caso de tratarse de un fragmento de tipo lista, al
hacer scroll, solo se moverán los ítems de una de las listas. Este tipo de comportamientos deberán evitarse al
realizar el correcto diseño de los diferentes layouts de una aplicación en función de la resolución de los diferentes
dispositivos.
Para realizar las transacciones, el objeto FragmentTransaction provee los típicos métodos
add(), remove() y replace(). Para que las operaciones de la transacción tengan efecto, se
deberá invocar a commit(). Previamente se podrá haber invocado a addToBackStack() para
poder recuperar el estado anterior del fragmento posteriormente si se pulsa el botón “atrás”.
En el código de la página anterior, se realiza una transacción para añadir el fragmento de tipo
ListaMusicaFragment al contenedor (ViewGroup) cuyo identificador es
R.id.listaMusicaFragment. Esta operación no se podrá deshacer puesto que la transacción
no se ha añadido a la back stack. Si se quiere sustituir dicho fragmento por otro, y tener la
posibilidad de recuperar el nuevo fragmento posteriormente, se deberá implementar un
código similar al siguiente:
ListaVideoFragment listaVideo =
new ListaVideoFragment();
fragmentTransaction.replace(R.id.listaMusicaFrameLayout, listaVideo);
fragmentTransaction.addToBackStack();
fragmentTransaction.setTransition(
FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
fragmentTransaction.commit();
Finalemente, es necesario tener en cuenta que las transacciones se deben realizar antes de
que la actividad cambie de estado, es decir, antes de que el usuario, por ejemplo, abandone la
misma, debido a que se puede perder el estado final de los fragmentos en caso de que la
actividad sea restaurada. Si esta situación no produce ningún problema en la aplicación, se
podrá utilizar el método commitAllowingStateLoss().
Los fragmentos pueden acceder a recursos de la actividad que los instancia con tan solo
invocar al método getActivity(). Por ejemplo, dentro de un fragmento se puede acceder a
una View de la actividad:
TextView tw =
(TextView) getActivity().findViewById(R.id.tituloDetalleMusica);
Por otro lado, las actividades pueden acceder también a recursos de los fragmentos que
instancian, a través de FragmentManager. Por ejemplo:
En algunos casos será necesario que la actividad que implementa un fragmento reaccione ante
eventos que suceden dentro del mismo y, por ejemplo, reciba información de dichos eventos
para pasársela a otros fragmentos. Para poder implementar este comportamiento será
necesario definir una interfaz con un método callback dentro del fragmento, y obligar a que la
actividad lo implemente, comprobando que la actividad extiende dicha interfaz y lanzando una
excepción ClassCastException en caso de que no sea así 13.
El primer método invocado en el ciclo de vida del fragmento, onAttach(), deberá ser el
método donde se compruebe si la actividad que instancia el fragmento ha implementado la
interfaz que contiene el método callback.
13
Si una clase implementa un interfaz, deberá poderse realizar un cast de dicha clase al tipo de dicha interfaz.
Supóngase que una clase implementa una interfaz:
class A implements B.
OnCancionSelectedListener mCancionSelectedListener;
@Override
public void onAttach(Activity activity) {
try {
mCancionSelectedListener = (OnCancionSelectedListener) activity;
}
catch (ClassCastException e) {
throw new ClassCastException("La " + activity.toString() +
" debe implementar OnCancionSelectedListener");
}
}
@Override
public void onListItemClick(ListView listaCanciones, View v, int
position, long id) {
// Se envía el evento y el identificado de la canción
// seleccionada a la actividad que contiene el fragmento
mCancionSelectedListener.onCancionSelected(
((TextView) v).getText().toString());
}
Código de BibliotecaMusicalActivity.java
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
ReproductorMusicaFragment reproductor =
new ReproductorMusicaFragment();
fragmentTransaction.add(R.id.detalleMusicaFrameLayout, reproductor,
REPRODUCTOR_FRAGMENT_TAG);
fragmentTransaction.setTransition(
FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
fragmentTransaction.commit();
}
Código de main.xml
<FrameLayout
android:id="@+id/listaMusicaFrameLayout"
android:layout_width="0dp"
android:layout_height="fill_parent"
android:layout_weight="0.4" >
</FrameLayout>
<LinearLayout
android:id="@+id/detalleMusicaLayout"
android:layout_width="0dp"
android:layout_height="fill_parent"
android:layout_weight="0.6"
android:orientation="vertical" >
<TextView
android:id="@+id/tituloDetalleMusica"
android:layout_width="fill_parent"
android:layout_height="100dp"
android:gravity="center_vertical"
android:text="@string/tituloDetalle"
android:textColor="#ffffff"
android:textSize="16sp"
android:textStyle="bold" />
<FrameLayout
android:id="@+id/detalleMusicaFrameLayout"
android:layout_width="match_parent"
android:layout_height="fill_parent" >
</FrameLayout>
</LinearLayout>
</LinearLayout>
Código de ReproductorMusicaFragment.java
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.reproductor_musica_fragment,
container, false);
}
TextView tw = (TextView)
getActivity().findViewById(R.id.tituloDetalleMusica);
tw.setText(getString(R.string.reproducir) + " " + tituloCancion);
}
}
Código de reproductor_musica_fragment.xml
<TextView
android:id="@+id/tituloReproductorMusica"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/tituloReproductor"
android:textColor="#ffffff"
android:textSize="16dp"
android:textStyle="bold" />
</LinearLayout>
Fragmentos y ActionBar
Los fragmentos pueden contribuir con sus propios ítems al menú de opciones de la actividad y,
por lo tanto, a la ActionBar. Para que esto suceda, en su método onCreate() se deberá
invocar a setHasOptionsMenu() de forma que se indique al sistema que el fragmento
agregará opciones al menú. Entonces, el sistema invocará al método onCreateOptionsMenu()
implementado en el fragmento para añadir los ítems que ahí se definan, y el fragmento
recibirá callbacks a su método onOptionsItemSelected() cuando se seleccione alguna
opción del menú, siempre y cuando la actividad no haya gestionado el ítem seleccionado
(devolviendo true) en su propio método onOptionsItemSelected(), puesto que este será
invocado antes.
Loaders
Tanto en las actividades como en los fragmentos, muchas veces será necesario cargar datos
externos de diversas fuentes. Como la carga de estos datos puede requerir cierto tiempo, no
será aconsejable, por ejemplo, utilizar el método onCreate() u otros métodos del ciclo de vida
para realizar la carga de datos, puesto que la interfaz del usuario de la aplicación podría llegar
a “congelarse” durante el tiempo de carga de dichos datos externos. Además, la actualización
de estos datos externos en la aplicación propia (por ejemplo, una lista de noticias de última
hora) ha de ser también gestionada para visualizar siempre la última versión de las fuentes de
los datos.
Android 3.0 provee un conjunto de clases que facilitan todo este proceso, admitiendo además
la carga asíncrona de datos, característica que permitirá un rendimiento óptimo de la
aplicación. Además de monitorizar la fuente de datos y comunicar la existencia de nuevos
datos cuando el contenido cambia, estas clases permiten la reconexión automática al último
cursor del loader cuando la actividad o el fragmento son recreados, de forma que no es
necesario volver a solicitar los datos a la fuente.
Existen cuatro clases básicas más una interfaz, con los cuales se podrá realizar todo el trabajo
de carga asíncrona y refresco de datos. También se podrán extender los loaders para crear
otros propios con características únicas.
Para utilizar loaders en una actividad o fragmento, se deberá crear una instancia única de
LoaderManager, que gestionará todos los loaders de la actividad o fragmento. En general, se
14
Esta clase da acceso a las aplicaciones al modelo de contenidos de Android, ofreciendo los datos encapsulados
por el ContentProvider. Se tratará más adelante en un próximo tema.
getLoaderManager().initLoader(
ID_LOADER, loaderArguments, instanciaLoaderCallbacks);
Este método, que inicializa y activa el loader, recibe tres argumentos que, respectivamente,
identifican de forma unívoca al loader creado, pasan argumentos adicionales al loader y, por
último, asocian la interfaz de tipo LoaderCallbacks para comunicar los eventos que ocurren
en el loader. Esta interfaz suele ser la propia clase (actividad o fragmento), que implementa
LoaderCallbacks (por lo que se puede pasar this como tercer argumento) 16.
Interfaz LoaderCallbacks
Como ya se ha mencionado, esta interfaz será en general implementada por la propia actividad
o fragmento y se utilizarán sus métodos para interactuar indirectamente con los loaders
instanciados, a través de LoaderManager.
15
Se podrían implementar subclases de Loader o AsyncTaskLoader para cargar datos de otras fuentes distintas a
los ContentProvider.
16
Si se necesita reiniciar el loader, se podrá invocar a getLoaderManager().restartLoader(), método que recibe
los mismos argumentos que initLoader().
Por ejemplo:
mAdaptador.swapCursor(datos);
}
17
Cursor es una interfaz que da acceso de lectura y escritura al conjunto de resultados obtenidos en una consulta
a base de datos.
Por último, cuando el loader sea cerrado, los datos anteriores dejarán de estar disponibles, por
lo que se deberá eliminar toda referencia a los mismos, para liberar memoria. Para ello, se
implementará el método onLoaderReset() de la siguiente forma:
mAdaptador.swapCursor(null);
}
LoaderManager
LoaderCallbacks
Interfaz cuyos tres métodos deberán ser implementados, puesto que son invocados por el
LoaderManager en las diferentes etapas del ciclo de vida de cada loader.
CursorLoader
El CursorLoader debe ser instanciado con toda la información necesaria para realizar la
consulta, bien a través del constructor o bien a través de los métodos accesores tipo “set”. Se
deberá informar el contexto de la aplicación, la Uri, la selección, los argumentos de la
selección, el orden y la proyección (columnas que se mostrarán).
Cursor
Interfaz que provee acceso aleatorio (o no ordenado a priori) de lectura y escritura a los datos
obtenidos en una consulta a base de datos.
ContentResolver
Clase abstracta que permite el acceso, por parte de las aplicaciones, al modelo de contenido
de Android.
MediaStore
Clase que contiene todos los metadatos de todo el contenido multimedia almacenado tanto en
la memoria interna del dispositivo como en la externa.
ContentProvider
Los proveedores de contenido son uno de los pilares fundamentales de las aplicaciones
Android, ya que proveen de contenido a las mismas. Encapsulan los datos que les son
solicitados y los hacen disponibles a través del interfaz ContentResolver. Solo será necesario
implementar un proveedor de contenido propio si se necesita compartir información entre
varias aplicaciones. En cambio, para almacenar datos que no se quieran compartir con otras
aplicaciones, bastará con usar directamente una base de datos por medio de
SQLiteDatabase.
Cuando se realiza una consulta a través del ContentResolver el sistema examina la autoridad
contenida en la Uri (por ejemplo, content://media) y le pasa la consulta al proveedor de
contenido que esté registrado para dicha autoridad. El resto de la Uri se podrá interpretar de
múltiples formas.