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

bvuwiixz  于 2023-03-06  发布在  Android
关注(0)|答案(3)|浏览(150)

谷歌有它的时钟应用程序,其中包括它的秒表。我目前正在我的应用程序中创建一个计时器,或者你可以叫它秒表,它可以在后台运行,当它在后台运行时,我希望它也显示一个通知,显示计时时间和"停止"按钮(所有这些都发生在谷歌时钟应用程序(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);
    }
};

我的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秒的延迟后显示。
1.通知在启动/恢复后约30秒后停止更新(计时器在后台持续运行,但通知不会随计时器持续更新)。
下面是我的Service s代码:

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

更新

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

cwtwac6a

cwtwac6a1#

有两个问题。我会尽力解决它们。

首次发布

当我在大约5分钟后运行应用程序时,通知会在10秒的延迟后显示。
为此,您需要使用通知的代码更新通知。现在,由于通知需要时间显示,因此请在启动服务的Activity中显示它,然后使用其构造函数将通知ID传递给服务。使用该ID在服务中更新通知。
希望这能解决第一个问题。

第二次发布

通知在启动/恢复后约30秒后停止更新(计时器在后台持续运行,但通知不会随计时器持续更新)。
要解决这个问题,你可以在10秒后清除之前的通知,然后你可以为通知创建一个新的随机密钥(我更喜欢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上查看我的repo,它是一个完整的秒表应用程序

mv1qrgav

mv1qrgav2#

我已经找到了为什么我的通知在30秒后停止更新的原因!显然,(根据this thread)在一些运行Android版本高于9的设备上有后台限制。
这些限制是从应用程序关闭的那一刻起30秒后阻止我的通知更新的限制,或者换句话说,从它们成为后台活动的那一刻起(即使它们是通过startForeground()调用的)。
无法绕过此设置。您无法以编程方式禁用此设置。唯一的选择是使用ActivityManager.isBackgroundRestricted()以编程方式检查是否已启用此设置,并显示一个弹出窗口,通知用户如何禁用此设置
用户根据线程中接受的答案回答。
这样,通知没有按预期更新的问题就解决了。但显示第一个通知的延迟问题仍然没有解决,还有另一个问题-每次通知更新时,整个通知面板都会冻结一小段时间。

um6iljoc

um6iljoc3#

如果你在onStartCommand函数内的TimerService中用scheduleAtFixedRate替换递归的postDelayed,解决方案的效果会更好,即使在启用了电池限制的情况下也是如此。

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仓库)

相关问题