如何检测Android中的用户不活动
Posted
技术标签:
【中文标题】如何检测Android中的用户不活动【英文标题】:How to detect user inactivity in Android 【发布时间】:2011-05-11 15:48:51 【问题描述】:用户启动我的应用并登录。 选择会话超时为 5 分钟。 对应用程序进行一些操作。 (全部在前台) 现在用户将 Myapp 带到后台并启动其他应用程序。 ----> 倒计时开始并在 5 分钟后注销用户 或用户关闭屏幕。 ----> 倒计时开始并在 5 分钟后注销用户
即使应用程序在前台,但用户长时间不与应用程序交互,例如 6-7 分钟,我也想要相同的行为。假设屏幕一直处于开启状态。我想检测一种用户不活动(即使应用在前台,也没有与应用交互)并启动我的倒计时。
【问题讨论】:
您能否始终让计时器运行并在用户执行某项操作时重置它? 【参考方案1】:根据 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();
【讨论】:
这将为每个创建的Activity
创建多个Handler
和Runnable
实例。如果我们将这两个成员转换为static
,就可以避免这种情况。另外,你能告诉我你为什么在onStop()
中打电话给stopDisconnectTimer()
吗?`
@Gaurav 就我而言,这仅在一项活动中实现(因此我没有发现static
修饰符的问题)。至于onStop()
,据我所知,我调用onBackPressed()
是为了在断开回调中返回登录屏幕,而断开回调又调用onStop()
方法。当用户手动返回登录屏幕时,通过按返回,计时器也需要停止,因此onStop()
中的stopDisconnectTimer()
。我想这部分取决于您的需求和实施。
@gfrigon 是否可以将用户重定向到登录活动?
@Apostrifix,当然可以。就我而言,只有一项活动:调用onBackPressed()
vas 就足够了。如果您的堆栈中有多个活动,则只需为此创建一个意图。您可能需要查看以下答案以清除 Activity 任务(并防止用户重新连接):***.com/questions/7075349/…
干得好!我为可运行对象添加了 getter 和 setter,然后根据需要使用 onCreate 方法将其设置在扩展类中......完美,再次感谢。【参考方案2】:
我不知道如何跟踪不活动,但有一种方法可以跟踪用户活动。您可以在您的活动中捕获一个名为onUserInteraction()
的回调,每次用户与应用程序进行任何交互时都会调用该回调。我建议做这样的事情:
@Override
public void onUserInteraction()
MyTimerClass.getInstance().resetTimer();
如果您的应用包含多个 Activity,为什么不将此方法放在抽象超类中(扩展 Activity
),然后让所有 Activity 扩展它。
【讨论】:
是的,这是一种方法......但是我的应用程序有 30 个不同的活动,当用户处于活动状态时会有太多的交互......所以每次重置计时器都需要昂贵的操作......在最坏的情况下可以在一分钟内完成 50 到 60 次。 我还没有计时,但我会说像这样重置计时器 lastInteraction = System.currentTimeMillis();比如说,2 毫秒。每分钟做 60 次,你会“放松”120 毫秒。超过 60000 个。 Fredrik... 我也在使用您的建议来满足这种情况。屏幕超时设置为设备上的最大 30 分钟。 MyApp shd 15 分钟后超时...如果用户超过 1 分钟没有触摸屏幕上的任何内容,那么我将启动 15 分钟注销计时器...。在这种情况下,我将检查差异(lastInteractionTime 和 System.currentTimeMills( )) 超过 1 分钟...然后开火.. onUserInteraction() 在某些情况下不会被调用,但是(对话框不会调用它,并且在微调器中滚动)是否有解决这些情况的方法? 你能分享你的 MyTimerClass 吗?【参考方案3】:我认为你应该使用这段代码,这是 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 救了我的命【参考方案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 ===> 此处未显示逻辑,因为它超出了问题的范围
您可以使用下面的设备代码唤醒对 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);
【讨论】:
@Nappy:那么请解释一下正确的做法。您的评论含糊不清且优柔寡断。 @AKh:其他答案已经显示了可能性。在您的解决方案中,我看不到每 15 秒轮询一次的任何好处。当您在“ACTION_SCREEN_OFF”上启动一个具有 0-15 秒随机持续时间的计时器时,它会产生相同的效果。这只是没有意义.. @Nappy:每 15 秒我不仅会检查 SCREEN_ON 或 SCREEN_OFF,还会检查用户的最后一次交互时间和 App 前台状态。基于这三个因素,我对用户与应用交互的活跃程度做出了合乎逻辑的决定。 请完成您的评论。 ....“如果你的 isScreenof 布尔值是?”并且还必须考虑应用程序的前景状态。 这段代码充满了错误,一些变量没有初始化。【参考方案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】:除了ACTION_SCREEN_OFF
和ACTION_USER_PRESENT
广播之外,操作系统级别没有“用户不活动”的概念。您必须在自己的应用程序中以某种方式定义“不活动”。
【讨论】:
【参考方案7】:在 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
【讨论】:
【参考方案8】:在我的活动基类中,我创建了受保护的类:
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();
【讨论】:
如何检查用户是否处于非活动状态??来自系统的任何输入?【参考方案9】:在搜索过程中,我找到了很多答案,但这是我得到的最佳答案。但此代码的局限性在于它仅适用于活动而不适用于整个应用程序。以此作为参考。
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 秒后完成。
【讨论】:
【参考方案10】:可以在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");
【讨论】:
【参考方案11】:这是一个完整的解决方案,可以在几分钟(例如 3 分钟)后处理用户不活动。 解决了App在后台超时Activity跳到前台等常见问题。
首先,我们创建一个所有其他 Activity 都可以扩展的 BaseActivity。
这是 BaseActivity 代码。
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();
接下来,我们为“Logout Listener”创建一个接口
package com.example.timeout;
public interface LogoutListener
void onSessionLogout();
最后,我们创建一个扩展“应用程序”的 Java 类
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();
注意:不要忘记将“TimeOutApp”类添加到清单文件中的应用程序标签
<application
android:name=".TimeOutApp">
</application>
【讨论】:
【参考方案12】:我认为需要将计时器与上次活动时间相结合。
像这样:
在 onCreate(Bundle savedInstanceState) 中启动一个计时器,比如 5 分钟
在 onUserInteraction() 中只存储当前时间
到目前为止非常简单。
现在当计时器弹出时这样做:
-
以当前时间减去存储的交互时间得到timeDelta
如果 timeDelta >= 5 分钟,您就完成了
如果 timeDelta
【讨论】:
【参考方案13】:我遇到了与 SO 问题类似的情况,我需要跟踪用户不活动 1 分钟然后重定向用户以启动 Activity,我还需要清除 Activity 堆栈。
根据@gfrigon 的回答,我想出了这个解决方案。
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();
补充资源
Android: Clear Activity Stack
This Handler class should be static or leaks might occur
【讨论】:
【参考方案14】:最好的办法是通过在 Application calss 中注册 AppLifecycleCallbacks
在整个应用程序中处理此问题(假设您有多个活动)。您可以在 Application 类中使用 registerActivityLifecycleCallbacks()
和以下回调(我建议创建一个扩展 ActivityLifecycleCallbacks 的 AppLifecycleCallbacks 类):
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】: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)
使用
扩展您的活动YourActivity:SubActivity()
当用户在 YourActivity 上 3000 毫秒后处于非活动状态时进入 MainActivity
我使用了以前的答案并将其转换为 kotlin。
【讨论】:
【参考方案16】:真正的方式
您可以使用此技术来检测用户处于非活动状态的时间(即使应用程序处于后台)。
-
创建一个
SharedPreference
及其编辑器对象。然后声明 3 个长变量,例如:
mMillisUntilFinished = pref.getLong("millisUntilFinished",60*1000); // Replace with your time
long userExitedMillis = pref.getLong("userExitedMillis",0);
long timeLeft = mMillisUntilFinished - (System.currentTimeMillis() - userExitedMillis);
-
将
timeLeft
传递为millisInFuture。在计时器内部,在每个滴答声中将 millisUntilFinished 分配给一个公共变量
new CountDownTimer(timeLeft,1000)
@Override
public void onTick(long millisUntilFinished)
Log.d("TAG", "Time left : " + millisUntilFinished/1000 + " sec");
mMillisUntilFinished = millisUntilFinished;
@Override
public void onFinish()
// Timer completed
.start();
-
在 onStop() 处以共享偏好保存此
mMillisUntilFinished
变量和当前时间。
@Override
protected void onStop()
super.onStop();
editor.putLong("millisUntilFinished",mMillisUntilFinished);
editor.putLong("userExitedMillis",System.currentTimeMillis());
editor.apply();
说明
如果你从System.currentTimeMillis()
(用户开始活动的时间)中减去userExitedMillis
(用户退出的时间),那么你将得到活动的非活动时间(以毫秒为单位)。只需从timeLeft
中减去此不活动时间即可
【讨论】:
以上是关于如何检测Android中的用户不活动的主要内容,如果未能解决你的问题,请参考以下文章