Android多Activity下用户闲置检测与自动登出功能异常排查
解决多Activity场景下的Android会话超时自动登出问题
看起来你已经在多Activity架构下尝试用抽象基类实现会话超时逻辑,但遇到了两个棘手的问题,我来帮你逐一梳理并解决:
问题根源拆解
- 锁屏时无法触发自动登出:设备锁屏时,当前Activity会进入
onStop状态,但你的实现依赖onUserInteraction捕获用户操作,这类系统级锁屏事件不会触发该方法,加上代码里没有针对锁屏的特殊处理,导致计时器无法正常触发登出回调。 - 正常使用时弹出超时提示:你用了静态Handler,这会让所有继承基类的Activity共用同一个Handler实例。切换Activity时,旧的回调没有被彻底清除,新的重置操作和旧回调冲突,导致即使用户在正常操作,过期的回调依然会触发。另外你注释掉了
onResume和onStop里的计时器管理代码,进一步加剧了逻辑混乱。
修复后的完整实现
我们需要做几个关键调整,确保多Activity场景下逻辑稳定,同时覆盖锁屏场景:
修改后的SessionTimeOutActivity代码
public abstract class SessionTimeOutActivity extends BaseActivity { public static final long DISCONNECT_TIMEOUT = 5 * 60 * 1000; // 5分钟超时阈值 private Handler disconnectHandler; private Runnable disconnectCallback; private BroadcastReceiver screenOffReceiver; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); initTimerLogic(); initScreenOffListener(); } private void initTimerLogic() { // 每个Activity实例持有独立的Handler,避免多实例冲突 disconnectHandler = new Handler(Looper.getMainLooper()); disconnectCallback = new Runnable() { @Override public void run() { Log.d("SessionTimeOutActivity", "会话超时,执行自动登出"); // 先执行登出日志上传 uploadLogoutLog(); // 执行登出跳转逻辑 Toast.makeText(getApplicationContext(), "Session time out", Toast.LENGTH_LONG).show(); Intent intent = new Intent(getApplicationContext(), LoginActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(intent); finish(); } }; } private void initScreenOffListener() { // 监听锁屏事件,确保锁屏后计时器正常运行 screenOffReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) { resetDisconnectTimer(); } } }; IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF); registerReceiver(screenOffReceiver, filter); } public void resetDisconnectTimer() { if (disconnectHandler != null && disconnectCallback != null) { disconnectHandler.removeCallbacks(disconnectCallback); disconnectHandler.postDelayed(disconnectCallback, DISCONNECT_TIMEOUT); } } public void stopDisconnectTimer() { if (disconnectHandler != null && disconnectCallback != null) { disconnectHandler.removeCallbacks(disconnectCallback); } } @Override public void onUserInteraction() { super.onUserInteraction(); Log.d("SessionTimeOutActivity", "检测到用户交互,重置计时器"); resetDisconnectTimer(); } @Override protected void onResume() { super.onResume(); // 回到当前页面时重置计时器 resetDisconnectTimer(); } @Override protected void onDestroy() { super.onDestroy(); // 页面销毁时清理资源,避免内存泄漏 stopDisconnectTimer(); if (screenOffReceiver != null) { unregisterReceiver(screenOffReceiver); } if (disconnectHandler != null) { disconnectHandler.removeCallbacksAndMessages(null); disconnectHandler = null; } } // 抽象方法,让子类实现具体的日志上传逻辑 protected abstract void uploadLogoutLog(); }
关键调整说明
- 移除静态Handler:每个Activity实例持有独立的Handler,彻底解决多Activity间的回调冲突和内存泄漏风险。
- 新增锁屏监听:通过
ACTION_SCREEN_OFF广播捕获锁屏事件,重置计时器确保锁屏后仍能按时触发登出。 - 规范计时器生命周期:
onResume时重置计时器,保证回到页面时重新开始计时onDestroy时清理计时器和广播接收器,避免内存泄漏- 不再在
onStop中停止计时器,因为锁屏/切换Activity时仍需保持计时
- 抽象日志上传:将登出日志上传逻辑抽象为抽象方法,让子类实现,保证代码扩展性。
子类继承示例
public class MenuActivtyNav extends SessionTimeOutActivity { // ... 你的页面业务代码 @Override protected void uploadLogoutLog() { // 在这里实现具体的服务器日志上传逻辑 Log.d("MenuActivtyNav", "上传登出日志到服务器"); // 示例:调用你的API接口 // apiService.uploadLog(new LogInfo(userId, LogType.LOGOUT, System.currentTimeMillis())); } }
额外优化:应用前后台监听(可选)
如果需要在应用完全退到后台时灵活控制计时器(比如暂停/继续),可以用ProcessLifecycleOwner监听应用全局生命周期:
先添加依赖:
implementation "androidx.lifecycle:lifecycle-process:2.6.2"
然后在基类中添加监听逻辑:
private LifecycleObserver appLifecycleObserver; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // ... 其他初始化代码 initAppLifecycleListener(); } private void initAppLifecycleListener() { appLifecycleObserver = new DefaultLifecycleObserver() { @Override public void onStop(@NonNull LifecycleOwner owner) { // 应用退到后台时,可根据需求选择停止或继续计时 // stopDisconnectTimer(); } @Override public void onStart(@NonNull LifecycleOwner owner) { // 应用回到前台时重置计时器 resetDisconnectTimer(); } }; ProcessLifecycleOwner.get().getLifecycle().addObserver(appLifecycleObserver); } @Override protected void onDestroy() { super.onDestroy(); // ... 其他清理代码 if (appLifecycleObserver != null) { ProcessLifecycleOwner.get().getLifecycle().removeObserver(appLifecycleObserver); } }
测试验证点
- 锁屏场景:锁屏后等待5分钟,应自动跳转登录页并提示会话超时
- 正常使用场景:在应用内点击、滑动等操作,不会弹出超时提示,每次操作都会重置计时器
- Activity切换场景:在不同继承的页面间切换,计时器会在新页面
onResume时重置,不会触发错误超时
内容的提问来源于stack exchange,提问作者MJM
相关产品推荐
相关产品推荐

