问题
User start my app and logs in.
Selects Session Timeout to be 5 mins.
Does some operations on the app. (all in foreground)
Now User bring Myapp to background and starts some other app.
----> Count down timer starts and logs out user after 5 mins
OR user turns the screen OFF.
----> Count down timer starts and logs out user after 5 mins
I want the same behavior even when the app is in the foreground but user doesn't interact with the app for a long-time say 6-7 mins. Assume the screen is ON all the time. I want to detect kind of user inactivity (No interaction with app even though the app is in the foreground) and kick start my count down timer.
回答1:
I came up with a solution that I find quite simple based on Fredrik Wallenius's answer. This a base activity class that needs to be extended by all activities.
public class MyBaseActivity extends Activity {
public static final long DISCONNECT_TIMEOUT = 300000; // 5 min = 5 * 60 * 1000 ms
private Handler disconnectHandler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
// todo
return true;
}
});
private 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();
}
}
回答2:
I don't know a way of tracking inactivity but there is a way to track user activity. You can catch a callback called onUserInteraction()
in your activities that is called every time the user does any interaction with the application. I'd suggest doing something like this:
@Override
public void onUserInteraction(){
MyTimerClass.getInstance().resetTimer();
}
If your app contains several activities, why not put this method in an abstract super class (extending Activity
) and then have all you activities extending it.
回答3:
I think you should go with this code, this is for 5min idle session timeout:->
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
}
回答4:
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 ===> logic is not shown here as it is out of scope of the question
You can wake the lock to the cpu by using the device code below-
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:
@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));
}
回答6:
There is no concept of "user inactivity" at the OS level, beyond the ACTION_SCREEN_OFF
and ACTION_USER_PRESENT
broadcasts. You will have to define "inactivity" somehow within your own application.
回答7:
Even you can manage your requirement with @gfrigon or @AKh solutions.
But here is Timer and Handlers free solution for this. I already have well managed Timer solution for this. But i have successfully implemented Timer and Handler free solution.
First i tell you what you have to manage if you use Timer or Handlers.
- If your app is killed by user or by a optimizer, your app will never be logout automatically, because all your callbacks are destroyed. (Manage some Alarm Manager or Service?)
- Is it good to have timer in every base class? You are making many threads for just invoking logout process (Manage static Handler or Timer at app level?).
- What if user is in background, your Handler will start Login Activity if user is doing some other work outside your app. (Manage app foreground or background?).
- What if screen gets off automatically. (Manage screen off on broadcast receiver?)
Finally I implemented a solution which is
- NO Handler or Timer.
- NO Alarm Manager.
- NOT managing App LifeCycle.
- NO
ACTION_SCREEN_ON
/ACTION_SCREEN_OFF
Broadcast Receiver.
Easiest Reliable Solution
We will not observe user inactivity by timers rather than we will check last activity time on user activity. So when user interact app next time, i check last interaction time.
Here is BaseActivity.class
which you will extend from every of your activity class instead of LoginActivity
. You will define your logout time in field TIMEOUT_IN_MILLI
in this class.
import android.content.Intent;
import android.content.SharedPreferences;
import android.support.v7.app.AppCompatActivity;
import android.widget.Toast;
public class BaseActivity extends AppCompatActivity {
public static final long TIMEOUT_IN_MILLI = 1000 * 20;
public static final String PREF_FILE = "App_Pref";
public static final String KEY_SP_LAST_INTERACTION_TIME = "KEY_SP_LAST_INTERACTION_TIME";
@Override
public void onUserInteraction() {
super.onUserInteraction();
if (isValidLogin())
getSharedPreference().edit().putLong(KEY_SP_LAST_INTERACTION_TIME, System.currentTimeMillis()).apply();
else logout();
}
public SharedPreferences getSharedPreference() {
return getSharedPreferences(PREF_FILE, MODE_PRIVATE);
}
public boolean isValidLogin() {
long last_edit_time = getSharedPreference().getLong(KEY_SP_LAST_INTERACTION_TIME, 0);
return last_edit_time == 0 || System.currentTimeMillis() - last_edit_time < TIMEOUT_IN_MILLI;
}
public void logout() {
Intent intent = new Intent(this, LoginActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
finish();
Toast.makeText(this, "User logout due to inactivity", Toast.LENGTH_SHORT).show();
getSharedPreference().edit().remove(KEY_SP_LAST_INTERACTION_TIME).apply(); // make shared preference null.
}
}
回答8:
In my activity base class I created protected class:
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();
}
So in onResume method - you can specify action in your callback what do you wish to do with it...
idleTimer = new IdleTimer(60000, new IIdleCallback() {
@Override
public void inactivityDetected() {
...your move...
}
});
idleTimer.startIdleTimer();
回答9:
During my Search I found a lot of answers but this is the best answer I got. But limitation of this code is that it works only for activity not for whole application. Take this as a reference.
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*/);
}
for e.g you used 8000, the task will be done after 8 seconds of user inactivity.
回答10:
User inactivity can detect using onUserInteraction()
override method in android
@Override
public void onUserInteraction() {
super.onUserInteraction();
}
Here is the sample code, signout (HomeActivity-->LoginActivity) after 3min when user inactive
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");
}
}
回答11:
Handling user in interaction timeout in 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 reset() {
timeoutHandler!!.removeCallbacks(interactionTimeoutRunnable);
timeoutHandler!!.postDelayed(interactionTimeoutRunnable, 10*1000); //for 10 second
}
// start countdown
fun startHandler() {
timeoutHandler!!.postDelayed(interactionTimeoutRunnable, 10*1000); //for 10 second
}
回答12:
I think it needs to be by combining the timer with the last activty time.
So like this:
In onCreate(Bundle savedInstanceState) start a timer, say 5 minutes
In onUserInteraction() just store the current time
Pretty simple so far.
Now when the timer pop do like this:
- Take the current time and subtract the stored interaction time to get timeDelta
- If timeDelta is >= the 5 minutes, you are done
- If timeDelta is < the 5 minutes start the timer again, but this time use 5 minutes - the stored time. In other words, 5 minute form the last interaction
回答13:
I had similar situation to the SO question, where i needed to track the user inactivity for 1 minute then redirect the user to start Activity, i needed also to clear the activity stack.
Based on @gfrigon answer i come up with this solution.
ActionBar.java
public abstract class ActionBar extends AppCompatActivity {
public static final long DISCONNECT_TIMEOUT = 60000; // 1 min
private final MyHandler mDisconnectHandler = new MyHandler(this);
private Context mContext;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mContext = this;
}
/*
|--------------------------------------------------------------------------
| Detect user inactivity in Android
|--------------------------------------------------------------------------
*/
// Static inner class doesn't hold an implicit reference to the outer class
private static class MyHandler extends Handler {
// Using a weak reference means you won't prevent garbage collection
private final WeakReference<ActionBar> myClassWeakReference;
public MyHandler(ActionBar actionBarInstance) {
myClassWeakReference = new WeakReference<ActionBar>(actionBarInstance);
}
@Override
public void handleMessage(Message msg) {
ActionBar actionBar = myClassWeakReference.get();
if (actionBar != null) {
// ...do work here...
}
}
}
private Runnable disconnectCallback = new Runnable() {
@Override
public void run() {
// Perform any required operation on disconnect
Intent startActivity = new Intent(mContext, StartActivity.class);
// Clear activity stack
startActivity.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(startActivity);
}
};
public void resetDisconnectTimer() {
mDisconnectHandler.removeCallbacks(disconnectCallback);
mDisconnectHandler.postDelayed(disconnectCallback, DISCONNECT_TIMEOUT);
}
public void stopDisconnectTimer() {
mDisconnectHandler.removeCallbacks(disconnectCallback);
}
@Override
public void onUserInteraction(){
resetDisconnectTimer();
}
@Override
public void onResume() {
super.onResume();
resetDisconnectTimer();
}
@Override
public void onStop() {
super.onStop();
stopDisconnectTimer();
}
}
Complementary resources
Android: Clear Activity Stack
This Handler class should be static or leaks might occur
回答14:
Best thing is to handle this across your whole app (assuming you have multiple activities) by registering AppLifecycleCallbacks
in the Application calss. You can use registerActivityLifecycleCallbacks()
in the Application class with the following callbacks (I recommend creating an AppLifecycleCallbacks class that extends the ActivityLifecycleCallbacks):
public interface ActivityLifecycleCallbacks {
void onActivityCreated(Activity activity, Bundle savedInstanceState);
void onActivityStarted(Activity activity);
void onActivityResumed(Activity activity);
void onActivityPaused(Activity activity);
void onActivityStopped(Activity activity);
void onActivitySaveInstanceState(Activity activity, Bundle outState);
void onActivityDestroyed(Activity activity);
}
回答15:
Here is a complete solution that Handles user inactivity after few mins (e.g. 3mins). This solves the common issues such as Activity jumping into the foreground when the App is in the background upon time out.
Firstly, we create a BaseActivity which all other Activity can extend.
This is the BaseActivity code.
package com.example.timeout;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.view.Window;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import javax.annotation.Nullable;
public class BaseActivity extends AppCompatActivity implements LogoutListener {
private Boolean isUserTimedOut = false;
private static Dialog mDialog;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
((TimeOutApp) getApplication()).registerSessionListener(this);
((TimeOutApp) getApplication()).startUserSession();
}
@Override
public void onUserInteraction() {
super.onUserInteraction();
}
@Override
protected void onResume() {
super.onResume();
if (isUserTimedOut) {
//show TimerOut dialog
showTimedOutWindow("Time Out!", this);
} else {
((TimeOutApp) getApplication()).onUserInteracted();
}
}
@Override
public void onSessionLogout() {
isUserTimedOut = true;
}
public void showTimedOutWindow(String message, Context context) {
if (mDialog != null) {
mDialog.dismiss();
}
mDialog = new Dialog(context);
mDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
mDialog.setContentView(R.layout.dialog_window);
mDialog.setCancelable(false);
mDialog.setCanceledOnTouchOutside(false);
TextView mOkButton = (TextView) mDialog.findViewById(R.id.text_ok);
TextView text_msg = (TextView) mDialog.findViewById(R.id.text_msg);
if (message != null && (!TextUtils.isEmpty(message)) && (!message.equalsIgnoreCase("null"))) {
text_msg.setText(message);
}
mOkButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mDialog != null){
mDialog.dismiss();
Intent intent = new Intent(BaseActivity.this, LoginActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);
finish();
}
}
});
if(!((Activity) context).isFinishing())
{
//show dialog
mDialog.show();
}
}
}
Next, we create an interface for our "Logout Listener"
package com.example.timeout;
public interface LogoutListener {
void onSessionLogout();
}
Finally, we create a Java class which extend "Application"
package com.example.timeout;
import android.app.Application;
import java.util.Timer;
import java.util.TimerTask;
public class TimeOutApp extends Application {
private LogoutListener listener;
private Timer timer;
private static final long INACTIVE_TIMEOUT = 180000; // 3 min
public void startUserSession () {
cancelTimer ();
timer = new Timer ();
timer.schedule(new TimerTask() {
@Override
public void run() {
listener.onSessionLogout ();
}
}, INACTIVE_TIMEOUT);
}
private void cancelTimer () {
if (timer !=null) timer.cancel();
}
public void registerSessionListener(LogoutListener listener){
this.listener = listener;
}
public void onUserInteracted () {
startUserSession();
}
}
Note: Don't forget to add the "TimeOutApp" class to your application tag inside your manifest file
<application
android:name=".TimeOutApp">
</application>
回答16:
open class SubActivity : AppCompatActivity() {
var myRunnable:Runnable
private var myHandler = Handler()
init {
myRunnable = Runnable{
toast("time out")
var intent = Intent(this, MainActivity::class.java)
startActivity(intent)
}
}
fun toast(text: String) {
runOnUiThread {
val toast = Toast.makeText(applicationContext, text, Toast.LENGTH_SHORT)
toast.show()
}
}
override fun onUserInteraction() {
super.onUserInteraction();
myHandler.removeCallbacks(myRunnable)
myHandler.postDelayed(myRunnable, 3000)
}
override fun onPause() {
super.onPause()
myHandler.removeCallbacks(myRunnable)
}
override fun onResume() {
super.onResume()
myHandler.postDelayed(myRunnable, 3000)
}
}
Extend your Activity with
YourActivity:SubActivity(){}
to get to MainActivity when User is inactive after 3000 millisec on YourActivity
I used an previous answer and converted it to kotlin.
来源:https://stackoverflow.com/questions/4208730/how-to-detect-user-inactivity-in-android