在Android中如何检测用户不活动状态

119

用户启动我的应用程序并登录。
选择会话超时时间为5分钟。
在应用程序上执行一些操作。(全部在前景中)
现在,用户将Myapp移到后台并启动其他应用程序。
----> 倒计时定时器开始,在5分钟后注销用户
或者用户关闭屏幕。
----> 倒计时定时器开始,在5分钟后注销用户

即使应用程序在前台但用户长时间不与应用程序交互,例如6-7分钟,我也希望具有相同的行为。假设屏幕一直开启。 我想检测到用户不活动(即使应用程序在前台也没有与其交互),并启动倒计时计时器。


1
你能一直让计时器运行并在用户执行任何操作时重置它吗? - Kyle P
19个回答

121

我根据Fredrik Wallenius的答案提出了一个我认为相当简单的解决方案。这是一个需要所有活动扩展的基础活动类。

public class MyBaseActivity extends Activity {

    public static final long DISCONNECT_TIMEOUT = 300000; // 5 min = 5 * 60 * 1000 ms


    private static Handler disconnectHandler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            // todo
            return true;
        }
    });

    private static Runnable disconnectCallback = new Runnable() {
        @Override
        public void run() {
            // Perform any required operation on disconnect
        }
    };

    public void resetDisconnectTimer(){
        disconnectHandler.removeCallbacks(disconnectCallback);
        disconnectHandler.postDelayed(disconnectCallback, DISCONNECT_TIMEOUT);
    }

    public void stopDisconnectTimer(){
        disconnectHandler.removeCallbacks(disconnectCallback);
    }

    @Override
    public void onUserInteraction(){
        resetDisconnectTimer();
    }

    @Override
    public void onResume() {
        super.onResume();
        resetDisconnectTimer();
    }

    @Override
    public void onStop() {
        super.onStop();
        stopDisconnectTimer();
    }
}

4
这将在每次创建Activity时创建多个HandlerRunnable实例。如果我们将这两个成员转换为“静态”的,就可以避免这种情况发生。另外,你能告诉我为什么在onStop()中调用了stopDisconnectTimer()吗? - Gaurav Bhor
@gfrigon 是否可以将用户重定向到登录活动? - Apostrofix
即使应用程序正在使用,这也可以工作。我该如何克服这个问题? - Aparajita Sinha
@gfrigon..上述方法在登录和工作后都很好用。假设在工作活动期间,如果我们去打开另一个应用程序,计时器会停止。这个时候也需要自动注销。我们需要怎么做呢?我尝试过使用onPause和手动计时器调度程序,但是它影响到了常规功能超时。你能提供一些建议吗? - harikrishnan
1
@GauravBhor,如果我们将Handler和Runnable设置为静态的,那么如何在Runnable内部创建一个新的Intent(CurrentActivity.this, MainActivity.class)并且使用startActivity(intent)启动它呢?因为在静态上下文中无法引用CurrentActivity.thisstartActivity() - Tur1ng
显示剩余8条评论

108

我不知道如何跟踪用户的不活动时间,但可以跟踪用户的活动。您可以在活动中捕获一个名为onUserInteraction() 的回调函数,该函数在用户与应用程序进行任何交互时都会被调用。我建议像这样做:

@Override
public void onUserInteraction(){
    MyTimerClass.getInstance().resetTimer();
}

如果你的应用包含多个活动(Activity),为什么不将这个方法放在一个抽象的超类中(继承Activity),然后让所有的活动都继承它呢。


1
是的,这是一种方法...但我的应用程序有30个不同的活动,当用户处于活动状态时会有太多的交互...所以每次重置计时器都将是一项昂贵的操作...在最坏的情况下,每分钟可能会发生50到60次。 - Akh
3
我还没有计时,但我估计重置一个计时器像这样: lastInteraction = System.currentTimeMillis(); 大约需要2毫秒。如果每分钟做60次,你会“损失”120毫秒。而这只占了60000毫秒的一小部分。 - Fredrik Wallenius
1
Fredrik...我也使用了你的建议来满足这种情况...设备的屏幕超时时间设置为最大30分钟。MyApp应在15分钟后超时...如果用户在屏幕上超过1分钟没有触摸任何东西,那么我将启动15分钟的注销计时器...在这种情况下,我会检查差异(lastInteractionTime和System.currentTimeMills())是否超过1分钟...然后触发... - Akh
4
在某些情况下,onUserInteraction() 没有被调用(例如对话框不会调用它,以及在滚动下拉列表中)。这种情况下是否有解决方法? - AndroidNoob
MyTimerClass 的实现是什么? - McSullivan
显示剩余6条评论

24

我认为你应该使用这段代码,它用于设置5分钟的闲置会话超时:

