65.9K
CodeProject 正在变化。 阅读更多。
Home

适用于 Xamarin Android 的“粘性”Intent Service,用于长时间运行的任务

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.20/5 (5投票s)

2015年12月31日

Apache

5分钟阅读

viewsIcon

38068

一个结合了 Intent Service 的易用性与粘性服务的长时间运行功能的类

引言

Android 服务是允许在后台执行工作的 Android 组件。虽然顾名思义,服务可以用于构建长时间运行的持久性后台任务,但这并非其默认行为。Android 服务主要有两种类型:

  • Service 类,以及
  • 派生的 IntentService 类。

虽然 IntentService 类非常易于使用,并且适用于大多数情况,但它无法支持需要持久 TCP 连接(例如 XMPP 连接)或长时间等待的后台服务的情况。为此,需要 Sticky 服务行为,该行为仅适用于基类 Service。在本文中,我们为 Xamarin Android 提出了新的 StickyIntentService 类。该类结合了:

  1. IntentService 的易用性和内置功能,即操作在单独的后台线程中运行,只需要实现 OnHandleIntent 方法等。
  2. Service 类提供的 Sticky 后台行为,这使得服务在被 Android 停止后可以重新启动。

StickyIntentService 适用于包含对长时间运行的后台监听 TCP 连接的引用,例如 XMPP 库(如 Sharp.Xmpp)所需的连接。该类具有类似 IntentService 的接口供使用,但缺少 IntentService 的“粘性”行为。

背景

Android 服务由 Service 类提供。默认情况下,Service 对象的运算在主线程上运行,因此必须为这些运算构造一个后台线程。因此,Service 类可能很难使用。作为附加选项,Android 提供了 IntentService 类。IntentService 通过将所有 Intent 发送到工作队列进行处理来工作。此队列在单独的线程上串行处理每个 Intent,并将 Intent 传递给 OnHandleIntent 方法。当所有 Intent 都处理完毕后,IntentService 会通过内部调用 StopSelf 来停止自身。

最后这一点对于在 IntentService 对象中拥有长时间运行的 TCP 连接或其他后台任务(例如 XMPP 所需的任务)至关重要。当处理完提供的 Intent 后,IntentService 会停止自身,此时 Android 可以销毁 IntentService。指向对象或长时间连接(例如 TCP 连接)的任何引用现在都可能被操作系统销毁。此外,由于无法使 IntentService 成为 Sticky,因此服务稍后不会被重新启动。因此,尽管 IntentService 适用于各种应用程序,但它不适合作为长时间运行的后台服务,或用于支持长时间运行的 TCP 连接(例如使用 Sharp.Xmpp 的 XMPP 连接)。

需要注意的是,当系统内存不足时,Android 可能会停止任何正在运行的服务。但是,对于 Service 对象,可以传递 StickyRedeliverIntent Intent 来重新启动后台服务。摘录自 Xamarin Guides:

“当服务被系统停止时,Android 将使用 OnStartCommand 返回的值来确定如何或是否应重新启动服务。该值是 StartCommandResult 类型,可以是以下任何一种:

  • Sticky – 粘性服务将被重新启动,并在重启时向 OnStartCommand 传递一个空 Intent。用于服务持续执行长时间运行的操作,例如更新股票信息。
  • RedeliverIntent – 服务将被重新启动,并将系统停止服务之前传递给 OnStartCommand 的最后一个 Intent 重新传递。用于继续长时间运行的命令,例如完成大型文件上传。
  • NotSticky – 服务不会自动重新启动。
  • StickyCompatibility – 在 API 级别 5 或更高版本上,重启将表现为 Sticky,但在早期版本上将降级为 API 级别 5 之前的行为。”

有关服务的更详细解释,请参阅 Xamarin Android Guides 中的相关部分。

因此,本文的目的是构建一个类,该类结合了 IntentService 类的易用性和 Sticky 行为,从而使该类适用于在长时间运行的 TCP 连接中运行,例如 Xmpp 库(例如 Sharp.Xmpp)所需的连接。

关注点

StickyIntentService 类派生自 Service 类。我们要求 IntentService 类具有所有功能和行为,但除了:

  1. 将服务定义为 Sticky
  2. 服务在提供的 Intent 处理完成时不会停止。

幸运的是,Android 的 IntentService 代码是 可用的,因此 Java Android 代码可以迁移到 C# 和 Xamarin,并进行修改,以显示所需行为。IntentService 本质上是一个 Service

 public abstract class StickyIntentService : Service
它具有一个 Looper 对象
  private volatile Looper mServiceLooper;
  private volatile ServiceHandler mServiceHandler;
  private String mName;
  private bool mRedelivery;

该类将初始 Java 文件中的功能和代码(除了 OnStartCommand 和 HandleMessage 方法)从 Java 迁移到 C#。在那里,初始 Java 代码

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    onStart(intent, startId);
    return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}

StickyIntentService 中被迁移到 C# 并进行了修改,如下所示:

public override StartCommandResult OnStartCommand(Intent intent,  StartCommandFlags flags, int startId){
        OnStart(intent, startId);
        return StartCommandResult.Sticky;
}

因此,OnStartCommand 方法现在返回 Sticky 值。请注意,原始 IntentService 代码中定义的 *RedeliverIntent* 不适用,因为它将重新传递最后一次传递给服务的 Intent。如果这是一个已处理并完成的消息,这可能会导致错误的结果。

