java - start_sticky - Esperando la devolución de llamada asincrónica en el servicio de intención de Android
servicios android (6)
Tengo un IntentService
que inicia una tarea asincrónica en otra clase y debería estar esperando el resultado.
El problema es que IntentService
terminará tan pronto como el onHandleIntent(...)
haya terminado de ejecutarse, ¿no?
Eso significa que, normalmente, IntentService
se cerrará inmediatamente después de iniciar la tarea asíncrona y ya no estará allí para recibir los resultados.
public class MyIntentService extends IntentService implements MyCallback {
public MyIntentService() {
super("MyIntentService");
}
@Override
protected final void onHandleIntent(Intent intent) {
MyOtherClass.runAsynchronousTask(this);
}
}
public interface MyCallback {
public void onReceiveResults(Object object);
}
public class MyOtherClass {
public void runAsynchronousTask(MyCallback callback) {
new Thread() {
public void run() {
// do some long-running work
callback.onReceiveResults(...);
}
}.start();
}
}
¿Cómo puedo hacer que el fragmento de arriba funcione? Ya he intentado poner Thread.sleep(15000)
(duración arbitraria) en onHandleIntent(...)
después de comenzar la tarea. Parece funcionar.
Pero definitivamente no parece ser una solución limpia. Tal vez incluso hay algunos problemas serios con eso.
¿Alguna mejor solución?
Acepto, probablemente tenga más sentido usar Service
directamente en lugar de IntentService
, pero si usa Guava , puede implementar AbstractFuture
como su manejador de devolución de llamada, lo que le permite ignorar convenientemente los detalles de la sincronización:
public class CallbackFuture extends AbstractFuture<Object> implements MyCallback {
@Override
public void onReceiveResults(Object object) {
set(object);
}
// AbstractFuture also defines `setException` which you can use in your error
// handler if your callback interface supports it
@Override
public void onError(Throwable e) {
setException(e);
}
}
AbstractFuture
define get()
qué bloques hasta que se llaman los métodos set()
o setException()
, y devuelve un valor o genera una excepción, respectivamente.
Entonces su onHandleIntent
convierte en:
@Override
protected final void onHandleIntent(Intent intent) {
CallbackFuture future = new CallbackFuture();
MyOtherClass.runAsynchronousTask(future);
try {
Object result = future.get();
// handle result
} catch (Throwable t) {
// handle error
}
}
Estás condenado sin cambiar MyOtherClass
.
Al cambiar esa clase, tienes dos opciones:
- Haga una llamada sincrónica.
IntentService
ya está generando unThread
fondo para ti. - Devuelve el
Thread
recién creado enrunAsynchronousTask()
y llama ajoin()
en él.
Estoy de acuerdo con corsair992 en que normalmente no debería tener que realizar llamadas asincrónicas desde un servicio de intención porque IntentService ya hace su trabajo en un hilo de trabajo. Sin embargo, si debe hacerlo, puede usar CountDownLatch .
public class MyIntentService extends IntentService implements MyCallback {
private CountDownLatch doneSignal = new CountDownLatch(1);
public MyIntentService() {
super("MyIntentService");
}
@Override
protected final void onHandleIntent(Intent intent) {
MyOtherClass.runAsynchronousTask(this);
doneSignal.await();
}
}
@Override
public void onReceiveResults(Object object) {
doneSignal.countDown();
}
public interface MyCallback {
public void onReceiveResults(Object object);
}
public class MyOtherClass {
public void runAsynchronousTask(MyCallback callback) {
new Thread() {
public void run() {
// do some long-running work
callback.onReceiveResults(...);
}
}.start();
}
}
Mi opción favorita es exponer dos métodos similares, por ejemplo:
public List<Dog> getDogsSync();
public void getDogsAsync(DogCallback dogCallback);
Entonces la implementación podría ser la siguiente:
public List<Dog> getDogsSync() {
return database.getDogs();
}
public void getDogsAsync(DogCallback dogCallback) {
new AsyncTask<Void, Void, List<Dog>>() {
@Override
protected List<Dog> doInBackground(Void... params) {
return getDogsSync();
}
@Override
protected void onPostExecute(List<Dog> dogs) {
dogCallback.success(dogs);
}
}.execute();
}
Luego, en su IntentService
puede llamar a getDogsSync()
porque ya está en una getDogsSync()
fondo.
Si aún está buscando maneras de utilizar el Servicio de Intento para la devolución de llamada asincrónica, puede esperar y notificar sobre el hilo de la siguiente manera:
private Object object = new Object();
@Override
protected void onHandleIntent(Intent intent) {
// Make API which return async calback.
// Acquire wait so that the intent service thread will wait for some one to release lock.
synchronized (object) {
try {
object.wait(30000); // If you want a timed wait or else you can just use object.wait()
} catch (InterruptedException e) {
Log.e("Message", "Interrupted Exception while getting lock" + e.getMessage());
}
}
}
// Let say this is the callback being invoked
private class Callback {
public void complete() {
// Do whatever operation you want
// Releases the lock so that intent service thread is unblocked.
synchronized (object) {
object.notifyAll();
}
}
}
Utilice la clase de Service
estándar Service
lugar de IntentService
, inicie la tarea asincrónica desde la devolución de llamada onStartCommand()
y destruya el Service
cuando reciba la devolución de llamada de finalización.
El problema con eso sería manejar correctamente la destrucción del Service
en el caso de ejecutar simultáneamente tareas como resultado de que el Service
se inicie de nuevo mientras ya se estaba ejecutando. Si necesita manejar este caso, puede necesitar configurar un contador en ejecución o un conjunto de devoluciones de llamadas, y destruir el Service
solo cuando estén completos.