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

时间:2014-06-19 16:45:03

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

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

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

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

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

解决方案数字2:

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

问题:

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

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

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

修改

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

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

4 个答案:

答案 0 :(得分:5)

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

首先,基于Diane的建议:https://groups.google.com/forum/#!topic/android-developers/Nb58dOQ8Xfw我应该绑定到应用程序上下文 - 所以我失去上下文的问题已经消失了 - 我可以使用Non更改配置中的ServiceConnection -UI片段 - 太棒了。然后,当我完成后,我可以使用应用程序上下文来交回服务连接并取消绑定。我不应该收到任何泄漏的服务连接警告。 (我应该指出,这是一种标准的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中的问题......

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

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

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

最后总结:

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

所有这些同时确保服务的生存时间与其所需的时间完全相同 - 即它与Android一起运行良好。

我很高兴很快就能将这款应用到应用中。

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

答案 1 :(得分:0)

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

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

  

    class MyServiceConnection implements ServiceConnection,Parcelable {
                public static final Parcelable.Creator CREATOR
                = new Parcelable.Creator() {
                    public MyServiceConnection createFromParcel(Parcel in) {
                        return new MyServiceConnection(in);
                    }

                    public MyServiceConnection[] newArray(int size) {
                        return new MyServiceConnection[size];
                    }
                };

                @Override
                public int describeContents() {
                    return 0;
                }

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

                }

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

                }

                @Override
                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();
                }

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

答案 2 :(得分:0)

有一种更容易处理这种情况的方法,称为IntentService,您可以阅读更多有关here的内容。来自android网站:

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

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

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

修改 - 回答评论中的问题

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

答案 3 :(得分:0)

我遇到了类似的问题,我在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;
    }
};

使用此方法,无论何时更改方向,我都会在引用NullPointerException变量时获得syncService,尽管服务正在运行,我尝试了几种从未起作用的方法。

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

所以现在我有了

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