11

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

解决方法一:从activity绑定

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

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

解决方案二:

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

问题:

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

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

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

编辑

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

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

4

4 回答 4

5

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

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

这个问题的最后一个症结是我不确定我是否可以从同一个上下文中多次绑定 - 关于绑定的文档暗示绑定和上下文的生命周期之间存在一些依赖关系,所以我担心我必须自己做引用计数的形式。我查看了源代码并最终来到这里: 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 /

于 2014-06-27T18:50:32.623 回答
0

有一种更简单的方法可以处理这种情况,称为 anIntentService您可以在此处了解更多信息。从安卓网站:

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

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

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

编辑 - 在评论中回答问题

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

于 2014-06-27T17:50:50.787 回答
0

您能否仅在清单中使用 configChanges 属性选择要处理的配置并手动更改 UI 中的方向?在这种情况下,您只需要绑定到服务 inonCreate然后 unBind in 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);
                }
            }
        }

于 2014-06-26T13:42:40.577 回答
0

我有一个类似的问题,我在活动中使用了绑定服务。在活动中,我定义了一个ServiceConnection, mConnection,并在里面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 提出的方案,使用一个保留的 Fragment 来保留变量,但首先记得尝试一个简单的事情:将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;
    }
};
于 2014-07-07T14:07:58.017 回答