【问题标题】:Bind to service from new Context for configuration changes or bind from app context?从新上下文绑定到服务以进行配置更改或从应用上下文绑定?
【发布时间】:2014-06-19 16:45:02
【问题描述】:

我正在尝试确定绑定服务是否适合在我的应用中进行后台工作。要求是各种应用程序组件可以通过它发出不同优先级的 Web 请求。 (因此服务必须维护某种队列,并且能够取消它对其他更高优先级的正在进行的请求)。我希望该服务对用户来说相对不显眼,这样他们在完成应用程序后就不会发现它正在运行 - 如果我想做一些更重要的事情,在应用程序关闭时继续,我可以使用 startForeground( ) 在此过程中推送通知。

解决方法一:从activity绑定

因此,对于给定的应用程序组件,它应该能够绑定到服务以完成工作。但是似乎有一个众所周知的问题,即如果一个活动正在执行绑定,那么在配置更改(轮换)期间,该绑定将丢失,因为活动将被关闭。

所以,我想我可以使用我创建的另一个上下文 (new Context()) 并将其绑定到服务,然后使用非 UI 片段来跨配置更改维护此上下文,直到我认为我完成用它。我只能在配置更改期间执行此操作,或者作为从活动绑定的永久替代方案。 (我可能应该指出,这是一种标准的 recommended 方式来维护跨配置更改的实例)

解决方案 2:

我看到的主要替代方法是我可以使用应用程序上下文进行绑定 - 但这会持续太久吗?和/或应用上下文和服务之间是否存在某种循环关系,从而防止服务和应用上下文被破坏?

问题:

所以我试图回答自己的问题是:我应该使用第一种方法(具有临时上下文的活动)吗?还是第二个(只是将服务绑定到应用上下文)?

我是否认为应用上下文可以多次绑定到服务,然后以相同的次数解除绑定? (即每个上下文可以有多个有效绑定)?

在第一个解决方案中使用我自己的上下文 (new Context()) 会导致任何问题吗?

编辑

找到更多信息:https://groups.google.com/forum/#!topic/android-developers/Nb58dOQ8Xfw

似乎也很难任意“创建”上下文,因此解决方案 1 和 2 的组合似乎适合在跨配置更改时维护服务连接但绑定到应用程序上下文的情况下。我仍然担心从应用程序上下文中解绑两次的可能性。自己计算绑定似乎没有必要 - 任何人都可以确认/否认绑定是每个连接而不是每个上下文吗?

【问题讨论】:

  • 它旨在为活动服务链接使用绑定。我不明白你为什么不按照它本来的方式使用它。只需在配置更改后再次绑定您的活动即可。
  • Raph - 当上下文被销毁时服务不会死
  • 即我不想因为手机旋转而耽误长时间的网络动作......
  • 视情况而定。如果您使用绑定启动服务。删除所有绑定后,该服务将被销毁。但是你可以独立于绑定启动服务,这样即使没有任何绑定它也能存活。你想延迟任何事情。
  • 是的,对不起,我没有说清楚 - 我想用 bind 开始它,这样它只在需要时存在。

标签: java android android-service android-background android-service-binding


【解决方案1】:

所以在进行了一些挖掘之后,我想我已经想出了一个(迄今为止)未经测试的解决方案。

首先,根据 Diane 的建议:https://groups.google.com/forum/#!topic/android-developers/Nb58dOQ8Xfw 我应该绑定到应用程序上下文 - 所以我丢失上下文的问题已经消失了 - 我可以在使用非 UI 片段更改的配置中维护我的 ServiceConnection - 很棒。然后,当我完成后,我可以使用应用程序上下文交回服务连接并取消绑定。我不应该收到任何泄漏的服务连接警告。 (我可能应该指出,这是一种标准的 recommended 方式来维护跨配置更改的实例)

这个问题的最后一个症结是我不确定我是否可以从同一个上下文中多次绑定 - 关于绑定的文档暗示绑定和上下文的生命周期之间存在一些依赖关系,所以我担心我必须做我自己的引用计数形式。我查看了源代码并最终来到这里:http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.4.2_r1/android/app/LoadedApk.java#LoadedApk.forgetServiceDispatcher%28android.content.Context%2Candroid.content.ServiceConnection%29

