Android系统 获取用户最后操作时间回调实现和原理分析

简介: Android系统 获取用户最后操作时间回调实现和原理分析

在本文中,我将介绍如何获取Android系统的最后操作时间,即用户最后一次触摸屏幕的时间。这个功能在一些场景下是非常有用的,比如需要在一段时间内无操作时自动执行某些任务 , 或者在人体感应场景 休眠唤醒屏时的优先级处理 。我将以Android 11.0+为例,先介绍Android系统中最后操作时间的原理, 然后增加回调 可以给系统其他服务调用。

用户最后操作时间原理

Android系统中有一个服务叫做PowerManagerService,它负责管理设备的电源状态,包括唤醒、休眠、亮屏、暗屏等。这个服务中有一个变量叫做mLastUserActivityTime,它记录了用户最后一次触摸屏幕的时间。每当用户触摸屏幕时,这个变量就会被更新为当前的系统时间

因此,我们可以通过读取这个变量的值,来判断用户是否在一段时间内没有操作屏幕。如果当前的系统时间减去这个变量的值大于我们设定的阈值(比如5秒),那么就说明用户已经超过5秒没有操作屏幕了。这时候,我们就可以执行相应的逻辑。

业务逻辑实现

如果你的业务逻辑比较简单 比如发个广播 启动个应用要实现这个功能,我们需要修改PowerManagerService.java这个文件,它位于frameworks/base/services/core/java/com/android/server/power/目录下。我们需要在updateUserActivitySummaryLocked()这个方法中添加我们的判断逻辑,以及发送一个消息给我们自己定义的Handler来处理播放视频的操作。

步骤1,在PowerManagerService类中定义一个常量和一个Handler:

// The message to indicate that the user has not touched the screen for a while
private static final int MSG_USER_NOT_ACTION = 100;
// The handler to handle the message
private final Handler mHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case MSG_USER_NOT_ACTION:
                // 比如执行命令 启动服务 或者 播放视频
                break;
            default:
                super.handleMessage(msg);
        }
    }
};

步骤2,在updateUserActivitySummaryLocked()方法中添加我们的判断逻辑:

private void updateUserActivitySummaryLocked(long now, int dirty) {
    // Update the status of the user activity timeout timer.
    if ((dirty & (DIRTY_WAKE_LOCKS | DIRTY_USER_ACTIVITY
            | DIRTY_WAKEFULNESS | DIRTY_SETTINGS)) != 0) {
        mHandler.removeMessages(MSG_USER_ACTIVITY_TIMEOUT);
        long nextTimeout = 0;
        if (getWakefulnessLocked() == WAKEFULNESS_AWAKE
                || getWakefulnessLocked() == WAKEFULNESS_DREAMING
                || getWakefulnessLocked() == WAKEFULNESS_DOZING) {
            final long attentiveTimeout = getAttentiveTimeoutLocked();
            final long sleepTimeout = getSleepTimeoutLocked(attentiveTimeout);
            final long screenOffTimeout = getScreenOffTimeoutLocked(sleepTimeout,
                    attentiveTimeout);
            final long screenDimDuration = getScreenDimDurationLocked(screenOffTimeout);
            final boolean userInactiveOverride = mUserInactiveOverrideFromWindowManager;
            final long nextProfileTimeout = getNextProfileTimeoutLocked(now);
            mUserActivitySummary = 0;
            if (mLastUserActivityTime >= mLastWakeTime) {
                nextTimeout = mLastUserActivityTime
                        + screenOffTimeout - screenDimDuration;
                if (now < nextTimeout) {
                    mUserActivitySummary = USER_ACTIVITY_SCREEN_BRIGHT;
                } else {
                    nextTimeout = mLastUserActivityTime + screenOffTimeout;
                    if (now < nextTimeout) {
                        mUserActivitySummary = USER_ACTIVITY_SCREEN_DIM;
                    }
                }
            }
            if (mUserActivitySummary == 0
                    && mLastUserActivityTimeNoChangeLights >= mLastWakeTime) {
                nextTimeout = mLastUserActivityTimeNoChangeLights + screenOffTimeout;
                if (now < nextTimeout) {
                    if (mDisplayPowerRequest.policy == DisplayPowerRequest.POLICY_BRIGHT
                            || mDisplayPowerRequest.policy == DisplayPowerRequest.POLICY_VR) {
                        mUserActivitySummary = USER_ACTIVITY_SCREEN_BRIGHT;
                    } else if (mDisplayPowerRequest.policy == DisplayPowerRequest.POLICY_DIM) {
                        mUserActivitySummary = USER_ACTIVITY_SCREEN_DIM;
                    }
                }
            }
            if (mUserActivitySummary == 0) {
                if (sleepTimeout >= 0) {
                    final long anyUserActivity = Math.max(mLastUserActivityTime,
                            mLastUserActivityTimeNoChangeLights);
                    if (anyUserActivity >= mLastWakeTime) {
                        nextTimeout = anyUserActivity + sleepTimeout;
                        if (now < nextTimeout) {
                            mUserActivitySummary = USER_ACTIVITY_SCREEN_DREAM;
                        }
                    }
                } else {
                    mUserActivitySummary = USER_ACTIVITY_SCREEN_DREAM;
                    nextTimeout = -1;
                }
            }
            if (mUserActivitySummary != USER_ACTIVITY_SCREEN_DREAM && userInactiveOverride) {
                if ((mUserActivitySummary &
                        (USER_ACTIVITY_SCREEN_BRIGHT | USER_ACTIVITY_SCREEN_DIM)) != 0) {
                    // Device is being kept awake by recent user activity
                    if (nextTimeout >= now && mOverriddenTimeout == -1) {
                        // Save when the next timeout would have occurred
                        mOverriddenTimeout = nextTimeout;
                    }
                }
                mUserActivitySummary = USER_ACTIVITY_SCREEN_DREAM;
                nextTimeout = -1;
            }
            if ((mUserActivitySummary & USER_ACTIVITY_SCREEN_BRIGHT) != 0
                    && (mWakeLockSummary & WAKE_LOCK_STAY_AWAKE) == 0) {
                nextTimeout = mAttentionDetector.updateUserActivity(nextTimeout,
                        screenDimDuration);
            }
            if (nextProfileTimeout > 0) {
                nextTimeout = Math.min(nextTimeout, nextProfileTimeout);
            }
            if (Integer.MAX_VALUE == screenOffTimeout) {
                mUserActivitySummary = USER_ACTIVITY_SCREEN_BRIGHT;
                //Slog.d(TAG, "set mUserActivitySummary USER_ACTIVITY_SCREEN_BRIGHT never sleep " + nextTimeout);
            }
            // Add our logic here to check if the user has not touched the screen for 10 seconds
            // -------------------------------
            if(now-mLastUserActivityTime>=5000 && mBootCompleted && mSystemReady){
                Message msg = mHandler.obtainMessage(MSG_USER_NOT_ACTION);
                msg.setAsynchronous(true);
                mHandler.sendMessage(msg);
            }
            // -------------------------------
            if (mUserActivitySummary != 0 && nextTimeout >= 0) {
                scheduleUserInactivityTimeout(nextTimeout);
            }
        } else {
            mUserActivitySummary = 0;
        }
        if (DEBUG_SPEW) {
            Slog.d(TAG, "updateUserActivitySummaryLocked: mWakefulness="
                    + PowerManagerInternal.wakefulnessToString(getWakefulnessLocked())
                    + ", mUserActivitySummary=0x" + Integer.toHexString(mUserActivitySummary)
                    + ", nextTimeout=" + TimeUtils.formatUptime(nextTimeout));
        }
    }
}

步骤3,在我们自己定义的Handler中添加我们的逻辑来播放视频,这里可以根据自己的需求来实现,比如调用一个广播或者一个服务来启动服务。

添加用户最后操作时间回调

什么? 你告诉我 你的业务逻辑在PowerManagerService.java 不方便搞? 什么 你告诉我有N个地方要获取最后时间 , 🆗 我们添加一个回调接口。

