Android:应用关闭30秒通知延迟显示并停止更新(在OnePlus 8T上)
Android: Notification Shows in Delay And Stops Updating When App Closed for 30 Seconds (on OnePlus 8T)
Google 有它的时钟应用程序,其中包括秒表。我目前正在尝试在我的应用程序中创建一个(递增)计时器,或者您可以将其称为秒表,它将能够在后台 运行 并且在 运行 进入时背景 我希望它也显示一个通知,显示它计算的时间和一个“停止”按钮(所有这些都发生在 google 时钟应用程序(see here)中)。对于我的应用程序中的计时器,我使用了一个处理程序来发布一个 Runnable,它正在发布自己。我正在 Java.
中编写我的应用程序
定义'timer'(Handler 和 Runnable)的代码:
Handler timerHandler = new Handler();
Runnable timerRunnable = new Runnable() {
@Override
public void run() {
long millis = System.currentTimeMillis() - startTime;
seconds = (millis / 1000) + PrefUtil.getTimerSecondsPassed();
timerHandler.postDelayed(this, 500);
}
};
我的 onPause 函数:
@Override
public void onPause() {
super.onPause();
if (timerState == TimerState.Running) {
timerHandler.removeCallbacks(timerRunnable);
//TODO: start background timer, show notification
}
PrefUtil.setTimerSecondsPassed(seconds);
PrefUtil.setTimerState(timerState);
}
如何在我的应用程序中实现后台服务和通知?
编辑
我已经成功地创建了一个 运行 我的计时器的前台服务,但是我有两个问题:
- 当我在大约 5 分钟后 运行 应用程序时,通知会延迟 10 秒显示。
- 通知在 starts/resumes 后约 30 秒后停止更新(计时器在后台保持 运行ning,但通知不会随着计时器持续更新)。
这是我的 Service
代码:
public class TimerService extends Service {
Long startTime = 0L, seconds = 0L;
boolean notificationJustStarted = true;
Handler timerHandler = new Handler();
Runnable timerRunnable;
NotificationCompat.Builder timerNotificationBuilder = new NotificationCompat.Builder(this, CHANNEL_ID);
public static final String TIMER_BROADCAST_ID = "TimerBroadcast";
Intent timerBroadcastIntent = new Intent(TIMER_BROADCAST_ID);
@Override
public void onCreate() {
Log.d(TAG, "onCreate: started service");
startForeground(1, new NotificationCompat.Builder(this, CHANNEL_ID).setSmallIcon(R.drawable.timer).setContentTitle("Goal In Progress").build());
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
String goalName = intent.getStringExtra(PublicMethods.getAppContext().getString(R.string.timer_notification_service_current_goal_extra_name));
startTime = System.currentTimeMillis();
notificationJustStarted = true;
timerRunnable = new Runnable() {
@Override
public void run() {
long millis = System.currentTimeMillis() - startTime;
seconds = (millis / 1000) + PrefUtil.getTimerSecondsPassed();
updateNotification(goalName, seconds);
timerHandler.postDelayed(this, 500);
}
};
timerHandler.postDelayed(timerRunnable, 0);
return START_STICKY;
}
public void updateNotification(String goalName, Long seconds) {
try {
if (notificationJustStarted) {
Intent notificationIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this,
0, notificationIntent, PendingIntent.FLAG_IMMUTABLE);
timerNotificationBuilder.setContentTitle("Goal In Progress")
.setOngoing(true)
.setSmallIcon(R.drawable.timer)
.setContentIntent(pendingIntent)
.setOnlyAlertOnce(true)
.setOngoing(true)
.setPriority(NotificationCompat.PRIORITY_MAX);
notificationJustStarted = false;
}
timerNotificationBuilder.setContentText(goalName + " is in progress\nthis session's length: " + seconds);
startForeground(1, timerNotificationBuilder.build());
} catch (Exception e) {
Log.d(TAG, "updateNotification: Couldn't display a notification, due to:");
e.printStackTrace();
}
}
@Override
public void onDestroy() {
timerHandler.removeCallbacks(timerRunnable);
PrefUtil.setTimerSecondsPassed(seconds);
super.onDestroy();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
这是我在我的片段中开始它的方式:
private void startTimerService() {
Intent serviceIntent = new Intent(getContext(), TimerService.class);
serviceIntent.putExtra(getString(R.string.timer_notification_service_current_goal_extra_name), "*Current Goal Name Here*");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Objects.requireNonNull(getContext()).startForegroundService(serviceIntent);
}
}
更新
当我在 google 像素模拟器上 运行 应用程序时,我没有遇到列出的任何问题
有两个问题。我会尝试解决这两个问题。
第一期
When I run the app after something like 5 minutes, the notification shows up in a 10-second delay.
为此,您需要使用代码更新通知。现在,因为显示需要时间,请在启动服务的 activity 中显示它,然后使用其构造函数将通知 ID 传递给服务。使用该 ID,在服务中更新它。
希望这能解决第一个问题。
第二期
the notification stops updating after around 30 seconds from the time it starts/resumes (The timer keeps running in the background, but the notification won't keep updating with the timer).
为了解决这个问题,您可以在 10 秒后通过它的 id 清除以前的通知。然后你可以为通知制作一个新的随机密钥(我更喜欢new Random().nextInt()
)然后显示它。但是你或任何人都会说当通知来的时候声音太大了。只需在创建频道时以这种方式禁用它:
notificationChannel.setSound(null, null);
注意:您可能需要重新安装您的应用才能正常工作
如果这看起来很复杂,请看这个:
Runnable running -> When 10 seconds done from previous notification display -> Clear the notification -> Make a new notification id -> show notification with that id -> Repeat
编辑
这是我的工作代码:
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
import java.util.concurrent.TimeUnit;
public class TimerService extends Service {
Long startTime = 0L, seconds = 0L;
boolean notificationJustStarted = true;
Handler timerHandler = new Handler();
Runnable timerRunnable;
private final String CHANNEL_ID = "Channel_id";
NotificationManager mNotificationManager;
NotificationCompat.Builder timerNotificationBuilder = new NotificationCompat.Builder(this, CHANNEL_ID).setContentTitle(CHANNEL_ID);
@SuppressLint("InlinedApi")
@Override
public void onCreate() {
super.onCreate();
Toast.makeText(this, "created", Toast.LENGTH_SHORT).show();
String TAG = "Timer Service";
Log.d(TAG, "onCreate: started service");
startForeground(1, new NotificationCompat.Builder(TimerService.this, createChannel()).setContentTitle("Goal In Progress").setPriority(NotificationManager.IMPORTANCE_MAX).build());
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
String goalName = "Sample Goal";
Toast.makeText(this, "started", Toast.LENGTH_SHORT).show();
startTime = System.currentTimeMillis();
notificationJustStarted = true;
timerRunnable = new Runnable() {
@Override
public void run() {
long millis = System.currentTimeMillis() - startTime;
seconds = (millis / 1000) + PrefUtil.getTimerSecondsPassed(TimerService.this);
updateNotification(goalName, seconds);
Log.d("timerCount", seconds + "");
timerHandler.postDelayed(this, 1000);
}
};
timerHandler.postDelayed(timerRunnable, 0);
return Service.START_STICKY;
}
@SuppressLint("NewApi")
public void updateNotification(String goalName, long seconds) {
if (notificationJustStarted) {
Intent notificationIntent = new Intent(this, MainActivity.class);
@SuppressLint("InlinedApi") PendingIntent pendingIntent = PendingIntent.getActivity(this,
0, notificationIntent, PendingIntent.FLAG_IMMUTABLE);
timerNotificationBuilder.setContentTitle("Goal In Progress")
.setOngoing(true)
.setContentIntent(pendingIntent)
.setOnlyAlertOnce(true)
.setOngoing(true)
.setPriority(NotificationCompat.PRIORITY_MAX)
.setSmallIcon(R.drawable.ic_launcher_foreground);
notificationJustStarted = false;
}
long minutes = TimeUnit.SECONDS.toMinutes(seconds);
String time = minutes + ":" + (seconds - TimeUnit.MINUTES.toSeconds(minutes));
timerNotificationBuilder.setContentText(goalName + " is in progress\nthis session's length: " + time);
mNotificationManager.notify(1, timerNotificationBuilder.build());
startForeground(1, timerNotificationBuilder.build());
}
@Override
public void onDestroy() {
timerHandler.removeCallbacks(timerRunnable);
PrefUtil.setTimerSecondsPassed(this, seconds);
super.onDestroy();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@NonNull
@TargetApi(26)
private synchronized String createChannel() {
mNotificationManager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
String name = "STOPWATCH";
int importance = NotificationManager.IMPORTANCE_LOW;
NotificationChannel mChannel = new NotificationChannel(CHANNEL_ID, name, importance);
mChannel.setName("Notifications");
if (mNotificationManager != null) {
mNotificationManager.createNotificationChannel(mChannel);
} else {
stopSelf();
}
return CHANNEL_ID;
}
}
您还可以在此 here 上查看我的存储库。这是一个完整的秒表应用
我找到通知在 30 秒后停止更新的原因了!显然,(根据)在某些设备运行 Android 高于9 的版本上有后台限制。
这些限制会阻止我的通知在应用程序关闭后 30 秒后更新,或者换句话说 - 从它们成为后台活动的那一刻起(即使它们是通过 startForeground()
).
There is no way around this setting. You cannot programmatically disable it. Your only option is to programmatically check if it's enabled using ActivityManager.isBackgroundRestricted() and display a pop-up informing your users on how to disable this setting
从话题中接受的答案中说出用户。
这样,通知没有按预期更新的问题就解决了。虽然延迟显示第一个通知的问题仍未解决,但还有另一个问题 - 每次更新通知时,整个通知面板会冻结第二部分。
Google 有它的时钟应用程序,其中包括秒表。我目前正在尝试在我的应用程序中创建一个(递增)计时器,或者您可以将其称为秒表,它将能够在后台 运行 并且在 运行 进入时背景 我希望它也显示一个通知,显示它计算的时间和一个“停止”按钮(所有这些都发生在 google 时钟应用程序(see here)中)。对于我的应用程序中的计时器,我使用了一个处理程序来发布一个 Runnable,它正在发布自己。我正在 Java.
中编写我的应用程序定义'timer'(Handler 和 Runnable)的代码:
Handler timerHandler = new Handler();
Runnable timerRunnable = new Runnable() {
@Override
public void run() {
long millis = System.currentTimeMillis() - startTime;
seconds = (millis / 1000) + PrefUtil.getTimerSecondsPassed();
timerHandler.postDelayed(this, 500);
}
};
我的 onPause 函数:
@Override
public void onPause() {
super.onPause();
if (timerState == TimerState.Running) {
timerHandler.removeCallbacks(timerRunnable);
//TODO: start background timer, show notification
}
PrefUtil.setTimerSecondsPassed(seconds);
PrefUtil.setTimerState(timerState);
}
如何在我的应用程序中实现后台服务和通知?
编辑
我已经成功地创建了一个 运行 我的计时器的前台服务,但是我有两个问题:
- 当我在大约 5 分钟后 运行 应用程序时,通知会延迟 10 秒显示。
- 通知在 starts/resumes 后约 30 秒后停止更新(计时器在后台保持 运行ning,但通知不会随着计时器持续更新)。
这是我的 Service
代码:
public class TimerService extends Service {
Long startTime = 0L, seconds = 0L;
boolean notificationJustStarted = true;
Handler timerHandler = new Handler();
Runnable timerRunnable;
NotificationCompat.Builder timerNotificationBuilder = new NotificationCompat.Builder(this, CHANNEL_ID);
public static final String TIMER_BROADCAST_ID = "TimerBroadcast";
Intent timerBroadcastIntent = new Intent(TIMER_BROADCAST_ID);
@Override
public void onCreate() {
Log.d(TAG, "onCreate: started service");
startForeground(1, new NotificationCompat.Builder(this, CHANNEL_ID).setSmallIcon(R.drawable.timer).setContentTitle("Goal In Progress").build());
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
String goalName = intent.getStringExtra(PublicMethods.getAppContext().getString(R.string.timer_notification_service_current_goal_extra_name));
startTime = System.currentTimeMillis();
notificationJustStarted = true;
timerRunnable = new Runnable() {
@Override
public void run() {
long millis = System.currentTimeMillis() - startTime;
seconds = (millis / 1000) + PrefUtil.getTimerSecondsPassed();
updateNotification(goalName, seconds);
timerHandler.postDelayed(this, 500);
}
};
timerHandler.postDelayed(timerRunnable, 0);
return START_STICKY;
}
public void updateNotification(String goalName, Long seconds) {
try {
if (notificationJustStarted) {
Intent notificationIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this,
0, notificationIntent, PendingIntent.FLAG_IMMUTABLE);
timerNotificationBuilder.setContentTitle("Goal In Progress")
.setOngoing(true)
.setSmallIcon(R.drawable.timer)
.setContentIntent(pendingIntent)
.setOnlyAlertOnce(true)
.setOngoing(true)
.setPriority(NotificationCompat.PRIORITY_MAX);
notificationJustStarted = false;
}
timerNotificationBuilder.setContentText(goalName + " is in progress\nthis session's length: " + seconds);
startForeground(1, timerNotificationBuilder.build());
} catch (Exception e) {
Log.d(TAG, "updateNotification: Couldn't display a notification, due to:");
e.printStackTrace();
}
}
@Override
public void onDestroy() {
timerHandler.removeCallbacks(timerRunnable);
PrefUtil.setTimerSecondsPassed(seconds);
super.onDestroy();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
这是我在我的片段中开始它的方式:
private void startTimerService() {
Intent serviceIntent = new Intent(getContext(), TimerService.class);
serviceIntent.putExtra(getString(R.string.timer_notification_service_current_goal_extra_name), "*Current Goal Name Here*");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Objects.requireNonNull(getContext()).startForegroundService(serviceIntent);
}
}
更新
当我在 google 像素模拟器上 运行 应用程序时,我没有遇到列出的任何问题
有两个问题。我会尝试解决这两个问题。
第一期
When I run the app after something like 5 minutes, the notification shows up in a 10-second delay.
为此,您需要使用代码更新通知。现在,因为显示需要时间,请在启动服务的 activity 中显示它,然后使用其构造函数将通知 ID 传递给服务。使用该 ID,在服务中更新它。
希望这能解决第一个问题。
第二期
the notification stops updating after around 30 seconds from the time it starts/resumes (The timer keeps running in the background, but the notification won't keep updating with the timer).
为了解决这个问题,您可以在 10 秒后通过它的 id 清除以前的通知。然后你可以为通知制作一个新的随机密钥(我更喜欢new Random().nextInt()
)然后显示它。但是你或任何人都会说当通知来的时候声音太大了。只需在创建频道时以这种方式禁用它:
notificationChannel.setSound(null, null);
注意:您可能需要重新安装您的应用才能正常工作
如果这看起来很复杂,请看这个:
Runnable running -> When 10 seconds done from previous notification display -> Clear the notification -> Make a new notification id -> show notification with that id -> Repeat
编辑
这是我的工作代码:
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
import java.util.concurrent.TimeUnit;
public class TimerService extends Service {
Long startTime = 0L, seconds = 0L;
boolean notificationJustStarted = true;
Handler timerHandler = new Handler();
Runnable timerRunnable;
private final String CHANNEL_ID = "Channel_id";
NotificationManager mNotificationManager;
NotificationCompat.Builder timerNotificationBuilder = new NotificationCompat.Builder(this, CHANNEL_ID).setContentTitle(CHANNEL_ID);
@SuppressLint("InlinedApi")
@Override
public void onCreate() {
super.onCreate();
Toast.makeText(this, "created", Toast.LENGTH_SHORT).show();
String TAG = "Timer Service";
Log.d(TAG, "onCreate: started service");
startForeground(1, new NotificationCompat.Builder(TimerService.this, createChannel()).setContentTitle("Goal In Progress").setPriority(NotificationManager.IMPORTANCE_MAX).build());
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
String goalName = "Sample Goal";
Toast.makeText(this, "started", Toast.LENGTH_SHORT).show();
startTime = System.currentTimeMillis();
notificationJustStarted = true;
timerRunnable = new Runnable() {
@Override
public void run() {
long millis = System.currentTimeMillis() - startTime;
seconds = (millis / 1000) + PrefUtil.getTimerSecondsPassed(TimerService.this);
updateNotification(goalName, seconds);
Log.d("timerCount", seconds + "");
timerHandler.postDelayed(this, 1000);
}
};
timerHandler.postDelayed(timerRunnable, 0);
return Service.START_STICKY;
}
@SuppressLint("NewApi")
public void updateNotification(String goalName, long seconds) {
if (notificationJustStarted) {
Intent notificationIntent = new Intent(this, MainActivity.class);
@SuppressLint("InlinedApi") PendingIntent pendingIntent = PendingIntent.getActivity(this,
0, notificationIntent, PendingIntent.FLAG_IMMUTABLE);
timerNotificationBuilder.setContentTitle("Goal In Progress")
.setOngoing(true)
.setContentIntent(pendingIntent)
.setOnlyAlertOnce(true)
.setOngoing(true)
.setPriority(NotificationCompat.PRIORITY_MAX)
.setSmallIcon(R.drawable.ic_launcher_foreground);
notificationJustStarted = false;
}
long minutes = TimeUnit.SECONDS.toMinutes(seconds);
String time = minutes + ":" + (seconds - TimeUnit.MINUTES.toSeconds(minutes));
timerNotificationBuilder.setContentText(goalName + " is in progress\nthis session's length: " + time);
mNotificationManager.notify(1, timerNotificationBuilder.build());
startForeground(1, timerNotificationBuilder.build());
}
@Override
public void onDestroy() {
timerHandler.removeCallbacks(timerRunnable);
PrefUtil.setTimerSecondsPassed(this, seconds);
super.onDestroy();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@NonNull
@TargetApi(26)
private synchronized String createChannel() {
mNotificationManager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
String name = "STOPWATCH";
int importance = NotificationManager.IMPORTANCE_LOW;
NotificationChannel mChannel = new NotificationChannel(CHANNEL_ID, name, importance);
mChannel.setName("Notifications");
if (mNotificationManager != null) {
mNotificationManager.createNotificationChannel(mChannel);
} else {
stopSelf();
}
return CHANNEL_ID;
}
}
您还可以在此 here 上查看我的存储库。这是一个完整的秒表应用
我找到通知在 30 秒后停止更新的原因了!显然,(根据
这些限制会阻止我的通知在应用程序关闭后 30 秒后更新,或者换句话说 - 从它们成为后台活动的那一刻起(即使它们是通过 startForeground()
).
There is no way around this setting. You cannot programmatically disable it. Your only option is to programmatically check if it's enabled using ActivityManager.isBackgroundRestricted() and display a pop-up informing your users on how to disable this setting
从话题中接受的答案中说出用户。
这样,通知没有按预期更新的问题就解决了。虽然延迟显示第一个通知的问题仍未解决,但还有另一个问题 - 每次更新通知时,整个通知面板会冻结第二部分。