此外,在 HanldeMessage 中处理完 Intent 后,服务不会停止,从而保留了后台运行的 Looper。更详细地说,初始 Java IntentService 代码

@Override
        public void handleMessage(Message msg) {
            onHandleIntent((Intent)msg.obj);
            stopSelf(msg.arg1);
        }

StickyIntentService 中被迁移到 C# 并进行了修改,如下所示:

         public override void HandleMessage(Message msg) {
                sis.OnHandleIntent((Intent)msg.Obj);
         }

Using the Code

您可以完全按照使用 IntentService 类的方式来使用 StickyIntentServiceClass。有关更多信息,请参阅 Xamarin 的服务指南。整个 StickyIntentService 类代码(C#)可在下方找到。请注意,保留了最初的 Android 源代码注释。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;

namespace Pgstath.Utils
{
          
/**
 * IntentService is a base class for {@link Service}s that handle asynchronous
 * requests (expressed as {@link Intent}s) on demand.  Clients send requests
 * through {@link android.content.Context#startService(Intent)} calls; the
 * service is started as needed, handles each Intent in turn using a worker
 * thread, and stops itself when it runs out of work.
 *
 * <p>This "work queue processor" pattern is commonly used to offload tasks
 * from an application's main thread.  The IntentService class exists to
 * simplify this pattern and take care of the mechanics.  To use it, extend
 * IntentService and implement {@link #onHandleIntent(Intent)}.  IntentService
 * will receive the Intents, launch a worker thread, and stop the service as
 * appropriate.
 *
 * <p>All requests are handled on a single worker thread -- they may take as
 * long as necessary (and will not block the application's main loop), but
 * only one request will be processed at a time.
 *
 * <div class="special reference">
 * <h3>Developer Guides</h3>
 * <p>For a detailed discussion about how to create services, read the
 * <a href="{@docRoot}guide/topics/fundamentals/services.html">Services</a> developer guide.</p>
 * </div>
 *
 * @see android.os.AsyncTask
 */
    public abstract class StickyIntentService : Service
    {

        private volatile Looper mServiceLooper;
        private volatile ServiceHandler mServiceHandler;
        private String mName;
        private bool mRedelivery;

        private sealed class ServiceHandler : Handler {
            private StickyIntentService sis;
            public ServiceHandler(Looper looper, StickyIntentService sis): base(looper) {
                this.sis = sis;
            }
        
            public override void HandleMessage(Message msg) {
                sis.OnHandleIntent((Intent)msg.Obj);
            }
        }

    /**
     * Creates an IntentService.  Invoked by your subclass's constructor.
     *
     * @param name Used to name the worker thread, important only for debugging.
     */
    public StickyIntentService(String name):base()
    {
        mName = name;
    }

    /**
     * Sets intent redelivery preferences.  Usually called from the constructor
     * with your preferred semantics.
     *
     * <p>If enabled is true,
     * {@link #onStartCommand(Intent, int, int)} will return
     * {@link Service#START_REDELIVER_INTENT}, so if this process dies before
     * {@link #onHandleIntent(Intent)} returns, the process will be restarted
     * and the intent redelivered.  If multiple Intents have been sent, only
     * the most recent one is guaranteed to be redelivered.
     *
     * <p>If enabled is false (the default),
     * {@link #onStartCommand(Intent, int, int)} will return
     * {@link Service#START_NOT_STICKY}, and if the process dies, the Intent
     * dies along with it.
     */
    public void setIntentRedelivery(bool enabled) {
        mRedelivery = enabled;
    }

    public override void OnCreate()
    {
        // TODO: It would be nice to have an option to hold a partial wakelock
        // during processing, and to have a static startService(Context, Intent)
        // method that would launch the service & hand off a wakelock.

        base.OnCreate();
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.Start();

        mServiceLooper = thread.Looper;
        mServiceHandler = new ServiceHandler(mServiceLooper,this);
    }

    public override void OnStart(Intent intent, int startId) {
        Message msg = mServiceHandler.ObtainMessage();
        msg.Arg1 = startId;
        msg.Obj = intent;
        mServiceHandler.SendMessage(msg);
    }

    /**
     * You should not override this method for your IntentService. Instead,
     * override {@link #onHandleIntent}, which the system calls when the IntentService
     * receives a start request.
     * @see android.app.Service#onStartCommand
     */

    public override StartCommandResult OnStartCommand(Intent intent,  StartCommandFlags flags, int startId)
    {
        OnStart(intent, startId);
        return StartCommandResult.Sticky;
    }

    public override void OnDestroy() {
        mServiceLooper.Quit();
    }

    /**
     * Unless you provide binding for your service, you don't need to implement this
     * method, because the default implementation returns null. 
     * @see android.app.Service#onBind
     */
    public override IBinder OnBind(Intent intent) {
        return null;
    }

    /**
     * This method is invoked on the worker thread with a request to process.
     * Only one Intent is processed at a time, but the processing happens on a
     * worker thread that runs independently from other application logic.
     * So, if this code takes a long time, it will hold up other requests to
     * the same IntentService, but it will not hold up anything else.
     * When all requests have been handled, the IntentService stops itself,
     * so you should not call {@link #stopSelf}.
     *
     * @param intent The value passed to {@link
     *               android.content.Context#startService(Intent)}.
     */
    protected abstract void OnHandleIntent(Intent intent);


    }
}

 

历史

第一个版本

© . All rights reserved.