从新的Context绑定服务以进行配置更改或从应用程序上下文绑定?

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

第一个解决方案:从活动绑定

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

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

解决方案数字2:

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

问题:

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

我是否正确认为应用程序上下文可以多次绑定到服务,然后从中取消绑定相同的次数? (即你可以有多个有效的绑定PER上下文)?

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

编辑

find了更多信息: https : //groups.google.com/forum/#!topic / android-Developers / Nb58dOQ8Xfw

似乎很难“任意”创建一个上下文,因此解决方案1和2的组合似乎是合适的,其中跨配置的服务连接保持变化但绑定是应用程序上下文。 我仍然担心从应用程序上下文解除绑定两次的可能性。 保持绑定计数本身似乎是不必要的 – 任何人都可以确认/否认绑定是每个连接而不是每个上下文?

所以在做了一些挖掘后,我想我已经提出了一个(尚未)未经测试的解决方案。

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

这个问题的最后关键是我不确定我是否可以从同一个上下文中多次绑定 – 绑定的文档意味着绑定和上下文的生命周期之间存在某种依赖性,因此我担心我必须自己做引用计数的forms。 我看了一下源代码并最终到了这里: 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中的问题……

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

  • 我是否正确认为应用程序上下文可以多次绑定到服务,然后从中取消绑定相同的次数? (即你可以有多个有效的绑定PER上下文)?

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

最后总结:

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

所有这一切,同时确保服务只在它需要的时间内生活 – 即它与android很好地兼容。

我很高兴能很快把它变成一个应用程序。

更新:我已经尝试写这篇文章并在博客条目中提供更广泛的后台工作问题: http : //blog.airsource.co.uk/2014/09/10/android-bound-services /

您是否可以只选择清单中使用configChanges属性处理的配置,并手动在UI中进行方向更改? 在这种情况下,您只需要绑定到onCreate的服务,然后绑定到onDestroy unBind。

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

  

     class MyServiceConnection实现ServiceConnection,Parcelable {
                 public static final Parcelable.Creator CREATOR
                 = new Parcelable.Creator(){
                     public MyServiceConnection createFromParcel(Parcel in){
                        返回新的MyServiceConnection(in);
                     }

                     public MyServiceConnection [] newArray(int size){
                        返回新的MyServiceConnection [size];
                     }
                 };

                 @覆盖
                 public int describeContents(){
                    返回0;
                 }

                 @覆盖
                 public void writeToParcel(Parcel dest,int flags){

                 }

                 @覆盖
                 public void onServiceConnected(ComponentName name,IBinder service){

                 }

                 @覆盖
                 public void onServiceDisconnected(ComponentName name){

                 }
             }
             MyServiceConnection myServiceConnection;
             boolean configChange = false;

             protected void onCreate(Bundle savedInstanceState){
                 super.onCreate(savedInstanceState);
                的setContentView(R.layout.activity_main);
                 if(savedInstanceState!= null){
                     myServiceConnection = savedInstanceState.getParcelable(“serviceConnection”);
                 } else {
                     myServiceConnection = new MyServiceConnection();
                 }

             }
             @覆盖
             protected void onSaveInstanceState(Bundle outState){
                 super.onSaveInstanceState(outState);
                 if(myServiceConnection!= null){
                     outState.putParcelable( “serviceConnection”,myServiceConnection);
                     configChange = true;
                 }
             }
             @覆盖
             protected void onDestroy(){
                 super.onDestroy();
                 if(!configChange && myServiceConnection!= null){
                     unbindService(myServiceConnection);
                 }
             }
         }

有一种更简单的方法来处理这种称为IntentService ,你可以在这里阅读更多信息。 来自android网站:

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

您可以通过简单地使用启动IntentService的intent来启动后台线程上的长时间运行操作,而不是将服务绑定到您的活动。

 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文档中获取的示例。 您将使用相关数据发送意图,然后在服务中处理该数据以执行您想要的操作。 例如,您可以只为您的意图添加一个优先级标志,以便您的服务知道哪些请求先于其他请求。

意图服务的好处是它在后台线程上运行,并且与启动活动的生命周期无关。 这意味着配置更改不应对服务执行产生影响。

当您的服务完成后,您可以使用本地广播报告工作状态 – 将结果直接发送回活动(通过广播接收器),甚至可能通过onNewIntent()(尽管使其工作更加笨拙。

编辑 – 在评论中回答问题

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

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

 private SynchronizerService 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; } }; 

使用这种方法,每当方向改变时,我会在引用syncServicevariables时得到NullPointerException ,尽管服务正在运行,我尝试了几种从未起作用的方法。

我准备实现Sam提出的解决方案,使用保留的片段来保存variables,但首先要记住尝试一个简单的事情:将syncServicevariables设置为static .. 并且当方向改变时保持连接引用!

所以现在我有

 private static SynchronizerService 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; } };