仅当应用程序打开并运行时,如何在后台运行方法?
Posted
技术标签:
【中文标题】仅当应用程序打开并运行时,如何在后台运行方法?【英文标题】:How to run a method in the background only when app is open and running? 【发布时间】:2017-12-25 20:38:26 【问题描述】:一旦应用程序打开并运行,我想要一个后台进程来检查数据库并根据数据库中的数据进行更新。我想每隔一分钟做一次检查。我只希望当应用程序处于前台并在用户视图中时发生这种情况。
有人可以就我如何做到这一点给我一些建议吗?我假设我可以从这里调用一个方法,但我不知道该怎么做。另外我不知道如何停止,或者即使我需要手动取消/停止该过程。当应用不在前台时它会自行取消,并在应用回到前台时重新启动吗?
public partial class App : Application
protected override void OnStart()
App.DB.InitData();
MainPage = new Japanese.MainPage();
但是我需要让它在不同的线程上运行吗?如果需要,我该怎么做。
对不起,如果我的问题不清楚。请询问,如果没有意义,我可以更新。
【问题讨论】:
取决于你所说的“背景”。如果您的应用程序运行时是指非 UI 线程,那么 Manish 答案将起作用。如果您在应用关闭时需要任务,请查看 Steven 的回答 一种方法是同时使用 TIMER 【参考方案1】:我们在表单应用程序中所做的是利用 System.Diagnostics 和 Xamarin.Forms 中可用的 Device.Timer 和 Stopwatch 类来创建一个非常通用的托管计时器,我们可以使用 onStart 与之交互, Xamarin.Forms 中的 onSleep 和 onResume 方法。
这个特定的解决方案不需要任何特殊的平台特定逻辑,并且设备计时器和秒表是非 UI 阻塞的。
using Xamarin.Forms;
using System;
using System.Linq;
using System.Diagnostics;
namespace YourNamespace
public partial class App : Application
private static Stopwatch stopWatch = new Stopwatch();
private const int defaultTimespan = 1;
protected override void OnStart()
// On start runs when your application launches from a closed state,
if (!stopWatch.IsRunning)
stopWatch.Start();
Device.StartTimer(new TimeSpan(0, 0, 1), () =>
// Logic for logging out if the device is inactive for a period of time.
if (stopWatch.IsRunning && stopWatch.Elapsed.Minutes >= defaultTimespan)
//prepare to perform your data pull here as we have hit the 1 minute mark
// Perform your long running operations here.
Device.InvokeOnMainThread(()=>
// If you need to do anything with your UI, you need to wrap it in this.
);
stopwatch.Restart();
// Always return true as to keep our device timer running.
return true;
);
protected override void OnSleep()
// Ensure our stopwatch is reset so the elapsed time is 0.
stopWatch.Reset();
protected override void OnResume()
// App enters the foreground so start our stopwatch again.
stopWatch.Start();
编辑:
逐步说明上述解决方案的工作原理:
应用程序从关闭状态开始,'OnStart()' 方法创建我们的 Device.Timer,每秒滴答一次。它还会启动我们的秒表,最多可以计时一分钟。
当应用程序进入后台时,如果我们将“false”值传递给 Device.StartTimer() 操作,它此时会触发“OnSleep”方法,它不会再次启动。因此,我们只需重置秒表,以便在应用再次打开时做好准备。
当应用返回前台时,它会触发“OnResume”方法,该方法只是启动现有的秒表。
2018 年编辑:
即使在 2018 年,这个答案仍然有一些优点,但主要是针对非常具体的情况。即使在 Xamarin.Forms 中也有更好的特定于平台的方法来复制此功能。考虑到用户的活动/不活动,上述方法仍然是一种平台无关的方式来执行一段时间后的任务。
【讨论】:
为什么要使用秒表? Timer 已经在为你做这件事了。 StopWatch + Task.Run 在计时器委托内添加可重入性。为什么要使用 Task.Run 在主线程上调用?为什么要保持计时器始终运行?为什么 StopWatch.restart();和停止();而只是 Reset();?马安 @IvanBukashkin 'Device.Timer' 不能用作可传递的对象,它是一个静态类。因此,尽管我们可以间隔启动它,但实际上无法停止或暂停它。秒表允许我们这样做。我将任务运行用于长时间运行的进程,并使用“InvokeOnMainThread”来提交针对 UI 的操作。至于重置和停止,是的,重启很合适。 使用停止当前计时器的字段标志或 CancellationToken 字段停止计时器。秒表根本不需要。如果您认为每次都执行 Device.StartTimer 很困难,请封装它。您正在使用 Device.StartTimer 进行长时间运行。 Task.Run 中不需要。 秒表很有用,因为如果您快速停止旧计时器并开始新计时器,它可以保护您免于重入。这是我看到的唯一原因。只有当你删除 Task.Run 时它才会起作用。在所有其他情况下,您需要一些锁来同步线程 使用一秒计时器,即使无事可做,甚至当应用程序在后台运行时,电池寿命也会每秒钟耗尽一次。我相信 ios 甚至会在几分钟后为此终止应用程序。【参考方案2】:你可以用这个,
System.Threading.Tasks.Task.Run(() =>
//Add your code here.
).ConfigureAwait(false);
【讨论】:
那么我是从 App onStart 还是 App 构造函数启动这个? 看你的要求你可以使用它。 通过使用您的代码,我可以立即登录吗? &Task
中的代码将在后台继续运行?
与使用 async /await 和 configureawait(false) 有很大不同吗?【参考方案3】:
在 iOS 和 android 中都有几种方法可以做到这一点。在 Xamarin Forms 中,大多数此功能属于 Backgrounding 的名称。那里有很多教程。这个非常精致,绝对值得一试:
http://arteksoftware.com/backgrounding-with-xamarin-forms/
在 Android 中,很多此类工作都是在 后台服务 中完成的。对于 iOS,请查看 Long Running 或 Finite-Length Tasks。从这里可以看出,没有 Xamarin Forms 方法可以做到这一点。您将需要编写 Xamarin.Android 和 Xamarin.iOS 特定代码。
【讨论】:
【参考方案4】:要运行后台任务,请使用服务。通常将任务分类为长时间运行的任务或定期任务。
android中的服务代码如下所示
[Service]
public class PeriodicService : Service
public override IBinder OnBind(Intent intent)
return null;
public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId)
// From shared code or in your PCL
return StartCommandResult.NotSticky;
并在后台调用服务
var intent = new Intent (this, typeof(PeriodicService));
StartService(intent);
如果想在每分钟后调用和检查
private void StartBackgroundDataRefreshService ()
var pt = new PeriodicTask.Builder ()
.SetPeriod (1800) // in seconds; minimum is 30 seconds
.SetService (Java.Lang.Class.FromType (typeof(BackgroundService)))
.SetRequiredNetwork (0)
.SetTag (your package name) // package name
.Build ();
GcmNetworkManager.GetInstance (this).Schedule (pt);
要了解哪种服务类型适合您,请阅读本教程 Types of Services
用于定期后台服务的 Xamarin 博客 Xamarin Service Blog
另一个例子是
public class PeriodicService : Service
private static Timer timer = new Timer();
public override IBinder OnBind(Intent intent)
return null;
public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId)
timer.scheduleAtFixedRate(new mainTask(), 0, 5000);
return StartCommandResult.NotSticky;
private class mainTask extends TimerTask
public void run()
//your code
这是 XAMARIN Android 服务的示例代码,它将在每 10 秒后执行一次任务
using System;
using System.Threading;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Util;
namespace SimpleService
[Service]
public class SimpleStartedService : Service
static readonly string TAG = "X:" + typeof(SimpleStartedService).Name;
static readonly int TimerWait = 10000;
Timer timer;
DateTime startTime;
bool isStarted = false;
public override void OnCreate()
base.OnCreate();
public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId)
Log.Debug(TAG, $"OnStartCommand called at startTime, flags=flags, startid=startId");
if (isStarted)
TimeSpan runtime = DateTime.UtcNow.Subtract(startTime);
Log.Debug(TAG, $"This service was already started, it's been running for runtime:c.");
else
startTime = DateTime.UtcNow;
Log.Debug(TAG, $"Starting the service, at startTime.");
timer = new Timer(HandleTimerCallback, startTime, 0, TimerWait);
isStarted = true;
return StartCommandResult.NotSticky;
public override IBinder OnBind(Intent intent)
// This is a started service, not a bound service, so we just return null.
return null;
public override void OnDestroy()
timer.Dispose();
timer = null;
isStarted = false;
TimeSpan runtime = DateTime.UtcNow.Subtract(startTime);
Log.Debug(TAG, $"Simple Service destroyed at DateTime.UtcNow after running for runtime:c.");
base.OnDestroy();
void HandleTimerCallback(object state)
TimeSpan runTime = DateTime.UtcNow.Subtract(startTime);
Log.Debug(TAG, $"This service has been running for runTime:c (since $state)." );
【讨论】:
参考:-developer.xamarin.com/recipes/android/fundamentals/service/…【参考方案5】:您可以使用
Device.StartTimer(TimeSpan.FromMinutes(1), () =>
var shouldTimerContinueWork = true;
/*your code*/
return shouldTimerContinueWork;
);
此计时器在后台线程上运行,使用设备时钟和重入安全。 要在应用程序处于后台时停止此计时器,您可以使用 Xamarin.Forms.Application 方法 OnSleep 和 OnResume,如 here
所述>【讨论】:
那么你会把这段代码放在哪里,在'OnResume'方法中?还是在“OnStart”方法中?因为您不想每次设备从后台恢复时都创建它。如果你把它放在'OnStart'方法中,一旦你停止它,你将无法再次启动它。 在你的代码中你可以做一个实际的工作,或者只是跳过当前的刻度。取决于许多因素。你是不是在后台。多久前是 prev 同步。这适合你的需要吗?我不知道。所以这取决于你。 或者您可以重新创建它并在应用程序恢复时及时同步,但要注意可重入性。可能会更好?由你决定。 这是最好的解决方案并且工作正常。只需根据您的需要进行调整【参考方案6】:我正在做这样的事情是我的 Xamarin Forms 应用程序。
public void execute()
var thread = new Thread(new ThreadStart(startAuthenticationProcess))
IsBackground = true
;
thread.Start();
private void startAuthenticationProcess()
Thread.Sleep(2000);
if (!Utils.isNetworkAvailable(splashActivity))
splashActivity.RunOnUiThread(() => Utils.showToast(splashActivity, splashActivity.GetString(Resource.String.r30025)));
splashActivity.FinishAffinity();
else
try
if (StringUtils.isBlank(strIPAdd) || (StringUtils.isNotBlank(strIPAdd) && (StringUtils.isBlank(strDbName) || "site".Equals(strDbName,StringComparison.OrdinalIgnoreCase))))
splashActivity.RunOnUiThread(() => DependencyService.Get<IAuthenticationDialog>().showAuthenticationDialog(new Command(() =>
var intent = new Intent(splashActivity, typeof(MainActivity));
intent.PutExtra("startLoginActivity", false);
splashActivity.StartActivity(intent);
splashActivity.Finish();
)));
else
gotoLoginScreen();
catch (Exception e)
Log.Error(TAG, e.Message);
【讨论】:
【参考方案7】:简单,试试这样的方法并在这些方法中实现你的逻辑:
public partial class App : Application
protected override void OnStart()
// Your App On start code should be here...
// and then:
Task.Run(() =>
//Add your code here, it might looks like:
CheckDatabase();
MakeAnUpdateDependingOnDatabase();
);
希望对你有帮助。
【讨论】:
以上是关于仅当应用程序打开并运行时,如何在后台运行方法?的主要内容,如果未能解决你的问题,请参考以下文章