Vincular al servicio desde el nuevo Contexto para cambios de configuración o vincular desde el contexto de la aplicación?

Estoy tratando de averiguar si el servicio enlazado es apropiado para hacer el trabajo de fondo en mi aplicación. Los requisitos son que varios componentes de la aplicación pueden hacer que las solicitudes web a través de ella tengan una prioridad variable. (Por lo tanto, el servicio debe mantener algún tipo de cola y ser capaz de cancelar sus solicitudes en curso para otros de mayor prioridad). Me gustaría que el servicio sea relativamente discreto para el usuario de tal manera que no lo encuentren en ejecución después de que se terminen con la aplicación – si quiero hacer algo más importante que continúa mientras la aplicación está cerrada puedo usar startForeground ) Para empujar una notificación durante el proceso.

Solución el primero: enlazar de la actividad

Por lo tanto, para un componente de aplicación dado debe ser capaz de enlazar con el servicio para hacer el trabajo. Pero parece que existe un problema bien conocido de que si una actividad realiza la vinculación, la vinculación se perderá durante el cambio de configuración (rotación) a medida que se cierre la actividad.

Por lo tanto, estaba pensando que podría utilizar otro contexto que crear ( new Context() ) y enlazar desde que el servicio, a continuación, utilizar un fragmento no UI para mantener este contexto a través de los cambios de configuración hasta que creo que he terminado con él . Podría hacer esto sólo durante el cambio de configuración o como una alternativa permanente a la vinculación de la actividad. (Debo señalar probablemente que esto es una manera estándar y recomendada de mantener instancias a través de cambios de configuración)

Solución numero 2:

La alternativa principal que veo es que puedo usar el contexto de la aplicación para hacer el enlace – pero ¿podría esto persistir demasiado tiempo? Y / o podría haber alguna relación cíclica entre el contexto de la aplicación y el servicio, evitando así que el servicio y el contexto de la aplicación se destruya?

Preguntas:

Así que la pregunta que estoy tratando de responder a mí mismo es: ¿debo usar el primer método (actividades con contextos temporales)? ¿O el segundo (sólo enlazar el servicio al contexto de la aplicación)?

¿Tengo razón al pensar que el contexto de la aplicación puede vincularse al servicio varias veces y luego desvincularlo del mismo número de veces? (Es decir, que puede tener múltiples vínculos válidos por contexto)?

¿Podría utilizar mi propio contexto ( new Context() ) en la primera solución los problemas?

Editar

Más información: https://groups.google.com/forum/#!topic/android-developers/Nb58dOQ8Xfw

También parece que será difícil "crear" un contexto arbitrariamente, por lo que una combinación de solución 1 y 2 parece apropiada donde la conexión de servicio se mantiene a través de las configuraciones cambian pero la vinculación es al contexto de la aplicación. Aún me preocupa la posibilidad de desenlazar dos veces desde el contexto de la aplicación. Mantener la cuenta de los enlaces me parece innecesario – ¿alguien puede confirmar / negar que los enlaces son por conexión y no por contexto?

Así que después de hacer un poco de excavación creo que he llegado con una solución (todavía) no probado.

En primer lugar, basándome en la sugerencia de Diane aquí: https://groups.google.com/forum/#!topic/android-developers/Nb58dOQ8Xfw Debería ser vinculante para el contexto de la aplicación – por lo que mi problema de perder el contexto se ha ido – puedo Mantener mi ServiceConnection a través de la configuración cambiada con un fragmento no UI – genial. Luego, cuando haya terminado, puedo usar el contexto de la aplicación para devolver la conexión de servicio y desvincularla. No debería recibir ninguna advertencia de conexión de servicio con fugas. (Debo señalar probablemente que esto es una manera estándar y recomendada de mantener instancias a través de cambios de configuración)

El último punto crucial de este problema era que no estaba seguro de si podía enlazar varias veces desde el mismo contexto – las documentaciones sobre los enlaces implican que hay cierta dependencia entre la vinculación y el contexto del ciclo de vida y por lo que estaba preocupado tendría que hacer mi propio Forma de recuento de referencia. Eché un vistazo al código fuente y terminé aquí: http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.4.2_r1/android/app/LoadedApk .java # LoadedApk.forgetServiceDispatcher% 28android.content.Context% 2Candroid.content.ServiceConnection% 29

