Xamarin表单:在一段时间后退出应用程序,检查用户是否处于非活动状态

Son*_*ali 7 c# portable-class-library xamarin xamarin.forms

嗨,我正在尝试使用PCL以xamarin形式构建应用程序.如果应用程序闲置超过10分钟或更长时间,我正尝试从我的应用程序注销用户.我通过在app即将进入睡眠状态时调用的事件来尝试它.但是如果设备screentimeout被设置为永不超时那么也许它永远不会进入睡眠状态.那么我怎样才能做到这一点.我是xamarin表格的新手.当我为所有平台构建应用程序时,我很困惑如何管理此超时?

Wol*_*urs 10

现在我使用以下方法.可能需要做一些额外的测试,以确保一切按预期工作.例如,我不确定如果应用程序(iOS或Android)在后台工作了很长时间会发生什么.定时器是否仍会每秒调用一次?也许当使用具有足够短的到期时间(约5分钟)的计时器时,这不是问题吗?等等...

我的方法基于我在网上找到的几段代码(一些Xamarin代码,一些Swift/Java代码) - 似乎没有一个很好的综合解决方案.

无论如何,一些初步测试表明这种方法工作正常.


首先,我创建了一个名为的单例类SessionManager.这个类包含一个计时器(实际上只是一个每秒睡眠的while循环)以及启动,停止和扩展计时器的方法.如果会话到期计时器到期,它还将触发事件.

public sealed class SessionManager
{
    static readonly Lazy<SessionManager> lazy =
        new Lazy<SessionManager>(() => new SessionManager());

    public static SessionManager Instance { get { return lazy.Value; } }

    SessionManager() { 
        this.SessionDuration = TimeSpan.FromMinutes(5);
        this.sessionExpirationTime = DateTime.FromFileTimeUtc(0);
    }

    /// <summary>
    /// The duration of the session, by default this is set to 5 minutes.
    /// </summary>
    public TimeSpan SessionDuration;

    /// <summary>
    /// The OnSessionExpired event is fired when the session timer expires.
    /// This event is not fired if the timer is stopped manually using 
    /// EndTrackSession.
    /// </summary>
    public EventHandler OnSessionExpired;

    /// <summary>
    /// The session expiration time.
    /// </summary>
    DateTime sessionExpirationTime;

    /// <summary>
    /// A boolean value indicating wheter a session is currently active.
    /// Is set to true when StartTrackSessionIsCalled. Becomes false if 
    /// the session is expired manually or by expiration of the session 
    /// timer.
    /// </summary>
    public bool IsSessionActive { private set; get; }

    /// <summary>
    /// Starts the session timer.
    /// </summary>
    /// <returns>The track session async.</returns>
    public async Task StartTrackSessionAsync() {
        this.IsSessionActive = true;

        ExtendSession();

        await StartSessionTimerAsync();
    }

    /// <summary>
    /// Stop tracking a session manually. The OnSessionExpired will not be 
    /// called.
    /// </summary>
    public void EndTrackSession() {
        this.IsSessionActive = false;

        this.sessionExpirationTime = DateTime.FromFileTimeUtc(0);
    }

    /// <summary>
    /// If the session is active, then the session time is extended based 
    /// on the current time and the SessionDuration.
    /// duration.
    /// </summary>
    public void ExtendSession()
    {
        if (this.IsSessionActive == false) {
            return;
        }

        this.sessionExpirationTime = DateTime.Now.Add(this.SessionDuration);
    }

    /// <summary>
    /// Starts the session timer. When the session is expired and still 
    /// active the OnSessionExpired event is fired. 
    /// </summary>
    /// <returns>The session timer async.</returns>
    async Task StartSessionTimerAsync() {
        if (this.IsSessionActive == false) {
            return;
        }

        while (DateTime.Now < this.sessionExpirationTime) {
            await Task.Delay(1000);                
        }

        if (this.IsSessionActive && this.OnSessionExpired != null) {
            this.IsSessionActive = false;

            this.OnSessionExpired.Invoke(this, null);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

对于Android应用程序,我然后:

  • 配置SessionManagerMainActivity中的会话到期时注销.

  • 覆盖该OnUserInteraction方法以MainActivity在用户交互上扩展会话计时器.

    public class MainActivity /* ... */ {
        protected override void OnCreate(Bundle bundle)
        {   
            // ...
    
            SessionManager.Instance.SessionDuration = TimeSpan.FromSeconds(10);
            SessionManager.Instance.OnSessionExpired = HandleSessionExpired;
        }
    
        public override void OnUserInteraction()
        {
            base.OnUserInteraction();
    
            SessionManager.Instance.ExtendSession();
        }
    
        async void HandleSessionExpired(object sender, EventArgs e)
        {
            await App.Instance.DoLogoutAsync();
        }    
    }
    
    Run Code Online (Sandbox Code Playgroud)

对于iOS,我执行以下操作:

  • SessionManager在会话到期时配置AppDelegate中的注销.

  • 向关键窗口添加自定义手势处理程序,以扩展用户交互的会话计时器.

    public partial class AppDelegate /* ... */
    {
        public override bool FinishedLaunching(UIApplication app, NSDictionary options)
        {
            // ...
    
            var success = base.FinishedLaunching(app, options);
    
            if (success) {
                SessionManager.Instance.SessionDuration = TimeSpan.FromSeconds(10);
                SessionManager.Instance.OnSessionExpired += HandleSessionExpired;
    
                var allGesturesRecognizer = new AllGesturesRecognizer(delegate
                {
                    SessionManager.Instance.ExtendSession();
                });
    
                this.Window.AddGestureRecognizer(allGesturesRecognizer);
            }
    
            return success;
        }
    
        async void HandleSessionExpired(object sender, EventArgs e)
        {
            await App.instance.DoLogoutAsync();
        }
    
        class AllGesturesRecognizer: UIGestureRecognizer {
            public delegate void OnTouchesEnded();
    
            private OnTouchesEnded touchesEndedDelegate;
    
            public AllGesturesRecognizer(OnTouchesEnded touchesEnded) {
                this.touchesEndedDelegate = touchesEnded;
            }
    
            public override void TouchesEnded(NSSet touches, UIEvent evt)
            {
                this.State = UIGestureRecognizerState.Failed;
    
                this.touchesEndedDelegate();
    
                base.TouchesEnded(touches, evt);
            }
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

编辑:博洛在下面问了一个很好的问题,所以我会在这里添加.用户登录后立即调用StartTrackSessionAsync.当用户退出应用程序时,应调用EndTrackSession.

  • @JustinLim 是有道理的,因为 iOS 和 Android 应用程序将默认的 SessionDuration 属性更改为 10 秒。如果您希望保留默认的 5 分钟持续时间,您可以删除这些行。请参阅 Android `onCreate` 和 iOS `FinishedLaunching` 方法。 (2认同)

小智 6

调整了@Wolfgang 版本

public sealed class SessionManager
{
    static readonly Lazy<SessionManager> lazy =
        new Lazy<SessionManager>(() => new SessionManager());

    public static SessionManager Instance { get { return lazy.Value; } }
    private Stopwatch StopWatch = new Stopwatch();

    SessionManager()
    {
        SessionDuration = TimeSpan.FromMinutes(5);
    }

    public TimeSpan SessionDuration;

    public void EndTrackSession()
    {
        if (StopWatch.IsRunning)
        {
            StopWatch.Stop();
        }
    }

    public void ExtendSession()
    {
        if (StopWatch.IsRunning)
        {
            StopWatch.Restart();
        }
    }

    public void StartTrackSessionAsync()
    {
        if (!StopWatch.IsRunning)
        {
            StopWatch.Restart();
        }

        Xamarin.Forms.Device.StartTimer(new TimeSpan(0, 0, 2), () =>
        {
            if (StopWatch.IsRunning && StopWatch.Elapsed.Minutes >= SessionDuration.Minutes)
            {
                Xamarin.Forms.Device.BeginInvokeOnMainThread(async () =>
                {
                    await Prism.PrismApplicationBase.Current.Container.Resolve<INavigationService>().NavigateAsync("/Index/Navigation/LoginPage");
                });

                StopWatch.Stop();
            }

            return true;
        });

    }

}
Run Code Online (Sandbox Code Playgroud)

在主要活动下添加了以下内容

    public override void OnUserInteraction()
    {
        base.OnUserInteraction();
        SessionManager.Instance.ExtendSession();
    }
Run Code Online (Sandbox Code Playgroud)


小智 1

不幸的是,这并不是您可以在客户端轻松完成的事情。也没有办法仅通过 PCL 来完成此操作。可能有一个插件可以添加到您的项目中,但我还没有找到。

造成这种情况的原因是 iOS 和 Android 处理应用程序生命周期的方式不同。两者非常不同。例如,一旦 iOS 挂起您的应用程序,实际上只有 2 种方法可以唤醒它。GPS 位置更新和推送通知。在 Android 中,这更容易,因为他们有 AlarmManager,您可以注册并意图为您执行注销。

我的建议是,如果您控制正在使用的 api,请让会话在服务器端过期,这样 10 分钟后传入的任何请求都会失败,并在客户端适当处理这些失败。

如果您纯粹关心应用程序何时位于前台且处于活动状态但未使用,则您将必须实现一个计时器,并在每次有用户交互时重置它。