如果有多个服务需要获取回调,你可以将单个回调接口改为回调接口的列表。以下是具体的步骤:

  1. 修改回调的定义:将UserActivityCallback的单个实例改为List<UserActivityCallback>的实例。例如:
private List<UserActivityCallback> mUserActivityCallbacks = new ArrayList<>();
  1. 修改设置回调的方法:将setUserActivityCallback()方法改为addUserActivityCallback()方法,用于添加回调,而不是设置回调。例如:
public void addUserActivityCallback(UserActivityCallback callback) {
    mUserActivityCallbacks.add(callback);
}

你也可以添加一个removeUserActivityCallback()方法,用于移除回调:

public void removeUserActivityCallback(UserActivityCallback callback) {
    mUserActivityCallbacks.remove(callback);
}
  1. 修改调用回调的代码:在updateUserActivitySummaryLocked()方法中,你需要遍历回调列表,对每个回调都调用onUserActivity()方法。例如:
if (mLastUserActivityTime >= mLastWakeTime) {
    for (UserActivityCallback callback : mUserActivityCallbacks) {
        callback.onUserActivity(mLastUserActivityTime);
    }
    // ...
}
  1. 在你的服务中添加回调:在你的每个服务中,你都需要调用addUserActivityCallback()方法来添加回调 (SystemBootAppServer 是我自定义的)。例如:
public class SystemBootAppServer extends SystemService {
    // ...
    @Override
    public void onStart() {
        PowerManagerService pms = (PowerManagerService) getSystemService(Context.POWER_SERVICE);
        pms.addUserActivityCallback(new PowerManagerService.UserActivityCallback() {
            @Override
            public void onUserActivity(long lastActivityTime) {
                // 在这里处理用户最后操作的时间
            }
        });
    }
}

以上就是在有多个服务需要获取回调时的具体步骤。这种方法可以使得每个服务都能获取到用户最后操作的时间,而不仅仅是一个服务。

人体感应问题补充

在我开发过程中 , 我遇到过一个问题 , 就是添加人体感应之后 , 会休眠和唤醒屏幕。

假设人体感应被(误)触发需要熄屏 , 但是你用户正在操作呢 , 这个时候应该熄屏幕吗?

很显然 不应该熄灭 , 这是个优先级的问题 , 所以我补充一点。

在PowerManagerService.java这个文件的

@Override // Binder call
        public void wakeUp(long eventTime, @WakeReason int reason, String details,
                String opPackageName) {
            if (eventTime > mClock.uptimeMillis()) {
                throw new IllegalArgumentException("event time must not be in the future");
            }
            mContext.enforceCallingOrSelfPermission(
                    android.Manifest.permission.DEVICE_POWER, null);
            final int uid = Binder.getCallingUid();
            final long ident = Binder.clearCallingIdentity();
            try {
                wakeUpInternal(eventTime, reason, details, uid, opPackageName, uid);
            } finally {
                Binder.restoreCallingIdentity(ident);
            }
        }
        @Override // Binder call
        public void goToSleep(long eventTime, int reason, int flags) {
            // 假设人体感应被触发休眠指令, 但是人还在操作呢 , 会走这个流程 , 
            // 我们在这里添加一个5秒的判断 , 如果最后操作时间小于5秒
            // 则不执行休眠动作 ,
            //--------------------------
            long now = SystemClock.uptimeMillis();
            Log.d("goToSleepTest","mLastUserActivityTime:"+mLastUserActivityTime+",now!!:"+now);
            if(now-mLastUserActivityTime>=5000 ){
            //--------------------------
              if (eventTime > mClock.uptimeMillis()) {
                  throw new IllegalArgumentException("event time must not be in the future");
              }
  
              mContext.enforceCallingOrSelfPermission(
                      android.Manifest.permission.DEVICE_POWER, null);
  
              final int uid = Binder.getCallingUid();
              final long ident = Binder.clearCallingIdentity();
              try {
                  goToSleepInternal(eventTime, reason, flags, uid);
              } finally {
                  Binder.restoreCallingIdentity(ident);
              }
          }
        }

总结