Crucialmente, estas líneas:

 sd = map.get(c); if (sd != null) { map.remove(c); sd.doForget(); if (map.size() == 0) { mServices.remove(context); } 

Revela que el map está siendo usado para el conteo de referencia que estaba preocupado.

Así que la toma de casa es la siguiente:

  • Servicio enlazado funcionará bien con el contexto de la aplicación y deberíamos hacer esto para evitar que se escape una conexión de servicio de una actividad a otra durante un cambio de configuración
  • Puedo mantener mi conexión de servicio en un fragmento que no sea de IU de forma segura y usarlo para desvincularme cuando haya terminado

Voy a probar y publicar algún código probado pronto.

Solución UPDATE y probada: he hecho un código para probar esto y publicado aquí: https://github.com/samskiter/BoundServiceTest

Parece que funciona bastante bien y el fragmento no-ui (fragmento de datos) actúa como un buen oyente proxy durante los cambios de rotación para capturar los resultados del servicio (la intención de los oyentes es vincular estrechamente las solicitudes a la interfaz de usuario para garantizar Obviamente, cualquier cambio de modelo puede propagarse a la interfaz de usuario a través de observadores.)

Edit: Pensé que debería responder explícitamente a las preguntas en el OP …

  • ¿Debo usar el primer método (actividades con contextos temporales)? ¿O el segundo (sólo enlazar el servicio al contexto de la aplicación)? El segundo

  • ¿Tengo razón al pensar que el contexto de la aplicación puede vincularse al servicio varias veces y luego desvincularlo del mismo número de veces? (Es decir, que puede tener múltiples vínculos válidos por contexto)?

  • ¿Podría utilizar mi propio contexto (nuevo Context ()) en la primera solución los problemas? Esto no es posible

Un resumen final:

Este patrón debe ser bastante potente – puedo dar prioridad a las IO de red (u otras tareas) que provienen de una variedad de fuentes en mi aplicación. Podría tener una actividad de primer plano haciendo un pequeño io el usuario ha pedido, al mismo tiempo que podría haber pateado de un servicio de primer plano para sincronizar todos los datos de mis usuarios. Tanto el servicio de primer plano como la actividad pueden enlazarse al mismo servicio de red para obtener sus solicitudes.

Todo esto mientras se asegura de que el servicio sólo vive exactamente lo que necesita, es decir, juega muy bien con Android.

Estoy emocionado de conseguir esto en una aplicación pronto.

ACTUALIZACIÓN: He intentado escribir esto y dar un poco de contexto al problema más amplio de trabajo de fondo en una entrada de blog aquí: http://blog.airsource.co.uk/2014/09/10/android-bound-services /

¿No puedes simplemente seleccionar configuraciones que te gustaría manejar con el atributo configChanges en tu manifiesto y hacer los cambios de orientación en la interfaz de usuario manualmente? En este caso sólo es necesario vincular al servicio en onCreate y luego unBind in onDestroy .

O tal vez intentar algo como esto (no he hecho la comprobación de errores adecuada):

  

     Class MyServiceConnection implementa ServiceConnection, Parcelable {
                 Public static final Parcelable.Creator CREATOR
                 = Nuevo Parcelable.Creator () {
                     Public MyServiceConnection createFromParcel (Paquete en) {
                         Return nuevo MyServiceConnection (in);
                     }

                     Public MyServiceConnection [] newArray (tamaño int) {
                         Return nuevo MyServiceConnection [size];
                     }
                 };

                 @Anular
                 Public int describeContents () {
                     Return 0;
                 }

                 @Anular
                 Public void writeToParcel (Parcel dest, int flags) {

                 }

                 @Anular
                 Public void onServiceConnected (Nombre de ComponentName, servicio de IBinder) {

                 }

                 @Anular
                 Public void onServiceDisconnected (Nombre de ComponentName) {

                 }
             }
             MyServiceConnection myServiceConnection;
             Boolean configChange = false;

             Protected void onCreate (Paquete savedInstanceState) {
                 Super.onCreate (savedInstanceState);
                 SetContentView (R.layout.activity_main);
                 If (savedInstanceState! = Null) {
                     MyServiceConnection = savedInstanceState.getParcelable ("serviceConnection");
                 } Else {
                     MyServiceConnection = nuevo MyServiceConnection ();
                 }

             }
             @Anular
             Protected void onSaveInstanceState (Bundle outState) {
                 Super.onSaveInstanceState (outState);
                 If (myServiceConnection! = Null) {
                     OutState.putParcelable ("serviceConnection", myServiceConnection);
                     ConfigChange = true;
                 }
             }
             @Anular
             Protected void onDestroy () {
                 Super.onDestroy ();
                 If (! ConfigChange && myServiceConnection! = Null) {
                     UnbindService (myServiceConnection);
                 }
             }
         }

Hay una manera mucho más fácil manejar esta situación llamada un IntentService que usted puede leer más sobre aquí . Desde el sitio de Android:

"La clase IntentService proporciona una estructura sencilla para ejecutar una operación en un único subproceso de fondo, lo que le permite manejar las operaciones largas sin afectar la capacidad de respuesta de la interfaz de usuario. Sigue funcionando en circunstancias que cerrarían un AsyncTask "

