¿Singletons vs. Contexto de aplicación en Android?

362

Recordando esta publicación que enumera varios problemas de uso de singletons y después de haber visto varios ejemplos de aplicaciones de Android que usan el patrón de singleton, me pregunto si es una buena idea usar Singletons en lugar de instancias individuales compartidas a través del estado global de la aplicación (subclasificar android.os.Aplicación y obtenerla a través de context.getApplication ()).

¿Qué ventajas / inconvenientes tendrían ambos mecanismos?

Para ser honesto, espero la misma respuesta en este patrón Singleton post con aplicación web, ¡no es una buena idea! pero aplicado a Android. ¿Estoy en lo correcto? ¿Qué es diferente en DalvikVM de lo contrario?

EDITAR: Me gustaría tener opiniones sobre varios aspectos involucrados:

  • Sincronización
  • Reusabilidad
  • Pruebas
mschonaker
fuente

Respuestas:

295

Estoy muy en desacuerdo con la respuesta de Dianne Hackborn. Estamos eliminando poco a poco todos los singletons de nuestro proyecto a favor de objetos livianos con ámbito de tarea que pueden recrearse fácilmente cuando realmente los necesita.

Los singletons son una pesadilla para las pruebas y, si se inicializan perezosamente, introducirán "indeterminismo de estado" con efectos secundarios sutiles (que pueden surgir repentinamente cuando se trasladan llamadas getInstance()de un alcance a otro). La visibilidad ha sido mencionada como otro problema, y ​​dado que los singletons implican acceso "global" (= aleatorio) al estado compartido, pueden surgir errores sutiles cuando no se sincronizan adecuadamente en aplicaciones concurrentes.

Lo considero un antipatrón, es un mal estilo orientado a objetos que esencialmente equivale a mantener el estado global.

Para volver a su pregunta:

Aunque el contexto de la aplicación puede considerarse un singleton en sí mismo, está administrado por el marco y tiene un ciclo de vida , un alcance y una ruta de acceso bien definidos . Por lo tanto, creo que si necesita administrar el estado global de la aplicación, debería ir aquí, en ningún otro lado. Para cualquier otra cosa, reconsidere si realmente necesita un objeto singleton, o si también sería posible reescribir su clase singleton para crear instancias de objetos pequeños y de corta duración que realicen la tarea en cuestión.

Matías
fuente
131
Si está recomendando la Aplicación, está recomendando el uso de singletons. Honestamente, no hay forma de evitarlo. La aplicación es un singleton, con una semántica más desagradable. No entraré en argumentos religiosos sobre los singletons, algo que nunca deberías usar. Prefiero ser práctico: hay lugares donde son una buena opción para mantener el estado por proceso y pueden simplificar las cosas al hacerlo, y también puede usarlos en la situación incorrecta y pegarse un tiro en el pie.
Hackbod
18
Es cierto, y mencioné que "el contexto de la aplicación puede considerarse un singleton en sí mismo". La diferencia es que con la instancia de la aplicación, dispararse en el pie es mucho más difícil, ya que su ciclo de vida es manejado por el marco. Los marcos DI como Guice, Hivemind o Spring también hacen uso de singletons, pero ese es un detalle de implementación que al desarrollador no debería importarle. Creo que generalmente es más seguro confiar en que la semántica del marco se implemente correctamente en lugar de su propio código. ¡Sí, lo admito! :-)
Matthias
93
Honestamente, no evita que te dispares en el pie más de lo que lo hace un singleton. Es un poco confuso, pero no hay un ciclo de vida de la aplicación. Se crea cuando se inicia la aplicación (antes de que se instancia cualquiera de sus componentes) y se llama a onCreate () en ese punto, y ... eso es todo. Se sienta allí y vive para siempre, hasta que se termina el proceso. Como un singleton. :)
hackbod
30
Una cosa que puede ser confusa es que Android está muy diseñado para ejecutar aplicaciones en procesos y administrar el ciclo de vida de esos procesos. Entonces, en Android, los singletons son una forma muy natural de aprovechar la gestión de ese proceso: si desea almacenar en caché algo en su proceso hasta que la plataforma necesite recuperar la memoria del proceso para otra cosa, colocar ese estado en un singleton hará eso.
Hackbod
77
Está bien lo suficientemente justo. Solo puedo decir que no he mirado atrás desde que nos alejamos de los singletons autogestionados. Ahora estamos optando por una solución ligera de estilo DI, donde mantenemos un singleton de fábrica (RootFactory), que a su vez es administrado por la instancia de la aplicación (es un delegado si lo desea). Este singleton gestiona dependencias comunes de las que dependen todos los componentes de la aplicación, pero la instanciación se gestiona en una única ubicación: la clase de aplicación. Si bien con ese enfoque queda un singleton, se limita a la clase Aplicación, por lo que ningún otro módulo de código conoce ese "detalle".
Matthias
231

