Служба намерений не работает в режиме ожидания

#android #android-wake-lock #android-doze

#Android #android-6.0-marshmallow #android-intentservice #android-doze

Вопрос:

Один из моих коллег-разработчиков написал intent service приложение, которое выполняет вызов API, а затем спит в течение 2 минут. После пробуждения он отправляет снова.

Ниже приведен код:

 public class GpsTrackingService extends IntentService {

....

@Override
    protected void onHandleIntent(Intent intent) {
      do{
        try{
          //make API call here

           //then go to sleep for 2 mins
          TimeUnit.SECONDS.sleep(120);
       
        } catch(InterruptedException ex){
            ex.printStackTrace();
        }
      } while (preferences.shouldSendGps()); //till the user can send gps.

    }

....

}


Manifest

<service android:name=".commons.GpsTrackingService" /> 

Это работает нормально, когда телефон активен. Однако всякий раз, когда телефон переходит в режим ожидания, он не просыпается.

Решит ли это с помощью alarm manager WAKE permission ?

Я только что получил кодовую базу, и мне нужно исправить это в течение сегодняшнего дня. Будет здорово, если кто-нибудь сможет помочь.

Комментарии:

1. Это бесконечно запущенный сервис? В этом случае цикла нет. Является ли какой-либо другой объект, вызывающий эту службу повторно? В этом случае также опубликуйте этот код здесь.

2. @n.arrow001 Он работает бесконечно, я обновил цикл do while.

Ответ №1:

Как говорится в документации:

В режиме ожидания система пытается сэкономить заряд батареи, ограничивая доступ приложений к сетевым и ресурсоемким службам. Это также предотвращает доступ приложений к сети и откладывает их задания, синхронизацию и стандартные сигналы тревоги.

Периодически система на короткое время выходит из режима ожидания, чтобы позволить приложениям завершить свои отложенные действия. Во время этого окна обслуживания система запускает все ожидающие синхронизации, задания и аварийные сигналы и позволяет приложениям получать доступ к сети.

В нескольких словах, в режиме ожидания система приостанавливает доступ к сети, игнорирует блокировки пробуждения, прекращает сбор данных с датчиков, откладывает задания AlarmManager до следующего окна обслуживания Doze (которые вызываются все реже), также не выполняются сканирование Wi-Fi, задания JobScheduler и адаптеры синхронизации.

Ни setAndAllowWhileIdle(), ни setExactAndAllowWhileIdle() не могут запускать сигналы тревоги более одного раза за 9 (?) минут для каждого приложения.

И похоже, что службы переднего плана также вовлечены в эту «Драму сна», по крайней мере, в MarshMellow (M).

Чтобы выжить в этой ситуации, необходимо, по крайней мере, пересмотреть множество приложений. Можете ли вы представить себе простой mp3-плеер, который перестает воспроизводить музыку, когда устройство переходит в режим ожидания?

Режим ожидания запускается автоматически, когда устройство отключено от источника питания и оставлено на столе примерно на 1 час или около того, или даже раньше, когда пользователь нажимает кнопку питания, чтобы выключить экран, но я думаю, что это также может зависеть от производителя устройства.

Я перепробовал много контрмер, некоторые из них действительно веселые.

В конце моих тестов я нашел возможное решение:

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

Что вам нужно сделать, это в основном следующее (вы можете создать простой проект для его тестирования):

  • 1 — В вашем новом проекте создайте новый класс, который расширяет Application (MyApp), или используйте основную активность нового проекта.
  • 2 — В MyApp onCreate() запустите службу (myAntiDozeService)
  • 3 — В myAntiDozeService onStartCommand() создайте уведомление, необходимое для запуска службы в качестве службы переднего плана, запустите службу с помощью startForeground(id, notification) и получите частичную блокировку пробуждения.

ПОМНИТЕ!Это будет работать, но это только отправная точка, потому что вы должны быть осторожны с «побочными эффектами», которые этот подход создаст:

  • 1 — Разрядка батареи: процессор будет работать для вашего приложения вечно, если вы не используете какую-либо стратегию и не оставляете блокировку всегда активной.
  • уведомление 2 — One будет отображаться всегда, даже на экране блокировки, и это уведомление нельзя удалить, просто проведя по нему пальцем, оно всегда будет там, пока вы не остановите службу переднего плана.

Хорошо, давайте сделаем это.

myApp.java

     public class myApp extends Application {
    private static final String STARTFOREGROUND_ACTION = "STARTFOREGROUND_ACTION";
    private static final String STOPFOREGROUND_ACTION = "STOPFOREGROUND_ACTION";

        @Override
        public void onCreate() {

            super.onCreate();            

            // start foreground service
            startForeService();
    }

    private void stopForeService() {
        Intent service = new Intent(this, myAntiDozeService.class);
        service.setAction(STOPFOREGROUND_ACTION);
        stopService(service);
    }

    private void startForeService(){
        Intent service = new Intent(this, myAntiDozeService.class);
        service.setAction(STARTFOREGROUND_ACTION);
        startService(service);
    }

    @Override
    public void onTerminate() {
        stopForeService();
        super.onTerminate();
    }
}
 

