Ванька Жуков, начинающий Android-пограммист, n-цати годов от роду, отданный в ученье неизвестно когда, не ложился спать. Дождавшись, когда коллеги и начальство уйдут к обедне, достал пузырек с тёмны… чаем, клавиатуру с заржавевшим выводом, запустил Android Studio и стал писать. Прежде чем вывести первую букву, он несколько раз пугливо оглянулся на окна Скайпа, и прерывисто вздохнул.



«Здравствуй, милый дедушка Хабр Хабрович! — писал он. — Пишу тебе письмо. Поздравляю вас со светлой Пятницей, и желаю тебе всего на выходных».

Ванька покосился на Скайп и живо вообразил себе Хабра Хабровича. Образ получился впечатляющий, но слишком объёмный. Ванька вздохнул и продолжил писать.
«А вчерась мне была выволочка. Надумал я написать свой таймер, с автоматическим запуском и ручным управлением. Написал, и любовался им долго. Но дядьки, сурово отчитали меня. Ругали, но за чуб не таскали. Дали книжек умных, и советов пользительных.
Дядька Dimezis, ругался сильно за неакуратные имена переменных, да за ключи переменных захардкоженные. Кодстайл ругал тож. Сказал переписать и не позориться.
Дядьки ivazhnov и Alex837 ругали за неаккуратное использование батареи. В морду, мордой ейной не сували, но хмурились сильно. Сказали переписать и не позориться.
Дядька MetAmfetamin, утешил, но поддержал других. Сказал переписать и не позориться».
Ванька почесал за ухом, и продолжил стучать по клавишам.
«Сказали, что нельзя для отлова изменения состояния сети использовать BroadcastReceiver, для которого в манифесте прописано:
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />

Нельзя так писать, ибо батарейку выносит не по-детски:
Образец плохого кода
public class UniversalReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d("AlertTest", "Произошла смена статуса");
        Intent intentNew = new Intent(context, MainActivity.class);
        intentNew.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(intentNew);
    }
}


<receiver android:name=".UniversalReceiver">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
                <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
            </intent-filter>
        </receiver>


Сказали смотреть в сторону JobScheduler или GcmNetworkManager или SyncAdapter.
Долго я думал и решил остановиться на GcmNetworkManager, потому как он для старых версий Android подходит и универсальней мне кажется».
Ванька покосился на гору документации, прочитанной вчера, и зевнул.
«Удалил я для начала все упоминания о UniversalReceiver. Ликвидировал, так сказать, как класс. И из манифеста потёр. Далее создал класс служебный, в который вынес все теги.

Служебный класс Utils
public class Utils {
    public static final String EXTRA_KEY_OUT = "EXTRA_OUT";
    public static final String ACTION_MYINTENTSERVICE = "ru.timgor.alerttest.RESPONSE";
    public static final String TAG = "AlertTest";
    public static final String SUCCESS = "success";
    public static final String AUTOMATIC = "chbAutomatic";
}


Далее в зависимости gradle добавил строчку:
compile 'com.google.android.gms:play-services-gcm:8.1.0'

В MainActivity добавил изменения. Объявил:
private GcmNetworkManager mGcmNetworkManager;

В onCreate:
mGcmNetworkManager = GcmNetworkManager.getInstance(this);
setAutoStart(true);

