Servicio Restful API


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

Básicamente quiero iniciar un servicio en app init y luego quiero poder pedirle a ese servicio que solicite una url y devuelva los resultados. Mientras tanto quiero ser capaz de mostrar una ventana de progreso o algo similar.

He creado un servicio actualmente que utiliza IDL, he leído en alguna parte que solo necesita esto para la comunicación entre aplicaciones, así que creo que estas necesidades de extracción pero no estoy seguro de cómo hacer callbacks sin él. También cuando me golpeó el post(Config.getURL("login"), values) la aplicación parece pausar por un tiempo (parece extraño - pensé que la idea detrás de un servicio era que se ejecuta en un hilo diferente!)

Actualmente tengo un servicio con métodos post y get http dentro, un par de archivos AIDL( para comunicación bidireccional), un ServiceManager que se ocupa de iniciar, detener, vincular, etc. al servicio y estoy creando dinámicamente un Controlador con código específico para las devoluciones de llamada como necesario.

No quiero que nadie me dé una base de código completa para trabajar, pero algunos indicadores serían muy apreciados.

Código en (en su mayoría) completo:

public class RestfulAPIService extends Service  {

final RemoteCallbackList<IRemoteServiceCallback> mCallbacks = new RemoteCallbackList<IRemoteServiceCallback>();

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<String, String> values = new HashMap<String, String>();
        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<String, String> 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 administrador de servicios:

public class ServiceManager {

    final RemoteCallbackList<IRemoteServiceCallback> mCallbacks = new RemoteCallbackList<IRemoteServiceCallback>();
    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;
    }
}

Inicio y enlace de servicio:

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

Llamada a la 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);
        }

    }

};
Author: Michael Currie, 2010-07-07

11 answers

Si su servicio va a ser parte de su aplicación, entonces lo está haciendo mucho más complejo de lo que necesita ser. Dado que tiene un caso de uso simple de obtener algunos datos de un Servicio Web RESTful, debe buscar en ResultReceiver y IntentService.

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

En el servicio se implementa onHandleIntent para realizar la operación especificada en la Intent. Cuando se completa la operación se utiliza el passed in ResultReceiver para enviarun mensaje a la Actividad en el que se llamará onReceiveResult.

Por ejemplo, desea extraer algunos datos de su Servicio web.

  1. Usted crea la intent y llama startService.
  2. La operación en el servicio se inicia y envía a la actividad un mensaje diciendo que se inició
  3. La actividad procesa el mensaje y muestra un progreso.
  4. El servicio finaliza la operación y envía algunos datos de vuelta 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 diciendo que está hecho, y se mata a sí mismo.
  7. La actividad obtiene el mensaje finish y oculta el diálogo de progreso.

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

Actualizado para añadir código de ejemplo:

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 ResultReceiver - editada a punto de implementar MyResultReceiver.Receptor

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);
        }
    }
}
 281
Author: Robby Pond,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-06-02 20:12:22

Desarrollar aplicaciones de cliente Android REST ha sido un recurso increíble para mí. El orador no muestra ningún código, solo repasa las consideraciones de diseño y las técnicas para armar una api Rest sólida como una roca en Android. Si es una persona de podcast o no, recomendaría darle a este al menos una escucha, pero personalmente lo he escuchado como 4 o cinco veces hasta ahora y probablemente lo vuelva a escuchar.

Desarrollo del cliente Android REST aplicaciones
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, la integración de la plataforma y los 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 he tenido que refactorizar

 16
Author: Terrance,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-05-18 12:25:26

También cuando golpeo the post (Config.getURL ("iniciar sesión"), valores) la aplicación parece pausar para un mientras que (parece raro-pensó la idea detrás servicio que se ejecuta en un subproceso diferente!)

No tienes que crear un subproceso tú mismo, un servicio Local se ejecuta en el subproceso de interfaz de usuario de forma predeterminada.

 15
Author: Soumya Simanta,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-07-07 18:34:35

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

10 Aplicaciones Android de código abierto que cada desarrollador de Android debe buscar

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

 11
Author: panchicore,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-09-12 19:38:47

Recomiendo encarecidamente el cliente REST Retrofit.

He encontrado esta entrada de blog bien escrita extremadamente útil, también contiene un código de ejemplo simple. El autor utiliza Retrofit para realizar las llamadas a la 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

 6
Author: Pete,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-09-27 10:04:52

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

Http://github.com/StlTenny/RestService

Ejecuta la solicitud sin bloqueo y devuelve los resultados en un manejador fácil de implementar. Incluso viene con una implementación de ejemplo.

 5
Author: StlTenny,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-07-11 18:01:09

Digamos que quiero iniciar el servicio en un evento - onItemClicked() de un botón. El mecanismo Receptor no funcionaría en ese caso porque: -
a) Pasé el Receptor al servicio (como en Intent extra) de onItemClicked ()
b) La actividad pasa a un segundo plano. En onPause () establezco la referencia del receptor dentro del ResultReceiver a null para evitar filtrar la Actividad.
c) La actividad se destruye.
d) La actividad se crea de nuevo. Sin embargo, en este punto el Servicio no ser capaz de hacer una devolución de llamada a la Actividad ya que se pierde esa referencia del receptor.
El mecanismo de una emisión limitada o un PendingIntent parece ser más útil en tales escenarios-consulte Notificar la actividad del servicio

 4
Author: Nikhil_Katre,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:46:57

Tenga en cuenta que la solución de Robby Pond carece de alguna manera: de esta manera solo permite hacer una llamada a la api a la vez, ya que el IntentService solo maneja una intent a la vez. A menudo desea realizar llamadas api paralelas. Si quieres hacer esto tienes que extender Service en lugar de IntentService y crear tu propio hilo.

 4
Author: TjerkW,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-04-28 08:15:11

También cuando golpeo el poste (Config.getURL ("login"), valores) la aplicación parece pausarse por un tiempo (parece extraño - pensé que la idea detrás de un servicio era que se ejecuta en un hilo diferente!)

En este caso es mejor usar asynctask, que se ejecuta en un subproceso diferente y devuelve el resultado al subproceso de la interfaz de usuario al completarlo.

 2
Author: Aakash,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-11-04 03:49:13

Hay otro enfoque aquí que básicamente le ayuda a olvidarse de toda la gestión de las solicitudes. Se basa en un método de cola asíncrona y una respuesta basada en callback/callback. La principal ventaja es que al usar este método podrás hacer que todo el proceso (solicitar, obtener y analizar la respuesta, sabe a db) sea completamente transparente para ti. Una vez que obtenga el código de respuesta, el trabajo ya está hecho. Después de eso, solo tiene que hacer una llamada a su base de datos y listo. Ayuda así como con la problemática de lo que sucede cuando su actividad no está activa. Lo que sucederá 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 forma ideal.

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

Pondré un código de ejemplo específico en las próximas publicaciones. Espero que ayude, no dude en ponerse en contacto conmigo para compartir el abordar y resolver posibles dudas o problemas.

 2
Author: Jose L Ugia,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-07-05 14:59:01

Robby proporciona una gran respuesta, aunque puedo ver que todavía está buscando más información. Implementé llamadas REST api de la manera fácil PERO incorrecta. No fue hasta ver este Google I/O video que entendí dónde me equivoqué. No es tan simple como armar una AsyncTask con una llamada get/put de HttpURLConnection.

 0
Author: Andrew Halloran,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-04-08 18:06:20