如何在 Android 中检测用户不活跃

用户启动我的应用程序并登录。
选择会话超时5分钟。
在应用程序上做一些操作
现在用户将 Myapp 带到后台,并启动其他一些应用程序。
倒数计时器启动,5分钟后注销用户
OR user turns the screen OFF.
倒数计时器启动,5分钟后注销用户

我希望同样的行为,即使应用程序在前台,但用户不与应用程序交互很长一段时间,比如6-7分钟。假设屏幕一直处于打开状态。我想检测种 用户不活跃(没有与应用程序交互,即使应用程序在前台) ,并启动我的倒计时计时器。

103600 次浏览

除了 ACTION_SCREEN_OFFACTION_USER_PRESENT广播之外,在操作系统级别没有“用户不活跃”的概念。您必须在自己的应用程序中以某种方式定义“不活动”。

我不知道一种跟踪不活跃状态的方法,但是有一种方法可以跟踪用户的活动。您可以在活动中捕获一个称为 onUserInteraction()的回调,每次用户与应用程序进行任何交互时都会调用该回调。我建议你这样做:

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

如果您的应用程序包含多个活动,为什么不将这个方法放在一个抽象超类中(扩展 Activity) ,然后让您的所有活动对其进行扩展。

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

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

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

所以在 继续方法中-你可以在你的回调函数中指定动作,你想用它做什么..。

idleTimer = new IdleTimer(60000, new IIdleCallback() {
@Override
public void inactivityDetected() {
...your move...
}
});
idleTimer.startIdleTimer();
@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));
}

我觉得应该把计时器和最后一次活动时间结合起来。

就像这样:

  1. 在 onCreate (Bundle savedInstanceState)中启动一个计时器,比如5分钟

  2. In onUserInteraction() just store the current time

Pretty simple so far.

现在,当计时器像这样弹出时:

  1. 取当前时间,减去存储的交互时间,得到 timeDelta
  2. 如果时间 Delta 大于等于5分钟,那么就完成了
  3. 如果 timeDelta 小于5分钟,再次启动计时器,但这次使用5分钟-存储的时间。换句话说,5分钟形成了最后的互动

在我的搜索中,我找到了很多答案,但这是我得到的最好的答案。但是这段代码的局限性在于,它只适用于活动,而不适用于整个应用程序。拿这个当参考吧。

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.

我认为你应该使用这个代码,这是为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
}

我有类似于 SO 问题的情况,我需要跟踪用户1分钟不活动,然后重定向用户启动活动,我还需要清除活动堆栈。

基于这个答案,我想出了这个解决方案。

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 Android: 清除活动堆栈

此 Handler 类应该是静态的,否则可能会发生泄漏

在机器人中,用户不活动可以使用 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");


}


}

最好的办法是通过在 Application 类中注册 AppLifecycleCallbacks来处理整个应用程序(假设您有多个活动)。您可以通过以下回调在 Application 类中使用 registerActivityLifecycleCallbacks()(我建议创建一个扩展 ActivityLificycleCallback 的 AppLificycleCallback 类) :

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

在 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
}

Here is a complete solution that Handles user inactivity after few mins (e.g. 3mins). 这解决了一些常见问题,比如当超时时应用程序处于后台时,活动会跳到前台。

首先,我们创建一个所有其他活动都可以扩展的 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();


}

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




}

注意: 不要忘记将“ TimeOutApp”类添加到清单文件中的应用程序标记中

<application
android:name=".TimeOutApp">
</application>
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(){}

当用户在3000毫秒之后不活动时访问 MainActivity

我用了之前的答案,换成了 Kotlin。

真正的方式

您可以使用此技术来检测用户处于非活动状态的时间(即使应用程序处于后台)。

  1. 创建一个 SharedPreference及其编辑器对象,然后声明3个长变量,如:
mMillisUntilFinished = pref.getLong("millisUntilFinished",60*1000); // Replace with your time
long userExitedMillis = pref.getLong("userExitedMillis",0);
long timeLeft = mMillisUntilFinished - (System.currentTimeMillis() - userExitedMillis);
  1. timeLeft作为 millisInFuture 传递。在内部计时器中,在每个刻度中将 millisUntilFinish 分配给一个公共变量
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();


  1. Save this mMillisUntilFinished variable & current time in shared preference at onStop().
@Override
protected void onStop() {
super.onStop();
editor.putLong("millisUntilFinished",mMillisUntilFinished);
editor.putLong("userExitedMillis",System.currentTimeMillis());
editor.apply();
}

解释

如果从 System.currentTimeMillis()中减去 userExitedMillis(用户退出的时间)(用户启动活动的时间) ,那么将得到活动非活动时间(毫秒)。只要从 timeLeft中减去这个非活动时间