简体   繁体   English

我的 Android 11 应用程序在用户将其置于后台时停止运行,除非手机已连接到电源

[英]My Android 11 app stops running when the user puts it into the background unless the phone is connected to power

I wrote a foreground service to make sure my app can continue running when put into the background.我编写了一个前台服务,以确保我的应用程序在进入后台时可以继续运行。 The app needs to run in the background because after its timer elapses, it sounds a tone and vibrates to alert the user.该应用程序需要在后台运行,因为在其计时器结束后,它会发出提示音并振动以提醒用户。 However, when the Power or Home button is pressed, the app's timer stops running after about 15 minutes unless the phone is plugged into power.但是,当按下电源或主页按钮时,应用程序的计时器会在大约 15 分钟后停止运行,除非手机已插入电源。 The phone was fully charged when I tested this.我测试时手机已充满电。

Incidentally, I also set the app to NOT be optimized for battery life after reading on various sites that that would ensure that the app would continue running.顺便说一句,在阅读各种网站后,我还将应用程序设置为不针对电池寿命进行优化,以确保应用程序继续运行。 From everything I read, I'm doing everything right, yet I still can't get this to work.从我阅读的所有内容来看,我做的一切都是正确的,但我仍然无法让它发挥作用。 I'm running Android 11 on a Pixel 2. I know that Google limited foreground processing for later versions of Android, but setting the app to not optimize for battery life is supposed to get around the problem, isn't it?我在 Pixel 2 上运行 Android 11。我知道 Google 限制了更高版本的 Android 的前台处理,但是将应用程序设置为不优化电池寿命应该可以解决这个问题,不是吗? To be safe, when the app starts, it asks the user to approve background operation:为了安全起见,当应用程序启动时,它会要求用户批准后台操作:

     PowerManager pm = (PowerManager)getSystemService(POWER_SERVICE);
     if (!pm.isIgnoringBatteryOptimizations(APPLICATION_ID)) {
         // Ask user to allow app to not optimize battery life. This will keep
         // the app running when the user puts it in the background by pressing
         // the Power or Home button.
         Intent intent = new Intent();
         intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
         intent.setData(Uri.parse("package:" + APPLICATION_ID));
         startActivity(intent);
     }

so the user sees the following when the app is run and while optimized for battery:因此,当应用程序运行并针对电池进行优化时,用户会看到以下内容:

在此处输入图像描述

I start the foreground service as follows:我按如下方式启动前台服务:

    private void startForegroundMonitoring() {
    broadcastIntent = new Intent(context, BroadcastService.class);
    broadcastIntent.putExtra(ALLOWEDTIME, allowed_time);
    broadcastIntent.putExtra(BEEP, beep.isChecked());
    broadcastIntent.putExtra(VIBRATE, vibrate.isChecked());
    broadcastIntent.putExtra(NOTIFY, notify_monitor.isChecked());
    broadcastIntent.putExtra(CURFEW, curfew_config.isChecked());
    broadcastIntent.putExtra(CURFEWSTARTTIME, curfew_start_time);
    broadcastIntent.putExtra(CURFEWENDTIME, curfew_end_time);
    startService(broadcastIntent);
}

UPDATE: Here is some code that demonstrates the problem:更新:这是一些演示问题的代码:

Main activity:主要活动:

package com.testapp.showbug;

import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.PowerManager;
import android.provider.Settings;

import static com.testapp.showbug.BuildConfig.APPLICATION_ID;

public class MainActivity extends AppCompatActivity {

    private Context context;

    private Intent broadcastIntent;

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

        context = getApplicationContext();

        PowerManager pm = (PowerManager)getSystemService(POWER_SERVICE);
        if (!pm.isIgnoringBatteryOptimizations(APPLICATION_ID)) {
            // Ask user to allow app to not optimize battery life. This will keep
            // the app running when the user puts it in the background by pressing
            // the Power or Home button.
            Intent intent = new Intent();
        
            intent.setAction(
            Settings.
                ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
            intent.setData(Uri.parse("package:" + 
                APPLICATION_ID));
            startActivity(intent);
        }

    broadcastIntent = new Intent(context, 
      BroadcastService.class);
    startService(broadcastIntent);
    }
    public void onDestroy() {
        super.onDestroy();

        stopService(broadcastIntent);
    }
}

BroadcastService:广播服务:

import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.Build;
import android.os.CountDownTimer;
import android.os.IBinder;
import android.widget.Toast;

import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;

import static android.content.pm.ServiceInfo.
  FOREGROUND_SERVICE_TYPE_LOCATION;

public class BroadcastService extends Service {
    private static final int ONE_MINUTE = 60000;

    private int allowed_time = 30, tickCounter;

    private CountDownTimer countDown;

    private NotificationManagerCompat notificationManager;

    private NotificationCompat.Builder notification;

    @Override
    public void onCreate() {
        super.onCreate();

        // Clear all notifications sent earlier.
        notificationManager = 
          NotificationManagerCompat.from(this);
        notificationManager.cancelAll();

        createNotificationChannel();
    }

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