myAntiDozeService.java

 public class myAntiDozeService extends Service {

    private static final String TAG = myAntiDozeService.class.getName();
    private static boolean is_service_running = false;
    private Context mContext;
    private PowerManager.WakeLock mWakeLock;
    private static final int NOTIFICATION_ID = 12345678;
    private static final String STARTFOREGROUND_ACTION = "STARTFOREGROUND_ACTION";
    private static final String STOPFOREGROUND_ACTION = "STOPFOREGROUND_ACTION";

    @Override
    public void onCreate() {
        super.onCreate();
        mContext = getApplicationContext();

    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        if (!is_service_running amp;amp; STARTFOREGROUND_ACTION.equals(intent.getAction())) {
            Log.i(TAG, "Received Start Foreground Intent ");
            showNotification();
            is_service_running = true;
            acquireWakeLock();

        } else if (is_service_running amp;amp; STOPFOREGROUND_ACTION.equals(intent.getAction())) {
            Log.i(TAG, "Received Stop Foreground Intent");
            is_service_running = false; 
            stopForeground(true);
            stopSelf();
        }

        return START_STICKY;
    }

    @Override
    public void onDestroy() {
        releaseWakeLock();
        super.onDestroy();
    }

    private void showNotification(){

        Intent notificationIntent = new Intent(mContext, ActivityMain.class);
        notificationIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
        PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, notificationIntent, 0);

        Notification notification = new NotificationCompat.Builder(mContext)
                .setContentTitle("myApp")
                .setTicker("myApp")
                .setContentText("Application is running")
                .setSmallIcon(R.drawable.ic_launcher)
                .setContentIntent(pendingIntent)
                .build();

        // starts this service as foreground
        startForeground(NOTIFICATION_ID, notification);
    }

    public void acquireWakeLock() {
        final PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
        releaseWakeLock();
        //Acquire new wake lock
        mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG "PARTIAL_WAKE_LOCK");
        mWakeLock.acquire();
    }

    public void releaseWakeLock() {
        if (mWakeLock != null amp;amp; mWakeLock.isHeld()) {
            mWakeLock.release();
            mWakeLock = null;
        }
    }
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}
 

AndroidManifest.xml перемены.


В AndroidManifest.xml добавьте это разрешение:

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

Не забудьте добавить название вашего приложения в <application> тег:

 <application
        ....
        android:name=".myApp"
        ....
 

И, наконец, добавьте свою службу переднего плана, запущенную в другой процесс:

    <service
        android:name=".myAntiDozeService"
        android:process=":MyAntiDozeProcessName">
    </service>
 

Пара замечаний.

  • В предыдущем примере созданное уведомление при нажатии открывает действие ActivityMain вашего тестового проекта.

    Intent notificationIntent = new Intent(mContext, ActivityMain.class) ;

    но вы можете использовать и другой вид намерения.

  • Чтобы протестировать его, вам нужно добавить в свой ActivityMain.java , например, какой-либо повторяющийся сигнал тревоги (который обычно прекращался, когда устройство переходило в режим ожидания), или преждевременный доступ к сети, или воспроизводимый по времени звуковой сигнал, или…. все, что вы хотите.
  • Помните, что задание, выполняемое основным действием, должно выполняться вечно, потому что для тестирования этого антидождя вам нужно подождать не менее 1 часа, чтобы убедиться, что устройство переходит в режим ожидания.
  • Чтобы войти в режим ожидания, устройство должно быть тихим и отключенным от сети, поэтому вы не сможете протестировать его во время отладки. Сначала отладьте свое приложение, убедитесь, что все запущено, затем остановите его, отключите, перезапустите приложение снова и оставьте устройство в покое и тихо на вашем столе.
  • adb Команды, предлагаемые документацией для имитации режимов ожидания и ожидания, могут и не могут дать вам правильные результаты (я полагаю, это зависит от производителя устройства, драйверов и прочего). Пожалуйста, сделайте свои тесты в РЕАЛЬНОМ поведении.

В моем первом тесте я использовал AlarmManager и генератор сигналов, чтобы воспроизводить сигнал каждые 10 минут, просто чтобы понять, что мое приложение все еще активно. И он все еще работает примерно с 18 часов, разрывая мои уши громким звуком ровно каждые 10 минут. 🙂

Счастливого кодирования!

Комментарии:

1. Что происходит в случае, когда пользователь удаляет приложение из списка задач? Я думаю, что служба умирает, как и повторяющийся сигнал тревоги. Если у вас есть фоновая работа, которая тоже будет остановлена.

2. Пролистывание приложения из списка не завершает работу приложения. Вам нужно перейти в настройки -> приложения -> выбрать приложение и завершить его с помощью кнопки завершения.

3. Я пробовал это на Android 6, но когда я запускаю службу переднего плана для другого процесса, моя служба больше не запускается.. Любой намек? android:process=":JobServiceProcess"

4. Спасибо за это! По сути, это спасло мое приложение 🙂

Ответ №2:

Один из моих коллег-разработчиков написал службу intent, которая выполняет вызов API, а затем спит в течение 2 минут. После пробуждения он отправляет снова.

Запускайте службу только тогда, когда она активно предоставляет пользователю ценность. Сидение в течение двух минут, наблюдая за тиканьем часов, не приносит активной пользы пользователю.

Решит ли это использование alarm manager с разрешением WAKE?

Это зависит от того, что вы подразумеваете под «решить это». Вы можете использовать AlarmManager для запроса, чтобы получать контроль каждые две минуты, чтобы вы могли выполнять работу. Пока устройство находится в режиме ожидания, вы фактически будете получать управление не каждые две минуты, а один раз за окно обслуживания.

Комментарии:

1. Есть ли способ переопределить режим ожидания, чтобы приложение отправляло информацию каждые 2 минуты, независимо от того, находится ли телефон в режиме ожидания или используется.

2. @hannanessay: пользователь может поместить ваше приложение в белый список оптимизации заряда батареи через приложение «Настройки».