En lugar de vincular el servicio a su actividad, puede iniciar acciones largas en un subproceso de fondo simplemente utilizando una intención que inicie su IntentService

 public class RSSPullService extends IntentService { @Override protected void onHandleIntent(Intent workIntent) { // Gets data from the incoming Intent String dataString = workIntent.getDataString(); ... // Do work here, based on the contents of dataString ... } } 

Este es un ejemplo tomado de los documentos Android. Usted enviará una intención con los datos pertinentes a continuación, manejar los datos dentro del servicio para hacer lo que desea. Por ejemplo, podría agregar una bandera de prioridad a su intención para que su servicio conozca qué solicitudes vienen antes que otras.

El beneficio de un servicio de intención es que se ejecuta en un hilo de fondo y no está vinculado al ciclo de vida de la actividad de inicio. Esto significa que los cambios de configuración no deberían tener un efecto en la ejecución del servicio.

Cuando su servicio está hecho, puede reportar el estado de trabajo usando una emisión local, ya sea enviando los resultados directamente a la actividad (vía receptor de difusión) o posiblemente a través de onNewIntent () (aunque conseguir que funcione es un poco más torpe.

Editar – responder preguntas en el comentario

IntentService es una clase relativamente pequeña. Esto hace que sea fácil de modificar. El código de stock para IntentService llama a stopSelf () y muere cuando se queda sin trabajo para hacer. Esto se puede arreglar fácilmente . Examinando la fuente para el IntentService (ver el enlace anterior) se puede ver que ya funciona bastante fuera de una cola, la recepción de mensajes en onStart () y luego ejecutarlos en el orden recibido como se describe en el comentario. La modificación de onStart () le permitirá implementar una nueva estructura de colas para satisfacer sus necesidades. Utilice el código de ejemplo allí para saber cómo manejar el mensaje entrante y obtenga el Intent luego cree su propia estructura de datos para manejar la prioridad. Usted debe ser capaz de iniciar / detener sus solicitudes web en el IntentService la misma manera que lo haría en un Service . Por lo tanto, al sobreescribir onStart () y onHandleIntent () deberías ser capaz de hacer lo que quieras.

Tuve un problema similar, donde tengo un servicio enlazado utilizado en una actividad. Dentro de la actividad defino un ServiceConnection , mConnection y dentro onServiceConnected establezco un campo de clase, syncService que es una referencia al Servicio:

 private SynchronizerService<Entity> syncService; (...) /** Defines callbacks for service binding, passed to bindService() */ private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName className, IBinder service) { // We've bound to LocalService, cast the IBinder and get // LocalService instance Log.d(debugTag, "on Service Connected"); LocalBinder binder = (LocalBinder) service; //HERE syncService = binder.getService(); //HERE mBound = true; onPostConnect(); } @Override public void onServiceDisconnected(ComponentName arg0) { Log.d(debugTag, "on Service Disconnected"); syncService = null; mBound = false; } }; 

Utilizando este método, siempre que cambiara la orientación, obtendría una NullPointerException al referenciar la variable syncService , a pesar del hecho de que el servicio estuviera en ejecución, y probé varios métodos que nunca funcionaron.

Estaba a punto de implementar la solución propuesta por Sam, usando un fragmento retenido para mantener la variable, pero primero se acordó de intentar una cosa simple: establecer la variable syncService a static .. y la referencia de conexión se mantiene cuando la orientación cambia!

Así que ahora tengo

 private static SynchronizerService<Entity> syncService = null; ... /** Defines callbacks for service binding, passed to bindService() */ private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName className, IBinder service) { // We've bound to LocalService, cast the IBinder and get // LocalService instance Log.d(debugTag, "on Service Connected"); LocalBinder binder = (LocalBinder) service; //HERE if(syncService == null) { Log.d(debugTag, "Initializing service connection"); syncService = binder.getService(); } //HERE mBound = true; onPostConnect(); } @Override public void onServiceDisconnected(ComponentName arg0) { Log.d(debugTag, "on Service Disconnected"); syncService = null; mBound = false; } }; 
  • Kotlin es más difícil de realizar ingeniería inversa que java
  • Android obtiene programaticamente el uso de datos para una aplicación específica, por ejemplo: Uso de datos utilizado en "Facebook"
  • Error El método es demasiado complejo para analizar mediante el algoritmo de flujo de datos
  • Android Cómo activar el hotspot en Android Programáticamente
  • Problemas con la importación de clases de plataformas Android
  • NumberFormatException en número válido Cadena
  • No se puede limpiar el proyecto en Android Studio
  • ¿Cómo configuro la versión Gradle en Android Studio 1.3?
  • Java.text.ParseException: fecha inquebrantable
  • Cómo crear matriz de objetos con For Loop en Android
  • Arquitectura de la aplicación de Android: RxJava
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.