梳理开机累计时长的源码,分析实现方式
文章目录
前言
我记得以前有客户要做限制开机使用时长功能,机器开机一段时间后自动关机功能。 当时想法就是通过开机累计时长功能实现。
一、需求
实现机器开机后,指定小时后 自动关机功能。
实现方案
- 开机统计时长本身就有现成的api, 在自己的系统签名应用里面统计时长,到大时长后自动触发关机业务逻辑
- 很多产品本身是没有定制的应用的,或者集成了部分第三方应用而已。 此时 统计开机时长的逻辑就需要系统 framework 层实现了。
二、实现思路
参考系统设置 开机累计时长 实现方式实现:
虽然已经有了实现方案了,但是还是强烈建议 看看系统设置源码是怎么实现的,系统源码还是值得分析、梳理业务的。
三、源码分析
相关源码文件
/packages/apps/Settings/src/com/android/settings/deviceinfo/aboutphone/MyDeviceInfoFragment.java
/packages/apps/Settings/src/com/android/settings/deviceinfo/UptimePreferenceController.java
/packages/apps/Settings/res/xml/my_device_info.xml
/frameworks/base/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractUptimePreferenceController.java
参考资料
RKAndroid11-系统设置新增开关选项
这里建议看看,如果对系统源码Settings 本身就已经有一定的了解,就不用参考了。 主要理解 Controller、布局、布局属性的关联关系。
设置的页面-MyDeviceInfoFragment
找到统计时长对应的页面,如下:
还是看看日志 这个页面对应的fragment 是哪一个,日志如下:
找到 Fragment 页面的目的是找对应的 布局xml 文件和 控制Controler 如下:
@Override
protected int getPreferenceScreenResId() {
return R.xml.my_device_info;
}
设置的页面-开机累计时长控制器-UptimePreferenceController和关联的AbstractUptimePreferenceController
UptimePreferenceController 源码如下,发现非常简单,那么它的逻辑应该就在父类AbstractUptimePreferenceController里面了,代码如下
/**
* Concrete subclass of uptime preference controller
*/
public class UptimePreferenceController extends AbstractUptimePreferenceController
implements PreferenceControllerMixin {
public UptimePreferenceController(Context context, Lifecycle lifecycle) {
super(context, lifecycle);
}
// This space intentionally left blank
}
/**
* Preference controller for uptime
*/
public abstract class AbstractUptimePreferenceController extends AbstractPreferenceController
implements LifecycleObserver, OnStart, OnStop {
@VisibleForTesting
static final String KEY_UPTIME = "up_time";
private static final int EVENT_UPDATE_STATS = 500;
private Preference mUptime;
private Handler mHandler;
public AbstractUptimePreferenceController(Context context, Lifecycle lifecycle) {
super(context);
if (lifecycle != null) {
lifecycle.addObserver(this);
}
}
@Override
public void onStart() {
getHandler().sendEmptyMessage(EVENT_UPDATE_STATS);
}
@Override
public void onStop() {
getHandler().removeMessages(EVENT_UPDATE_STATS);
}
@Override
public boolean isAvailable() {
return true;
}
@Override
public String getPreferenceKey() {
return KEY_UPTIME;
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
mUptime = screen.findPreference(KEY_UPTIME);
updateTimes();
}
private Handler getHandler() {
if (mHandler == null) {
mHandler = new MyHandler(this);
}
return mHandler;
}
private void updateTimes() {
mUptime.setSummary(DateUtils.formatElapsedTime(SystemClock.elapsedRealtime() / 1000));
}
private static class MyHandler extends Handler {
private WeakReference<AbstractUptimePreferenceController> mStatus;
public MyHandler(AbstractUptimePreferenceController activity) {
mStatus = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
AbstractUptimePreferenceController status = mStatus.get();
if (status == null) {
return;
}
switch (msg.what) {
case EVENT_UPDATE_STATS:
status.updateTimes();
sendEmptyMessageDelayed(EVENT_UPDATE_STATS, 1000);
break;
default:
throw new IllegalStateException("Unknown message " + msg.what);
}
}
}
}
可以看到,AbstractUptimePreferenceController 源码里面处理了开机累计时长的业务和实现。 通过 handler 发送 EVENT_UPDATE_STATS 事件,每秒更新一次。
status.updateTimes(); --》
private void updateTimes() {
mUptime.setSummary(DateUtils.formatElapsedTime(SystemClock.elapsedRealtime() / 1000));
} ---》
mUptime.setSummary --》
mUptime = screen.findPreference(KEY_UPTIME);
KEY_UPTIME 是什么,不就是key 嘛,它就是布局里面唯一性的key,拿到展示数据的 Preference
小结
这里的开机累计时长逻辑很清晰
- handler 使用
- 获取 开机累计时长的 Preference, 并调用 setSummary 每秒更新数据
- 统计累计时长用到一个api SystemClock.elapsedRealtime()
设置的页面-开机累计时长关联的布局 my_device_info
。。。。。。。。
<!-- Device up time -->
<Preference
android:key="up_time"
android:order="48"
android:title="@string/status_up_time"
android:summary="@string/summary_placeholder"
android:selectable="false"/>
。。。。。。。。
这里需要强调的是 这个 key 不就代表唯一性嘛,在上面的Controler里面通过 screen.findPreference(KEY_UPTIME); 找到 开机累计时长的item,然后设置setSummary 数据。
四、理解 SystemClock.elapsedRealtime
SystemClock.elapsedRealtime() 是 Android 系统提供的一个非常重要的计时方法,用于获取设备从启动到现在经过的时间(包括深度睡眠时间)。
基本概念
-
返回值:返回自系统启动以来的毫秒数,包括设备处于深度睡眠的时间
-
时钟类型:这是一个"单调时钟"(monotonic clock),意味着它只会向前走,不会受系统时间设置的影响
-
基准点:从设备上次启动开始计时,设备重启后会重置
与相关方法的比较
方法 | 描述 | 受系统时间设置影响 | 包含睡眠时间 |
---|---|---|---|
SystemClock.elapsedRealtime() | 自启动以来的时间(包括睡眠) | 否 | 是 |
SystemClock.uptimeMillis() | 自启动以来的时间(不包括睡眠) | 否 | 否 |
System.currentTimeMillis() | 当前系统时间(墙钟时间) | 是 | 不适用 |
主要特点
不受系统时间更改影响:
-
即使用户手动修改了设备时间,这个值也不会受到影响
-
适合用于需要精确计算时间间隔的场景
包含深度睡眠时间:
-
即使设备进入深度睡眠状态,这个时钟也会继续计时
-
适合记录设备的总运行时间
单调递增:
-
保证每次调用返回的值都大于或等于前一次调用的值
-
不会出现时间回退的情况
五、总结
- 这里针对一个简单需求,去分析了系统设置Settings 开机累计时长 关联的源码进行了分析
- 这里其实就是一个api 的应用 SystemClock.elapsedRealtime。 如果需求是 控制 使用机器时间,那么就是另外关联的一个api 也可以实现了。