Crear Cron en Android

publicado por: Anonymous

Estoy tratando de hacer un cron que se ejecute cada X tiempo en el sistema para que llame a un servicio que tengo hecho.

Las características del cron que deseo hacer son las siguientes:

  1. se ejecute independientemente si la Aplicación esté en ejecución o no (parcialmente hecho con el OnBootReceiver)
  2. se ejecute cuando se reinicie el dispositivo (lo tengo hecho, ver código más abajo en OnBootReceiver)
  3. se ejecute cada X intervalo de tiempo (cada 10 minutos por ejemplo)
  4. si no hay conexión a la hora de ejecutar el servicio cuando el cron se ha activado, que se active un Receiver de conexión para que cuando haya conexión se ejecute el servicio y luego se desactive este Receiver (lo tengo hecho ver código más abajo en ConnectivityReceiver).

Algunas de estas características ya las he conseguido haciéndolas por separado, a continuación os pongo código de lo que tengo.

ConnectivityReceiver

public class ConnectivityReceiver extends WakefulBroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {

        if (intent.getAction().equals(ConnectivityManager.CONNECTIVITY_ACTION)) {

            boolean noConnectivity =
                    intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);

            if(!noConnectivity){
                ConnectivityManager cm = (ConnectivityManager) context
                        .getSystemService(Context.CONNECTIVITY_SERVICE);
                NetworkInfo netInfo = cm.getActiveNetworkInfo();

                // only when connected or while connecting...
                if (netInfo != null && netInfo.isConnectedOrConnecting()) {
                    // if we have mobile or wifi connectivity...
                    if ((netInfo.getType() == ConnectivityManager.TYPE_MOBILE)
                            || (netInfo.getType() == ConnectivityManager.TYPE_WIFI)) {

                        Intent i =  new Intent(context, EnvioEstadisticasService.class);

                        startWakefulService(context, i);

                        // disable receiver after we started the service
                        disableReceiver(context);
                    }
                }
            }
        }
    }

    /**
     * Enables ConnectivityReceiver
     *
     * @param context
     */
    public static void enableReceiver(Context context) {
        ComponentName component = new ComponentName(context, ConnectivityReceiver.class);

        context.getPackageManager().setComponentEnabledSetting(component,
                PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
    }

    /**
     * Disables ConnectivityReceiver
     *
     * @param context
     */
    public static void disableReceiver(Context context) {
        ComponentName component = new ComponentName(context, ConnectivityReceiver.class);

        context.getPackageManager().setComponentEnabledSetting(component,
                PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
    }
}

EnvioEstadisticasService

public class EnvioEstadisticasService extends IntentService {

    private static EstadisticasDAO daoEst;

    public EnvioEstadisticasService() {
        super("EnvioEstadisticasService");
    }

    @Override
    protected void onHandleIntent(Intent intent) {

        //hago todas las operaciones en envio de estadisticas

        // Release the wake lock provided by the WakefulBroadcastReceiver.
        ConnectivityReceiver.completeWakefulIntent(intent);
    }
}

OnBootReceiver

public class OnBootReceiver extends BroadcastReceiver {
    private static final String TAG = OnBootReceiver.class.getSimpleName();

    @Override
    public void onReceive(Context context, Intent intent) {

        if (intent.getAction().equals("android.intent.action.BOOT_COMPLETED")) {
            Log.i(TAG, "EnvioEstadisService: entra en el on bootreceiver");

            Intent i =  new Intent(context, EnvioEstadisticasService.class);
            startWakefulService(context, i);
        }
    }
}

Y por último en manifest

<!-- Cron -->
        <receiver android:name=".cron.OnBootReceiver">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
            </intent-filter>
        </receiver>
        <receiver
            android:name=".cron.ConnectivityReceiver"
            android:enabled="false" >
            <intent-filter>
                <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
            </intent-filter>
        </receiver>

        <service android:name=".services.EnvioEstadisticasService"
            android:exported="false"
            android:enabled="true">
        </service>
        <!--  -->

He conseguido que el servicio se active cuando el dispositivo se encienda o se reinicie independientemente de la ejecución de la aplicación, además de que se controle el tema de la conexión a la hora de realizar el envío para poder llamar a otro receiver.

¿Alguien conoce la forma de hacer que se ejecute cada X intervalo
independientemente de si está la aplicación en marcha o no?.

solución

He dado con una solución a mi problema cumpliendo los puntos que quería que tuviera el cron descritos en la pregunta.

Explicación

Después de investigar y mirar de utilizar el Timer en el OnBootReceiver como me aconsejó @sioesi, encontré que los AlarmManager consumian menos recursos y según esta pregunta de StackOverflow en inglés es más aconsejable utilizarlos, ya que funcionan a nivel de Kernel. En este enlace se puede ver como se define una alarma y los diferentes tipos que hay.

Por otro lado para garantizar que las operaciones del servicio se realicen sin que el servicio se quede dormido y no complete sus operaciones he decidido utilizar wakefulBroadcastReceiver tanto en el ConnectivityReceiver como en el OnAlarmWakefulReceiver, como se explica aquí. Ya que estos Receivers son los encargados de llamar al servicio que hará las operaciones y no interesa que este se queden sin terminar sus operaciones, para garantizar su correcto funcionamiento.

Código

He modificado el OnBootReceiver para que cree una alarma que se
encargará de despertar el proceso de envío cada 10 minutos en mi caso,
independientemente de si la aplicación se está ejecutando o no. El código quedaría de la siguiente forma:

public class OnBootReceiver extends BroadcastReceiver {

    private static final String TAG = OnBootReceiver.class.getSimpleName();

    private static final int PERIOD = 1000 * 60 * 10;  // 10 minutes

    @Override
    public void onReceive(Context context, Intent intent) {

        if (intent.getAction().equals("android.intent.action.BOOT_COMPLETED")) {
            setAlarm(context);
        }
    }

    public static void setAlarm(Context context) {
        AlarmManager mgr =(AlarmManager)context.getSystemService(Context.ALARM_SERVICE);

        //configuramos una alarma para que se haga el envio de las estadisticas sino esta creada ya
        boolean alarmUp = (PendingIntent.getBroadcast(context, 0,
                new Intent(context, OnAlarmWakefulReceiver.class),PendingIntent.FLAG_NO_CREATE) != null);

        if (!alarmUp){    
            mgr.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                    SystemClock.elapsedRealtime()+60000,
                    PERIOD,
                    getPendingIntent(context));
        }else{
            Log.i(TAG, "EnvioEstadisService: Alarm is already active");
        }
    }

    public static void cancelAlarm(Context ctxt) {
        AlarmManager mgr=(AlarmManager)ctxt.getSystemService(Context.ALARM_SERVICE);

        mgr.cancel(getPendingIntent(ctxt));
    }

    private static PendingIntent getPendingIntent(Context ctxt) {
        Intent i=new Intent(ctxt, OnAlarmWakefulReceiver.class);

        return(PendingIntent.getBroadcast(ctxt, 0, i, PendingIntent.FLAG_UPDATE_CURRENT));
    }
 }

además para llamar al servicio de envío de estadísticas desde la
alarma me he creado el siguiente Receiver

public class OnAlarmWakefulReceiver extends WakefulBroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {

        Intent i =  new Intent(context, EnvioEstadisticasService.class);
        i.putExtra(Constants.proviene, Constants.provAlarm);

        startWakefulService(context, i);
    }
}

al manifest de la pregunta habría que añadirle :

<receiver android:name=".cron.OnAlarmWakefulReceiver"></receiver>

el ConnectivityReceiver quedaría igual que el de la pregunta salvo que habría que añadirle i.putExtra(Constants.proviene, Constants.provConnectivity); al hacer el intent para poder decirle al Servicio desde donde se llama

Y por último al servicio se le tendría que añadir el siguiente código para indicarle a los WakefulBroadcastReceiver que lo han llamado que ha terminado de hacer las operaciones :

 // Release the wake lock provided by the WakefulBroadcastReceiver.
 if(Constants.provConnectivity.equals(proviene))
      ConnectivityReceiver.completeWakefulIntent(intent);
 else
     OnAlarmWakefulReceiver.completeWakefulIntent(intent);

Creo que lo he descrito bastante detallado, pero si hay algo que no quede muy claro intentaré explicarlo lo mejor posible editando la respuesta.

Respondido por: Anonymous

Leave a Reply

Your email address will not be published. Required fields are marked *