通过以上的步骤,我们就可以实现在Android系统层获取最后操作时间的功能,并在一段时间内无操作时自动播放视频。这个功能可以用于一些需要不间断播放视频的应用场景,比如广告机、展示机等。我还介绍了如何添加回调 给系统其他自定义服务使用还考虑了多个服务同时获取的情况 , 最后补充了人体感应休眠优先级的问题。希望这篇博客能对你有所帮助,如果你有任何问题或建议,欢迎留言讨论。谢谢

相关文章
|
10小时前
|
Java Linux API
微信API:探究Android平台下Hook技术的比较与应用场景分析
微信API:探究Android平台下Hook技术的比较与应用场景分析
|
11小时前
|
存储 算法 Java
Android 进阶——代码插桩必知必会&ASM7字节码操作
Android 进阶——代码插桩必知必会&ASM7字节码操作
6 0
|
12小时前
|
安全 Android开发 iOS开发
探索安卓与iOS开发的差异:平台特性与用户体验的对比分析
移动应用开发的两大阵营——安卓与iOS,各自拥有独特的开发环境、用户群体和市场定位。本文将深入探讨这两个操作系统在应用开发过程中的主要差异,包括编程语言、开发工具、用户界面设计、性能优化、安全性考量以及发布流程等方面。通过比较分析,旨在为开发者提供跨平台开发的见解和策略,以优化应用性能和提升用户体验。
6 0
|
5天前
|
存储 监控 调度
Android系统服务:WMS、AMS相关知识
参考文献 Android窗口管理服务WindowManagerService计算Activity窗口大小的过程分析 Android窗口管理服务WindowManagerService显示Activity组件的启动窗口(Starting Window)的过程分析 Android窗口管理服务WindowManagerService对输入法窗口(Input Method Window)的管理分析 Android窗口管理服务WindowManagerService显示窗口动画的原理分析
|
6天前
|
安全 Android开发 iOS开发
探索Android与iOS开发的差异:平台特性与用户体验的对比分析
在移动应用开发的广阔天地中,Android和iOS两大阵营各据一方。本文将深入探讨这两个操作系统在开发环境、编程语言、用户界面设计及市场分布等方面的主要区别。通过比较分析,我们将揭示各自平台的特有优势,并讨论如何根据目标受众和业务需求选择适合的开发平台。
|
8天前
|
JavaScript Java 测试技术
基于SpringBoot+Vue+uniapp的安卓的微博客系统的详细设计和实现
基于SpringBoot+Vue+uniapp的安卓的微博客系统的详细设计和实现
9 0
|
8天前
|
Java Linux Android开发
Android面试题之说说系统的启动流程(总结)
这篇文章概述了Android系统的启动流程,从Boot Rom到Zygote进程和SystemServer的启动。init进程作为用户级别的第一个进程,负责创建文件目录、初始化服务并启动Zygote。Zygote通过预加载资源和创建Socket服务,使用fork函数生成SystemServer进程。fork过程中,子进程继承父进程大部分信息但具有独立的进程ID。Zygote预加载资源以减少后续进程的启动时间,而SystemServer启动众多服务并最终开启Launcher应用。文中还讨论了为何从Zygote而非init或SystemServer fork新进程的原因。
16 2
|
9天前
|
搜索推荐 安全 Android开发
安卓与iOS操作系统的对比分析
在移动设备市场上,安卓和iOS操作系统一直是主要竞争对手。本文将从用户界面、应用生态系统、定制化程度和安全性等方面对安卓和iOS进行对比分析,并探讨两者在不同场景下的适用性。
|
9天前
|
Java Android开发 iOS开发
深入探讨移动操作系统的性能优化:安卓与iOS的对比分析
在现代移动设备中,操作系统的性能优化至关重要。本文从系统架构、内存管理、电池续航和应用程序运行效率等多个维度,深入探讨了安卓(Android)和iOS两大主流移动操作系统的优化策略及其实际效果,旨在为开发者和用户提供更清晰的了解和选择依据。
20 0
|
14天前
|
XML API 开发工具
Android Bitmap 加载与像素操作
Android Bitmap 加载与像素操作
13 2