至关重要的是,这些行:

sd = map.get(c);
    if (sd != null) {
        map.remove(c);
        sd.doForget();
        if (map.size() == 0) {
            mServices.remove(context);
        }

显示map 正在用于我担心的引用计数。

所以带回家是这样的:

  • 绑定的服务可以在应用上下文中正常工作,我们应该这样做以防止在配置更改期间将服务连接从一个活动泄漏到另一个活动
  • 我可以将我的服务连接安全地保留在非 UI 片段上,并在完成后使用它解除绑定

我会尽快发布一些经过测试的代码。

更新和测试的解决方案:我已经编写了一些代码来测试并在此处发布:https://github.com/samskiter/BoundServiceTest

它似乎工作得很好,并且非 UI 片段(数据片段)在轮换更改期间充当一个很好的代理侦听器以捕获来自服务的结果(侦听器的目的是将请求紧密绑定到 UI为了保证它保持响应。显然,任何模型更改都可以通过观察者传播到 UI。)

编辑:我认为我应该明确回答 OP 中的问题...

  • 我应该使用第一种方法(具有临时上下文的活动)吗?还是第二个(只是将服务绑定到应用程序上下文)? 第二个

  • 我是否认为应用上下文可以多次绑定到服务,然后以相同的次数解除绑定? (即每个上下文可以有多个有效的绑定)?

  • 在第一个解决方案中使用我自己的上下文 (new Context()) 会导致任何问题吗? 这根本不可能

最后总结:

这种模式应该非常强大 - 我可以在我的应用程序中优先考虑来自各种来源的网络 IO(或其他任务)。我可以有一个前台活动来制作用户要求的一些小 io,同时我可以踢一个前台服务来同步我所有的用户数据。前台服务和活动都可以绑定到同一个网络服务以完成它们的请求。

所有这一切,同时确保服务只在它需要的时间内存活 - 即它与 android 配合得很好。

我很高兴能尽快将它应用到应用中。

更新:我已尝试将其写下来,并在此处的博客条目中为更广泛的后台工作问题提供一些背景信息:http://blog.airsource.co.uk/2014/09/10/android-bound-services/

【讨论】:

  • 为什么需要通过背景片段进行调解?它似乎不必要地复杂。
  • 这样您就可以保持连接。这是使用onretainnonconfigurationinstance 的新替代方案,并不复杂
  • 这只是另一层复杂性。您正在使用片段来维护您绑定服务的上下文,这对我来说很奇怪,因为服务有自己的上下文并且可以在不绑定任何东西的情况下运行。但是发布您的最终解决方案,因为我有兴趣看看您最终使用的是什么。
  • 抱歉,我可能解释得不好。我只是打算使用片段来维护连接而不是上下文。维护这样的上下文会导致泄漏。保持连接实际上只是保持指向服务的指针并使其保持活动状态的一种方式。因此,当所有指向服务的“指针”都消失时,它可能会被销毁。
  • 你的意思是参考。但是您的片段的生命周期仍然与托管它的活动相关联,无论是后台还是其他。那么配置更改如何不会导致片段生命周期发生变化并无论如何都会失去与服务的连接呢?
【解决方案2】:

你能不能只选择你想在清单中使用 configChanges 属性处理的配置,并在 UI 中手动更改方向?这种情况下只需要绑定onCreate中的服务,然后在onDestroy中解除绑定即可。

或者尝试这样的事情(我没有做正确的错误检查):

类 MyServiceConnection 实现 ServiceConnection,Parcelable { 公共静态最终 Parcelable.Creator CREATOR = 新 Parcelable.Creator() { public MyServiceConnection createFromParcel(Parcel in) { 返回新的 MyServiceConnection(in); } 公共 MyServiceConnection[] newArray(int size) { 返回新的 MyServiceConnection[大小]; } }; @覆盖 公共 int describeContents() { 返回0; } @覆盖 公共无效writeToParcel(包裹目的地,int标志){ } @覆盖 public void onServiceConnected(ComponentName name, IBinder service) { } @覆盖 公共无效 onServiceDisconnected(组件名称名称){ } } 我的服务连接我的服务连接; 布尔配置更改 = 假; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if (savedInstanceState != null) { myServiceConnection = savedInstanceState.getParcelable("serviceConnection"); } 别的 { 我的服务连接 = 新的我的服务连接(); } } @覆盖 受保护的无效 onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); if (myServiceConnection != null) { outState.putParcelable("serviceConnection",myServiceConnection); 配置更改=真; } } @覆盖 受保护的无效 onDestroy() { super.onDestroy(); if (!configChange && myServiceConnection != null){ 取消绑定服务(myServiceConnection); } } }

【讨论】:

  • 并非如此 - 这是最后的办法。请在此处查看 Romain Guy 的 cmets:stackoverflow.com/questions/2620917/… 为否决票道歉 - 我不希望其他人提出这个问题并以这种方式解决问题。
  • 不用担心。我添加了另一个解决方案。
  • 嗯,我看到您正在尝试使服务连接可打包,但我不知道它是如何打包的。你能解释一下吗?
  • 为了解除绑定服务,您需要绑定服务时使用的服务连接实例。由于服务连接是一个接口,您的类不需要任何字段。这就是我有空构造函数的原因。但是,如果您愿意,您可以添加一些字段,但您必须适当地更改 parcelable 方法。如果您需要更多解释,请告诉我
  • 你是绝对正确的。当我运行测试时,我没有意识到存在 ServiceConnection 泄漏。
【解决方案3】:

有一种更简单的方法可以处理这种情况,称为IntentService,您可以阅读更多关于here 的信息。来自安卓网站:

“IntentService 类为在单个后台线程上运行操作提供了一个简单的结构。这允许它处理长时间运行的操作而不会影响用户界面的响应能力。此外,IntentService 不受大多数​​用户界面生命周期的影响事件,因此它会在会关闭 AsyncTask 的情况下继续运行”

您可以在后台线程上启动长时间运行的操作,而不是将您的服务绑定到您的活动,只需使用启动 IntentService 的意图即可。

public class RSSPullService extends IntentService {

    @Override
    protected void onHandleIntent(Intent workIntent) {
    // Gets data from the incoming Intent
    String dataString = workIntent.getDataString();
    ...
    // Do work here, based on the contents of dataString
    ...
    }
}

这是取自 android 文档的示例。您将发送带有相关数据的意图,然后在服务中处理该数据以执行您想要的操作。例如,您可以在您的意图中添加一个优先级标志,以便您的服务知道哪些请求先于其他请求。

intent 服务的好处是它在后台线程上运行,并且不依赖于启动活动的生命周期。这意味着您的配置更改不应影响服务执行。