Recomiendo mucho los singletons. Si tiene un singleton que necesita un contexto, tenga:

MySingleton.getInstance(Context c) {
    //
    // ... needing to create ...
    sInstance = new MySingleton(c.getApplicationContext());
}

Prefiero los singletons sobre la aplicación porque ayuda a mantener una aplicación mucho más organizada y modular; en lugar de tener un lugar donde se deba mantener todo su estado global en la aplicación, cada pieza por separado puede cuidarse sola. También es bueno el hecho de que los singletons se inicializan perezosamente (a petición) en lugar de guiarlo por el camino de hacer toda la inicialización por adelantado en Application.onCreate ().

No hay nada intrínsecamente malo con el uso de singletons. Solo úselos correctamente, cuando tenga sentido. El marco de Android en realidad tiene muchos de ellos, para mantener cachés por proceso de recursos cargados y otras cosas similares.

Además, para aplicaciones simples, el subprocesamiento múltiple no se convierte en un problema con los singletons, ya que, por diseño, todas las devoluciones de llamada estándar a la aplicación se envían en el hilo principal del proceso, por lo que no tendrá lugar el subproceso múltiple a menos que lo introduzca explícitamente a través de hilos o implícitamente mediante la publicación de un proveedor de contenido o servicio de IBinder en otros procesos.

Solo sea reflexivo sobre lo que está haciendo. :)

hackbod
fuente
1
Si algún tiempo después quiero escuchar un evento externo, o compartir en un IBinder (supongo que no sería una aplicación simple), tendría que agregar doble bloqueo, sincronización, volátil, ¿verdad? Gracias por su respuesta :)
mschonaker
2
No es para un evento externo: BroadcastReceiver.onReceive () también se llama en el hilo principal.
hackbod
2
Bueno. ¿Me indicaría algún material de lectura (preferiría el código) donde pueda ver el mecanismo principal de envío de hilos? Creo que eso me aclarará varios conceptos a la vez. Gracias por adelantado.
mschonaker
2
Este es el código de envío principal del lado de la aplicación: android.git.kernel.org/?p=platform/frameworks/…
hackbod
8
No hay nada intrínsecamente malo con el uso de singletons. Solo úselos correctamente, cuando tenga sentido. .. claro, precisamente, bien dicho. El marco de Android en realidad tiene muchos de ellos, para mantener cachés por proceso de recursos cargados y otras cosas similares. Exactamente como dices. De tus amigos en el mundo de iOS, "todo es un singleton" en iOS ... nada podría ser más natural en dispositivos físicos que un concepto de singleton: el gps, el reloj, los giroscopios, etc. como solteros? Así que sí.
Fattie
22

De: Desarrollador> referencia - Solicitud

Normalmente no hay necesidad de subclase Aplicación. En la mayoría de las situaciones, los singletons estáticos pueden proporcionar la misma funcionalidad de una manera más modular. Si su singleton necesita un contexto global (por ejemplo, para registrar receptores de difusión), la función para recuperarlo puede recibir un Context que internamente usa Context.getApplicationContext () cuando construye el singleton por primera vez.

