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

相关文章
|
14天前
|
安全 Android开发 数据安全/隐私保护
深入探讨iOS与Android系统安全性对比分析
在移动操作系统领域,iOS和Android无疑是两大巨头。本文从技术角度出发,对这两个系统的架构、安全机制以及用户隐私保护等方面进行了详细的比较分析。通过深入探讨,我们旨在揭示两个系统在安全性方面的差异,并为用户提供一些实用的安全建议。
|
14天前
|
算法 JavaScript Android开发
|
16天前
|
安全 搜索推荐 Android开发
揭秘安卓与iOS系统的差异:技术深度对比
【10月更文挑战第27天】 本文深入探讨了安卓(Android)与iOS两大移动操作系统的技术特点和用户体验差异。通过对比两者的系统架构、应用生态、用户界面、安全性等方面,揭示了为何这两种系统能够在市场中各占一席之地,并为用户提供不同的选择。文章旨在为读者提供一个全面的视角,理解两种系统的优势与局限,从而更好地根据自己的需求做出选择。
42 2
|
15天前
|
安全 搜索推荐 程序员
深入探索Android系统的碎片化问题及其解决方案
在移动操作系统的世界中,Android以其开放性和灵活性赢得了广泛的市场份额。然而,这种开放性也带来了一个众所周知的问题——系统碎片化。本文旨在探讨Android系统碎片化的现状、成因以及可能的解决方案,为开发者和用户提供一种全新的视角来理解这一现象。通过分析不同版本的Android系统分布、硬件多样性以及更新机制的影响,我们提出了一系列针对性的策略,旨在减少碎片化带来的影响,提升用户体验。
|
15天前
|
安全 Android开发 iOS开发
深入探索iOS与Android系统的差异性及优化策略
在当今数字化时代,移动操作系统的竞争尤为激烈,其中iOS和Android作为市场上的两大巨头,各自拥有庞大的用户基础和独特的技术特点。本文旨在通过对比分析iOS与Android的核心差异,探讨各自的优势与局限,并提出针对性的优化策略,以期为用户提供更优质的使用体验和为开发者提供有价值的参考。
|
17天前
|
安全 Android开发 iOS开发
安卓系统与iOS系统的比较####
【10月更文挑战第26天】 本文将深入探讨安卓(Android)和iOS这两大主流移动操作系统的各自特点、优势与不足。通过对比分析,帮助读者更好地理解两者在用户体验、应用生态、系统安全等方面的差异,从而为消费者在选择智能手机时提供参考依据。无论你是技术爱好者还是普通用户,这篇文章都将为你揭示两大系统背后的故事和技术细节。 ####
38 0
|
Linux API Android开发
Android中mmap原理及应用简析
Android中mmap原理及应用简析
522 0
Android中mmap原理及应用简析
|
前端开发 Java API
Android drawFunctor原理及应用
一. 背景AntGraphic项目Android平台中使用了基于TextureView环境实现GL渲染的技术方案,而TextureView需使用与Activity Window独立的GraphicBuffer,RenderThread在上屏TextureView内容时需要将GraphicBuffer封装为EGLImage上传为纹理再渲染,内存占用较高。为降低内存占用,经仔细调研Android源码,
617 0
|
5天前
|
搜索推荐 Android开发 开发者
探索安卓开发中的自定义视图:打造个性化UI组件
【10月更文挑战第39天】在安卓开发的世界中,自定义视图是实现独特界面设计的关键。本文将引导你理解自定义视图的概念、创建流程,以及如何通过它们增强应用的用户体验。我们将从基础出发,逐步深入,最终让你能够自信地设计和实现专属的UI组件。
|
7天前
|
Android开发 Swift iOS开发
探索安卓与iOS开发的差异和挑战
【10月更文挑战第37天】在移动应用开发的广阔舞台上,安卓和iOS这两大操作系统扮演着主角。它们各自拥有独特的特性、优势以及面临的开发挑战。本文将深入探讨这两个平台在开发过程中的主要差异,从编程语言到用户界面设计,再到市场分布的不同影响,旨在为开发者提供一个全面的视角,帮助他们更好地理解并应对在不同平台上进行应用开发时可能遇到的难题和机遇。