ejemplo - sincronizar en android studio
Cómo manejar la actualización RESTful del servidor remoto con SyncAdapter (2)
He visto hablar y leer las diapositivas de Google I / O REST: http://www.google.com/events/io/2010/sessions/developing-RESTful-android-apps.html
Todavía no estoy seguro de cómo manejar bien, digamos, un error de actualización generado por el servidor remoto. He implementado mi propio ContentProvider y SyncAdapter. Considere este escenario:
Actualizar los detalles de contacto de un usuario a través de la llamada REST:
- Solicitar una actualización utilizando un ContentResolver.
- My ContentProvider actualiza inmediatamente la base de datos local de Sqlite de la aplicación y solicita una Sincronización (según las recomendaciones de la charla de Google I / O).
- Se llama a mi SyncAdapter.onPerformSync () y realiza una llamada REST para actualizar los datos remotos.
- El servidor remoto responde con "ERROR: Número de teléfono no válido" (por ejemplo).
Mi pregunta es, ¿cuál es la mejor manera para que SyncAdapter indique a mi ContentProvider que este cambio necesita ser retirado de la base de datos local de la aplicación, y también para indicar a mi actividad que la solicitud de actualización falló (y pasa los mensajes de error devueltos) desde el servidor)?
Mi actividad necesita mostrar un control de progreso mientras espera el resultado y saber si la solicitud se realizó correctamente o no.
Para actualizar la base de datos de la aplicación local con el contenido del Servidor, el patrón SyncAdapter tiene mucho sentido para mí, y tengo que trabajar bien. Pero para las actualizaciones de la aplicación al servidor, parece que no puedo encontrar una buena manera de manejar el escenario anterior.
Y otra cosa... ;)
Digamos que llamo a ContentResolver.notifyChange (uri, null, true); desde dentro del método update () de mi ContentProvider. true
junto con android:supportsUploading="true"
supportUploading android:supportsUploading="true"
hará que se llame a onPerformSync () de mi SyncAdapter. Genial, pero dentro de OnPerformSync (), ¿cómo puedo saber qué URI debo sincronizar? No quiero simplemente actualizar mi DB completo cada vez que recibo una solicitud de sincronización. Pero ni siquiera puede pasar un paquete a los notificaráCambiar llamadas () para que se pasen a onPerformSync ().
Todos los ejemplos que he visto de onPerformSync () han sido tan simples, y no utilizando un ContentProvider personalizado, ¿hay ejemplos del mundo real por ahí? Y los documentos son un poco como un nido de pájaro. Virgil Dobjanschi, señor, me dejó en el arroyo sin una paleta.
¿Qué pasa con el patrón de diseño Observer? ¿Puede su actividad ser un observador del SyncAdapter o la base de datos? De esa manera, cuando falla una actualización, el adaptador notificará a sus observadores y luego podrá actuar sobre los datos que cambiaron. Hay un montón de clases observables en el SDK, vea cuál funciona mejor en su situación. http://developer.android.com/search.html#q=Observer&t=0
Respuesta corta, si está apuntando a ~ API nivel 7, es "No". Es posible que la situación haya mejorado en las API posteriores, pero tal como era ... Recomendaría encarecidamente evitar SyncAdapter completamente; está muy mal documentado y la administración "automática" de la cuenta / autenticación tiene un alto precio, ya que la API también es complicada y está poco documentada. Esta parte de la API no se ha pensado más allá de los casos de uso más triviales.
Así que aquí está el patrón con el que terminé yendo. Dentro de mis actividades tenía un manejador con una adición simple de una superclase de manejador personalizada (podría buscar un bool m_bStopped
en m_bStopped
):
private ResponseHandler mHandler = new ResponseHandler();
class ResponseHandler extends StopableHandler {
@Override
public void handleMessage(Message msg) {
if (isStopped()) {
return;
}
if (msg.what == WebAPIClient.GET_PLANS_RESPONSE) {
...
}
...
}
}
La actividad invocaría las solicitudes REST como se muestra a continuación. Tenga en cuenta que el controlador pasa a la clase de WebClient (una clase auxiliar para crear / realizar las solicitudes HTTP, etc.). WebClient utiliza este controlador cuando recibe la respuesta HTTP para enviar un mensaje a la actividad e informarle que se han recibido los datos y, en mi caso, almacenados en una base de datos SQLite (que recomendaría). En la mayoría de las Actividades, llamaría a mHandler.stopHandler();
en onPause()
y mHandler.startHandler();
en onResume()
para evitar que la respuesta HTTP se devuelva a una actividad inactiva, etc. Esto resultó ser un enfoque bastante sólido.
final Bundle bundle = new Bundle();
bundle.putBoolean(WebAPIRequestHelper.REQUEST_CREATESIMKITORDER, true);
bundle.putString(WebAPIRequestHelper.REQUEST_PARAM_KIT_TYPE, sCVN);
final Runnable runnable = new Runnable() { public void run() {
VendApplication.getWebClient().processRequest(null, bundle, null, null, null,
mHandler, NewAccountActivity.this);
}};
mRequestThread = Utils.performOnBackgroundThread(runnable);
Handler.handleMessage()
se invoca en el hilo principal. Así que puedes detener tus diálogos de progreso aquí y hacer otras actividades de forma segura.
Declaré un proveedor de contenido:
<provider android:name="au.com.myproj.android.app.webapi.WebAPIProvider"
android:authorities="au.com.myproj.android.app.provider.webapiprovider"
android:syncable="true" />
Y lo implementó para crear y administrar el acceso a la base de datos SQLite:
public class WebAPIProvider extends ContentProvider
Entonces, puede obtener cursores sobre la base de datos en sus Actividades como esta:
mCursor = this.getContentResolver().query (
WebAPIProvider.PRODUCTS_URI, null,
Utils.getProductsWhereClause(this), null,
Utils.getProductsOrderClause(this));
startManagingCursor(mCursor);
Encontré que la clase org.apache.commons.lang3.text.StrSubstitutor
es inmensamente útil para construir las solicitudes de XML torpes requeridas por la API REST que tuve que integrar, por ejemplo, en WebAPIRequestHelper
tuve métodos de ayuda como:
public static String makeAuthenticateQueryString(Bundle params)
{
Map<String, String> valuesMap = new HashMap<String, String>();
checkRequiredParam("makeAuthenticateQueryString()", params, REQUEST_PARAM_ACCOUNTNUMBER);
checkRequiredParam("makeAuthenticateQueryString()", params, REQUEST_PARAM_ACCOUNTPASSWORD);
valuesMap.put(REQUEST_PARAM_APIUSERNAME, API_USERNAME);
valuesMap.put(REQUEST_PARAM_ACCOUNTNUMBER, params.getString(REQUEST_PARAM_ACCOUNTNUMBER));
valuesMap.put(REQUEST_PARAM_ACCOUNTPASSWORD, params.getString(REQUEST_PARAM_ACCOUNTPASSWORD));
String xmlTemplate = VendApplication.getContext().getString(R.string.XMLREQUEST_AUTHENTICATE_ACCOUNT);
StrSubstitutor sub = new StrSubstitutor(valuesMap);
return sub.replace(xmlTemplate);
}
Que añadiría a la URL de punto final apropiada.
Aquí hay algunos detalles más sobre cómo la clase de WebClient hace las solicitudes HTTP. Este es el método processRequest()
llamado anteriormente en Runnable. Observe el parámetro del handler
que se usa para enviar los resultados al ResponseHandler
que describí anteriormente. syncResult
está en el parámetro out utilizado por SyncAdapter para hacer un retroceso exponencial, etc. Lo uso en executeRequest()
, incrementando sus diversos recuentos de errores, etc. Una vez más, muy mal documentado y un PITA para comenzar a trabajar. parseXML()
aprovecha la excelente parseXML()
XML simple .
public synchronized void processRequest(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult, Handler handler, Context context)
{
// Helper to construct the query string from the query params passed in the extras Bundle.
HttpUriRequest request = createHTTPRequest(extras);
// Helper to perform the HTTP request using org.apache.http.impl.client.DefaultHttpClient.
InputStream instream = executeRequest(request, syncResult);
/*
* Process the result.
*/
if(extras.containsKey(WebAPIRequestHelper.REQUEST_GETBALANCE))
{
GetServiceBalanceResponse xmlDoc = parseXML(GetServiceBalanceResponse.class, instream, syncResult);
Assert.assertNotNull(handler);
Message m = handler.obtainMessage(WebAPIClient.GET_BALANCE_RESPONSE, xmlDoc);
m.sendToTarget();
}
else if(extras.containsKey(WebAPIRequestHelper.REQUEST_GETACCOUNTINFO))
{
...
}
...
}
Debería poner algunos tiempos de espera en las solicitudes HTTP para que la aplicación no espere para siempre si los datos móviles se desconectan o cambia de Wifi a 3G. Esto provocará que se genere una excepción si se produce el tiempo de espera.
// Set the timeout in milliseconds until a connection is established.
int timeoutConnection = 30000;
HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection);
// Set the default socket timeout (SO_TIMEOUT) in milliseconds which is the timeout for waiting for data.
int timeoutSocket = 30000;
HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket);
HttpClient client = new DefaultHttpClient(httpParameters);
Así que en general, el SyncAdapter y las cuentas fueron un dolor total y me costó mucho tiempo sin ganancia. El ContentProvider fue bastante útil, principalmente para el cursor y el soporte de transacciones. La base de datos SQLite era realmente buena. Y la clase Handler es impresionante. Yo usaría la clase AsyncTask ahora en lugar de crear tus propios subprocesos como lo hice anteriormente para generar las solicitudes HTTP.
Espero que esta explicación entrecortada ayude a alguien un poco.