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

时间:2017-01-04 14:53:53

标签: c# xamarin xamarin.forms portable-class-library

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

4 个答案:

答案 0 :(得分:5)

目前,我使用以下方法。可能需要进行一些其他测试,以确保一切正常。例如,我不确定如果应用程序(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);
        }
    }
}

然后,对于Android应用程序:

  • 在MainActivity中配置SessionManager以在会话到期时注销。

  • 覆盖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();
        }    
    }
    

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

  • 在AppDelegate中配置SessionManager以在会话过期时注销。

  • 在按键窗口中添加自定义手势处理程序,以在用户交互时扩展会话计时器。

    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(20);
                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);
            }
        }
    }
    

编辑:Bolo在下面提出了一个好问题,因此我将其添加到此处。用户登录后立即调用StartTrackSessionAsync。当然,用户退出应用程序时也应调用EndTrackSession。

答案 1 :(得分:2)

不幸的是,这并不是您可以在客户端轻松完成的事情。从PCL也无法做到这一点。可能有一个插件可以添加到您的项目中,但我还没有找到它。

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

我的建议是,如果你控制你正在使用的api,让会话到期服务器端,以便在那10分钟之后进入的任何请求都会失败,并在客户端适当地处理这些失败。

如果您的关注纯粹是因为应用程序处于前台且处于活动状态但未使用时,则必须实现计时器,并在每次有用户交互时重置它。

答案 2 :(得分:1)

我能够使用Xamarin Forms中的Device.StartTimer来创建过期时间。对于我的应用程序,用户经常切换屏幕,因此我会在屏幕转换之间重置不活动。然后将方法绑定到每个按钮和屏幕点击,这有点令人讨厌。容纳逻辑的类看起来像这样:

public class InactivityService
{  
    public ActivityMonitorService( )
    { 
    }

    public DateTime LastClick { get; private set; }
    public TimeSpan MaxLength { get; private set; }

    public void Start(TimeSpan maxDuration, Action expirationCallback = null)
    {
        MaxLength = maxDuration;
        Notify();
        _expirationCallBack = expirationCallback;
        ResetTimer();
    }

    public void Notify()
    { 
        LastClick = DateTime.Now;
    }

    public void Stop()
    {
    }

    public TimeSpan TimeSinceLastNotification()
    {
        var now = DateTime.Now;
        var timeSinceLastClick = now - LastClick;
        return timeSinceLastClick;
    }

    public TimeSpan GetNewTimerSpan()
    {
        var newDuration = MaxLength - TimeSinceLastNotification();
        return newDuration;
    }

    public bool IsExpired(DateTime time)
    {
        return time - LastClick > MaxLength;
    }

    private bool CallBack()
    {
        if (IsExpired(DateTime.Now))
        {
            Expire();
        }
        else
        {
            ResetTimer();
        }

        return false;
    }

    public async void Expire()
    {
        if (_expirationCallBack != null)
            _expirationCallBack.Invoke();
        Stop(); 
        //Notify user of logout
        //Do logout navigation
    }

    private void ResetTimer()
    {
        Device.StartTimer(GetNewTimerSpan(), CallBack);
    }
}

答案 3 :(得分:0)

调整了@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;
        });

    }

}

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

    public override void OnUserInteraction()
    {
        base.OnUserInteraction();
        SessionManager.Instance.ExtendSession();
    }