Servicio de API relajante

Estoy buscando hacer un servicio que pueda usar para hacer llamadas a una API REST basada en la web.

Básicamente quiero comenzar un servicio en la aplicación init y luego quiero poder pedirle a ese servicio que solicite una url y devolver los resultados. Mientras tanto, quiero poder mostrar una ventana de progreso o algo similar.

Creé un servicio actualmente que usa IDL, leí en alguna parte que realmente solo necesita esto para la comunicación cruzada de aplicaciones, así que piense que estas necesidades se deben eliminar, pero no está seguro de cómo hacer devoluciones de llamadas sin este. Además, cuando post(Config.getURL("login"), values) la post(Config.getURL("login"), values) la aplicación parece detenerse por un tiempo (parece extraño: ¡la idea detrás de un servicio era que funciona con un hilo diferente!)

Actualmente tengo un servicio con publicación y metodos http dentro, un par de archivos AIDL (para comunicación bidireccional), un ServiceManager que trata de iniciar, detener, enlazar etc. al servicio y estoy creando dinámicamente un Handler con código específico para las devoluciones de llamadas según sea necesario.

No quiero que nadie me proporcione una base de código completa para trabajar, pero algunos consejos serán muy apreciados.

Código en (mayormente) completo:

 public class RestfulAPIService extends Service { final RemoteCallbackList mCallbacks = new RemoteCallbackList(); public void onStart(Intent intent, int startId) { super.onStart(intent, startId); } public IBinder onBind(Intent intent) { return binder; } public void onCreate() { super.onCreate(); } public void onDestroy() { super.onDestroy(); mCallbacks.kill(); } private final IRestfulService.Stub binder = new IRestfulService.Stub() { public void doLogin(String username, String password) { Message msg = new Message(); Bundle data = new Bundle(); HashMap values = new HashMap(); values.put("username", username); values.put("password", password); String result = post(Config.getURL("login"), values); data.putString("response", result); msg.setData(data); msg.what = Config.ACTION_LOGIN; mHandler.sendMessage(msg); } public void registerCallback(IRemoteServiceCallback cb) { if (cb != null) mCallbacks.register(cb); } }; private final Handler mHandler = new Handler() { public void handleMessage(Message msg) { // Broadcast to all clients the new value. final int N = mCallbacks.beginBroadcast(); for (int i = 0; i < N; i++) { try { switch (msg.what) { case Config.ACTION_LOGIN: mCallbacks.getBroadcastItem(i).userLogIn( msg.getData().getString("response")); break; default: super.handleMessage(msg); return; } } catch (RemoteException e) { } } mCallbacks.finishBroadcast(); } public String post(String url, HashMap namePairs) {...} public String get(String url) {...} }; 

Un par de archivos AIDL:

 package com.something.android oneway interface IRemoteServiceCallback { void userLogIn(String result); } 

y

 package com.something.android import com.something.android.IRemoteServiceCallback; interface IRestfulService { void doLogin(in String username, in String password); void registerCallback(IRemoteServiceCallback cb); } 

y el gerente de servicio:

 public class ServiceManager { final RemoteCallbackList mCallbacks = new RemoteCallbackList(); public IRestfulService restfulService; private RestfulServiceConnection conn; private boolean started = false; private Context context; public ServiceManager(Context context) { this.context = context; } public void startService() { if (started) { Toast.makeText(context, "Service already started", Toast.LENGTH_SHORT).show(); } else { Intent i = new Intent(); i.setClassName("com.something.android", "com.something.android.RestfulAPIService"); context.startService(i); started = true; } } public void stopService() { if (!started) { Toast.makeText(context, "Service not yet started", Toast.LENGTH_SHORT).show(); } else { Intent i = new Intent(); i.setClassName("com.something.android", "com.something.android.RestfulAPIService"); context.stopService(i); started = false; } } public void bindService() { if (conn == null) { conn = new RestfulServiceConnection(); Intent i = new Intent(); i.setClassName("com.something.android", "com.something.android.RestfulAPIService"); context.bindService(i, conn, Context.BIND_AUTO_CREATE); } else { Toast.makeText(context, "Cannot bind - service already bound", Toast.LENGTH_SHORT).show(); } } protected void destroy() { releaseService(); } private void releaseService() { if (conn != null) { context.unbindService(conn); conn = null; Log.d(LOG_TAG, "unbindService()"); } else { Toast.makeText(context, "Cannot unbind - service not bound", Toast.LENGTH_SHORT).show(); } } class RestfulServiceConnection implements ServiceConnection { public void onServiceConnected(ComponentName className, IBinder boundService) { restfulService = IRestfulService.Stub.asInterface((IBinder) boundService); try { restfulService.registerCallback(mCallback); } catch (RemoteException e) {} } public void onServiceDisconnected(ComponentName className) { restfulService = null; } }; private IRemoteServiceCallback mCallback = new IRemoteServiceCallback.Stub() { public void userLogIn(String result) throws RemoteException { mHandler.sendMessage(mHandler.obtainMessage(Config.ACTION_LOGIN, result)); } }; private Handler mHandler; public void setHandler(Handler handler) { mHandler = handler; } } 

Service init y bind:

 // this I'm calling on app onCreate servicemanager = new ServiceManager(this); servicemanager.startService(); servicemanager.bindService(); application = (ApplicationState)this.getApplication(); application.setServiceManager(servicemanager); 

llamada de función de servicio:

 // this lot i'm calling as required - in this example for login progressDialog = new ProgressDialog(Login.this); progressDialog.setMessage("Logging you in..."); progressDialog.show(); application = (ApplicationState) getApplication(); servicemanager = application.getServiceManager(); servicemanager.setHandler(mHandler); try { servicemanager.restfulService.doLogin(args[0], args[1]); } catch (RemoteException e) { e.printStackTrace(); } ...later in the same file... Handler mHandler = new Handler() { public void handleMessage(Message msg) { switch (msg.what) { case Config.ACTION_LOGIN: if (progressDialog.isShowing()) { progressDialog.dismiss(); } try { ...process login results... } } catch (JSONException e) { Log.e("JSON", "There was an error parsing the JSON", e); } break; default: super.handleMessage(msg); } } }; 

Si su servicio va a ser parte de su aplicación, entonces lo hará mucho más complejo de lo necesario. Como tiene un caso de uso simple para obtener algunos datos de un servicio web RESTful, debe buscar ResultReceiver e IntentService .

Este patrón Service + ResultReceiver funciona iniciando o vinculando al servicio con startService () cuando desea realizar alguna acción. Puede especificar la operación para realizar y pasar su ResultReceiver (la actividad) a través de los extras en el Intent.

En el servicio, implementa onHandleIntent para realizar la operación que se especifica en Intent. Cuando se completa la operación, utiliza el ResultReceiver pasado para enviar un mensaje de vuelta a la Actividad en el que se llamará a onReceiveResult .

Entonces, por ejemplo, desea extraer algunos datos de su servicio web.

  1. Usted crea el bash y llama a startService.
  2. La operación en el servicio se inicia y envía a la actividad un mensaje que dice que comenzó
  3. La actividad procesa el mensaje y muestra un progreso.
  4. El servicio finaliza la operación y envía algunos datos a su actividad.
  5. Su actividad procesa los datos y los coloca en una vista de lista
  6. El servicio te envía un mensaje que dice que ya está hecho, y se mata a sí mismo.
  7. La actividad obtiene el mensaje de finalización y oculta el cuadro de diálogo de progreso.

Sé que usted mencionó que no quería una base de código, pero la aplicación de código abierto Google I / O 2010 usa un servicio de esta manera que estoy describiendo.

Actualizado para agregar código de muestra:

La actividad.

 public class HomeActivity extends Activity implements MyResultReceiver.Receiver { public MyResultReceiver mReceiver; public void onCreate(Bundle savedInstanceState) { mReceiver = new MyResultReceiver(new Handler()); mReceiver.setReceiver(this); ... final Intent intent = new Intent(Intent.ACTION_SYNC, null, this, QueryService.class); intent.putExtra("receiver", mReceiver); intent.putExtra("command", "query"); startService(intent); } public void onPause() { mReceiver.setReceiver(null); // clear receiver so no leaks. } public void onReceiveResult(int resultCode, Bundle resultData) { switch (resultCode) { case RUNNING: //show progress break; case FINISHED: List results = resultData.getParcelableList("results"); // do something interesting // hide progress break; case ERROR: // handle the error; break; } } 

El servicio:

 public class QueryService extends IntentService { protected void onHandleIntent(Intent intent) { final ResultReceiver receiver = intent.getParcelableExtra("receiver"); String command = intent.getStringExtra("command"); Bundle b = new Bundle(); if(command.equals("query") { receiver.send(STATUS_RUNNING, Bundle.EMPTY); try { // get some data or something b.putParcelableArrayList("results", results); receiver.send(STATUS_FINISHED, b) } catch(Exception e) { b.putString(Intent.EXTRA_TEXT, e.toString()); receiver.send(STATUS_ERROR, b); } } } } 

Extensión de ResultReceiver: editado a punto de implementar MyResultReceiver.Receiver

 public class MyResultReceiver implements ResultReceiver { private Receiver mReceiver; public MyResultReceiver(Handler handler) { super(handler); } public void setReceiver(Receiver receiver) { mReceiver = receiver; } public interface Receiver { public void onReceiveResult(int resultCode, Bundle resultData); } @Override protected void onReceiveResult(int resultCode, Bundle resultData) { if (mReceiver != null) { mReceiver.onReceiveResult(resultCode, resultData); } } } 

Desarrollar aplicaciones de cliente REST de Android ha sido un recurso increíble para mí. El hablante no muestra ningún código, solo repasa las consideraciones de diseño y las técnicas para armar un Rest Icoso como una roca en Android. Si eres un poco un podcast o no, recomiendo darle al menos una escucha, pero personalmente la he escuchado como 4 o 5 veces hasta ahora y probablemente la vuelva a escuchar.

Desarrollar aplicaciones de cliente REST de Android
Autor: Virgil Dobjanschi
Descripción:

Esta sesión presentará consideraciones arquitectónicas para desarrollar aplicaciones RESTful en la plataforma Android. Se centra en los patrones de diseño, integración de plataformas y problemas de rendimiento específicos de la plataforma Android.

Y hay tantas consideraciones que realmente no había hecho en la primera versión de mi API que tuve que refactorizar

Además, cuando presiono la publicación (Config.getURL (“login”), valores) la aplicación parece detenerse por un tiempo (parece extraño: ¡la idea detrás de un servicio era que funciona con un hilo diferente!)

No, debe crear un hilo usted mismo, un servicio local se ejecuta en el hilo de la interfaz de usuario de forma predeterminada.

Sé que @Martyn no quiere el código completo, pero creo que esta anotación es buena para esta pregunta:

10 aplicaciones de código abierto de Android que todo desarrollador de Android debe analizar

Foursquared para Android es de código abierto y tiene un patrón de código interesante que interactúa con la API REST foursquare.

Recomiendo encarecidamente el Retrofit del cliente REST.

He encontrado esta publicación de blog bien escrita extremadamente útil, sino que también contiene un código de ejemplo simple. El autor usa Retrofit para realizar llamadas de red y Otto para implementar un patrón de bus de datos:

http://www.mdswanson.com/blog/2014/04/07/durable-android-rest-clients.html

Solo quería señalarles a todos en la dirección de una clase independiente que rodé y que incorpora toda la funcionalidad.

http://github.com/StlTenny/RestService

Ejecuta la solicitud como no bloqueante y devuelve los resultados en un controlador fácil de implementar. Incluso viene con una implementación de ejemplo.

Digamos que quiero iniciar el servicio en un evento – onItemClicked () de un botón. El mecanismo del receptor no funcionaría en ese caso porque:
a) Pasé el Receptor al servicio (como en el Intento adicional) de onItemClicked ()
b) La actividad se mueve al fondo. En onPause () configuré la referencia del receptor dentro del ResultReceiver como nulo para evitar la fuga de la actividad.
c) La actividad se destruye.
d) La actividad se crea nuevamente. Sin embargo, en este punto, el Servicio no podrá realizar una callback a la Actividad ya que la referencia del receptor se pierde.
El mecanismo de una transmisión limitada o de un PendingIntent parece ser más útil en tales escenarios: consulte Notificar actividad del servicio.

Tenga en cuenta que la solución de Robby Pond es de alguna manera deficiente: de esta manera, solo permite hacer una llamada api de una sola vez, ya que IntentService solo maneja un bash a la vez. A menudo quieres realizar llamadas api paralelas. Si desea hacerlo todo, debe extender el servicio en lugar de IntentService y crear su propio hilo.

Además, cuando presiono la publicación (Config.getURL (“login”), valores) la aplicación parece detenerse por un tiempo (parece extraño: ¡la idea detrás de un servicio era que funciona con un hilo diferente!)

En este caso, es mejor utilizar asynctask, que se ejecuta en un subproceso diferente y devolver el resultado al subproceso ui una vez completado.

Aquí hay otro enfoque que básicamente te ayuda a olvidarte de la gestión completa de las solicitudes. Se basa en un método de cola asíncrona y una respuesta invocable / de callback. La principal ventaja es que al usar este método podrá hacer que todo el proceso (solicitud, obtención y análisis de respuesta, sabe a DB) sea completamente transparente para usted. Una vez que obtenga el código de respuesta, el trabajo ya está hecho. Después de eso solo necesita hacer una llamada a su base de datos y listo. También ayuda con la problemática de lo que sucede cuando tu actividad no está activa. Lo que ocurrirá aquí es que tendrá todos sus datos guardados en su base de datos local, pero la respuesta no será procesada por su actividad, esa es la manera ideal.

Escribí sobre un enfoque general aquí http://ugiagonzalez.com/2012/07/02/theres-life-after-asynctasks-in-android/

Voy a poner código de muestra específico en próximas publicaciones. Espero que ayude, no dude en ponerse en contacto conmigo para compartir el enfoque y resolver posibles dudas o problemas.

Robby proporciona una excelente respuesta, aunque puedo ver que todavía está buscando más información. Implementé las llamadas a la API REST de forma sencilla pero equivocada. No fue hasta ver este video de Google I / O que entendí dónde me equivoqué. No es tan simple como armar una AsyncTask con una llamada get / put HttpUrlConnection.