Android ¿cómo espero hasta que un servicio esté realmente conectado?

Tengo una actividad que llama a un servicio definido en IDownloaderService.aidl:

public class Downloader extends Activity { IDownloaderService downloader = null; // ... 

En Downloader.onCreate (Bundle) intenté bindService

 Intent serviceIntent = new Intent(this, DownloaderService.class); if (bindService(serviceIntent, sc, BIND_AUTO_CREATE)) { // ... 

Y dentro del objeto ServiceConnection sc hice esto

 public void onServiceConnected(ComponentName name, IBinder service) { Log.w("XXX", "onServiceConnected"); downloader = IDownloaderService.Stub.asInterface(service); // ... 

Al agregar todo tipo de Log.xx encontré que el código después de if (bindService (…)) realmente va ANTES de que ServiceConnection.onServiceConnected se está llamando – es decir, cuando el descargador todavía es nulo – lo que me mete en problemas. Todas las muestras de ApiDemos evitan este problema de sincronización llamando solamente a los servicios cuando son activados por las acciones del usuario. ¿Pero qué debo hacer para usar correctamente este servicio después de que bindService tenga éxito? ¿Cómo puedo esperar a que ServiceConnection.onServiceConnected se llame confiablemente?

Otra pregunta relacionada. ¿Están todos los manejadores de eventos: Activity.onCreate, View.onClickListener.onClick, ServiceConnection.onServiceConnected, etc. realmente llamado en el mismo hilo (mencionado en el documento como el "hilo principal")? ¿Hay intercalaciones entre ellos, o Android iba a programar todos los eventos vienen a ser manejado uno por uno? O, ¿Cuándo exactamente se va a llamar a ServiceConnection.onServiceConnected? Al finalizar Activity.onCreate o alguna vez cuando A.oC todavía está funcionando?

¿Cómo puedo esperar a que ServiceConnection.onServiceConnected se llame confiablemente?

No lo haces. Usted sale fuera de onCreate() (o dondequiera que usted es obligatorio) y usted pone "necesita la conexión establecida" código en onServiceConnected() .

¿Están todos los manejadores de eventos: Activity.onCreate, cualquier View.onClickListener.onClick, ServiceConnection.onServiceConnected, etc. realmente llamado en el mismo subproceso

Sí.

¿Cuándo exactamente se va a llamar a ServiceConnection.onServiceConnected? Al finalizar Activity.onCreate o alguna vez cuando A.oC todavía está funcionando?

Su solicitud de enlace probablemente no va a empezar hasta después de dejar onCreate() . Por lo tanto, onServiceConnected() se llamará en algún momento después de dejar onCreate() .

Yo tuve el mismo problema. No quería poner mi código dependiente dependiente del servicio en onServiceConnected , sin embargo, porque quería enlazar / desvincular con onStart y onStop, pero no quería que el código se ejecute de nuevo cada vez que la actividad volvía al frente. Sólo quería que se ejecutara cuando la actividad se creó por primera vez.

Finalmente conseguí sobre mi visión del túnel del onStart() y utilicé un Boolean para indicar si éste era el primer funcionamiento de onServiceConnected o no. De esa manera, puedo unbindService en onStop y bindService de nuevo en onStart sin ejecutar todas las cosas de inicio cada vez.

Terminé con algo como esto:

1) para dar el material auxiliar un cierto alcance, he creado una clase interna. Al menos, los elementos internos feos están separados del resto del código. Necesitaba un servicio remoto haciendo algo , por lo tanto la palabra Something en el nombre de la clase

 private RemoteSomethingHelper mRemoteSomethingHelper = new RemoteSomethingHelper(); class RemoteSomethingHelper { //... } 

2) hay dos cosas necesarias para invocar un método de servicio remoto: el IBinder y el código para ejecutar. Puesto que no sabemos cuál se conoce primero, los almacenamos:

 private ISomethingService mISomethingService; private Runnable mActionRunnable; 

Cada vez que escribimos en uno de estos archivos, invocamos _startActionIfPossible() :

  private void _startActionIfPossible() { if (mActionRunnable != null && mISomethingService != null) { mActionRunnable.run(); mActionRunnable = null; } } private void performAction(Runnable r) { mActionRunnable = r; _startActionIfPossible(); } 

Esto, por supuesto, supone que el Runnable tiene acceso a mISomethingService, pero esto es cierto para runnables creados dentro de los métodos de la clase RemoteSomethingHelper .

Es realmente bueno que las devoluciones de llamada de ServiceConnection se llamen en el hilo de interfaz de usuario : si vamos a invocar los métodos de servicio del hilo principal, no necesitamos preocuparnos por la sincronización.

ISomethingService es, por supuesto, definido a través de AIDL.

3) En lugar de simplemente pasar argumentos a los métodos, creamos un Runnable que invocará el método con estos argumentos más adelante, cuando la invocación es posible:

  private boolean mServiceBound; void startSomething(final String arg1) { // ... starting the service ... final String arg2 = ...; performAction(new Runnable() { @Override public void run() { try { // arg1 and arg2 must be final! mISomethingService.startSomething(arg1, arg2); } catch (RemoteException e) { e.printStackTrace(); } } }); } 

4) finalmente, obtenemos:

