高频率Android服务与活动的通信:最佳选择?

5
在 Android 应用程序(API 级别 14 及更高版本)中,有一个服务被不同的活动使用(它们使用本地绑定调用其函数),该服务以每秒60次的频率计算游戏元素的位置(坐标)。[我有充分的理由在服务中进行计算而不是直接在活动中进行]。这些连续的位置更新在服务中(用于游戏逻辑检查)和游戏屏幕活动中(用于绘图)都是必需的。
我的问题是:对于这种情况,选择哪种选项来进行服务到活动的通信?我想将毫秒级别的延迟最小化(最好几乎没有延迟),使得服务计算出新位置的时间点和 UI(活动)知道新位置的时间点尽可能接近。
您可以假设所有内容(活动、服务)都在一个进程中。
我考虑了一种解决方案,即每个活动在恢复时告诉服务其存在(并在 onPause() 中告诉服务它们不可用),以便服务在计算新坐标时可以直接检查活动是否可见,并且如果可见,则直接调用 visibleActivity.someMethod()。应该注意的是,.someMethod() 内部会将控制权移交给 UI 线程。
我提出这个方案,因为我相信通过广播传递会降低性能。
请让我知道您的想法!

如果您不需要获取结果,建议查看http://developer.android.com/reference/android/support/v4/content/LocalBroadcastManager.html。 - Kristopher Micinski
我对广播的不满在于,你必须编写大量的样板代码。例如,如果我想调用activity.someMethod(),我实际上必须创建一个意图(intent),将一些键/值对作为额外内容放入其中以指定我要调用哪个方法,然后在接收方(BroadcastReceiver)进行字符串比较。这对我来说似乎只是丑陋和不必要的。 - MShekow
2个回答

3
你的活动应该在服务中注册使用AIDL实现的侦听器,并且服务应该在RemoteCallbackList中维护它们。
你的服务应该有两个方法,可以在onResume/onPause中被你的活动调用。
public void registerListener(IYourListenerInterface listener) {
     callbackList.register(listener);
}


public void unregisterListener(IYourListenerInterface listener) {
     callbackList.unregister(listener);
}

当你需要发送消息时:

int numOfListeners = callbackList.beginBroadcast();

for (int i = 0; i < numOfListeners; i++) {
    try {
        callbackList.getBroadcastItem(i).whatever();
    } catch (RemoteException e) {
        //ignore, listener probably gone
    }
}
callbackList.finishBroadcast();

我明白了。谢谢你的例子。我猜调用beginBroadcast()finishBroadcast()的目的是为了在执行(someInterface.doSomething())时阻塞registerListener()unregisterListener()的调用? - MShekow
如果您的服务和活动在同一进程中,则它们共享相同的主应用程序线程。如果您从主线程进行回调,您应该确保在此期间您的活动不能注册/注销,因为 onResume 和 onPause 始终在其中调用。是的,您的接口需要继承自 IInterface。 - Andy McSherry
你实际上不需要使用IPC来使用AIDL。事实上,你可以直接传递对象,因为它们在同一个进程中,但是要非常小心地避免泄漏Activity/Service。 - Andy McSherry
是的,关于泄漏问题,我最初发布了“...每个活动在恢复时(在onResume()中)告诉服务其存在,并且在onPause()中活动告诉服务它们不可用”。那应该足够了,对吧? - MShekow
在我看来,如果它们在同一个进程中,则不需要使用AIDL。Messinger / Broadcast接收器可以在这里完成工作。 - Ewoks
显示剩余4条评论

1

我会使用LocalBroadcastManager,或者可能是Otto。它们比已经提出的解决方案(绑定和回调)提供更好的组件解耦。这很重要,因为活动可以根据用户操作(配置更改、返回按钮)而来来去去。

我有充分的理由在服务中进行计算,而不仅仅是直接在活动中进行

说实话,我无法想象那些理由会是什么。


Otto在内部如何进行通信?我担心即使使用LocalBroadcastManager,60 Hz的服务到活动的通信也会产生太多的开销。计算在服务中也是必要的原因之一是因为游戏逻辑检查。我不希望UI成为除了呈现正在发生的事情之外的任何其他东西,所以我永远不会在那里放置任何游戏逻辑。 - MShekow
@NameZero912: "Otto在内部是如何进行通信的?" - 你需要查看它的实现。"我担心即使使用LocalBroadcastManager,60 Hz的服务到活动之间的通信也会产生过多的开销" - 以我的观点来看,并不会多出几个数量级。"所以我绝对不会在那里放置任何游戏逻辑" - 但这并不能解释为什么它需要在服务中而不是POJO中。 - CommonsWare
关于“但这并不能解释为什么它需要在服务中而不是POJO”:实际上,我有一个处理网络的组件(因为这是一款多人游戏)。这个组件必须在服务中,因为它应该在没有可见活动的情况下工作(特别是对于托管游戏的设备) 。 Networking和我的GameLogic组件之间存在双向通信,需要尽可能低延迟。同样,GameLogic和可见活动之间也存在通信。Networking和活动之间也有一些通信,但我... - MShekow
需要它们具有低延迟。因此,我认为GameLogic和Networking应该一起作为服务,并可以使用普通的POJO引用有效地相互通信。我可以使用Otto从服务类到活动的通信,并使用服务绑定从活动到服务进行通信。我严格反对实现意图生成的胡说八道(即使是一对一的通信)。对我来说,发明字符串以进行比较并将其放置为意图额外信息,仅以此方式进行方法调用似乎是难以理解的愚蠢行为。 - MShekow
@NameZero912,将繁重的计算放入服务中并不意味着您从UI线程中释放它。特别是当服务不是远程而是本地时。 - Ewoks

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