И метод добавил:
setAutoStart
 public void setAutoStart(boolean isOn){
        if(isOn){
            Log.d(Utils.TAG, "Автозапуск задачи");
            Random myRandom = new Random();
            Bundle bundle = new Bundle();
            bundle.putInt("randomNum", myRandom.nextInt(10));

            PeriodicTask periodicTask = new PeriodicTask.Builder()
                    .setService(AutomaticService.class)
                    .setTag("PeriodicTask")
                    .setPeriod(30)
                    .setPersisted(true)
                    .setExtras(bundle)
                    .setRequiredNetwork(Task.NETWORK_STATE_CONNECTED)
                    .setRequiresCharging(false)
                    .build();
            mGcmNetworkManager.schedule(periodicTask);
        } else {
            Log.d(Utils.TAG, "Остановка автозапуска задачи");
            mGcmNetworkManager.cancelAllTasks(AutomaticService.class);
        }


Тут, дорогой Хабр Хабрович, коль в метод, правда-истина придёт, объявляю я об намерении создать задачу, которая будет периодически запускать службу AutomaticService (setService), носить тег PeriodicTask(setTag), вызываться раз в 30 секунд(setPeriod), работать после перезапуска(setPersisted), передавать случайное число, не работать пока сеть не подключится (setRequiredNetwork) и не требовать подключения к зарядке(setRequiresCharging). А коли в метод кто-то соврамши передаёт, то автоматическая работа прекращается.
Далее создал я службу AutomaticService, да не простую, а наследуемую от GcmTaskService:
AutomaticService
import android.content.Intent;
import android.content.SharedPreferences;
import android.util.Log;
import com.google.android.gms.gcm.GcmNetworkManager;
import com.google.android.gms.gcm.GcmTaskService;
import com.google.android.gms.gcm.TaskParams;

public class AutomaticService extends GcmTaskService {

    @Override
    public int onRunTask(TaskParams taskParams) {
        Log.d(Utils.TAG, "Автоматический запуск. Начало работы");
        Log.d(Utils.TAG, "Переданное число: "+ taskParams.getExtras().getInt("randomNum"));
            if (!verify()) {
                Log.d(Utils.TAG, "AUTO. Задача не отработала");
                Intent responseIntent = new Intent();
                responseIntent.setAction(Utils.ACTION_MYINTENTSERVICE);
                responseIntent.addCategory(Intent.CATEGORY_DEFAULT);
                responseIntent.putExtra(Utils.EXTRA_KEY_OUT, false);
                Log.d(Utils.TAG, "Загрузка не произошла");
                sendBroadcast(responseIntent);
            } else {
                Log.d(Utils.TAG, "AUTO. Задача отработала успешно");
            }
        return GcmNetworkManager.RESULT_SUCCESS;
    }

    public boolean verify(){
        SharedPreferences settings = getSharedPreferences(Utils.TAG, MODE_PRIVATE);
        boolean success = settings.getBoolean("success", false);
        return success;
    }
}


Если служба хорошо отработала, то мы радуемся, а коли не судьба ей исполниться, то запускает BroadcastReceiver, который прописан в MainActivity. Дважды по десять пробует подключиться, с перерывом в 0.1 секунду, да плюнув в сердцах бросает это дело, до следующего тика.
В приложении тестовом я добавил галочку „Очень важная опция“. Коли она нажата, то благополучно задача отрабатывает.
BroadcastReceiver
    public class MyBroadRec extends BroadcastReceiver {
        public int qnt = 0;
        @Override
        public void onReceive(Context context, Intent intent) {
            Boolean result = intent.getBooleanExtra(Utils.EXTRA_KEY_OUT, false);
            Intent intentRec = new Intent(MainActivity.this, ManualService.class);
            if(!result && qnt<20){
                Log.d(Utils.TAG, "Новая попытка № "+qnt);
                qnt++;
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                startService(intentRec);
            }
            else {
                qnt=0;
            }
        }
    }


И в onCreate:

onCreate полностью
 protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        chbAuto = (CheckBox)findViewById(R.id.chb_Auto);
        chbVIP = (CheckBox)findViewById(R.id.chb_VIP);
        btnManual = (Button)findViewById(R.id.btn_Manual);
        mGcmNetworkManager = GcmNetworkManager.getInstance(this);
        sPref = getSharedPreferences(Utils.TAG, MODE_PRIVATE);
        editor = sPref.edit();
        editor.putBoolean(Utils.AUTOMATIC, chbAuto.isChecked());
        editor.putBoolean(Utils.SUCCESS, chbVIP.isChecked());
        editor.commit();

     chbAuto.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                editor.putBoolean(Utils.AUTOMATIC, isChecked);
                editor.commit();
                setAutoStart(isChecked);
            }
        });

        chbVIP.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                editor.putBoolean(Utils.SUCCESS, isChecked);
                editor.commit();
            }
        });

        btnManual.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.d(Utils.TAG, "Загрузка в ручном режиме");
                Intent intent = new Intent(MainActivity.this, ManualService.class);
                startService(intent);
            }
        });

        MyBroadRec myBroadRec = new MyBroadRec();
        IntentFilter intentFilter = new IntentFilter(Utils.ACTION_MYINTENTSERVICE);
        intentFilter.addCategory(Intent.CATEGORY_DEFAULT);
        registerReceiver(myBroadRec, intentFilter);
        setAutoStart(true);
    }


Регистрирую этот ресивер».
Ванька оттер пот, покосился на заманчиво запотевший пузырёк с тёмным чаем, решительно тряхнул головой, и продолжил. «А ещё, хочу запускать я вручную задачу, не дожидаясь тика таймера. Для этого написал я службу ManualService наследующуюся от IntentService.
ManualService
import android.app.IntentService;
import android.content.Intent;
import android.content.SharedPreferences;
import android.util.Log;

public class ManualService extends IntentService {

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

    @Override
    protected void onHandleIntent(Intent intent) {
            if (!verify()) {
                Intent responseIntent = new Intent();
                responseIntent.setAction(Utils.ACTION_MYINTENTSERVICE);
                responseIntent.addCategory(Intent.CATEGORY_DEFAULT);
                responseIntent.putExtra(Utils.EXTRA_KEY_OUT, false);
                sendBroadcast(responseIntent);
                Log.d(Utils.TAG, "MANUAL. Задача не отработала");
            } else {
                Log.d(Utils.TAG, "MANUAL. Задача отработала успешно");
            }
    }

    public boolean verify(){
        SharedPreferences settings = getSharedPreferences(Utils.TAG, MODE_PRIVATE);
        boolean success = settings.getBoolean(Utils.SUCCESS, false);
        return success;
    }
}


Запустил я приложение и радовался очень. Если в режим полёта перейти, да выключить интернеты — то автоматический запуск и не думает запускаться. А вот дорогой дедушка и все классы:
MainActivity
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;

import com.google.android.gms.gcm.GcmNetworkManager;
import com.google.android.gms.gcm.PeriodicTask;
import com.google.android.gms.gcm.Task;

import java.util.Random;

public class MainActivity extends AppCompatActivity {
    CheckBox chbAuto, chbVIP;
    Button btnManual;
    SharedPreferences sPref;
    SharedPreferences.Editor editor;
    private GcmNetworkManager mGcmNetworkManager;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        chbAuto = (CheckBox)findViewById(R.id.chb_Auto);
        chbVIP = (CheckBox)findViewById(R.id.chb_VIP);
        btnManual = (Button)findViewById(R.id.btn_Manual);

        mGcmNetworkManager = GcmNetworkManager.getInstance(this);

        sPref = getSharedPreferences(Utils.TAG, MODE_PRIVATE);
        editor = sPref.edit();
        editor.putBoolean(Utils.AUTOMATIC, chbAuto.isChecked());
        editor.putBoolean(Utils.SUCCESS, chbVIP.isChecked());
        editor.commit();

     chbAuto.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                editor.putBoolean(Utils.AUTOMATIC, isChecked);
                editor.commit();
                setAutoStart(isChecked);
            }
        });

        chbVIP.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                editor.putBoolean(Utils.SUCCESS, isChecked);
                editor.commit();
            }
        });

        btnManual.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.d(Utils.TAG, "Загрузка в ручном режиме");
                Intent intent = new Intent(MainActivity.this, ManualService.class);
                startService(intent);
            }
        });

        MyBroadRec myBroadRec = new MyBroadRec();
        IntentFilter intentFilter = new IntentFilter(Utils.ACTION_MYINTENTSERVICE);
        intentFilter.addCategory(Intent.CATEGORY_DEFAULT);
        registerReceiver(myBroadRec, intentFilter);
        setAutoStart(true);
    }

    public class MyBroadRec extends BroadcastReceiver {
        public int qnt = 0;
        @Override
        public void onReceive(Context context, Intent intent) {
            Boolean result = intent.getBooleanExtra(Utils.EXTRA_KEY_OUT, false);
            Intent intentRec = new Intent(MainActivity.this, ManualService.class);
            if(!result && qnt<20){
                Log.d(Utils.TAG, "Новая попытка № "+qnt);
                qnt++;
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                startService(intentRec);
            }
            else {
                qnt=0;
            }
        }
    }

    public void setAutoStart(boolean isOn){
        if(isOn){
            Log.d(Utils.TAG, "Автозапуск задачи");
            Random myRandom = new Random();
            Bundle bundle = new Bundle();
            bundle.putInt("randomNum", myRandom.nextInt(10));

            PeriodicTask periodicTask = new PeriodicTask.Builder()
                    .setService(AutomaticService.class)
                    .setTag("PeriodicTask")
                    .setPeriod(30)
                    .setPersisted(true)
                    .setExtras(bundle)
                    .setRequiresCharging(false)
                    .setRequiredNetwork(Task.NETWORK_STATE_CONNECTED)
                    .build();
            mGcmNetworkManager.schedule(periodicTask);
        } else {
            Log.d(Utils.TAG, "Остановка автозапуска задачи");
            mGcmNetworkManager.cancelAllTasks(AutomaticService.class);
        }
    }
}

AutomaticService
import android.content.Intent;
import android.content.SharedPreferences;
import android.util.Log;
import com.google.android.gms.gcm.GcmNetworkManager;
import com.google.android.gms.gcm.GcmTaskService;
import com.google.android.gms.gcm.TaskParams;

public class AutomaticService extends GcmTaskService {
   @Override
    public int onRunTask(TaskParams taskParams) {
        Log.d(Utils.TAG, "Автоматический запуск. Начало работы");
        Log.d(Utils.TAG, "Переданное число: "+ taskParams.getExtras().getInt("randomNum"));
            if (!verify()) {
                Log.d(Utils.TAG, "AUTO. Задача не отработала");
                Intent responseIntent = new Intent();
                responseIntent.setAction(Utils.ACTION_MYINTENTSERVICE);
                responseIntent.addCategory(Intent.CATEGORY_DEFAULT);
                responseIntent.putExtra(Utils.EXTRA_KEY_OUT, false);
                Log.d(Utils.TAG, "Загрузка не произошла");
                sendBroadcast(responseIntent);
            } else {
                Log.d(Utils.TAG, "AUTO. Задача отработала успешно");
            }
        return GcmNetworkManager.RESULT_SUCCESS;
    }

    public boolean verify(){
        SharedPreferences settings = getSharedPreferences(Utils.TAG, MODE_PRIVATE);
        boolean success = settings.getBoolean("success", false);
        return success;
    }
}


ManualService
import android.app.IntentService;
import android.content.Intent;
import android.content.SharedPreferences;
import android.util.Log;

public class ManualService extends IntentService {

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

    @Override
    protected void onHandleIntent(Intent intent) {
            if (!verify()) {
                Intent responseIntent = new Intent();
                responseIntent.setAction(Utils.ACTION_MYINTENTSERVICE);
                responseIntent.addCategory(Intent.CATEGORY_DEFAULT);
                responseIntent.putExtra(Utils.EXTRA_KEY_OUT, false);
                sendBroadcast(responseIntent);
                Log.d(Utils.TAG, "MANUAL. Задача не отработала");
            } else {
                Log.d(Utils.TAG, "MANUAL. Задача отработала успешно");
            }
    }

    public boolean verify(){
        SharedPreferences settings = getSharedPreferences(Utils.TAG, MODE_PRIVATE);
        boolean success = settings.getBoolean(Utils.SUCCESS, false);
        return success;
    }
}


Utils
public class Utils {
    public static final String EXTRA_KEY_OUT = "EXTRA_OUT";
    public static final String ACTION_MYINTENTSERVICE = "ru.timgor.alerttest.RESPONSE";
    public static final String TAG = "AlertTest";
    public static final String SUCCESS = "success";
    public static final String AUTOMATIC = "chbAutomatic";
}


Зависимости gradle

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:23.4.0'
    compile 'com.google.android.gms:play-services-gcm:8.1.0'
}


Manifest

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="ru.alerttest">
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <service android:name=".AutomaticService"
            android:permission="com.google.android.gms.permission.BIND_NETWORK_TASK_SERVICE"
            android:exported="true">
            <intent-filter>
                <action android:name="com.google.android.gms.gcm.ACTION_TASK_READY"/>
            </intent-filter>
        </service>
        <service android:name=".ManualService"/>
    </application>
</manifest>


activity_main

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="ru.alerttest.MainActivity">
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Ручной запуск"
        android:id="@+id/btn_Manual" />
    <CheckBox
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Автоматический  запуск"
        android:id="@+id/chb_Auto"
        android:checked="true"/>
    <CheckBox
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Очень важная опция"
        android:id="@+id/chb_VIP"
        android:checked="true"/>
</LinearLayout>


В общем, дорогой дедушка, получился таймер просто загляденье. Буду ждать, что дядьки скажут. А за меня не волнуйся. Хочу стать я разработчиком умным и стараться буду впредь. Засим желаю тебе здоровья крепкого и выходных увлекательных».
Ванька подвинул клавиатуру, набулькал из запотевшего пузырька чая и потянулся к кнопке «Опубликовать». Подумав немного, набрал в теге «На деревню дедушке». Почесал мышку за ухом, и добавил «Хабр Хабровичу».
Профессора из института рассказывали, что публикации разносятся по проводам оптоволоконным и медным, по всему интернету, управляемые веселыми админами. Ванька собрался с духом и нажал большую зелёную кнопку.
Убаюканный сладкими надеждами, он час спустя крепко спал… Ему снился Half-Life 3.

P.S.: Обновленный код, исправленный по замечаниям уважаемых комментаторов shakagamii и Parnt
MainActivity
public class MainActivity extends AppCompatActivity {
    CheckBox chbAuto, chbVIP;
    Button btnManual;
    SharedPreferences sPref;
    SharedPreferences.Editor editor;
    private GcmNetworkManager mGcmNetworkManager;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        chbAuto = (CheckBox)findViewById(R.id.chb_Auto);
        chbVIP = (CheckBox)findViewById(R.id.chb_VIP);
        btnManual = (Button)findViewById(R.id.btn_Manual);
        final MyApplicaion app = (MyApplicaion)getApplicationContext();

        mGcmNetworkManager = GcmNetworkManager.getInstance(this);
        final PrefHelper prefHelper = PrefHelper.getInstance();

     chbAuto.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                prefHelper.setAutomatic(isChecked);
                setAutoStart(isChecked);
            }
        });

        chbVIP.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                prefHelper.setSuccess(isChecked);
            }
        });

        prefHelper.setAutomatic(chbAuto.isChecked());
        prefHelper.setSuccess(chbVIP.isChecked());

        btnManual.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.d(Utils.TAG, "Загрузка в ручном режиме");
                Intent intent = new Intent(MainActivity.this, ManualService.class);
                startService(intent);
            }
        });

        MyBroadRec myBroadRec = new MyBroadRec();
        IntentFilter intentFilter = new IntentFilter(Utils.ACTION_MYINTENTSERVICE);
        intentFilter.addCategory(Intent.CATEGORY_DEFAULT);
        registerReceiver(myBroadRec, intentFilter);
        setAutoStart(prefHelper.isAutomatic());
    }

    public class MyBroadRec extends BroadcastReceiver {
        public int qnt = 0;
        @Override
        public void onReceive(Context context, Intent intent) {
            Boolean result = intent.getBooleanExtra(Utils.EXTRA_KEY_OUT, false);
            Intent intentRec = new Intent(MainActivity.this, ManualService.class);
            if(!result && qnt<20){
                Log.d(Utils.TAG, "Новая попытка № "+qnt);
                qnt++;
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                startService(intentRec);
            }
            else {
                qnt=0;
            }
        }
    }

    public void setAutoStart(boolean isOn){
        if(isOn){
            Log.d(Utils.TAG, "Автозапуск задачи");
            Random myRandom = new Random();
            Bundle bundle = new Bundle();
            bundle.putInt(Utils.RANDOM_NUMS, myRandom.nextInt(10));

            PeriodicTask periodicTask = new PeriodicTask.Builder()
                    .setService(AutomaticService.class)
                    .setTag("PeriodicTask")
                    .setPeriod(30)
                    .setPersisted(true)
                    .setExtras(bundle)
                    .setRequiresCharging(false)
                    .setRequiredNetwork(Task.NETWORK_STATE_CONNECTED)
                    .build();
            mGcmNetworkManager.schedule(periodicTask);
        } else {
            Log.d(Utils.TAG, "Остановка автозапуска задачи");
            mGcmNetworkManager.cancelAllTasks(AutomaticService.class);
        }
    }
}



Новый класс, для работы с настройками
public class PrefHelper {
    private static  PrefHelper mInstance;
    private static SharedPreferences sPref;
    private static SharedPreferences.Editor editor;


    public static void setInstance(Context context){
        mInstance = new PrefHelper(context);
    }

    public static PrefHelper getInstance(){
        return mInstance;
    }

    private PrefHelper(Context context){
        this.sPref = PreferenceManager.getDefaultSharedPreferences(context);
        this.editor = this.sPref.edit();
    }

    public boolean isAutomatic() {
        return sPref.getBoolean(Utils.AUTOMATIC, false);
    }

    public void setAutomatic(boolean automatic) {
        editor.putBoolean(Utils.AUTOMATIC,automatic);
        editor.apply();
    }

    public boolean isSuccess() {
        return sPref.getBoolean(Utils.SUCCESS, false);
    }

    public void setSuccess(boolean success) {
        editor.putBoolean(Utils.SUCCESS, success);
        editor.apply();
    }
}



Наследник от Application
public class MyApplicaion extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        PrefHelper.setInstance(this);
    }
}



AutomaticService - автоматический запуск
public class AutomaticService extends GcmTaskService {
   @Override
    public int onRunTask(TaskParams taskParams) {
        Log.d(Utils.TAG, "Автоматический запуск. Начало работы");
        Log.d(Utils.TAG, "Переданное число: "+ taskParams.getExtras().getInt(Utils.RANDOM_NUMS));
       if (!PrefHelper.getInstance().isSuccess()) {
                Log.d(Utils.TAG, "AUTO. Задача не отработала");
                Intent responseIntent = new Intent();
                responseIntent.setAction(Utils.ACTION_MYINTENTSERVICE);
                responseIntent.addCategory(Intent.CATEGORY_DEFAULT);
                responseIntent.putExtra(Utils.EXTRA_KEY_OUT, false);
                Log.d(Utils.TAG, "Загрузка не произошла");
                sendBroadcast(responseIntent);
            } else {
                Log.d(Utils.TAG, "AUTO. Задача отработала успешно");
            }
        return GcmNetworkManager.RESULT_SUCCESS;
    }
}



ManualService - запуск задачи вручную
public class ManualService extends IntentService {

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

    @Override
    protected void onHandleIntent(Intent intent) {
        if (!PrefHelper.getInstance().isSuccess()) {
                Intent responseIntent = new Intent();
                responseIntent.setAction(Utils.ACTION_MYINTENTSERVICE);
                responseIntent.addCategory(Intent.CATEGORY_DEFAULT);
                responseIntent.putExtra(Utils.EXTRA_KEY_OUT, false);
                sendBroadcast(responseIntent);
                Log.d(Utils.TAG, "MANUAL. Задача не отработала");
            } else {
                Log.d(Utils.TAG, "MANUAL. Задача отработала успешно");
            }
    }
}



Manifest
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="ru.cse.alerttest">
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:name=".MyApplicaion"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <service android:name=".AutomaticService"
            android:permission="com.google.android.gms.permission.BIND_NETWORK_TASK_SERVICE"
            android:exported="true">
            <intent-filter>
                <action android:name="com.google.android.gms.gcm.ACTION_TASK_READY"/>
            </intent-filter>
        </service>
        <service android:name=".ManualService"/>
    </application>
</manifest>



Utils
public class Utils {
    public static final String EXTRA_KEY_OUT = "EXTRA_OUT";
    public static final String ACTION_MYINTENTSERVICE = "ru.timgor.alerttest.RESPONSE";
    public static final String TAG = "AlertTest";
    public static final String SUCCESS = "success";
    public static final String AUTOMATIC = "chbAutomatic";
    public static final String RANDOM_NUMS = "randomNum";
}

Поделиться с друзьями
-->

Комментарии (23)


  1. kirill-93
    09.09.2016 22:23
    +1

    И попало письмо по проводам оптоволоконным и медным к администраторам веселым.
    И читали его программисты ученые и хвалили Ваньку за старания евоные.


    1. Snakecatcher
      09.09.2016 22:44

      А Ванька-то, слушал да радовался тихо. Похвала ж, она и кошке приятна, даром, что скотина она бессловесная, а уж начинающему разработчику, как бальзам на душу. :)


  1. shakagamii
    09.09.2016 22:24
    +1

    Хороший вы ресурс нашли для оттачивания кода)
    Вам в прошлом посте вроде советовали Rx, гляньте на него, ну правда.
    А теперь вопросы.
    1. Разметка активити, хардкод текста
    2. Класс Utils (который больше похож на «Constants»), часть переменных final, часть нет. Создается впечатление что вы не видите разницу.
    3. Метод verify(). Во первых, повторяется 2 раза, принцип DRY считай нарушен. Насколько я понимаю метод дергается при каждой новой попытке, и каждый раз создает новый экземпляр SharedPreferences, как-то это не правильно. Да и вообще хранить данные переменные в SharedPreferences не совсем верно.
    4. Я даже боюсь думать, что будет если запустить все это, и начать крутить телефон)
    Почитайте про Rx и MVP, вы сможете упростить и укоротить свой код в разы)


    1. Snakecatcher
      09.09.2016 22:41

      1. Уточните пожалуйста, что вы имеете в виду? Вроде от хардкода (если я правильно понимаю — жесткое «вшивание» в программный код различных данных, касающихся окружения программы), избавился. Разметка активности — самая простая.
      2. Спасибо, поправил. Невнимательность. Изначально там была пара функций, удаленных за ненадобностью, вот название и осталось.
      3. Была мысль вынести его в Utils, сделав статическим, а SharedPreferences брать из службы.
      Примерно так:

      public class Utils {
          public static final String EXTRA_KEY_OUT = "EXTRA_OUT";
          public static final String ACTION_MYINTENTSERVICE = "ru.timgor.alerttest.RESPONSE";
          public static final String TAG = "AlertTest";
          public static final String SUCCESS = "success";
          public static final String AUTOMATIC = "chbAutomatic";
      
          public static boolean verify(SharedPreferences settings){
              boolean success = settings.getBoolean(Utils.SUCCESS, false);
              return success;
          }
      }
      

      и соответственно, на примере AutomaticService:
      import android.content.Intent;
      import android.content.SharedPreferences;
      import android.util.Log;
      import com.google.android.gms.gcm.GcmNetworkManager;
      import com.google.android.gms.gcm.GcmTaskService;
      import com.google.android.gms.gcm.TaskParams;
      
      public class AutomaticService extends GcmTaskService {
         @Override
          public int onRunTask(TaskParams taskParams) {
          SharedPreferences settings = getSharedPreferences(Utils.TAG, MODE_PRIVATE);
              Log.d(Utils.TAG, "Автоматический запуск. Начало работы");
              Log.d(Utils.TAG, "Переданное число: "+ taskParams.getExtras().getInt("randomNum"));
                  if (!Utils.verify(settings)) {
                      Log.d(Utils.TAG, "AUTO. Задача не отработала");
                      Intent responseIntent = new Intent();
                      responseIntent.setAction(Utils.ACTION_MYINTENTSERVICE);
                      responseIntent.addCategory(Intent.CATEGORY_DEFAULT);
                      responseIntent.putExtra(Utils.EXTRA_KEY_OUT, false);
                      Log.d(Utils.TAG, "Загрузка не произошла");
                      sendBroadcast(responseIntent);
                  } else {
                      Log.d(Utils.TAG, "AUTO. Задача отработала успешно");
                  }
              return GcmNetworkManager.RESULT_SUCCESS;
          }
      }
      

      Как думаете, так лучше?
      4. Про повороты-то я не подумал. Спасибо.

      C Rx сижу, читаю, уму-разуму набираюсь :)


      1. shakagamii
        09.09.2016 23:00

        1. Я имел ввиду текст на кнопках и чекбоксах, не вынесенный в strings, хотя это скорее мои придирки учитывая что это тест проект на коленке.
        3. Вам не нужно постоянно лазить в SharedPreferences. Он вам нужен чтобы загрузить/сохранить «статус» элементов, а для работы в коде нужно использовать какую либо переменную. А SharedPreferences лучше вообще один раз инициализировать при запуске приложения.


        1. Snakecatcher
          09.09.2016 23:33

          А как предложенный вариант, на ваш взгляд?


          1. shakagamii
            09.09.2016 23:54

            Вариант чего? Инициализации SharedPreferences? Ну для примера.
            Наследуетесь от Application, инициализируете SharedPreferences. Данный App нужно также прописать в манифесте, чтобы работало.

            public class App extends Application {
                public static SharedPreferences sSharedPreferences;
            
                @Override
                public void onCreate() {
                    super.onCreate();
                    sSharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); // или ваш вариант, с кастомным именем
                }
            
                public static SharedPreferences getSharedPreferences() {
                    return sSharedPreferences;
                }
                
            }
            


            После, например создаете синглтон, что-то вида.

            public class PreferencesHelper {
                private static PreferencesHelper INSTANCE = null;
                private SharedPreferences mSharedPreferences;
                private SharedPreferences.Editor mEditor;
            
                public static PreferencesHelper getInstance() {
                    if(INSTANCE == null) {
                        INSTANCE = new PreferencesHelper();
                    }
                    return INSTANCE;
                }
            
                public PreferencesHelper() {
                    this.mSharedPreferences = App.getSharedPreferences();
                    this.mEditor = this.mSharedPreferences.edit();
                }
            
                //для примера сохранение данных в SharedPreferences
                public void saveStatusCheckbox(String name, boolean status){
                    mEditor.putBoolean(name, status);
                    mEditor.apply();
                }
            
                //для примера загрузка из данных из SharedPreferences
                public int loadStatusCheckbox(String name){
                    return mSharedPreferences.getBoolean(name, false);
                }
            }
            


            и используете его в Activity вот так.
            PreferencesHelper helper = PreferencesHelper.getInstance();
            Boolean vip_status =  helper.loadStatusCheckbox(Constants.CHECK_BOX_VIP_NAME);
            

            А с переменной vip_status прыгать по методам дальше. И при изменении самого чекбокса менять ее, а не значение в Preferences. Его сохранять при выходе из активити, например в том же onDestroy.

            Но это чисто работа с Preferences. Важнее продумать работу например при повороте экрана, или вариант того, что пользователь начнет упорно клацать кнопку ручного запуска задачи, тогда ваше приложение скорее всего упадет из-за нехватки памяти)


            1. Snakecatcher
              10.09.2016 00:49

              Понял. Спасибо, за уделенное время.


            1. Parnt
              11.09.2016 21:28
              +1

              Небольшое дополнение.
              Для классической реализации синглтон, конструктор private и чтобы избежать проблем добавить synchronized в метод getInstance

              private PreferencesHelper() {
                      this.mSharedPreferences = App.getSharedPreferences();
                      this.mEditor = this.mSharedPreferences.edit();
                  }
              
              public static synchronized PreferencesHelper getInstance() {
                      if(INSTANCE == null) {
                          INSTANCE = new PreferencesHelper();
                      }
                      return INSTANCE;
                  }
              


              или что мне больше нравится, разделить метод getInstance на два метода initInstance и getInstance:
              public class App extends Application {
              
                  @Override
                  public void onCreate() {
                      super.onCreate();
                      PreferencesHelper.initInstance(this);
                  }
                  
              }
              
              public class PreferencesHelper {
                  private static PreferencesHelper INSTANCE = null;
                  private SharedPreferences mSharedPreferences;
                  private SharedPreferences.Editor mEditor;
              
                  public static void initInstance(Conext context) {
                          INSTANCE = new PreferencesHelper(context);
                  }
              
                  public static PreferencesHelper getInstance() {
                      return INSTANCE;
                  }
              
                  private PreferencesHelper(Conext context) {
                      this.mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
                      this.mEditor = this.mSharedPreferences.edit();
                  }
              
                  //для примера сохранение данных в SharedPreferences
                  public void saveStatusCheckbox(String name, boolean status){
                      mEditor.putBoolean(name, status);
                      mEditor.apply();
                  }
              
                  //для примера загрузка из данных из SharedPreferences
                  public int loadStatusCheckbox(String name){
                      return mSharedPreferences.getBoolean(name, false);
                  }
              }
              


              1. shakagamii
                11.09.2016 21:54
                +1

                Ну, во-первых.
                Какой практический смысл разделения метода getInstance на 2 отдельных? Где может понадобиться инициализировать его, но не вызывать?
                Во-вторых.
                Вы предлагаете привязывать класс хелпера к контексту каждого нового активити и фрагмента? Так, а зачем вам тогда синглтон в данном случае?
                Каждый раз вы будете дергать новый SharedPreferences, я наоборот уводил от этого автора.


                1. Parnt
                  11.09.2016 22:28
                  +1

                  Отвечу сначала на второй вопрос: Как видно из кода в моем посте PreferencesHelper инициализируется с использованием контекста Application в методе onCreate наследника класса Application, а не Activity или фрагмента.

                  «Application.onCreate called when the application is starting, before any activity, service, or receiver objects (excluding content providers) have been created. »
                  и т.о. метод initInstance вызывается один раз и инициализирует static INSTANCE соответственно тоже один раз при старте процесса приложения.
                  Метод getInstance просто getter, предоставляющий доступ к INTANCE. Все время используется один и тот же SharedPreferences, который получен при старте приложения.


              1. shakagamii
                11.09.2016 22:09

                Извиняюсь за «наезд», прочитал ваш код еще раз и все таки увидел переписанный App и все стало на свои места)
                Целый день за монитором дает о себе знать. Надо идти спать…


                1. Parnt
                  11.09.2016 22:31

                  Взаимно :). Я написал свой ответ, а потом только увидел, Ваш второй ответ.


                  1. Snakecatcher
                    12.09.2016 23:29

                    Оффтопик, но как шикарно читать, когда спорят два вежливых человека :)


  1. MetAmfetamin
    12.09.2016 20:56

    Раз уж меня упомянули то предётся зайти. Итак:


    1. MetAmfetamin
      12.09.2016 21:51

      По GcmNetworkManager:

      • Итак, сразу бросается в глаза странное использование GcmNetworkManager. Фактически он сейчас заменяет простой AlarmManager. Хотелось бы чтобы он всё-таки использовался по назначению.
      • Задача явно должна быть не PeriodicTask. И тогда если она завершилась с ошибкой достаточно будет вернуть RESULT_RESCHEDULE. Система всё сделает самостоятельно её перезапустит в соответсвии с back-off policy. Я бы делал, например, 3 запроса подряд и возвращал RESULT_RESCHEDULE если всё плохо.
      • Также было бы замечательно добавить к задаче gap (flex или большой setExecutionWindow) для выполнения. А также setUpdateCurrent(true) для того чтобы не плодить несколько задач по ошибке. Как легко можно сделать сейчас, выходя и заходя в приложение снова.


      По общей структуре:
      • Допустим что серия из 20 вызовов это бизнес требование, тогда, мне кажется, что лучше это делать в одном вызове IntentService. Зачем эта петля с созданием «broadcast <> service <> broadcast» не совсем понятно.
      • Дублирование кода в сервисах нужно убрать, это ни к чему хорошему не приводит.
      • Вызывать Thread.sleep в MainThread (MyBroadRec.onReceive) не самая лучшая идея.
      • Если уж используете broadcast, то желательно использовать LocalBroadcastManager.


      Написал несколько сумбурно и урывками, т.к. достаточно неудобно это делать после статьи. Pull-request на порядок удобнее, даже несмотря на отсутствие навигации по коду (конечно исключая upsource).
      В общем, все стало немного лучше. Можно даже сказать — идём правильно дорогой… но пока что на руках и забираем немного в сторону.


      1. Snakecatcher
        12.09.2016 23:28

        Рад видеть вас. :)
        В рабочем приложении, все завязано на наличие устойчивой связи с интернетом. Если есть связь, то раз в 300 секунд отрабатывает задача синхронизации с сервером. Разве не так надо использовать? За образец я брал эту статью.
        Уточните, пожалуйста, а что это gap и flex? Не нашел в сети. А что нашел, то кажется не то.
        Дублирование убрал, и переписал через Application и синглтоны, как посоветовали выше.
        Завтра выложу, улучшенный код в примечаниях.


        1. MetAmfetamin
          13.09.2016 00:43

          Тогда всё в порядке, я не прав. Моя идея была запустить задачу только если нужно что-то синхронизировать и отдать на откуп системе когда всё это выполниться. Т.е. использовать OneoffTask и возвращать RESULT_RESCHEDULE пока всё-таки не получиться синхронизироваться. Но если важен вызов каждые N секунд, тогда конечно нужно использовать PeriodicTask, только нужно добавить условие выхода из цикла обновления, а то вызов каждые 30 секунд даже когда обновлять не нужно — выглядит не очень правильно.


          Под "gap" я подразумевал окно в которое может быть вызвана задача, для PeriodicTask это устанавливается методом setFlex. Лучше задавать окна, это поможет системе группировать выполнение различных задач и будить устройство немного реже.


          1. Snakecatcher
            13.09.2016 10:10

            Там такое требование, синхронизироваться каждые 5 минут и проверять, появилось ли что-то новенькое?
            А, под условием выхода, не подходит этот кусок:

             if(!result && qnt<20){
                            Log.d(Utils.TAG, "Новая попытка № "+qnt);
                            qnt++;
                            try {
                                Thread.sleep(100);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            startService(intentRec);
                        }
                        else {
                            qnt=0;
                        }
                    }
            


            Опубликовал новый код в примечании.


            1. MetAmfetamin
              13.09.2016 21:05

              Ага, тогда понятно. Тогда всё нормально, под условие выхода этот код вполне подходит.
              У меня несколько другой опыт — обычно что-то загружаю на сервер, а не скачиваю с него. Отсюда и попытки внести не нужные исправления.


              1. Snakecatcher
                13.09.2016 23:37

                Но ваши замечания очень ценные.
                И мне еще предстоит вторая часть задачи, как раз отправлять на сервер результаты деятельности пользователя. :)


  1. enginegl
    13.09.2016 18:31

    Почему вы решили не использовать систему контроля версий? Если кто-то ещё будет вносить предложения, появятся «P.S.S.»? :)


    1. Snakecatcher
      13.09.2016 23:34

      Буду использовать, обязательно. :)

      P.S.: А ведь шикарный заголовок мог бы получиться «P.S. Хабр, я люблю тебя» :)