当用户将其置于后台时,我的 Android 11 应用停止 运行,除非 phone 已连接电源

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

我写了一个前台服务来确保我的应用程序在进入后台时可以继续 运行ning。该应用程序需要在后台 运行,因为在其计时器结束后,它会发出提示音并振动以提醒用户。但是,当按下电源或主页按钮时,应用程序的计时器会在大约 15 分钟后停止 运行ning,除非 phone 已插入电源。我测试的时候phone已经充满电了

顺便说一句,在阅读各种网站后,我还将应用程序设置为不针对电池寿命进行优化,这将确保该应用程序将继续 运行ning。从我读到的所有内容来看,我做的一切都是对的,但我仍然无法让它发挥作用。我在 Pixel 2 上 运行ning Android 11。我知道 Google 对更高版本的 Android 的前台处理有限,但将应用程序设置为不针对电池进行优化生活应该绕过这个问题,不是吗?为了安全起见,当应用程序启动时,它会要求用户批准后台操作:

     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);
     }

因此,当应用 运行 并且针对电池进行了优化时,用户会看到以下内容:

我启动前台服务如下:

    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);
}

更新:这是一些演示问题的代码:

主要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);
    }
}

广播服务:

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;
    }
}

上面的代码启动了一个前台服务,该服务又启动了一个 CountDownTimer,每分钟将滴答数递增 1 并打印结果。 30 分钟后,它应该显示 30 个刻度。相反,它会提早停止,通常在 15-16 个滴答后。

运行代码的方法如下:

  1. 启动 activity,断开设备电源(这很重要,显然需要真实设备),然后点击设备上的电源按钮。
  2. 等待 28 分钟。
  3. 将应用程序放回前台并等待下一次滴答。
  4. 请注意,下一个刻度显示小于 28 的值。

感谢您对此提供的任何帮助。在我看来,它像是 Android SDK 中的错误,虽然看起来不太可能。我看不出任何其他原因。顺便说一下,我在 Pixel 2 和 Samsung Tab A 上测试了这段代码,两者都是 运行ning Android 11(我拥有的唯一设备),所以我不知道错误是否发生在Android 或不同设备的早期版本。

我终于解决了这个问题,使用唤醒锁。唤醒锁确保 CPU 在按下电源按钮后继续 运行。我所要做的就是在 BroadcastService.java:

中添加以下代码

在onCreate()中:

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

在onStartCommand()中:

    wakeLock.acquire(allowed_time * ONE_MINUTE);

在onDestroy()中:

wakeLock.release();

就是这样!后台服务 运行s 现在应该是这样了。