【问题标题】: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

标签: c# xamarin xamarin.forms


【解决方案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})." );
        }
    }
    

    }

    【解决方案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();
              });
         }
      

      希望对你有帮助。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-02-06
        • 2020-12-15
        • 1970-01-01
        • 1970-01-01
        • 2014-12-09
        • 1970-01-01
        • 2017-09-16
        • 1970-01-01
        相关资源
        最近更新 更多