Envio de datos a internet sin cobertura

PinVa

Buenas vengo preguntando una duda que no he conseguido solventar, y trata de el envio de datos pero como lo hace whatsapp.

A lo que me refiero q quiero hacer una app que va enviando informacion a una api web y si por ejemplo no hay internet debe de mandarse cuando tenga cobertura o internet, hasta cuando no esta en la aplicación. Como hace whatsapp o telegram.

He estado mirando el codigo abierto de telegram pero no consigo ver justamente ese detalle.

OJO: no quiero recibir nada sino ir mandando informacion y si no hay cobertura o no hay internet q finalmente se envie aunq no este en la app.

No quiero ninguna especie de tutorial ni nada sino que me puedan decir como mierda se hace eso, que en google no pude encontrar.

Un saludete.

Soltrac

Hay mil formas de plantearse esto, pero bueno, te digo la primera que se me ocurre.

Crear una cola con los mensajes a enviar. Enviarlos cada X, si recibo un timeout o cualquier error, intentarlo de nuevo en Y. Todo en un hilo separado.

La app, aunque tu no la veas, sí está corriendo, al igual que corren por detrás telegram o whatsapp.

1 respuesta
Dostoievski

Esas app, en android, registran un BroadcastReceiver para que lance un servicio cuando el movil tiene internet y entonces hacen la sincronización con el server.

Otra forma es a través de la cuenta, syncronizando la cuenta cada X tiempo parecido a como dice #2.

Pero la manera de #2 no funciona en android cuando entra en deep sleep ya que para todos los procesos y dejaría de intentarlo a parte de que no es recomendable, ni es una buenta practica y trae varios problemas. Hay forma de evitar este tipo de cosas como forzarlo a que no entre en deep sleep pero vamos, yo no lo recomiendo.

Como no tengo tiempo de explicartelo mejor, aqui hay un tio que ha hecho un ejemplo:

http://paragchauhan2010.blogspot.com.es/2012/04/check-network-connection-using.html

Pero ya te digo, no lo hagas en una actividad. Lo suyo es tener un servicio que sincronice tu BBDD, y lanzar ese servicio cuando haya datos nuevos por enviar, en plan algo así:

  • La app registra un BroadcastReceiver desde el manifest (importante, no desde la activity) para escuchar los cambios del la conectividad.

  • Tengo un servicio de sincronización que se encargará de enviar lo que sea al server en caso de que haya algo nuevo,así tienes esta lógica aquí nada más.

  • El broadcast cada vez que la conectividad cambie a activa, lanza el servicio de sincronización.

  • Cuando se ha creado otro mensaje por ejemplo, lo sincronizas a través del servicio.

Todo esto hablando de Android. En ios se pueden hacer ñapas con alarmas del sistema también si no me equivoco.

2 2 respuestas
Soltrac

#3 No conocía lo del deep sleep, no programo apenas aplicaciones móviles : ). Siempre está bien saberlo. Tu idea me parece más lógica.

1 respuesta
Dostoievski

#4 Ya ya xD. No pretendía ser ofensivo ni nada así, siento si lo ha parecido.

El problema es que con moviles hay que cambiar un poco el "chip" porque el sistema operativo trae herramientas para evitar cosas a las que estamos acostumbrados a hacer por el consumo de batería y recursos pero a cambio te propone otro tipo de soluciones xD.

Hay varios problemas más a parte de que entre en deep sleep como el consumo de batería (imaginate que esta un par de dias sin cobertura), de recursos, que Android decida machacar tu app para liberarlos y cosas así y al tenerlo en un hilo con una cola, perderías todo si no lo has previsto (lo cual puede ser un coñazo muy grande xD). Los servicios por ejemplo tienen prioridad y se les puede indicar que son importantes para que los relance cuando pueda en caso de que los tenga que destruir y cosas así.

Cuando hago apps que requiren sincronizaciones y modo offline, la app trabaja con la bbdd nada más y luego se va sincronizando mediante servicios que envian broadcasts para notificar a las activities de estas actualizaciones.

Un saludo.

1 respuesta
Soltrac

#5 Para nada!!!! Faltaría más : )))). El saber no ocupa lugar y aprender es bueno.

PinVa

#3 Muchas gracias por la orientación.

Ahora como dices haciendo uso de BroadcastReceiver cada vez que hay o no hay Internet se llama a el método onReceive, pero entonces eso lo hago en una clase que extienda de Service?

