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

问题描述

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

顺便说一句,在阅读各种网站后,我还将应用程序设置为不针对电池寿命进行优化,以确保应用程序继续运行。从我阅读的所有内容来看,我所做的一切都是正确的,但我仍然无法让它发挥作用。我在 Pixel 2 上运行 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);
     }

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

enter image description here

我启动前台服务如下:

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

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

主要活动:

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,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,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))
            .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. 开始活动,断开设备的电源(这很重要,显然需要一个真实的设备),然后点击设备上的电源按钮。
  2. 等待 28 分钟。
  3. 将应用放回前台并等待下一个滴答声。
  4. 请注意,下一个刻度显示的数字小于 28。

感谢您对此的任何帮助。在我看来,它像是 Android SDK 中的一个错误,虽然看起来不太可能。我没有看到任何其他原因。顺便说一句,我在运行 Android 11(我拥有的唯一设备)的 Pixel 2 和三星 Tab A 上测试了此代码,所以我不知道该错误是否出现在早期版本的 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();

仅此而已!后台服务现在按原样运行。

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...