仅当应用程序打开并运行时,如何在后台运行方法?

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 RunningFinite-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 方法 OnSleepOnResume,如 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();
        );
   

希望对你有帮助。

【讨论】:

以上是关于仅当应用程序打开并运行时,如何在后台运行方法?的主要内容,如果未能解决你的问题,请参考以下文章

在后台运行我的应用程序更长的时间并获取信息

小米手机如何防止微信后台被杀

App在后台运行时检测到解锁事件

如何在后台为ios音频文件运行Phonegap应用程序

如何查看电脑后台正在运行的程序

如何让用户知道我的应用程序没有在后台运行?