 private RemoteSomethingHelper mRemoteSomethingHelper = new RemoteSomethingHelper(); class RemoteSomethingHelper { private ISomethingService mISomethingService; private Runnable mActionRunnable; private boolean mServiceBound; private void _startActionIfPossible() { if (mActionRunnable != null && mISomethingService != null) { mActionRunnable.run(); mActionRunnable = null; } } private ServiceConnection mServiceConnection = new ServiceConnection() { // the methods on this class are called from the main thread of your process. @Override public void onServiceDisconnected(ComponentName name) { mISomethingService = null; } @Override public void onServiceConnected(ComponentName name, IBinder service) { mISomethingService = ISomethingService.Stub.asInterface(service); _startActionIfPossible(); } } private void performAction(Runnable r) { mActionRunnable = r; _startActionIfPossible(); } public void startSomething(final String arg1) { Intent intent = new Intent(context.getApplicationContext(),SomethingService.class); if (!mServiceBound) { mServiceBound = context.getApplicationContext().bindService(intent, mServiceConnection, 0); } ComponentName cn = context.getApplicationContext().startService(intent); final String arg2 = ...; performAction(new Runnable() { @Override public void run() { try { mISomethingService.startSomething(arg1, arg2); } catch (RemoteException e) { e.printStackTrace(); } } }); } } 

context es un campo en mi clase; En una Actividad, se puede definir como Context context=this;

No necesitaba acciones de cola; Si lo haces, puedes implementarlo.

Es probable que necesite un resultado de devolución de llamada en startSomething (); Lo hice, pero esto no se muestra en este código.

Hice algo similar antes, el único diferente es que no era obligatorio para el servicio, pero sólo empezando.

Me gustaría transmitir una intención del servicio para notificar a la persona que llama / actividad sobre el que se inicia.

* La idea básica es lo mismo con @ 18446744073709551615, pero voy a compartir mi código también.

Como respuesta de la pregunta principal,

¿Pero qué debo hacer para usar correctamente este servicio después de que bindService tenga éxito?

[Expectativa original (pero no trabajo)]

Espere hasta que el servicio se conecte como a continuación

  @Override protected void onStart() { bindService(service, mWebServiceConnection, BIND_AUTO_CREATE); synchronized (mLock) { mLock.wait(40000); } // rest of the code continues here, which uses service stub interface // ... } 

No funcionará porque tanto bindService() en onCreate()/onStart() y onServiceConnected() se llama en el mismo subproceso principal . onServiceConnected() nunca se llama antes de que termine la espera.

[Solución alternativa]

En lugar de "esperar", defina su propio Runnable para que se llame después de Service Connected y ejecute este runnable después de que el servicio esté conectado.

Implementar la clase personalizada de ServiceConnection como sigue.

 public class MyServiceConnection implements ServiceConnection { private static final String TAG = MyServiceConnection.class.getSimpleName(); private Context mContext = null; private IMyService mMyService = null; private ArrayList<Runnable> runnableArrayList; private Boolean isConnected = false; public MyServiceConnection(Context context) { mContext = context; runnableArrayList = new ArrayList<>(); } public IMyService getInterface() { return mMyService; } @Override public void onServiceConnected(ComponentName name, IBinder service) { Log.v(TAG, "Connected Service: " + name); mMyService = MyService.Stub.asInterface(service); isConnected = true; /* Execute runnables after Service connected */ for (Runnable action : runnableArrayList) { action.run(); } runnableArrayList.clear(); } @Override public void onServiceDisconnected(ComponentName name) { try { mMyService = null; mContext.unbindService(this); isConnected = false; Log.v(TAG, "Disconnected Service: " + name); } catch(Exception e) { Log.e(TAG, e.toString()); } } public void executeAfterServiceConnected(Runnable action) { Log.v(TAG, "executeAfterServiceConnected"); if(isConnected) { Log.v(TAG, "Service already connected, execute now"); action.run(); } else { // this action will be executed at the end of onServiceConnected method Log.v(TAG, "Service not connected yet, execute later"); runnableArrayList.add(action); } } } 

Y luego utilizarlo de la siguiente manera (en su clase de actividad o etc),

 private MyServiceConnection myServiceConnection = null; @Override protected void onStart() { Log.d(TAG, "onStart"); super.onStart(); Intent serviceIntent = new Intent(getApplicationContext(), MyService.class); startService(serviceIntent); myServiceConnection = new MyServiceConnection(getApplicationContext()); bindService(serviceIntent, myServiceConnection, BIND_AUTO_CREATE); // Instead of "wait" here, create callback which will be called after service is connected myServiceConnection.executeAfterServiceConnected(new Runnable() { @Override public void run() { // Rest of the code comes here. // This runnable will be executed after service connected, so we can use service stub interface IMyService myService = myServiceConnection.getInterface(); // ... } }); } 

Funcionó para mí. Pero puede haber una mejor manera.

Me imaginé que estas soluciones sólo valen la pena el esfuerzo y la espera sólo si los servicios vinculados se ejecutan en un proceso diferente al proceso principal de la aplicación.

Para acceder a datos y métodos en el mismo proceso (o aplicación), acabé implementando clases singleton. Si las clases necesitan un contexto para algunos métodos, pierdo el contexto de la aplicación a las clases singleton. Hay, por supuesto, una mala consecuencia de ella, ya que rompe el "instante de ejecución". Pero eso es un mejor compromiso global, creo.

  • Actualizar Android ListView en Widget
  • El servicio de Android solo se inicia en modo de depuración
  • Google Cloud Messaging - GCM - SERVICE_NOT_AVAILABLE
  • Cómo llamar al método en servicio de otra actividad
  • Eventos de apagado en Android
  • Una forma más eficiente de actualizar la interfaz de usuario del servicio que las intenciones?
  • ¿Puedo iniciarService desde la aplicación # onCreate ()?
  • Gran uso de memoria en las notificaciones
  • Obtener ubicación GPS a través de un servicio en Android
  • Ejemplo de servicio bind / unbind (android)
  • android - utiliza la cámara desde el servicio de fondo
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.