    @Override
    public int onStartCommand(Intent intent, int flags, 
        int startId) {
        if (intent == null) return START_STICKY;

        Intent notificationIntent = new Intent(this, 
          BroadcastService.class);
        PendingIntent pendingIntent =
            PendingIntent.getActivity(this, 0, 
            notificationIntent, 0);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            notification = new 
              NotificationCompat.Builder(this, 
              getString(
                R.string.default_notification_channel_id))
              .setContentTitle( 
                getText(R.string.notification_title))
              .setContentText(
                  getText(R.string.notification_message))
              .setStyle(new NotificationCompat.BigTextStyle()                           
              .bigText(
                  getText(R.string.notification_message)))
              .setContentIntent(PendingIntent.getActivity(
                  this, 0, new Intent(), 0))
              .setSmallIcon(R.mipmap.ic_launcher_round)
              .setLocalOnly(true)
              .setContentIntent(pendingIntent);
        } else {
            notification = new 
                NotificationCompat.Builder(this, 
                  getString(
                    R.string.default_notification_channel_id))
                
            .setContentTitle(
                getText(R.string.notification_title))
            .setContentText(
                getText(R.string.notification_message))
            .setStyle(new NotificationCompat.BigTextStyle()                          
            .bigText(
                getText(R.string.notification_message)))
            .setContentIntent(PendingIntent.getActivity(
                this, 0, new Intent(), 0))
            .setSmallIcon(R.mipmap.ic_launcher_round)
            .setContentIntent(pendingIntent);
        }

    startForeground(FOREGROUND_SERVICE_TYPE_LOCATION, notification.build());

    tickCounter = -1;
    // Start countdown timer for allowed time.
    countDown = new CountDownTimer(allowed_time * ONE_MINUTE, ONE_MINUTE) {
        @Override
        public void onTick(long millisUntilFinished) {
            tickCounter++;
            Toast.makeText(getApplicationContext(), "tickCounter = " + tickCounter, Toast.LENGTH_LONG).show();
        }

        @Override
        public void onFinish() {
            Toast.makeText(getApplicationContext(), "tickCounter = " + allowed_time, Toast.LENGTH_LONG).show();
        }
    }.start();
    return START_STICKY;
}

private void createNotificationChannel() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        CharSequence name = getString(R.string.channel_name);
       String description = getString(R.string.channel_description);
       int importance = NotificationManager.IMPORTANCE_DEFAULT;
       NotificationChannel channel = new NotificationChannel(getString(R.string.default_notification_channel_id), name, importance);
       channel.setDescription(description);
        
       notificationManager.createNotificationChannel(channel);
       }
    }

    @Override
    public IBinder onBind(Intent arg0) {
        return null;
    }
}

The above code starts a foreground service which in turn starts a CountDownTimer that increments the number of ticks by one every minute and prints the result.上面的代码启动了一个前台服务,该服务又启动了一个 CountDownTimer,它每分钟将刻度数增加 1 并打印结果。 After 30 minutes, it should show a count of 30 ticks. 30 分钟后,它应该显示 30 个滴答声。 Instead, it stops early, usually after 15-16 ticks.相反,它会提前停止,通常在 15-16 个滴答声之后。

Here's how to run the code:以下是运行代码的方法:

  1. Start the activity, disconnect the device from power (this is important and, obviously, requires a real device) and tap the Power button on the device.开始活动,断开设备电源(这很重要,显然需要真实设备),然后点击设备上的电源按钮。
  2. Wait 28 minutes.等待 28 分钟。
  3. Put the app back into the foreground and wait for the next tick.将应用程序放回前台并等待下一个滴答声。
  4. Note that the next tick displays something less than 28.请注意,下一个刻度显示的值小于 28。

Thanks for any help on this.感谢您对此的任何帮助。 It looks to me like a bug in the Android SDK, unlikely as that may seem.在我看来,它就像 Android SDK 中的一个错误,看起来不太可能。 I don't see any other cause.我没有看到任何其他原因。 By the way, I tested this code on a Pixel 2 and a Samsung Tab A, both running Android 11 (the only devices I own), so I don't know if the bug occurs on earlier versions of Android or different devices.顺便说一句,我在 Pixel 2 和三星 Tab A 上测试了这段代码,它们都运行 Android 11(我拥有的唯一设备),所以我不知道这个错误是否发生在早期版本的 Android 或其他设备上。

I finally solved the problem, using wakelocks.我终于解决了这个问题,使用唤醒锁。 Wakelocks ensure that the CPU continues to run after the Power button has been pressed.唤醒锁确保按下电源按钮后 CPU 继续运行。 All I had to do was add the following code in BroadcastService.java:我所要做的就是在 BroadcastService.java 中添加以下代码:

In onCreate():在 onCreate() 中:

    PowerManager pm = (PowerManager)getSystemService(POWER_SERVICE);
    wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "PeriSecure:MyWakeLock");

In onStartCommand():在 onStartCommand() 中:

    wakeLock.acquire(allowed_time * ONE_MINUTE);

In onDestroy():在 onDestroy() 中:

wakeLock.release();

That's all.就这样。 The background Service runs as is should now.后台服务现在按原样运行。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 手机连接电源时启动安卓应用 - Launching an android app when phone connected to power source 当我将其移动到后台时,为什么我的Android应用程序停止? - Why does my Android app stops, when I move it to the background? Android如何在用户杀死应用程序后继续运行后台服务? - Android How to keep Running the background service when app is killed by user? 在手机上运行android应用时出现SecurityException - SecurityException when running android app on phone Android-定位服务仅在连接到计算机后才在后台运行 - Android - location service not running in background, only when connected to the computer 如何在手机屏幕的Android 11 中调整我的应用程序? - How adjust my app in Android 11 in cell phone screen? 除非我的应用程序批准,否则防止Android手机连接到WiFi网络? - Prevent Android phone from connecting to WiFi network unless my app approves it? 我的应用程序在构建时没有任何错误,但是当我在手机上运行它时它停止工作 - my app does not have any errors when it builds, however, it stops working when I run it on my phone 我希望我的应用程序在(线程或作为服务)中运行,即使 onDestroy() 被调用(用户从后台删除) - I want my app to be running in a (Thread or as a service) even when onDestroy() is called (user removed from background) 在后台运行我的应用程序 - Running My App In The Background
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM