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

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

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

En Downloader.onCreate (Bundle) traté de 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 todos los tipos de Log.xx encontré que el código después de if (bindService (…)) en realidad funciona ANTES de que se llame a ServiceConnection.onServiceConnected, es decir, cuando el descargador aún es nulo, lo que me causa problemas. Todas las muestras en ApiDemos evitan este problema de tiempo al llamar solo a los servicios cuando se activan por las acciones del usuario. Pero, ¿qué debo hacer para utilizar correctamente este servicio después de que bindService tiene éxito? ¿Cómo puedo esperar que ServiceConnection.onServiceConnected se llame de manera confiable?

Otra pregunta relacionada. ¿Están todos los manejadores de eventos: Activity.onCreate, cualquier 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 progtwigría que todos los eventos se procesen uno a uno? O bien, ¿cuándo se llamará realmente ServiceConnection.onServiceConnected? ¿Al completar Activity.onCreate o en algún momento cuando A.oC aún se está ejecutando?

¿Cómo puedo esperar que ServiceConnection.onServiceConnected se llame de manera confiable?

Tu no onCreate() de onCreate() (o donde sea que estés vinculando) y pones el código “necesita la conexión establecida” onServiceConnected() .

Son todos los manejadores de eventos: Activity.onCreate, cualquier View.onClickListener.onClick, ServiceConnection.onServiceConnected, etc. realmente llamado en el mismo hilo

Sí.

¿Cuándo se llamará realmente ServiceConnection.onServiceConnected? ¿Al completar Activity.onCreate o en algún momento cuando A.oC aún se está ejecutando?

Su solicitud de enlace probablemente ni siquiera se iniciará hasta después de salir de onCreate() . Por lo tanto, onServiceConnected() se llamará en algún momento después de dejar onCreate() .

Yo tuve el mismo problema. Sin embargo, no quería poner mi código dependiente del servicio dependiente onServiceConnected , porque quería vincular / desvincularme con onStart y onStop, pero no quería que el código se ejecutara nuevamente cada vez que la actividad volviera al frente. Solo quería que se ejecutara cuando la actividad se creó por primera vez.

Finalmente onStart() mi visión de túnel onStart() y usé un booleano para indicar si esta fue la primera onServiceConnected ejecutó el servicio onServiceConnected o no. De esta forma, puedo desvincularService en onStop y bindService nuevamente en onStart sin ejecutar todas las cosas de inicio cada vez.

Terminé con algo como esto:

1) para dar algo de scope al material auxiliar, creé una clase interna. Al menos, los feos internos están separados del rest del código. Necesitaba un servicio remoto para hacer algo , por lo tanto, la palabra Something en nombre de 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. Como 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, asume que Runnable tiene acceso a mISomethingService, pero esto es cierto para los runnables creados dentro de los métodos de la clase RemoteSomethingHelper .

Es muy bueno que se invoquen las devoluciones de llamada de ServiceConnection en el hilo de UI : si vamos a invocar los métodos de servicio desde el 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 sea 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, puede definirlo como Context context=this;

No necesité acciones de puesta en cola; si lo haces, puedes implementarlo.

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

Hice algo similar antes, el único diferente es que no estaba vinculando al servicio, sino que lo comencé.

Transmitiría una intención del servicio para notificar a la persona que llama / actividad sobre el inicio.

* La idea básica es la misma con @ 18446744073709551615, pero también compartiré mi código.

Como respuesta a la pregunta principal,

Pero, ¿qué debo hacer para utilizar correctamente este servicio después de que bindService tiene é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 bindService() en onCreate()/onStart() y onServiceConnected() se llama con el mismo hilo principal . onServiceConnected() nunca se llama antes de que finalice la espera.

[Solución alternativa]

En lugar de “esperar”, defina el propio Runnable para que se llame después de Service Connected y ejecute este ejecutable una vez que el servicio esté conectado.

Implemente una clase personalizada de ServiceConnection de la siguiente manera.

 public class MyServiceConnection implements ServiceConnection { private static final String TAG = MyServiceConnection.class.getSimpleName(); private Context mContext = null; private IMyService mMyService = null; private ArrayList 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 utilícelo de la siguiente manera (en su clase de actividad, 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 manera mejor.

Descubrí que estas soluciones solo valen la pena el esfuerzo y la espera solo si sus servicios vinculados se ejecutan en un proceso diferente al proceso principal de su aplicación.

Para acceder a datos y métodos en el mismo proceso (o aplicación), terminé implementando clases singleton. Si las clases necesitan un contexto para algunos métodos, goteo el contexto de la aplicación a las clases singleton. Hay, por supuesto, una mala consecuencia de ello ya que rompe la “ejecución instantánea”. Pero ese es un mejor compromiso general, creo.