问题描述
我编写了一个前台服务,以确保我的应用程序在进入后台时可以继续运行。该应用程序需要在后台运行,因为在其计时器结束后,它会发出提示音并振动以提醒用户。但是,当按下电源或主页按钮时,除非手机接通电源,否则应用程序的计时器会在大约 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);
}
我启动前台服务如下:
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 个滴答之后。
感谢您对此的任何帮助。在我看来,它像是 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();
仅此而已!后台服务现在按原样运行。