当您的服务完成后,您可以使用本地广播 report work status - 将结果直接发送回活动(通过广播接收器),或者甚至可能通过 onNewIntent() (虽然让它工作有点更多笨重。

编辑 - 在评论中回答问题

IntentService 是一个比较小的类。这使得修改变得容易。 IntentService 的股票代码调用 stopSelf() 并在没有工作要做时死亡。 This can be easily fixed。检查IntentService 的源代码(请参阅上一个链接),您可以看到它几乎已经在队列中工作,在 onStart() 中接收消息,然后按照评论中描述的接收顺序执行它们。重写 onStart() 将允许您实现一个新的队列结构以满足您的需求。使用那里的示例代码来了解如何处理传入消息并获取Intent,然后只需创建自己的数据结构来处理优先级。您应该能够在 IntentService 中启动/停止 Web 请求,就像在 Service 中一样。因此,通过覆盖 onStart() 和 onHandleIntent() 你应该能够做你想做的事。

【讨论】:

  • 嗨@Rarw,感谢您的回复,不幸的是,我已经看过 IntentService 但它不符合我的要求,因为我需要能够按优先级取消和排队作业,因此需要一个合理定制服务实现:“要求是各种应用程序组件可以通过它发出不同优先级的 Web 请求。(因此服务必须维护某种队列并能够取消它对其他更高优先级的正在进行的请求)。”
  • 具体问题是 IntentService 被设计为顺序运行任务:“工作请求顺序运行。如果一个操作在 IntentService 中运行,并且您向它发送另一个请求,该请求将等待直到第一次手术结束。”当然,如果您觉得我误解了,请纠正我。
  • 最后一点:我不能立即从 IntentService 返回然后发布更新,因为 IntentService 在工作队列为空时会自行停止:“客户端通过 startService(Intent) 调用发送请求;服务是根据需要启动,使用工作线程依次处理每个 Intent,并在工作结束时自行停止。”
  • 我会回答你上面的问题
  • 谢谢,我在 android 源代码中进行了一些挖掘以减轻我的紧张情绪后,也发布了我自己的答案
【解决方案4】:

我遇到了类似的问题,我在 Activity 中使用了绑定服务。在活动中我定义了ServiceConnectionmConnection,在onServiceConnected 中我设置了一个类字段syncService,它是对服务的引用:

private SynchronizerService<Entity> syncService;

(...)

/** Defines callbacks for service binding, passed to bindService() */
private ServiceConnection mConnection = new ServiceConnection() {

    @Override
    public void onServiceConnected(ComponentName className, IBinder service) {
        // We've bound to LocalService, cast the IBinder and get
        // LocalService instance
        Log.d(debugTag, "on Service Connected");
        LocalBinder binder = (LocalBinder) service;
        //HERE
        syncService = binder.getService();
        //HERE
        mBound = true;
        onPostConnect();
    }

    @Override
    public void onServiceDisconnected(ComponentName arg0) {
        Log.d(debugTag, "on Service Disconnected");
        syncService = null;
        mBound = false;
    }
};

使用这种方法,每当方向改变时,我都会在引用syncService 变量时得到一个NullPointerException,尽管事实上服务正在运行,并且我尝试了几种从未奏效的方法。

我正准备实现Sam提出的方案,使用一个保留的片段来保留变量,但首先记得尝试一个简单的事情:将syncService变量设置为静态。和连接引用是方向改变时保持不变!

所以现在我有

private static SynchronizerService<Entity> syncService = null;

...

/** Defines callbacks for service binding, passed to bindService() */
private ServiceConnection mConnection = new ServiceConnection() {

    @Override
    public void onServiceConnected(ComponentName className, IBinder service) {
        // We've bound to LocalService, cast the IBinder and get
        // LocalService instance
        Log.d(debugTag, "on Service Connected");
        LocalBinder binder = (LocalBinder) service;
        //HERE
        if(syncService == null) {
            Log.d(debugTag, "Initializing service connection");
            syncService = binder.getService();
        }
        //HERE
        mBound = true;
        onPostConnect();
    }

    @Override
    public void onServiceDisconnected(ComponentName arg0) {
        Log.d(debugTag, "on Service Disconnected");
        syncService = null;
        mBound = false;
    }
};

【讨论】:

  • 我不认为将变量设置为静态(有效地使其成为全局变量)是维护活动中任何变量的标准或推荐方式。如果我重新使用该活动怎么办?或者如果由于其他原因被销毁怎么办?
  • @Sam 我不明白为什么不...一个私有静态变量,甚至是一个全局变量,在活动之外,如果有人想重用它?当然,它比无头片段成本更低,尽管我同意您的解决方案可能更优雅
  • 因为绑定的服务并不总是在运行。从 Activity 的 POV 来看,您只能保证它与该 Activity 一样长(不包括配置更改,现在我们已经修复了),因此您最好在正确销毁时清除它。因此,将其提供给其他任何人是没有意义的,因为您可能会死,删除您的连接并终止服务。如果您的意思是模型对象可以使用它 - 它们可以获取自己的引用并按预期使用绑定的活动 - 其中应用程序组件每个都单独绑定到它以使其保持活动状态。
  • @Sam 我明白你在说什么。我想这取决于架构。在我的应用程序的情况下,它不仅仅是一个“状态”......服务应该始终可用,例如 HTTP 池客户端或应用程序范围的图像缓存应该。至于您所说的并发问题,2 个活动更改静态变量,我认为如果 2 个活动尝试操作保留片段内的变量,您的解决方案也可能有同样的问题,不是吗?
  • 我使用绑定服务,因为它更容易通信并且可以直接调用方法等。哦,我没有意识到您保留的片段是每个实例的。如果我在使用静态或单例模式时遇到问题,那么我一定会实施您的解决方案!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-06-10
  • 2011-08-16
  • 2013-03-27
  • 2021-12-31
  • 1970-01-01
  • 2011-09-07
相关资源
最近更新 更多