从服务更新 UI 比意图更有效的方法?
Posted
技术标签:
【中文标题】从服务更新 UI 比意图更有效的方法?【英文标题】:More efficient way of updating UI from Service than intents? 【发布时间】:2011-02-06 23:05:22 【问题描述】:我目前在 android 中有一个 Service,它是一个示例 VOIP 客户端,因此它会侦听 SIP 消息,如果收到一条消息,它会启动一个带有 UI 组件的 Activity 屏幕。
然后以下 SIP 消息确定 Activity 在屏幕上显示的内容。 例如,如果它是一个来电,它将显示应答或拒绝或一个去电,它将显示一个拨号屏幕。
此刻我使用 Intents 让 Activity 知道它应该显示什么状态。
一个例子如下:
Intent i = new Intent();
i.setAction(SIPEngine.SIP_TRYING_INTENT);
i.putExtra("com.net.INCOMING", true);
sendBroadcast(i);
Intent x = new Intent();
x.setAction(CallManager.SIP_INCOMING_CALL_INTENT);
sendBroadcast(x);
Log.d("INTENT SENT", "INTENT SENT INCOMING CALL AFTER PROCESSINVITE");
因此,Activity 将为这些 Intent 注册一个广播接收器,并将根据它收到的最后一个 Intent 切换其状态。
示例代码如下:
SipCallListener = new BroadcastReceiver()
@Override
public void onReceive(Context context, Intent intent)
String action = intent.getAction();
if(SIPEngine.SIP_RINGING_INTENT.equals(action))
Log.d("cda ", "Got RINGING action SIPENGINE");
ringingSetup();
if(CallManager.SIP_INCOMING_CALL_INTENT.equals(action))
Log.d("cda ", "Got PHONE RINGING action");
incomingCallSetup();
;
IntentFilter filter = new IntentFilter(CallManager.SIP_INCOMING_CALL_INTENT);
filter.addAction(CallManager.SIP_RINGING_CALL_INTENT);
registerReceiver(SipCallListener, filter);
这可行,但它似乎效率不高,Intent 将在整个系统范围内广播,并且 Intent 必须针对不同的状态触发似乎效率越低,我必须包含的越多以及增加复杂性。
所以我想知道是否有其他更有效和更清洁的方法来做到这一点?
有没有办法让 Intent 只在应用程序内广播?
回调会更好吗?如果是这样,为什么以及以什么方式实施它们?
【问题讨论】:
【参考方案1】:2015 年更新:
这个问题/答案仍然有一点点活跃,但它已经 5 年多了,而且情况发生了很大变化。 5年前,下面的答案是我会如何处理它。后来我写了一个非常轻量级的依赖注入解决方案,我用了一段时间(我在 cmets 中提到过)。现在,我会使用 Dagger 和 RxAndroid 来回答这个问题。 Dagger 将一个“中介”类注入到 Service 和所有需要通知的活动中,Service 会将状态更新推送到中介类,中介类将公开一个可观察的活动以消耗状态更新(代替OP的广播接收器)。
原答案
我通常继承 Application 并让我的应用内通信通过这个类(或者让 Application 拥有的中介来完成工作......无论如何,Application 是服务与之通信的入口点)。我有一个绑定服务也需要更新 UI(比你的简单得多,但想法相同),它基本上告诉应用程序它的新状态,然后应用程序可以以一种或另一种方式将此信息传递给当前积极的活动。您还可以维护指向当前活动活动的指针(如果有多个活动),并决定是否简单地更新当前活动、广播启动不同活动的意图、忽略消息等。我会还继承 Activity 并让您的新活动基类告诉应用程序它当前是 onResume 中的活动,并且它正在 onPause 中暂停(对于您的服务在后台运行并且活动全部暂停的情况)。
编辑:
针对评论,这里有更多细节。
您的应用程序目前大部分由 Activity 派生类和 Service 派生类组成。本质上,您从 android.app.Application 类的实例中获取功能。这是在您的清单(默认情况下)中声明的,带有以下行:
<application android:icon="@drawable/icon" android:label="@string/app_name">
清单中的应用程序元素不使用 android:name 属性,因此它只是创建默认 android.app.Application 类的实例来表示您的全局应用程序上下文。
在我的应用程序中,我创建了一个应用程序的子类(例如 ApplicationEx),并通过清单告诉我的应用程序这是要实例化为我的全局应用程序上下文的类。例如:
<application
android:name="com.mycompany.myapp.app.ApplicationEx"
android:icon="@drawable/app_icon"
android:label="@string/app_name">
我现在可以向 ApplicationEx 添加方法,以便用于通信的活动和服务。您的全局应用程序上下文始终只有一个实例,因此如果您的应用程序需要全局化,这就是您的起点。
第二个部分是,我没有从 Service 和 Activity 派生我的服务和活动,而是使用 getAppContext 方法创建每个服务和活动的子类,该方法转换 getApplicationContext 的返回值(这两个类中已经存在,因为它们从 Context 派生)到我的 ApplicationEx 类。
所以........
话虽如此,您将 CurrentActivity 属性添加到 Activity 类型的 ApplicationEx 类(或 ActivityBase,如果您像我一样将其子类化)。在 ActivityBase 的 onResume 方法中,您将自己传递给 ApplicationEx 以将 CurrentActivity 设置为该活动。现在,您可以在 ApplicationEx 上公开方法以将信息直接传递给当前活动,而不是依赖 Intent 机制。
我能说的很清楚了
【讨论】:
我已经阅读了你的帖子几次 Rich,我不确定你的意思。您能否提供一个示例的链接,说明它是如何做到的? 我在上面提供了进一步的解释......如果您有更多的?或者不清楚,请告诉我。 我可以处理来自应用程序的 IntentService 回调消息吗? 问你的问题,Rich。一方面,我发现您的想法非常有趣,因为它允许使用某种 DI a la Spring 构建应用程序(我在项目中使用它取得了巨大成功!)另一方面,我扫描了 android 的开发指南以查找你描述的东西,但什么也没找到。所以我的问题是,谷歌真的推荐使用所有应用程序工件之间的直接引用而不是 IPC 或其他有趣的老式技术吗?是否有任何限制、陷阱或陷阱? TIA @injecteer 这个答案是 2 1/2 岁。此后,我构建了自己的轻量级 DI 容器,并将其包含在发布到 Github 的一个小标准库中。我还没有用示例完全填写自述文件,但我启动了一个使用它的示例项目。有问题的类是 ServiceLocator。我很乐意讨论我的实现、我如何使用它等等。github.com/aguynamedrich/beacon-utils【参考方案2】:您可以使用LocalBroadcastManager 将广播意图仅发送到您自己的应用程序而不是系统范围:
帮助注册 Intent 并将广播发送到进程中的本地对象。与使用 sendBroadcast(Intent) 发送全局广播相比,这具有许多优势:
您知道您正在广播的数据不会离开您的应用,因此无需担心泄露私人数据。
其他应用程序不可能将这些广播发送到您的应用程序,因此您不必担心它们会利用安全漏洞。
它比通过系统发送全局广播更有效。
但是,我仍然建议使用 Service 方法,并在必要时通过 Handler 进行本地绑定和对话,以提高效率。
【讨论】:
以上是关于从服务更新 UI 比意图更有效的方法?的主要内容,如果未能解决你的问题,请参考以下文章