Activity 关闭并重新打开时避免服务回调
Posted
技术标签:
【中文标题】Activity 关闭并重新打开时避免服务回调【英文标题】:Avoid Service callback when Activity gets closed and re-opened 【发布时间】:2011-08-11 12:14:31 【问题描述】:我有一个 LocalService,它使用一些 API 公开一个 Binder。我创建了一个服务监听器,就像这样:
if (dataServiceListener == null)
dataServiceListener = new DataServiceListener();
mainActivity.getApplicationContext().bindService
(new Intent(mainActivity, LocalService.class),
dataServiceListener.svcConn, mainActivity.BIND_AUTO_CREATE);
在调用dataServiceListener
中的Binder
公开的方法后,我在dataServiceListener
onResult()
方法中得到响应。到目前为止,没有任何问题,一切正常。
当我关闭等待服务侦听器回调的 Activity 并立即重新打开它时,会出现某种问题。尽管我在onCreate()
中重新实例化了dataServiceListener
,但我得到了两个回调而不是一个,旧的来自被破坏的Activity,后者(右)一个;这样,结果就会在 UI 上混淆。
有没有办法告诉服务或服务侦听器,当活动完成时,必须避免回调。或者甚至可能销毁 ServiceListener 对象。
我认为这是 Mark L. Murphy(Commonsware)在“The Busy Coder's Guide to android Development”中描述的问题:
最大的收获是确保活动完成后收回侦听器。
我该怎么做?有没有办法在活动结束时摆脱无用的侦听器?
谢谢!
【问题讨论】:
【参考方案1】:我有同样的问题。我在使用 AIDL 的远程服务中工作。当我尝试在 foreach 循环中使用 ArrayList 集合中的 remove 方法取消注册我的听众时遇到了这个问题,因为我没有在比较中使用 asBinder。在寻找解决方案时,我在 Android API 中找到了 RemoteCallbackList 类。这门课正是我需要的,我认为你应该做的,以一种简单的方式,为涉及这项任务的辛勤工作承担所有责任。
来自 Android API:
要使用此类,只需与您的服务一起创建一个实例,并在客户端注册和注销您的服务时调用其 register(E) 和 unregister(E) 方法。要回调已注册的客户端,请使用 beginBroadcast()、getBroadcastItem(int) 和 finishBroadcast()。
广播样本:
int i = callbacks.beginBroadcast();
while (i > 0)
i--;
try
callbacks.getBroadcastItem(i).somethingHappened();
catch (RemoteException e)
// The RemoteCallbackList will take care of removing
// the dead object for us.
callbacks.finishBroadcast();
【讨论】:
【参考方案2】:您展示的代码用于绑定到服务。您没有显示您在哪里注册该服务的侦听器。根据您的问题和您对onResult()
方法的引用,您显然是。鉴于您的问题的性质,我猜您正在做的是:
-
绑定到
onCreate()
中的服务
在onServiceConnected()
中,您在Binder
上调用某种setListener()
方法
在这种情况下,如果我们忽略配置更改,解决问题的正确方法是在onDestroy()
中调用Binder
上的一些removeListener()
方法,然后调用unbindService()
。
配置更改,尤其是在片段前的世界中,使这变得复杂。这就是为什么this sample project(以及the book 中的随附材料)如此恶心的原因。绑定是棘手的——如果你从旧的活动中解除绑定,并且没有其他东西可以保留服务,那么服务将在新活动有机会绑定之前关闭。绑定也是状态——你不能简单地取消绑定,以免泄露东西。
所以,配方变成:
-
使用
Application
Context
绑定到onCreate()
中的服务
在onServiceConnected()
中,在Binder
上调用某种setListener()
方法
在onRetainNonConfigurationInstance()
中,记下您正在进行配置更改的事实,并返回一些Object
,其中包含您的Binder
、Listener
以及您的所有其他状态
在onCreate()
中,使用getLastNonConfigurationInstance()
——如果是null
,照常进行,但如果不是null
,请继续使用Binder
和Listener
,不要重复绑定并重新注册监听器
在 onDestroy()
中,如果上面第 3 步中的标志是 false
(即,我们没有进行配置更改),请在 Binder
上调用一些 removeListener()
方法,然后拨打unbindService()
。
使用带有setRetainInstance(true)
的片段可能会简化一些,尽管我还没有完成示例。
【讨论】:
我就是这么做的。基本上,我有一个包含多个片段的活动。每个片段可以有一个或多个服务绑定,因为它可能需要同时执行多个任务。在主要活动的onBackPressed()
中,我通过boolean
值将所有服务侦听器设置为某种非工作状态。然后我完成活动。但这并没有按预期工作:如果我为 onBackPressed()
方法设置断点,它肯定有效,但由于某种原因,没有断点,onResult()
方法在 onBackPressed()
之前调用!
@MacGyver:“我就是这么做的。” ——嗯,你的描述似乎不匹配。例如,您会注意到我没有提到onBackPressed()
,部分原因是这还不够。 “每个片段可以有一个或多个服务绑定,因为它可能需要同时执行多个任务。” - 你不应该需要多个绑定。您只需要一项服务,因此需要一项Binder
。
好的,在片段的 onDestroy() 中,我现在有了:dataServiceListener.serviceBinder.removeListener();
getActivity().getApplicationContext().unbindService(dataServiceListener.svcConn);
但是,这并没有改变任何东西。 removeListener()
刚刚在ServiceBinder
中执行了listener = null
。
我不知道如何进一步帮助您。如果您的问题仍然是“我得到了两个回调而不是一个”,那么您需要弄清楚尽管您将其设置为 null
,但谁仍在持有侦听器,并修复它,或者其他什么。
好的,那么您是说将侦听器设置为null
应该可以完成工作吗?我想我将编写一些基本上什么都不做的简单服务,然后尝试使用它。但你为什么在书中提到弱引用?【参考方案3】:
我也有这个问题。完成后,您需要从服务中释放所有资源、侦听器和线程。
【讨论】:
我怎样才能做到这一点?我只需要在 Activity 关闭后立即销毁我的听众 (onBackPressed()
)
是的,您可以在活动完成的地方执行此操作并确保已完成,您还可以在服务中覆盖“onDestroy()”方法..
我正是这样做的,但看起来onDestroy
方法在 onResult 之后立即被调用,所以没有任何变化!【参考方案4】:
您的活动必须将自己注册/取消注册为侦听器。您需要使用正确的生命周期回调方法,而不是 onBackPressed()
。注册onStart()
,注销onStop()
。一种方法是让监听器成为服务的静态成员,并提供静态注册/注销方法。然后根据需要从您的活动中调用它们。
【讨论】:
【参考方案5】:我终于解决了这个问题(不,我已经很久没有解决这个问题了:D)。
在调用Fragment
的onDestroy
之前对侦听器进行了回调。所以布尔“dontupdate”值从未设置为false。在主活动中覆盖 onBackPressed
解决了这个问题,因为我为每个片段调用了一个 destroy()
方法,负责将布尔值设置为 false。
【讨论】:
以上是关于Activity 关闭并重新打开时避免服务回调的主要内容,如果未能解决你的问题,请参考以下文章
如何创建一个倒数计时器,当屏幕关闭时停止并“等待”,重新打开时恢复。安卓
RTI DDS 两个应用程序在同一域上发布数据。当一个应用程序关闭并重新打开时,它会丢失数据。怎么解决?