Handler handler;
Runnable r;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    handler = new Handler();
    r = new Runnable() {

       @Override
       public void run() {
            // TODO Auto-generated method stub
            Toast.makeText(MainActivity.this, "user is inactive from last 5 minutes",Toast.LENGTH_SHORT).show();
        }
    };
    startHandler();
}
@Override
public void onUserInteraction() {
     // TODO Auto-generated method stub
     super.onUserInteraction();
     stopHandler();//stop first and then start
     startHandler();
}
public void stopHandler() {
    handler.removeCallbacks(r);
}
public void startHandler() {
    handler.postDelayed(r, 5*60*1000); //for 5 minutes 
}

你通过 onUserInteraction 保存了我的生命。 - Mehdi Haghgoo

12
@Override
public void onUserInteraction() {
    super.onUserInteraction();
    delayedIdle(IDLE_DELAY_MINUTES);
}

Handler _idleHandler = new Handler();
Runnable _idleRunnable = new Runnable() {
    @Override
    public void run() {
        //handle your IDLE state
    }
};

private void delayedIdle(int delayMinutes) {
    _idleHandler.removeCallbacks(_idleRunnable);
    _idleHandler.postDelayed(_idleRunnable, (delayMinutes * 1000 * 60));
}

这是解决方案的基础,其余部分可以根据您的特定需求和应用程序架构复杂性进行修改!感谢您的回答! - Hack06
如何在应用程序类中应用此内容。 - Gaju Kollur
最简洁的解决方案!非常感谢你,伙计! - Damercy

12
public class MyApplication extends Application {
      private int lastInteractionTime;
      private Boolean isScreenOff = false; 
      public void onCreate() {
        super.onCreate();
        // ......   
        startUserInactivityDetectThread(); // start the thread to detect inactivity
        new ScreenReceiver();  // creating receive SCREEN_OFF and SCREEN_ON broadcast msgs from the device.
      }

      public void startUserInactivityDetectThread() {
        new Thread(new Runnable() {
          @Override
          public void run() {
            while(true) {
              Thread.sleep(15000); // checks every 15sec for inactivity
              if(isScreenOff || getLastInteractionTime()> 120000 ||  !isInForeGrnd)
                {
                  //...... means USER has been INACTIVE over a period of
                  // and you do your stuff like log the user out 
                }
              }
          }
        }).start();
      }

      public long getLastInteractionTime() {
        return lastInteractionTime;
      }

      public void setLastInteractionTime(int lastInteractionTime) {
        this.lastInteractionTime = lastInteractionTime;
      }

      private class ScreenReceiver extends BroadcastReceiver {

        protected ScreenReceiver() {
           // register receiver that handles screen on and screen off logic
           IntentFilter filter = new IntentFilter();
           filter.addAction(Intent.ACTION_SCREEN_ON);
           filter.addAction(Intent.ACTION_SCREEN_OFF);
           registerReceiver(this, filter);
        }

        @Override
        public void onReceive(Context context, Intent intent) {
          if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
            isScreenOff = true;
          } else if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
            isScreenOff = false;
          }
        }
      }
    }

isInForeGrnd ===> 逻辑未在此处显示,因为它超出了问题的范围

您可以使用下面的设备代码将锁唤醒到CPU -

  if(isScreenOff || getLastInteractionTime()> 120000 ||  !isInForeGrnd)
    {
      //...... means USER has been INACTIVE over a period of
      // and you do your stuff like log the user out 

      PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);

      boolean isScreenOn = pm.isScreenOn();
      Log.e("screen on.................................", "" + isScreenOn);

      if (isScreenOn == false) {

        PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.ON_AFTER_RELEASE, "MyLock");

        wl.acquire(10000);
        PowerManager.WakeLock wl_cpu = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyCpuLock");

        wl_cpu.acquire(10000);
      }
    }

5
@Nappy:那请解释一下正确的做法。你的评论含糊不清,没有明确的结论。 - Akh
2
@AKh:其他答案已经展示了可能性。在你的解决方案中,我看不到每15秒轮询的任何好处。这将产生相同的效果,就像你在“ACTION_SCREEN_OFF”上启动一个计时器,其随机持续时间为0-15秒。这根本没有意义... - Nappy
1
@Nappy:每15秒,我不仅检查SCREEN_ON或SCREEN_OFF,还会检查用户的最后交互时间和应用程序前台状态。基于这三个因素,我会做出逻辑决策,判断用户与应用程序的交互活跃程度。 - Akh
1
这段代码有很多错误,一些变量没有初始化。 - Big.Child
即使用户未接来电,也可能发生 SCREEN_ON 或 SCREEN_OFF,因此您应提供 isInForeGrnd 的代码。 - Chathura Wijesinghe
显示剩余4条评论

7

在操作系统层面上,没有“用户不活动”的概念,除了 ACTION_SCREEN_OFFACTION_USER_PRESENT 广播。您将需要在您自己的应用程序中定义“不活动”的某种方式。


4

在我的搜索中,我找到了许多答案,但这是我得到的最佳答案。但这段代码的限制是它仅适用于活动而不是整个应用程序。将其作为参考。

