功能实现
1.如何定位较为合理的地方去启动定时服务
2.如何根据系统运行情况打开/关闭服务
3.如何在系统休眠时保持服务运行状态
需求:Rk3288 Android 8.1 需实现 记录系统开机时间功能.
实现:在PowerManagerService中跑开机流程时,启动此计时服务,且不随着系统休眠而停止.
遇到的困难:
1.如何定位较为合理的地方去启动定时服务
2.如何手动停止即使服务
3.如何在系统休眠时保持服务运行状态
作者Android入门,对framework层见解尚浅,欢迎大佬指点一二,不胜感激!
在适当的位置加入计时服务,并设置好计时周期,换算好显示单位.
ScheduledExecutorService service = Executors.newScheduledThreadPool(10);
service.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
// command
}
}, initialDelay, period, unit);
同时需要将此Service设为即使PowerOff,扔可以在后台运行. 即可在此Service创建时,设置为:
private PowerManager.WakeLock wakeLock;
PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,MyService.class.getName());
wakeLock.acquire();
销毁时:按如下方法,便可以快速实现该功能.
if (wakeLock != null)
wakeLock.release();
wakeLock = null;
在 PowerManagerService.java 中的 BinderService 内部类中寻找.
各个内部类中增加Log判断启动顺序,Log如下:
Slog.e(TAG, "----MethodName-----");
对比后,选择在 getLastShutdownReason() 此方法中加入时机最好,且仅有在AC 上电的情况下.
若需要另外计算屏幕使用时间 , 需要在以下地方加入 :
关闭屏幕 : Android 关背光的逻辑在 goToSleep() 调用.
// 步骤一
@Override // Binder call
public void goToSleep(long eventTime, int reason, int flags) {
if (eventTime > SystemClock.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 {
// 重要代码,在此执行goToSleep操作
goToSleepInternal(eventTime, reason, flags, uid);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
// 步骤二
private void goToSleepInternal(long eventTime, int reason, int flags, int uid) {
synchronized (mLock) {
// 在此判断,是否需要更新电源状态
if (goToSleepNoUpdateLocked(eventTime, reason, flags, uid)) {
// 在此加入判断设备休眠状态,已关闭屏幕运行时间的计时器
Slog.e(TAG, "----goToSleep-----");
startBacklightTimer(false);
updatePowerStateLocked();
}
}
}
// 步骤三
@SuppressWarnings("deprecation")
private boolean goToSleepNoUpdateLocked(long eventTime, int reason, int flags, int uid) {
if (DEBUG_SPEW) {
Slog.d(TAG, "goToSleepNoUpdateLocked: eventTime=" + eventTime
+ ", reason=" + reason + ", flags=" + flags + ", uid=" + uid);
}
if (eventTime < mLastWakeTime
|| mWakefulness == WAKEFULNESS_ASLEEP
|| mWakefulness == WAKEFULNESS_DOZING
|| !mBootCompleted || !mSystemReady) {
return false;
}
// 判断由于不同的条件进入的休眠,可以分情况去区分
Trace.traceBegin(Trace.TRACE_TAG_POWER, "goToSleep");
try {
switch (reason) {
// 由于设备管理策略而进入睡眠状态
case PowerManager.GO_TO_SLEEP_REASON_DEVICE_ADMIN:
Slog.i(TAG, "Going to sleep due to device administration policy "
+ "(uid " + uid +")...");
break;
// 由于屏幕超时而进入睡眠状态 -- 无操作休眠
case PowerManager.GO_TO_SLEEP_REASON_TIMEOUT:
Slog.i(TAG, "Going to sleep due to screen timeout (uid " + uid +")...");
break;
// 由于盖子开关而进入睡眠状态
case PowerManager.GO_TO_SLEEP_REASON_LID_SWITCH:
Slog.i(TAG, "Going to sleep due to lid switch (uid " + uid +")...");
break;
// 由于电源按钮而进入睡眠状态
case PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON:
Slog.i(TAG, "Going to sleep due to power button (uid " + uid +")...");
break;
// 由于睡眠按钮而进入睡眠状态
case PowerManager.GO_TO_SLEEP_REASON_SLEEP_BUTTON:
Slog.i(TAG, "Going to sleep due to sleep button (uid " + uid +")...");
break;
// 因HDMI待机而进入睡眠状态
case PowerManager.GO_TO_SLEEP_REASON_HDMI:
Slog.i(TAG, "Going to sleep due to HDMI standby (uid " + uid +")...");
break;
// 按应用程序请求进入睡眠状态
default:
Slog.i(TAG, "Going to sleep by application request (uid " + uid +")...");
reason = PowerManager.GO_TO_SLEEP_REASON_APPLICATION;
break;
}
mLastSleepTime = eventTime;
mSandmanSummoned = true;
setWakefulnessLocked(WAKEFULNESS_DOZING, reason);
// Report the number of wake locks that will be cleared by going to sleep.
int numWakeLocksCleared = 0;
final int numWakeLocks = mWakeLocks.size();
for (int i = 0; i < numWakeLocks; i++) {
final WakeLock wakeLock = mWakeLocks.get(i);
switch (wakeLock.mFlags & PowerManager.WAKE_LOCK_LEVEL_MASK) {
case PowerManager.FULL_WAKE_LOCK:
case PowerManager.SCREEN_BRIGHT_WAKE_LOCK:
case PowerManager.SCREEN_DIM_WAKE_LOCK:
numWakeLocksCleared += 1;
break;
}
}
EventLog.writeEvent(EventLogTags.POWER_SLEEP_REQUESTED, numWakeLocksCleared);
// Skip dozing if requested.
if ((flags & PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE) != 0) {
reallyGoToSleepNoUpdateLocked(eventTime, uid);
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_POWER);
}
return true;
}
开启屏幕 : Android开屏幕的逻辑在 wakeUp() 调用.
// 步骤一
@Override // Binder call
public void wakeUp(long eventTime, String reason, String opPackageName) {
if (eventTime > SystemClock.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, uid, opPackageName, uid);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
// 步骤二
private void wakeUpInternal(long eventTime, String reason, int uid, String opPackageName,
int opUid) {
synchronized (mLock) {
// 判断是否有唤醒锁
if (wakeUpNoUpdateLocked(eventTime, reason, uid, opPackageName, opUid)) {
// 若有,则更新电源状态
// 在此判断屏幕是否唤醒,继而打开屏幕运行时间的计时器
Slog.e(TAG, "----wakeUp-----");
startBacklightTimer(true);
updatePowerStateLocked();
}
}
}
// 步骤三
private boolean wakeUpNoUpdateLocked(long eventTime, String reason, int reasonUid,
String opPackageName, int opUid) {
if (DEBUG_SPEW) {
Slog.d(TAG, "wakeUpNoUpdateLocked: eventTime=" + eventTime + ", uid=" + reasonUid);
}
if (eventTime < mLastSleepTime || mWakefulness == WAKEFULNESS_AWAKE
|| !mBootCompleted || !mSystemReady) {
return false;
}
Trace.asyncTraceBegin(Trace.TRACE_TAG_POWER, TRACE_SCREEN_ON, 0);
Trace.traceBegin(Trace.TRACE_TAG_POWER, "wakeUp");
try {
switch (mWakefulness) {
case WAKEFULNESS_ASLEEP:
Slog.i(TAG, "Waking up from sleep (uid=" + reasonUid + " reason=" + reason
+ ")...");
break;
case WAKEFULNESS_DREAMING:
Slog.i(TAG, "Waking up from dream (uid=" + reasonUid + " reason=" + reason
+ ")...");
break;
case WAKEFULNESS_DOZING:
Slog.i(TAG, "Waking up from dozing (uid=" + reasonUid + " reason=" + reason
+ ")...");
break;
}
mLastWakeTime = eventTime;
setWakefulnessLocked(WAKEFULNESS_AWAKE, 0);
mNotifier.onWakeUp(reason, reasonUid, opPackageName, opUid);
userActivityNoUpdateLocked(
eventTime, PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, reasonUid);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_POWER);
}
return true;
}
因为需要用到计时服务(打开/关闭),故采用 ScheduledExecutorService 的 scheduleAtFixedRate() 计时.
/**
*
* @param command the task to execute
* @param initialDelay the time to delay first execution
* @param period the period between successive executions
* @param unit the time unit of the initialDelay and period parameters
* @return a ScheduledFuture representing pending completion of
* the series of repeated tasks. The future's {@link
* Future#get() get()} method will never return normally,
* and will throw an exception upon task cancellation or
* abnormal termination of a task execution.
* @throws RejectedExecutionException if the task cannot be
* scheduled for execution
* @throws NullPointerException if command is null
* @throws IllegalArgumentException if period less than or equal to zero
*/
public ScheduledFuture> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit);
scheduleAtFixedRate 需要传四个参数,分别对应以下的关系.
command - 将要执行的方法
initialDelay - 第一次执行的延时时间
period - 后续执行的间隔时间
unit - 间隔时间的单位,常用的有:
TimeUnit.SECONDS(秒),TimeUnit.MINUTES(分),TimeUnit.HOURS(时),TimeUnit.DAYS(天)
关于计时器的代码也放出来了 , 此处为6分钟记一次时间.
private static void startTimer(boolean flag) {
if (service == null)
service = Executors.newScheduledThreadPool(10);
/* flag
true -> start timer
false -> stop timer
*/
if (flag) {
/* isRunning 默认为false,第一次打开需要声明计时器
if true , Do not restart the timer
if false , Start timer
*/
if (!isRunning) {
service.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
// 获取设置前的系统运行时间 time1
int time1 = Integer.parseInt(SystemProperties.get("com.android.ScreenUseTime"));
Log.d(TAG, "before Set " + time1);
SystemProperties.set("com.android.ScreenUseTime",String.valueOf(time1+1));
// 获取设置前的系统运行时间 time2
int time2 = Integer.parseInt(SystemProperties.get("com.android.ScreenUseTime"));
Log.d(TAG, "after Set " + time2);
}
}, 6, 6, TimeUnit.MINUTES);
// 当前已经打开了计时器,无需重复开启,故置为 true.
isRunning = true;
}
} else {
if (service != null)
service.shutdownNow();
service = null;
Log.d(TAG, "shutdown: is runnings ");
// 此时已经关闭了计时器,下次使用需重新打开,故置为 false.
isRunning = false;
}
}
前两个困难已经解决掉了,在PowerManagerSerivce 中调用 StartTimer() 的方法.系统运行时间眼看就要成功了.
但是这时候需要考虑当设备在休眠(也就是ScreenOff)时,如何才能继续计时,不随着系统的 goToSleep()而停止呢 ?
解决办法 : 为当前计时服务提高等级
在服务开启时, 需要实例化wakeLock锁,并赋予电源级别; 并且在onDestory时,将锁释放掉.具体代码如下:
这样设置初始化后, 即使在goToSleep()后,服务仍能保持存活并运行.
private PowerManager.WakeLock wakeLock;
@Override
public void onCreate() {
PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TimerService.class.getName());
wakeLock.acquire();
service = Executors.newScheduledThreadPool(10);
mVendorStorage = new VendorStorage();
startTimer(true);
super.onCreate();
}
@Override
public void onDestroy() {
if (wakeLock != null)
wakeLock.release();
wakeLock = null;
super.onDestroy();
}
以下是 PowerManager.java的一些常用系统的电源等级和标记.
//如果持有该类型的wakelock锁,则按Power键灭屏后,即使允许屏幕、按键灯灭,也不会释放该锁,CPU不会进入休眠状态
public static final int PARTIAL_WAKE_LOCK;
//Deprecated,如果持有该类型的wakelock锁,则使屏幕保持亮/Dim的状态,键盘灯允许灭,按Power键灭屏后,会立即释放
public static final int SCREEN_DIM_WAKE_LOCK;
//Deprecated,如果持有该类型的wakelock锁,则使屏幕保持亮的状态,键盘灯允许灭,按Power键灭屏后,会立即释放
public static final int SCREEN_BRIGHT_WAKE_LOCK
//Deprecated,如果持有该类型的wakelock锁,则使屏幕、键盘灯都保持亮,按Power键灭屏后,会立即释放
public static final int FULL_WAKE_LOCK
//如果持有该锁,则当PSensor检测到有物体靠近时关闭屏幕,远离时又亮屏,该类型锁不会阻止系统进入睡眠状态,比如
//当到达休眠时间后会进入睡眠状态,但是如果当前屏幕由该wakelock关闭,则不会进入睡眠状态。
public static final int PROXIMITY_SCREEN_OFF_WAKE_LOCK
//如果持有该锁,则会使屏幕处于DOZE状态,同时允许CPU挂起,该锁用于DreamManager实现Doze模式,如SystemUI的DozeService
public static final int DOZE_WAKE_LOCK
//如果持有该锁,则会时设备保持唤醒状态,以进行绘制屏幕,该锁常用于WindowManager中,允许应用在系统处于Doze状态下时进行绘制
public static final int DRAW_WAKE_LOCK
//该值为0x0000FFFF,用于根据flag判断Wakelock的级别,如:
//if((wakeLock.mFlags & PowerManager.WAKE_LOCK_LEVEL_MASK) == PowerManager.PARTIAL_WAKE_LOCK){}
public static final int WAKE_LOCK_LEVEL_MASK
//用于在申请锁时唤醒设备,一般情况下,申请wakelock锁时不会唤醒设备,它只会导致屏幕保持打开状态,如果带有这个flag,则会在申
//请wakelock时就点亮屏幕,如常见通知来时屏幕亮,该flag不能和PowerManager.PARTIAL_WAKE_LOCE一起使用。
public static final int ACQUIRE_CAUSES_WAKEUP
//在释放锁时,如果wakelock带有该标志,则会小亮一会再灭屏,该flag不能和PowerManager.PARTIAL_WAKE_LOCE一起使用。
public static final int ON_AFTER_RELEASE
//和其他标记不同,该标记是作为release()方法的参数,且仅仅用于释放PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK类型的
//锁,如果带有该参数,则会延迟释放锁,直到传感器不再感到对象接近
public static final int RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY