Таймер с ручным запуском (работа над ошибками)

в 14:19, , рубрики: android, на деревню дедушке Хабр Хабровичу, работа над ошибками, Разработка под android

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

Таймер с ручным запуском (работа над ошибками) - 1

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

Ванька покосился на Скайп и живо вообразил себе Хабра Хабровича. Образ получился впечатляющий, но слишком объёмный. Ванька, вздохнул продолжил писать.
«А вчерась мне была выволочка. Надумал я написать свой таймер, с автоматическим запуском и ручным управлением. Написал, и любовался им долго. Но дядьки, сурово отчитали меня. Ругали, но за чуб не таскали. Дали книжек умных, и советов пользительных.
Дядька 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.

Автор: Snakecatcher

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js