¿Y en la aplicación principal la ejecuto?

Pero si eso es así si esta fuera de la aplicación(que le dio a salir y ya no esta ejecutándose) el servicio también dejaría de ejecutarse no?

Y los datos que no se envíen deberán quedarse almacenados en una base de datos no? porque si cierras la aplicación en un array por ejemplo se pierden los datos.

Siento tantas preguntas :(

1 respuesta
Dostoievski

#7 Para los datos, sí, debes guardarlos en una bbdd o cualquier otro sitio de persistencia si quieres sincronizarlo más adelante.

No, el Broadcast y el Service son cosas independientes:
http://developer.android.com/reference/android/content/BroadcastReceiver.html
http://developer.android.com/guide/components/services.html

Ahora que lo pienso, ni si quiera hace falta que lo hagas con Broadcast xD. Registra directamente tu Service para que se lance cuando cambie la conectividad, para simplificar puedes usar un IntentService:

http://developer.android.com/reference/android/app/IntentService.html

Revisa todo lo que te escribo ahora porque puede que tenga fallos o esté muy simplificado y puedan fallar cosas a la hora de comprobar la connectividad :P

Manifest:

<manifest ... >

  <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
  <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

  <application ... >
    <service android:name=".ExampleService" >
      <intent-filter>
        <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
        <action android:name="com.example.intent.action.SYNC" />
      </intent-filter>
    </service>
    ...
  </application>
</manifest>

Y tu service así:

public class MyService extends IntentService {
  public static final String ACTION_SYNC = "com.example.intent.action.SYNC";
  public static final String ACTION_SYNC_FINISHED = "com.example.intent.action.SYNC_FINISHED";
 
  @Override
  protected void onHandleIntent(Intent intent) {
    // no nos hace falta comprobar el tipo de action porque siempre vamos a hacer lo mismo y el unico requisito es que esté conectado.
    if (isConnected()) { 
      sync();
    } 
  }
  private boolean isConnected() {
    ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
    NetworkInfo ni = cm.getActiveNetworkInfo();
    return ni != null && ni.isConnected();
  }

  private void sync() {
    // Sincronizas tus datos en caso de que sea necesario
    sendBroadcast(new Intent(ACTION_SYNC_FINISHED));
  }
}

Eso ya escucharía cuando hay cambios de la connectividad o cuando se ha lanzado un intent, para lanzar un intent desde tus activities o cualquier otro sitio que lance el service (no olvides registrar un broadcast para el ACTION_SYNC_FINISHED y así actualizar tu vista):

context.startService(new Intent(MyService.ACTION_SYNC));
// si lo de arriba no funciona que no estoy seguro ahora mismo:
Intent i = new Intent(context,MyService.class);
i.setAction(MyService.ACTION_SYNC); // realmente ni haría falta y podrías borrarlo del manifest tmb.
context.startService(i);
1 respuesta
PinVa

#8 Vale ahora si!, eso es lo que yo había entendido que tuviera el "listener" del broadcast en el servicio para saber cuando cambia la conectividad.

Ahora lo que no entiendo es porque lo de "com.example.intent.action.SYNC".

Edit:

Ya hice mi ActivityMain:

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
//Inicio el servicio y el mConnReceiver startService(new Intent(this, ExampleService.class)); mConnReceiver = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { if (intent.getAction().equals(ExampleService.ACTION_SYNC_FINISHED)){ //Lo que yo vea oportuno } } }; //Lo registro this.registerReceiver(this.mConnReceiver, new IntentFilter(ExampleService.ACTION_SYNC_FINISHED));

Y puse en el manifest lo que me pasaste y puse la clase de ExampleService que tu pasaste solo tocando el interior del metodo onHandleIntent.

Pero cuando cambia el estado de la conexión del móvil no llama al metodo onHandleIntent :(

Edit2: Si lo llama al empezar la aplicación, pero luego cuando va cambiando la conectividad ni se pasea por el método :(, puse esto por si creías que no ponía bien el servicio o algo.

1 respuesta
Dostoievski

#9 Copia tu manifest aqui y revisa el logcat también. Y esta linea

startService(new Intent(this, ExampleService.class));

es solo cuando quieras iniciar la sincronización

2 respuestas
PinVa

#10

    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"></uses-permission>
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"></uses-permission>

<application
    android:allowBackup="true"
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name"
    android:logo="@drawable/chirinex"
    android:theme="@style/AppTheme" >

    <service android:name=".ExampleService">
        <intent-filter>
            <action android:name="android.net.wifi.STATE_CHANGE" />
            <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
            <action android:name="com.example.masterdetail.intent.action.SYNC" />
        </intent-filter>
    </service>

    <activity
        android:name="com.example.masterdetail.MainActivity"
        android:label="@string/app_name" >
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
    <activity
        android:name="com.example.masterdetail.ItemDetail"
        android:label="@string/title_activity_item_detail" >
    </activity>
</application>

Vale ya se lo que tengo que hacer!

Primero: tengo que lanzar el BroadcastReceiver para saber cuando cambia la conexión.

Segundo: al querer enviar datos saber si hay o no conexión y si hay enviarla y sino guardar en base de datos.

Tercero: El receiver al detectar que hay internet, creo un IntentService y el intentService lo que hace es mandar los datos.

Y así sucesivamente.

Ahora tengo un problema, no se porque me da este error cuando ejecuto el BroadcastReceiver:

Error: Are you missing a call to unregisterReceiver()?

Y es al abandonar la actividad.

El problema que tengo es que quiero que me registre con el BroadcastReceiver cuando no esta en la aplicación para poder enviar los datos, y no se como se debe hacer.

Tig

Es mejor usar un WakefulBroadcastReceiver que poner al servicio a escuchar directamente. Si teléfono está en deep sleep necesitas bloquear la CPU para que no se vuelva a dormir en cuanto te despistes. También puedes adquirir el Wakelock a mano en el servicio directamente (oncreate y al final de onhandleintent).

Otra opción es usar notificaciones push, tú lo metes en la cola de envío y los Google Play Services ya lo mandarán, pero eso requiere trabajo también en el API.

Luego, dependiendo de la funcionalidad del API te vas a encontrar con problemas serios de sincronización, y tendrás que guardar timestamps de la última edición y saber si tienes que hacer caso o ignorar lo que te entra del servidor. Pero quizá simplemente mandas datos y no recibes nada, lo que simplificaría las cosas

1 respuesta
PinVa

#12

Me lo había planteado de la siguiente manera, un Receiver que cuando vuelva la conexión lo recibe y lanza un IntentService que envía los datos que no se enviaron.

Y porque exactamente el Wakelock?

Si necesito tenerlo sincronizado, porque de vez en cuando recojo datos, como es eso de los timestamps?

Gracias!

1 respuesta
Tig

#13 Describe tu aplicación y te podremos decir si te encontrarás con ese problema.

Si tienes varios medios por los que crear un registro (una app y una web, por ejemplo). Imagina que un usuario crea una Persona, y luego le cambia el nombre en la web. ¿Qué pasa si, sin estar sincronizado, vuelve a cambiar el nombre en la app? ¿Quién gana? Necesitas llevar contro de la fecha de la ultima edición para saber si el cambio que te llega del servidor es el más reciente o si tienes que ignorarlo.

Lo más sencillo es una arquitectura maestro - esclavo, donde solo hay una parte que crea y otra que lee.

Busca Wakelock y verás a lo que me refiero, y echa un vistazo a la documentación de WakefulBroadcastReceiver que he linkado

1 respuesta
PinVa

#14 Trata de usuarios de una autoescuela tienen una app, que aparte de tener pues cosas de la autoescuela pues puede iniciar una cosa en el móvil cuando empiezas una clase de coche y va recogiendo la localización de por donde va pasando, y si no hay Internet pues lo que reciba de localización cuando haya Internet lo manda y no tiene porque estar puesta la aplicación para que se envie los puntos por donde va pasando.

Y también puede ser que algunas normas de circulación cambien y pues habría que actualizarlo en el móvil del servidor, o si cambia el horario de un alumno para ir a una clase y cosas así pueden ir cambiando y hay que estar sincronizadas.

1 respuesta
Tig

#15 entonces no te encontrarás con ese problema, porque es una configuración maestro - esclavo

Suerte!

1 respuesta
PinVa

#16 #10 Gracias !!

1 respuesta
Tig

#17 echa un vistazo a esto si te interesa el cómo implementarlo.

https://www.youtube.com/watch?v=G6f9JQvGvr4&

1 respuesta
PinVa

#18 Gracias!! Que grande!