Android:当应用程序关闭 30 秒时通知延迟显示并停止更新(在 OnePlus 8T 上)

问题描述 投票:0回答:3

Google 有它的时钟应用程序,其中包括秒表。我目前正在尝试在我的应用程序中创建一个(计数)计时器,或者您可以将其称为秒表,它将能够在后台运行,并且当它在后台运行时我希望它也显示一个通知,显示它计算的时间和一个“停止”按钮(所有这些都发生在谷歌时钟应用程序中(see here))。对于我的应用程序中的计时器,我使用了一个处理程序来发布一个 Runnable,它正在发布自己。我正在用 Java 编写我的应用程序。

定义“计时器”(处理程序和可运行程序)的代码:

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

我的暂停功能:

@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. 通知在开始/恢复后约 30 秒后停止更新(计时器在后台保持运行,但通知不会随着计时器持续更新)。

这是我的

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

更新

当我在谷歌像素模拟器上运行应用程序时,我没有遇到任何列出的问题

java android notifications stopwatch background-service
3个回答
0
投票

有2个问题。我将尝试解决这两个问题。

第一期


当我在大约 5 分钟后运行该应用程序时,通知会延迟 10 秒出现。

为此,您需要使用代码更新通知。现在,因为显示需要时间,请在启动服务的活动中显示它,然后使用其构造函数将通知 ID 传递给服务。使用该 ID,在服务中更新它。

希望这能解决第一个问题。

第二期


通知在开始/恢复后约 30 秒后停止更新(计时器在后台保持运行,但通知不会随着计时器持续更新)。

为了解决这个问题,您可以在 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上查看我的回购协议。这是一个完整的秒表应用


0
投票

我找到通知在 30 秒后停止更新的原因了!显然,(根据this thread)在某些运行Android版本高于9的设备上有后台限制。

这些限制是阻止我的通知在应用程序关闭后 30 秒后更新的限制,或者换句话说 - 从它们成为后台活动的那一刻起(即使它们是通过

startForeground()
调用的)。

没有办法绕过这个设置。您不能以编程方式禁用它。您唯一的选择是使用 ActivityManager.isBackgroundRestricted() 以编程方式检查它是否已启用,并显示一个弹出窗口,通知您的用户如何禁用此设置

用户从线程中接受的答案中说。

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


0
投票

解决方案会更好,即使启用了电池限制,如果你将在你的

postDelayed
inside
scheduleAtFixedRate
函数中用
TimerService
替换递归
onStartCommand
。像这样的东西:

        TimerTask timerTaskNotify = new TimerTask() {
            @Override
            public void run() {
                // add a second to the counter
                seconds++;

                //update the notification with that second
                updateNotification(goalName, seconds);

                //Print the seconds
                Log.d("timerCount", seconds + "");

                //Save the seconds passed to shared preferences
                PrefUtil.setTimerSecondsPassed(TimerService.this,seconds);
            }
        };
        Timer timerNotify = new Timer();
        timerNotify.scheduleAtFixedRate(timerTaskNotify, 0, 1000);

附言如果您授予权限,我可以更新您的 git 存储库)

© www.soinside.com 2019 - 2024. All rights reserved.