我该如何处理在我的活动停止时发送的广播?

3
我的活动启动一个运行CountDownTimer的服务。随着倒计时的进行,定时器会向活动发送广播。活动会在BroadcastReceiver的onReceive方法中处理广播。这一切都很好地运行着。
但当以下事件按此顺序发生时就会出现问题:
1. 应用被停止(通过onPause())。 2. 计时器完成。 3. 应用恢复(通过onResume())。
应用恢复后,服务不再发送广播,因此活动无法知道计时器还剩多少时间或者是否已经完成。这会阻止活动更新用户界面。
我已经尝试了许多方式来解决这个问题,并查看了许多Stack Overflow的问题和答案,但我仍然没有找到解决方案。我认为应该有一种方法可以捕获在活动不活跃时发送的广播,但我仍未找到方法。
以下是我相关的活动和服务代码:
// Start service
timerIntent.putExtra("totalLength", totalLength);
this.startService(timerIntent);

// ...

// BroadcastReceiver
private BroadcastReceiver br = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.getExtras() != null && inSession) {
            session.setRemaining(intent.getExtras().getLong("millisUntilFinished"));
            updateProgress();
        }
    }
};

// ...

// onResume
@Override
public void onResume() {
    super.onResume();
    registerReceiver(br, new IntentFilter(TimerService.COUNTDOWN_TS));
}

service.java

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    long length = intent.getExtras().getLong("totalLength");

    countDownTimer = new CountDownTimer(length, 1000) {
        @Override
        public void onTick(long millisUntilFinished) {
            timerServiceIntent.putExtra("millisUntilFinished", millisUntilFinished);
            sendBroadcast(timerServiceIntent);
        }

        @Override
        public void onFinish() {
        }
    };

    countDownTimer.start();

    return super.onStartCommand(intent, flags, startId);
}

当活动被停止时,处理服务发送的广播的最佳方法是什么?


请解释一下您所说的“应用已停止”是什么意思。“我认为有一种方法可以在活动不活跃时接收到已发送的广播”——广播不是消息队列。 - CommonsWare
当我说“应用已停止”时,我的意思是该活动的onStop()方法已运行(因为屏幕已关闭,另一个应用程序处于活动状态等)。我会更新问题。 - Alex Johnson
4个回答

3
使用BroadcastReceiver存储最后一个请求(例如SharedPreferences),并在Activity启动时进行检查。
或者,可以不使用广播来处理倒计时,而是存储倒计时结束的时间。由于Activity知道应该何时结束,因此它可以独立处理倒计时。对于这样一个简单的任务,使用服务和广播似乎有点过度设计。
更新: 根据您描述任务的方式,我看到您需要处理2个情况。以下是我可能会采取的方法。
假设"XYZ"是启动倒计时的服务/意图/任何其他东西,而"ABC"是显示进度的Activity。(如果您希望如此,ABC和XYZ可以是同一活动)
要求: 当倒计时开始时,我会让XYZ将倒计时应该结束的时间存储在SharedPreferences中。
  1. ABC在倒计时开始时已经运行。正如Commonsware所说,事件总线模型非常适合处理此场景,只要XYZ和ABC在同一个进程中运行即可。只需触发一个事件以读取首选项值并倒计时到指定时间。如果用户关闭ABC并重新打开它,则会启动第2个场景。

  2. ABC没有运行。在OnResume中检查是否已经过了倒计时时间。如果没有,设置ABC再次显示倒计时。如果没有倒计时活动,则执行其他操作。

如果您还需要在倒计时结束时执行某些操作,无论是否有活动UI,则再次使用Commonsware的AlarmManager建议是完美的。

我肯定感觉我的解决方案过于复杂了(基于你和@commonsware的回答)。如果活动已经暂停/停止,我该如何返回到活动?AlarmManager?老实说,我很难弄清楚这种情况下的“最佳实践”。 - Alex Johnson
我不会说有什么“最佳实践”,而是“适合你的最佳实践”。使用最简单的方法完成任务。尽量避免启动不必要的服务。我的解决方案适用于所描述的任务,但如果您还有其他未指定的要求或处理的其他情况,那么答案可能会发生改变。请参见更新。 - Kuffs

1
假设使用具有CountDownTimer的Service来跟踪某些时间段以更新Activity实际上是个好主意。这并非不可能,只要Service确实在做一些真正的事情,而这个计时器只是副产品。
Activity在停止时不会接收广播,主要是出于性能/电池方面的考虑。相反,Activity需要在启动时获取当前状态,然后使用事件(例如当前广播)在其启动期间被通知数据更改。
使用像greenrobot的EventBus和它们的sticky events之类的东西将简化此过程,因为当Activity订阅获取事件时,它将自动获取最后一个事件。使用greenrobot的EventBus也将减少您通过在同一进程中的两个Java类之间使用系统广播引入的安全性和性能问题。
另外,请坚持使用生命周期对。 onResume() 不是 onStop() 的对应项。 onStart()onStop() 的对应项;onResume()onPause() 的对应项。在一对中初始化某些内容(例如,在 onResume() 中)并在另一对中清除它(例如,在 onStop() 中)会导致双重初始化或双重清除错误的风险。

服务的目的只是运行计时器。我基于这个答案构建模型:https://dev59.com/DGEh5IYBdhLWcg3wXCdL#22498307。有没有更好的方法?基本上,我的应用程序只是创建一个计时器,倒数计时,然后在计时器完成时播放声音。另外,我编辑了我的问题以反映onPause()和onResume()是成对的。 - Alex Johnson
@AlexJohnson:使用AlarmManager在时间结束时控制播放声音(或者可能会引发Notification)。如果您想显示活动倒计时,请遵循Kuffs的建议跟踪结束时间,然后使用类似于a postDelayed() loop这样的超级便宜的东西来更新您的UI。只有在向用户提供有效价值时才运行服务。观看时钟滴答声并不符合资格。 - CommonsWare
好的。谢谢你的指点。我会努力简化它,看看能否做到更简单。 - Alex Johnson

1

谢谢你的答案。我已经了解到 StickyBroadcasts,但不知道它们已被弃用。 - Alex Johnson

0

你可以使用有序广播(使用Context.sendOrderedBroadcast而非普通广播),在活动中定义BroadcastReceiver的同时,还需要在清单文件中定义IntentFilter相同的BroadcastReceiver。唯一的改变是在活动中注册BroadcastReceiver时需要将优先级设置为高,这样当活动运行且活动的BroadcastReceiver已注册时,它将首先被调用,并在该BroadcastReceiver的onReceive方法中使用abortBroadcast获取在android清单文件中定义的BroadcastReceiver的调用。现在,当您的活动未运行时,将调用在android清单文件中定义的BroadcastReceiver。因此,即使您的活动未运行,也可以通过通知向用户显示更新状态。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接