myHandler = new Handler();
myRunnable = new Runnable() {
    @Override
    public void run() {
        //task to do if user is inactive

    }
};
@Override
public void onUserInteraction() {
    super.onUserInteraction();
    myHandler.removeCallbacks(myRunnable);
    myHandler.postDelayed(myRunnable, /*time in milliseconds for user inactivity*/);
}

例如您使用了8000,任务将在用户闲置8秒后完成。

最佳/最简单的答案在这里,谢谢。 - Anonymous

4

Kotlin中处理用户交互超时的方法:

     //Declare handler
      private var timeoutHandler: Handler? = null
      private var interactionTimeoutRunnable: Runnable? = null

 override fun onCreate(savedInstanceState: Bundle?) {
      super.onCreate(savedInstanceState)
      setContentView(R.layout.activity_aspect_ratio)

       //Initialise handler
      timeoutHandler =  Handler();
      interactionTimeoutRunnable =  Runnable {
         // Handle Timeout stuffs here
          }

      //start countdown
      startHandler()
}

// reset handler on user interaction
override fun onUserInteraction() {
      super.onUserInteraction()
      resetHandler()
}

 //restart countdown
fun resetHandler() {
      timeoutHandler?.removeCallbacks(interactionTimeoutRunnable);
      timeoutHandler?.postDelayed(interactionTimeoutRunnable, 10*1000); //for 10 second

}

 // start countdown
fun startHandler() {
    timeoutHandler?.postDelayed(interactionTimeoutRunnable, 10*1000); //for 10 second
}

3

在Android中,可以使用onUserInteraction()重写方法来检测用户的不活动状态。

  @Override
    public void onUserInteraction() {
        super.onUserInteraction();

    }

以下是示例代码,当用户不活动时,在3分钟后退出(从HomeActivity -> LoginActivity)

public class HomeActivity extends AppCompatActivity {

    private static String TAG = "HomeActivity";
    private Handler handler;
    private Runnable r;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_home);


        handler = new Handler();
        r = new Runnable() {

            @Override
            public void run() {

                Intent intent = new Intent(getApplicationContext(), LoginActivity.class);
                startActivity(intent);
                Log.d(TAG, "Logged out after 3 minutes on inactivity.");
                finish();

                Toast.makeText(HomeActivity.this, "Logged out after 3 minutes on inactivity.", Toast.LENGTH_SHORT).show();
            }
        };

        startHandler();

    }

    public void stopHandler() {
        handler.removeCallbacks(r);
        Log.d("HandlerRun", "stopHandlerMain");
    }

    public void startHandler() {
        handler.postDelayed(r, 3 * 60 * 1000);
        Log.d("HandlerRun", "startHandlerMain");
    }

    @Override
    public void onUserInteraction() {
        super.onUserInteraction();
        stopHandler();
        startHandler();
    }

    @Override
    protected void onPause() {

        stopHandler();
        Log.d("onPause", "onPauseActivity change");
        super.onPause();

    }

    @Override
    protected void onResume() {
        super.onResume();
        startHandler();

        Log.d("onResume", "onResume_restartActivity");

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        stopHandler();
        Log.d("onDestroy", "onDestroyActivity change");

    }

}

2
在我的活动基础类中,我创建了受保护的类:
protected class IdleTimer
{
    private Boolean isTimerRunning;
    private IIdleCallback idleCallback;
    private int maxIdleTime;
    private Timer timer;

    public IdleTimer(int maxInactivityTime, IIdleCallback callback)
    {
        maxIdleTime = maxInactivityTime;
        idleCallback = callback;
    }

    /*
     * creates new timer with idleTimer params and schedules a task
     */
    public void startIdleTimer()
    {
        timer = new Timer();            
        timer.schedule(new TimerTask() {

            @Override
            public void run() {             
                idleCallback.inactivityDetected();
            }
        }, maxIdleTime);
        isTimerRunning = true;
    }

    /*
     * schedules new idle timer, call this to reset timer
     */
    public void restartIdleTimer()
    {
        stopIdleTimer();
        startIdleTimer();
    }

    /*
     * stops idle timer, canceling all scheduled tasks in it
     */
    public void stopIdleTimer()
    {
        timer.cancel();
        isTimerRunning = false;
    }

    /*
     * check current state of timer
     * @return boolean isTimerRunning
     */
    public boolean checkIsTimerRunning()
    {
        return isTimerRunning;
    }
}

protected interface IIdleCallback
{
    public void inactivityDetected();
}

因此,在 onResume 方法中,您可以在回调中指定要执行的操作...

idleTimer = new IdleTimer(60000, new IIdleCallback() {
            @Override
            public void inactivityDetected() {
                ...your move...
            }
        });
        idleTimer.startIdleTimer();

如何检查用户是否处于非活动状态?系统有任何输入吗? - MohsinSyd

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接