Somatik
fuente
1
Y si escribe una interfaz para el singleton, dejando getInstance no estático, incluso puede hacer que el constructor predeterminado de la clase singleton-using inyecte el singleton de producción a través de un constructor no predeterminado, que también es el constructor que usa para crear el clase singleton-using en sus pruebas unitarias.
android.weasel
11

La aplicación no es la misma que Singleton. Las razones son:

  1. El método de la aplicación (como onCreate) se llama en el hilo ui;
  2. El método de singleton se puede llamar en cualquier hilo;
  3. En el método "onCreate" de la aplicación, puede crear una instancia de Handler;
  4. Si el singleton se ejecuta en un subproceso sin interfaz de usuario, no podría crear una instancia de Handler;
  5. La aplicación tiene la capacidad de administrar el ciclo de vida de las actividades en la aplicación. Tiene el método "registerActivityLifecycleCallbacks". Pero los singletons no tienen la capacidad.
sunhang
fuente
1
Nota: puede crear una instancia de Handler en cualquier hilo. del documento: "Cuando creas un nuevo controlador, está vinculado a la cola de hilos / mensajes del hilo que lo está creando"
Christ
1
@Christ ¡Gracias! Hace un momento aprendí el "mecanismo del Looper". Si crea un controlador de instancia en el subproceso sin interfaz de usuario sin el código 'Looper.prepare ()', el sistema informará el error "java.lang.RuntimeException: Can no cree un manejador dentro del hilo que no haya llamado Looper.prepare () ".
sunhang
11

Tuve el mismo problema: Singleton o hacer una subclase android.os.Application?

Primero probé con Singleton pero mi aplicación en algún momento hace una llamada al navegador

Intent myIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.google.com"));

y el problema es que, si el teléfono no tiene suficiente memoria, la mayoría de sus clases (incluso Singletons) se limpian para obtener algo de memoria, por lo que, al regresar del navegador a mi aplicación, se bloquea cada vez.

Solución: coloque los datos necesarios dentro de una subclase de la clase Aplicación.

JoséMi
fuente
1
A menudo me encontré con publicaciones donde las personas afirman que esto puede ocurrir. Por lo tanto, simplemente adjunto objetos a la aplicación como singletons con carga lenta, etc., solo para asegurarme de que el ciclo de vida esté documentado y sea conocido. Solo asegúrese de no guardar cientos de imágenes en el objeto de su aplicación, ya que entiendo que no se borrará de la memoria si su aplicación está en segundo plano y todas las actividades se destruyen para liberar memoria para otros procesos.
Janusz
Bueno, la carga lenta de Singleton después del reinicio de la aplicación no es la forma correcta de permitir que los objetos sean barridos por el GC. Las referencias débiles son, ¿verdad?
mschonaker
15
De Verdad? Dalvik descarga clases y pierde el estado del programa? ¿Estás seguro de que no es que se trata de recolectar basura el tipo de objetos relacionados con la Actividad de ciclo de vida limitado que no deberías poner en singletons en primer lugar? ¡Tienes que dar ejemplos clrar para un reclamo tan extraordinario!
android.weasel
1
A menos que haya habido cambios que desconozco, Dalvik no descarga clases. Siempre. El comportamiento que están viendo es que su proceso se está ejecutando en segundo plano para dejar espacio al navegador. Probablemente estaban inicializando la variable en su actividad "principal", que tal vez no se haya creado en el nuevo proceso al regresar del navegador.
Groxx
5

Considere ambos al mismo tiempo:

  • tener objetos singleton como instancias estáticas dentro de las clases.
  • tener una clase común (Context) que devuelve las instancias singleton para todos los objetos singelton en su aplicación, lo que tiene la ventaja de que los nombres de los métodos en Context serán significativos, por ejemplo: context.getLoggedinUser () en lugar de User.getInstance ().

Además, le sugiero que expanda su Contexto para incluir no solo el acceso a objetos únicos, sino también algunas funcionalidades a las que se debe acceder globalmente, como por ejemplo: context.logOffUser (), context.readSavedData (), etc. Probablemente renombrando el Contexto a La fachada tendría sentido entonces.

