今天在爆UWP的定时后台任务,坑有点多,爆出屎来了。有的坑在很多网上的文章里都没提到,非常的坑。刚刚开荒成功了,把经验写出来分享:

1. 写一个后台任务的类,继承IBackgroundTask接口

通常,在设计应用程序结构的时候,我们会建类库项目(Class Library)放这些类。比如 FarkBackgroundTask.Core

因为是UWP工程,所以建的类库也要是Universal Windows的。注意,这里我们已经埋下了一个巨坑,稍后会解释。

我们的类代码如下:

public class SayFarkTask : IBackgroundTask
{
    public void Run(IBackgroundTaskInstance taskInstance)
    {
        Debug.Write("================ Fark the farking farkers ================");
    }
}

里面这个Run方法,就是执行任务用的,是接口里的方法。现在,我们又埋下了一个巨坑。

2. 在manifest文件里添加后台任务声明

对应的代码是:

<Extensions>
  <Extension Category="windows.backgroundTasks" EntryPoint="FarkBackgroundTask.Core.SayFarkTask">
    <BackgroundTasks>
      <Task Type="timer" />
    </BackgroundTasks>
  </Extension>
</Extensions>

3. 在应用程序启动的时候注册后台任务

踩过一堆小坑之后,封装了一个注册方法:

public static async Task<BackgroundTaskRegistration> RegisterBackgroundTask(Type taskEntryPoint,
                                                                string taskName,
                                                                IBackgroundTrigger trigger,
                                                                IBackgroundCondition condition)
{
    var status = await BackgroundExecutionManager.RequestAccessAsync();
    if (status == BackgroundAccessStatus.Unspecified || status == BackgroundAccessStatus.Denied)
    {
        return null;
    }

    foreach (var cur in BackgroundTaskRegistration.AllTasks)
    {
        if (cur.Value.Name == taskName)
        {
            cur.Value.Unregister(true);
        }
    }

    var builder = new BackgroundTaskBuilder
    {
        Name = taskName,
        TaskEntryPoint = taskEntryPoint.FullName
    };

    builder.SetTrigger(trigger);

    if (condition != null)
    {
        builder.AddCondition(condition);
    }

    BackgroundTaskRegistration task = builder.Register();

    Debug.WriteLine($"Task {taskName} registered successfully.");

    return task;
}

主要有几点小坑:

1. 先得请求权限,timer的任务是需要权限的

2. 如果注册过,建议先清除注册,再重新注册一次。这里我的代码和MSDN上的例子不太一样了,MSDN的做法是foreach里检查到name一样的就return那个task,但是我考虑的是如果你的task注册行为有变化就不好管理了,所以干脆全删了重建一个新的。

3. 入口点EntryPoint的类型我改用了Type,好处是强类型,编译时就知道会不会爆,防止手滑。MSDN的例子是用string的,容易改爆。

现在就可以在App.xaml.cs里面注册了:

private async Task RegisterBackgroundTask()
{
    var task = await RegisterBackgroundTask(
        typeof(FarkBackgroundTask.Core.SayFarkTask),
        "SayFarkTask",
        new TimeTrigger(15, false),
        null);

    task.Progress += TaskOnProgress;
    task.Completed += TaskOnCompleted;
}

在protected override async void OnLaunched(LaunchActivatedEventArgs e)里面:

...
Window.Current.Content = rootFrame;
await RegisterBackgroundTask();
...

现在看上去一切都是好的,然而运行就会爆出屎来,你会发现Task怎么也激活不了。这是因为之前埋下了2个巨坑。

4. 巨坑之一 sealed 类

因为一个神奇的原因,我们的后台任务类SayFarkTask如果不是sealed,就会爆。所以得改成:

public sealed class SayFarkTask : IBackgroundTask
...

现在运行还是会爆,爆了我2个多小时候,我发现了个巨坑!简直坑得史无前例……

5. 巨坑之二 Windows Runtime Component

在我们建类库的时候,选的Universal Windows没错,但是它的输出类型是Class Library,我们平时写应用确实一直在用Class Library,但是这里不行,必须改成Windows Runtime Component,不然就爆!!!

坑就坑在MSDN下载的范例里面一下子也发现不了,毕竟在VS里显示的时候都叫(Universal Windows)……

6. 现在执行应用程序,在VS的lifecycle events里选我们的SayFarkTask就能跑出结果了

 

代码见github:https://github.com/EdiWang/UWP-Demos/tree/master/FarkBackgroundTask