You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android多Activity下用户闲置检测与自动登出功能异常排查

解决多Activity场景下的Android会话超时自动登出问题

看起来你已经在多Activity架构下尝试用抽象基类实现会话超时逻辑,但遇到了两个棘手的问题,我来帮你逐一梳理并解决:

问题根源拆解

  1. 锁屏时无法触发自动登出:设备锁屏时,当前Activity会进入onStop状态,但你的实现依赖onUserInteraction捕获用户操作,这类系统级锁屏事件不会触发该方法,加上代码里没有针对锁屏的特殊处理,导致计时器无法正常触发登出回调。
  2. 正常使用时弹出超时提示:你用了静态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();
}

关键调整说明

  1. 移除静态Handler:每个Activity实例持有独立的Handler,彻底解决多Activity间的回调冲突和内存泄漏风险。
  2. 新增锁屏监听:通过ACTION_SCREEN_OFF广播捕获锁屏事件,重置计时器确保锁屏后仍能按时触发登出。
  3. 规范计时器生命周期:
    • onResume时重置计时器,保证回到页面时重新开始计时
    • onDestroy时清理计时器和广播接收器,避免内存泄漏
    • 不再在onStop中停止计时器,因为锁屏/切换Activity时仍需保持计时
  4. 抽象日志上传:将登出日志上传逻辑抽象为抽象方法,让子类实现,保证代码扩展性。

子类继承示例

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

测试验证点

  1. 锁屏场景:锁屏后等待5分钟,应自动跳转登录页并提示会话超时
  2. 正常使用场景:在应用内点击、滑动等操作,不会弹出超时提示,每次操作都会重置计时器
  3. Activity切换场景:在不同继承的页面间切换,计时器会在新页面onResume时重置,不会触发错误超时

内容的提问来源于stack exchange,提问作者MJM

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 08:04:15