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

如何在我的应用程序中实现后台服务和通知?

编辑

我已经成功地创建了一个 运行 我的计时器的前台服务,但是我有两个问题:

  1. 当我在大约 5 分钟后 运行 应用程序时,通知会延迟 10 秒显示。
  2. 通知在 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

从话题中接受的答案中说出用户。

这样,通知没有按预期更新的问题就解决了。虽然延迟显示第一个通知的问题仍未解决,但还有另一个问题 - 每次更新通知时,整个通知面板会冻结第二部分。