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

相关文章
|
1月前
|
开发框架 前端开发 Android开发
Flutter 与原生模块(Android 和 iOS)之间的通信机制,包括方法调用、事件传递等,分析了通信的必要性、主要方式、数据传递、性能优化及错误处理,并通过实际案例展示了其应用效果,展望了未来的发展趋势
本文深入探讨了 Flutter 与原生模块(Android 和 iOS)之间的通信机制,包括方法调用、事件传递等,分析了通信的必要性、主要方式、数据传递、性能优化及错误处理,并通过实际案例展示了其应用效果,展望了未来的发展趋势。这对于实现高效的跨平台移动应用开发具有重要指导意义。
150 4
|
1月前
|
人工智能 搜索推荐 物联网
Android系统版本演进与未来展望####
本文深入探讨了Android操作系统从诞生至今的发展历程,详细阐述了其关键版本迭代带来的创新特性、用户体验提升及对全球移动生态系统的影响。通过对Android历史版本的回顾与分析,本文旨在揭示其成功背后的驱动力,并展望未来Android可能的发展趋势与面临的挑战,为读者呈现一个既全面又具深度的技术视角。 ####
|
1月前
|
IDE Java 开发工具
移动应用与系统:探索Android开发之旅
在这篇文章中,我们将深入探讨Android开发的各个方面,从基础知识到高级技术。我们将通过代码示例和案例分析,帮助读者更好地理解和掌握Android开发。无论你是初学者还是有经验的开发者,这篇文章都将为你提供有价值的信息和技巧。让我们一起开启Android开发的旅程吧!
|
20天前
|
监控 Java Android开发
深入探索Android系统的内存管理机制
本文旨在全面解析Android系统的内存管理机制,包括其工作原理、常见问题及其解决方案。通过对Android内存模型的深入分析,本文将帮助开发者更好地理解内存分配、回收以及优化策略,从而提高应用性能和用户体验。
|
24天前
|
Java 开发工具 Android开发
安卓与iOS开发环境对比分析
在移动应用开发的广阔天地中,安卓和iOS两大平台各自占据半壁江山。本文深入探讨了这两个平台的开发环境,从编程语言、开发工具到用户界面设计等多个角度进行比较。通过实际案例分析和代码示例,我们旨在为开发者提供一个清晰的指南,帮助他们根据项目需求和个人偏好做出明智的选择。无论你是初涉移动开发领域的新手,还是寻求跨平台解决方案的资深开发者,这篇文章都将为你提供宝贵的信息和启示。
29 8
|
22天前
|
存储 安全 Android开发
探索Android系统的最新安全特性
在数字时代,智能手机已成为我们生活中不可或缺的一部分。随着技术的不断进步,手机操作系统的安全性也越来越受到重视。本文将深入探讨Android系统最新的安全特性,包括其设计理念、实施方式以及对用户的影响。通过分析这些安全措施如何保护用户免受恶意软件和网络攻击的威胁,我们希望为读者提供对Android安全性的全面了解。
|
27天前
|
安全 Android开发 iOS开发
深入探讨Android与iOS系统的差异及未来发展趋势
本文旨在深入分析Android和iOS两大移动操作系统的核心技术差异、用户体验以及各自的市场表现,进一步探讨它们在未来技术革新中可能的发展方向。通过对比两者的开放性、安全性、生态系统等方面,本文揭示了两大系统在移动设备市场中的竞争态势和潜在变革。
|
27天前
|
安全 Android开发 数据安全/隐私保护
深入探索Android与iOS系统安全性的对比分析
在当今数字化时代,移动操作系统的安全已成为用户和开发者共同关注的重点。本文旨在通过比较Android与iOS两大主流操作系统在安全性方面的差异,揭示两者在设计理念、权限管理、应用审核机制等方面的不同之处。我们将探讨这些差异如何影响用户的安全体验以及可能带来的风险。
34 1
|
20天前
|
搜索推荐 前端开发 API
探索安卓开发中的自定义视图:打造个性化用户界面
在安卓应用开发的广阔天地中,自定义视图是一块神奇的画布,让开发者能够突破标准控件的限制,绘制出独一无二的用户界面。本文将带你走进自定义视图的世界,从基础概念到实战技巧,逐步揭示如何在安卓平台上创建和运用自定义视图来提升用户体验。无论你是初学者还是有一定经验的开发者,这篇文章都将为你打开新的视野,让你的应用在众多同质化产品中脱颖而出。
40 19
|
20天前
|
JSON Java API
探索安卓开发:打造你的首个天气应用
在这篇技术指南中,我们将一起潜入安卓开发的海洋,学习如何从零开始构建一个简单的天气应用。通过这个实践项目,你将掌握安卓开发的核心概念、界面设计、网络编程以及数据解析等技能。无论你是初学者还是有一定基础的开发者,这篇文章都将为你提供一个清晰的路线图和实用的代码示例,帮助你在安卓开发的道路上迈出坚实的一步。让我们一起开始这段旅程,打造属于你自己的第一个安卓应用吧!
45 14