adranale
fuente
4

En realidad son lo mismo. Hay una diferencia que puedo ver. Con la clase Aplicación, puede inicializar sus variables en Application.onCreate () y destruirlas en Application.onTerminate (). Con Singleton, debe confiar en VM inicializando y destruyendo estadísticas.

Fedor
fuente
16
los documentos de onTerminate dicen que solo lo llama el emulador. En dispositivos, ese método probablemente no se llamará. developer.android.com/reference/android/app/…
danb
3

Mis 2 centavos:

Noté que algunos campos únicos / estáticos se restablecieron cuando se destruyó mi actividad. Me di cuenta de esto en algunos dispositivos de gama baja 2.3.

Mi caso era muy simple: solo tengo un archivo privado "init_done" y un método estático "init" al que llamé desde activity.onCreate (). Noté que el método init se estaba volviendo a ejecutar en alguna recreación de la actividad.

Si bien no puedo probar mi afirmación, puede estar relacionada con CUANDO el singleton / clase se creó / usó primero. Cuando la actividad se destruye / recicla, parece que todas las clases a las que solo se refiere esta actividad también se reciclan.

Moví mi instancia de singleton a una subclase de Aplicación. Los accedo desde la instancia de la aplicación. y, desde entonces, no notó el problema nuevamente.

Espero que esto pueda ayudar a alguien.

Cristo
fuente
3

De la boca del caballo proverbial ...

Al desarrollar su aplicación, es posible que necesite compartir datos, contexto o servicios a nivel mundial a través de su aplicación. Por ejemplo, si su aplicación tiene datos de sesión, como el usuario actualmente conectado, es probable que desee exponer esta información. En Android, el patrón para resolver este problema es hacer que su instancia de android.app.Application posea todos los datos globales y luego trate su instancia de Aplicación como un singleton con accesos estáticos a los diversos datos y servicios.

Al escribir una aplicación de Android, se garantiza que solo tendrá una instancia de la clase android.app.Application, por lo que es seguro (y recomendado por el equipo de Google Android) tratarlo como un singleton. Es decir, puede agregar de manera segura un método estático getInstance () a la implementación de su aplicación. Al igual que:

public class AndroidApplication extends Application {

    private static AndroidApplication sInstance;

    public static AndroidApplication getInstance(){
        return sInstance;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        sInstance = this;
    }
}
RMcGuigan
fuente
2

Mi actividad llama a terminar () (que no hace que termine de inmediato, pero lo hará eventualmente) y llama a Google Street Viewer. Cuando lo depuro en Eclipse, mi conexión con la aplicación se interrumpe cuando se llama a Street Viewer, lo que entiendo como la aplicación (completa) que se está cerrando, supuestamente para liberar memoria (ya que una sola actividad finalizada no debería causar este comportamiento) . Sin embargo, puedo guardar el estado en un paquete a través de onSaveInstanceState () y restaurarlo en el método onCreate () de la próxima actividad en la pila. Ya sea mediante el uso de un Singleton estático o la Aplicación de subclases, enfrento el estado de cierre y pérdida de la aplicación (a menos que la guarde en un Paquete). Entonces, desde mi experiencia, son lo mismo con respecto a la preservación del estado. Noté que la conexión se pierde en Android 4.1.2 y 4.2.2 pero no en 4.0.7 o 3.2.4,

Piovezan
fuente
"Noté que la conexión se pierde en Android 4.1.2 y 4.2.2 pero no en 4.0.7 o 3.2.4, lo que, en mi opinión, sugiere que el mecanismo de recuperación de memoria ha cambiado en algún momento". ..... Creo que sus dispositivos no tienen la misma cantidad de memoria disponible, ni la misma aplicación instalada. y, por lo tanto, su conclusión puede ser incorrecta
Cristo
@Christ: Sí, debes estar en lo cierto. Sería extraño si el mecanismo de recuperación de memoria cambiara entre versiones. Probablemente el uso diferente de la memoria causó los distintos comportamientos.
Piovezan