Android Sprd省電管理(三)鎖屏清理
我們接著上篇 Android Sprd省電管理(二)應用省電模式設定流程,講下鎖屏清理的原理
鎖屏清理簡介:
鎖屏清理目的是減少待機應用從而來減少待機功耗。鎖屏清理是在待機一段時間後才開始進行。
該時間值大於 1min。出於功耗考慮沒有采用可喚醒的 alarm 來設定該 1min 定時器。而是採
用非可喚醒的 alarm。而非可喚醒 alarm 需要有可喚醒 alarm 才會觸發,因此這會導致開始
清理的時間遠大於 1min。但一般 10 分鐘內都會有可喚醒 alarm,也就是 10 分鐘內會進行清理
一些應用不會被清理,即使在已經被設定為清理的情況下。這些應用包括:
a) 輸入法應用;
b) 預設的系統服務應用,如桌布服務應用,預設簡訊服務應用等;
c) 當前滅屏前可見的應用;
d)正在播放音樂的應用;
e) 正在下載的應用;
f) 內建系統應用;
上述這些型別的應用不能清理,清理後會導致使用上的體驗問題。
鎖屏清理流程:
在PowerContorller.java中註冊了一些裝置狀態變化的廣播
private void registerForBroadcasts() { IntentFilter intentFilter = new IntentFilter(); //亮屏廣播 intentFilter.addAction(Intent.ACTION_SCREEN_ON); //滅屏廣播 intentFilter.addAction(Intent.ACTION_SCREEN_OFF); intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED); intentFilter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED); intentFilter.addAction(ACTION_CHECK_APPSTATE); intentFilter.addAction(Intent.ACTION_BOOT_COMPLETED); intentFilter.addAction(PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED); intentFilter.addAction(INTENT_SCHEDULEMODE_ALARM); mContext.registerReceiver( new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (ACTION_CHECK_APPSTATE.equals(action)) { msgHandler.removeMessages(MSG_CHECK); msgHandler.sendMessage(msgHandler.obtainMessage(MSG_CHECK)); // for PowerGuru Interval upadate if (mPowerGuruAligningStart) { msgHandler.removeMessages(MSG_UPDATE_POWERGURU_INTERVAL); msgHandler.sendMessage(msgHandler.obtainMessage(MSG_UPDATE_POWERGURU_INTERVAL)); } } else if(Intent.ACTION_BOOT_COMPLETED.equals(action)) { msgHandler.sendMessage(msgHandler.obtainMessage(MSG_BOOT_COMPLETED)); } else if(PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED.equals(action)) { msgHandler.sendMessage(msgHandler.obtainMessage(MSG_WHITELIST_CHANGED)); } else if(INTENT_SCHEDULEMODE_ALARM.equals(action)) { msgHandler.sendMessage(msgHandler.obtainMessage(MSG_SCHEDULEMODE_ALARM)); } else { //亮滅屏時,傳送MSG_DEVICE_STATE_CHANGED msgHandler.sendMessage(msgHandler.obtainMessage(MSG_DEVICE_STATE_CHANGED, intent)); } } }, intentFilter);
case MSG_DEVICE_STATE_CHANGED:
handleDeviceStateChanged((Intent)msg.obj);
break;
PowerContorller.java-->handleDeviceStateChanged()
private void handleDeviceStateChanged(Intent intent) { String action = intent.getAction(); boolean oldScreenOn = mScreenOn; boolean oldMobileConnected = mMobileConnected; boolean oldCharging = mCharging; if (DEBUG) Slog.d(TAG, "- handleDeviceStateChanged() E -, action: " + action); if (action.equals(Intent.ACTION_SCREEN_ON)) { mScreenOn = true; } else if (action.equals(Intent.ACTION_SCREEN_OFF)) { mScreenOn = false; } else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) { boolean bNoConnection = intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false); if (!bNoConnection) { int networkType = intent.getIntExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, ConnectivityManager.TYPE_NONE); mMobileConnected = ConnectivityManager.isNetworkTypeMobile(networkType); mWifiConnected = ConnectivityManager.isNetworkTypeWifi(networkType); } else { mMobileConnected = false; mWifiConnected = false; } } else if (action.equals(Intent.ACTION_BATTERY_CHANGED)) { int pluggedType = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0); mCharging = (0 != intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0)); if (DEBUG) Slog.d(TAG, "pluggedType:" + pluggedType + " mCharging:" + mCharging); updateBatteryLevelLow(false); updatePowerSaveMode(); } else if (action.equals(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED)) { try { notifyDozeStateChanged(); } catch (Exception e) { // fall through } return; } if (DEBUG_MORE) Slog.d(TAG, "handleDeviceStateChanged: mCharging:" + mCharging + " mScreenOn:" + mScreenOn + " mMobileConnected:" + mMobileConnected); boolean bScreenChanged = (oldScreenOn != mScreenOn); boolean bConnectionChanged = (oldMobileConnected != mMobileConnected); boolean bChargingChanged = (oldCharging != mCharging); if (DEBUG) Slog.d(TAG, "handleDeviceStateChanged: bScreenChanged:" + bScreenChanged + " bConnectionChanged:" + bConnectionChanged + " bChargingChanged:" + bChargingChanged); if (bScreenChanged || bConnectionChanged || bChargingChanged) { checkPowerGuruInterval(); } if (bScreenChanged || bChargingChanged) { updateAppStateInfoForDeviceStateChanged(); } if (bScreenChanged) mRecogAlgorithm.reportDeviceState(RecogAlgorithm.DEVICE_STATE_TYPE_SCREEN, mScreenOn); if (bChargingChanged) { mRecogAlgorithm.reportDeviceState(RecogAlgorithm.DEVICE_STATE_TYPE_CHARGING, mCharging); } // if in standby state, trigger a delay MSG_CHECK operation if (!mScreenOn && !mCharging) { msgHandler.removeMessages(MSG_CHECK); msgHandler.sendMessageDelayed(msgHandler.obtainMessage(MSG_CHECK), SCREENOFF_CHECK_INTERVAL); } }
重點看MSG_CHECK
case MSG_CHECK:
checkAllAppStateInfo();
break;
PowerContorller.java-->checkAllAppStateInfo()
private void checkAllAppStateInfo() {
if (DEBUG) Slog.d(TAG, "- checkAllAppStateInfo() E -");
if (DEBUG) Slog.d(TAG, "mCharging:" + mCharging + " mScreenOn:" + mScreenOn + " mMobileConnected:" + mMobileConnected);
//set next check
//msgHandler.removeMessages(MSG_CHECK);
//msgHandler.sendMessageDelayed(msgHandler.obtainMessage(MSG_CHECK), CHECK_INTERVAL);
scheduleAlarmLocked(CHECK_INTERVAL);
if (mCharging || mScreenOn)
return;
checkSystemUpTime();
boolean bChanged = false;
// Note:use the same now elapsed time for all the AppStateInfo
// otherwise, when checking app Parole, some app may have not
// opportunity to exit app standby.
long now = SystemClock.elapsedRealtime();
updatePowerSaveMode();
try {
final List<UserInfo> users = mUserManager.getUsers();
for (int ui = users.size() - 1; ui >= 0; ui--) {
UserInfo user = users.get(ui);
if (DEBUG) Slog.d(TAG, "- checkAllAppStateInfo() for user: " + user.id);
final ArrayMap<String, AppState> appStateInfoList = mAppStateInfoCollector.getAppStateInfoList(user.id);
for (int i=0;i<appStateInfoList.size();i++) {
AppState appState = appStateInfoList.valueAt(i);
//let app to be parole
mAppIdleHelper.checkAppParole(appState, now);
// check app state info
if (checkAppStateInfo(appState, now)) {
//可以被清理
bChanged = true;
}
}
}
} catch (Exception e) {
}
// note AppidleHelper all check done
mAppIdleHelper.noteCheckAllAppStateInfoDone();
// note mWakelockConstraintHelper all check done
mWakelockConstraintHelper.noteCheckAllAppStateInfoDone();
//send notification to powerguru & appstandby
// changed for bug#891620
//if (bChanged) msgHandler.sendMessage(msgHandler.obtainMessage(MSG_NOTIFY_CHANGED));
if (bChanged) notifyChanged();
if (needCheckNetworkConnection(now)) {
if (DEBUG) Slog.d(TAG, "- checkNetworkConnection() in checkAllAppStateInfo -");
checkNetworkConnection(true);
}
// check doze state
checkDozeState();
}
PowerContorller.java-->checkAppStateInfo()
private boolean checkAppStateInfo(AppState appState, final long nowELAPSED) {
boolean bChanged = false;
// check app state info
for (int i = 0; i < mHelpers.size(); i++) {
PowerSaveHelper helper = mHelpers.get(i);
if (helper.checkAppStateInfo(appState, nowELAPSED)) {
bChanged = true;
}
}
return bChanged;
}
BackgroundCleanHelper.java -->checkAppStateInfo()
/*
* check this app should be constrained or not
* return true if this app should be constrained
* others return false
*/
boolean checkAppStateInfo(AppState appState, final long nowELAPSED) {
ArrayMap<String, Integer> mForceStopAppList = getForceStopAppList(appState.mUserId);
boolean bChanged = false;
if (canBeConstrained(appState)) {
bChanged = true;
//可以被清理的程序放入到mForceStopAppList
if (!mForceStopAppList.containsKey(appState.mPackageName)) {
mForceStopAppList.put(appState.mPackageName, appState.mUserId);
if (DEBUG) Slog.d(TAG, "checkAppStateInfo(), add " + appState.mPackageName + " to forcestop list");
}
} else {
// if already put it in mForceStopAppList, just remove
mForceStopAppList.remove(appState.mPackageName);
}
return bChanged;
}
現在就到核心方法了canBeConstrained(),我們來重點看下
boolean canBeConstrained(AppState appState) {
// if this function is disabled, just return false
if (!mEnabled) return false;
// set bgclean timer
long delayMs = SystemClock.elapsedRealtime() - mStandbyStartTime - DOCLEAN_TIMEOUT;
if (delayMs < 0) {
if (!mAlarmSet) {
if (DEBUG) Slog.d(TAG, "canBeConstrained(), set alarm");
// not use wake up alarm, otherwise a cts case of sensor will fail.
mAlarmManager.set(AlarmManager.ELAPSED_REALTIME,
mStandbyStartTime + DOCLEAN_TIMEOUT, TAG, mAlarmListener, mHandler);
mAlarmSet = true;
}
return false;
}
// not a system app
if (isSystemApp(appState)) { // system app
return false;
}
// app not exist
if ((appState.mProcState == ActivityManager.PROCESS_STATE_CACHED_EMPTY && Event.NONE == appState.mState)
|| appState.mProcState == ActivityManager.PROCESS_STATE_NONEXISTENT) {
return false;
}
// avoid kill input method
if (appState.mIsEnabledInputMethod)
return false;
if (isImportantPrefferedApp(appState.mPackageName))
return false;
boolean isVisible = false;
int index = mVisibleAppList.indexOf(appState.mPackageName);
if (index >= 0) {
isVisible = true;
}
// not a visible app
if (isVisible) {
return false;
}
// playing music App can not be constrained
if (isPlayingMusic(appState)) {
if (DEBUG) Slog.d(TAG, "canBeConstrained: " + appState.mPackageName + " Music is playing");
return false;
}
// doing download App can not be constrained
if (isDoingDownload(appState)) {
if (DEBUG) Slog.d(TAG, "canBeConstrained: " + appState.mPackageName
+ " Doing Download");
return false;
}
/** @hide Process is in the background, but it can't restore its state so we want
* to try to avoid killing it. */
if (appState.mProcState == ActivityManager.PROCESS_STATE_HEAVY_WEIGHT) {
return false;
}
// not app in whitelist
if (inCommonWhiteAppList(appState.mPackageName)) {
return false;
}
if (mLockScreen_WhiteList.contains(appState.mPackageName)) {
if (DEBUG) Slog.d(TAG, "bgclean CanBeConstrained: " + appState.mPackageName + " in my lockscreen whitelist");
return false;
} else if (mLockScreen_BlackList.contains(appState.mPackageName)) {
if (DEBUG) Slog.d(TAG, "bgclean CanBeConstrained: " + appState.mPackageName + " in my lockscreen blacklist");
return true;
}
// this app is avoid killing
if (appState.mAvoidKilling) return false;
// message App can not be constrained
// for bug#787547 don't kill preset message app
if (isMessageApp(appState)
||mAbsDeviceIdleController.isInPresetWhiteAppList(appState.mPackageName)
) {
if (DEBUG) Slog.d(TAG, "canBeConstrained: " + appState.mPackageName + " Message App");
return false;
}
// in low power mode, we will clean more bg app
if (PowerManagerEx.MODE_LOWPOWER == mPowerSaveMode) {
if (DEBUG) Slog.d(TAG, "canBeConstrained, low power mode clean, appname: " + appState.mPackageName + ", time diff: " + (SystemClock.elapsedRealtime() - mStandbyStartTime)
+ ", procState: " + appState.mProcState);
if (LOWPOWER_DOCLEAN_THRESHOLD <= SystemClock.elapsedRealtime() - mStandbyStartTime) {
if (appState.mProcState > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
if (DEBUG) Slog.d(TAG, "canBeConstrained: low power mode clean, " + appState.mPackageName);
return true;
}
}
}
// doing download App can not be constrained
if (isDoingDownload(appState)) {
if (DEBUG) Slog.d(TAG, "canBeConstrained: " + appState.mPackageName
+ " Doing Download (mUsedTimeSliceCount:" + appState.mUsedTimeSliceCount + ")");
// download is checked using a time slice (default 30s)
// if the count of time slice using to complete detecting download
// is >= 1, then we can be sure that user use this app doing download before standby
// in this case, we will avoid kill this app
if (appState.mUsedTimeSliceCount >=1 ) {
appState.mAvoidKilling = true;
}
return false;
}
long nowELAPSED = SystemClock.elapsedRealtime();
long now = System.currentTimeMillis(); // wall time
long fromBootup = now - nowELAPSED; // wall time
long idleDuration = 0;
long idleDurationBeforeStandby = 0;
boolean hasLaunched = false;
boolean hasNotification = false;
boolean mayPlayingMusic = false;
boolean launcherApp = isLauncherApp(appState.mPackageName);
// FixMe: if we has more exact method to jude app playing music, then we don't need to do this
mayPlayingMusic = mayBePlayingMusic(appState);
if (DEBUG) Slog.d(TAG, "pkg: " + appState.mPackageName
+ " flags:" + Integer.toHexString(appState.mFlags)
+ " ProcState:" + Util.ProcState2Str(appState.mProcState));
if (appState.mLastLaunchTime > 0) hasLaunched = true;
idleDuration = (appState.mLastTimeUsed > 0 ? (nowELAPSED -appState.mLastTimeUsed) : -1);
idleDurationBeforeStandby = (mStandbyStartTime > appState.mLastTimeUsed ? (mStandbyStartTime -appState.mLastTimeUsed) : 0);
hasNotification = hasActiveNotification(appState);
if (DEBUG) Slog.d(TAG, "STATE: pkg:" + appState.mPackageName
+ " idle for:" + idleDuration
+ " idleDurationBeforeStandby:" + idleDurationBeforeStandby
+ " hasLaunched:" + hasLaunched
+ " isVisable:" + isVisible
+ " hasNotification:" + hasNotification
+ " mayPlayingMusic:" + mayPlayingMusic
+ " launcherApp:" + launcherApp);
// if app is launched by user
// and may be playing music, then don't kill this app
if (hasLaunched && mayPlayingMusic) {
return false;
}
/*
* 7. ((app not lauched by user && app has not notification) ||
* (app not launched by user && app has notification && idle time > 1 h after standby) )
*/
if ((!hasLaunched && !hasNotification)
|| (!hasLaunched && hasNotification && idleDuration > (idleDurationBeforeStandby + FORCE_STOP_IDLE_THRESHOLD1) )
) {
return true;
}
/*
* 8. ((app launched by user && app has not notification && idle time > 2h) ||
* (app launched by user && app has notification && && idle time > 4h after standby))
*/
if (hasLaunched) {
int currentActiveLaunchedCount = mAppStateInfoCollector.getCountOfActiveLaunchedApps(mCurrentUserId);
if (DEBUG) Slog.d(TAG, "currentActiveLaunchedCount:" + currentActiveLaunchedCount);
if (!launcherApp && currentActiveLaunchedCount > MAX_LAUNCHED_APP_KEEP) {
if ((!hasNotification && idleDuration > ( FORCE_STOP_IDLE_THRESHOLD2))
|| (hasNotification && !appState.mHasNoClearNotification
&& idleDuration > ( idleDurationBeforeStandby + FORCE_STOP_IDLE_THRESHOLD3))
) {
return true;
}
}
}
return false;
}
isSystemApp(),判斷是否是系統應用
private boolean isSystemApp(AppState appState) {
if (appState != null) {
return ((appState.mFlags & (ApplicationInfo.FLAG_UPDATED_SYSTEM_APP | ApplicationInfo.FLAG_SYSTEM)) != 0
|| appState.mFlags == 0);
} else {
return true; // default
}
}
appState.mIsEnabledInputMethod 判斷是否是輸入法
我們看下這個值是怎麼設定的
AppState retVal = new AppState(packageName, userId, uid, stateEvent, procState, flags);
// check if is input method
retVal.mIsEnabledInputMethod = isEnabledIMEApp(packageName);
// if this app is a input Method
private boolean isEnabledIMEApp(String pkgName){
if (pkgName == null) return false;
IInputMethodManager service = IInputMethodManager.Stub.asInterface(
ServiceManager.getService(Context.INPUT_METHOD_SERVICE));
List<InputMethodInfo> inputMethods;
try {
inputMethods = service.getEnabledInputMethodList();
} catch (RemoteException e) {
return false;
}
if (inputMethods == null || inputMethods.size() == 0) return false;
for (InputMethodInfo info : inputMethods){
if (info == null || info.getPackageName() == null) continue;
if (info.getPackageName().equals(pkgName)) return true;
}
return false;
}
isVisible判斷是否有啟動的應用
boolean isVisible = false;
int index = mVisibleAppList.indexOf(appState.mPackageName);
if (index >= 0) {
isVisible = true;
}
// not a visible app
if (isVisible) {
return false;
}
從程式碼可以看出isVisible是通過mVisibleAppList判斷的
private void updateVisibleActivities() {
try {
// Clear first
mVisibleAppList.clear();
List<IBinder> activityTokens = null;
// Let's get top activities from all visible stacks
activityTokens = LocalServices.getService(ActivityManagerInternal.class).getTopVisibleActivities();
final int count = activityTokens.size();
for (int i = 0; i < count; i++) {
IBinder topActivity = activityTokens.get(i);
try {
String packageName = mActivityManager.getPackageForToken(topActivity);
if (packageName != null) {
mVisibleAppList.add(packageName);
Slog.d(TAG, "VisibleActivities:" + packageName);
}
} catch (RemoteException e) {
}
}
} catch (Exception e) {
e.printStackTrace();
}
Slog.d(TAG, "updateVisibleActivities done");
}
isPlayingMusic 判斷應用是否正在播放音樂
這個流程看了下有點兒負責,等有時間單獨寫個總結分析
isDoingDownload 判斷應用是否正在下載
private boolean isAppDoingDownloadInternal(AppState state) {
int procState = state.mProcState;
//if (procState > ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE)
// return false;
boolean doingDownload = false;
if (state.mRxBytesWhenStartEvaluated > 0) {
long now = SystemClock.elapsedRealtime();
long currentRxBytes = TrafficStats.getUidRxBytes(state.mUid);
long secondsSpended = (now - state.mTimeStampForRxBytes)/1000;
if (DEBUG) Slog.d(TAG, "uid:" + state.mUid + " packageName:" + state.mPackageName
+ " currentRxBytes:" + currentRxBytes + " startRxBytes:" + state.mRxBytesWhenStartEvaluated
+ " timespended: " + secondsSpended + " avgRate:" + (currentRxBytes - state.mRxBytesWhenStartEvaluated)/(secondsSpended+1));
if (currentRxBytes - state.mRxBytesWhenStartEvaluated
> (MIN_DATA_RATE * secondsSpended) || (secondsSpended < DOWNLOAD_CHECKING_MIN_TIME_SPAN))
doingDownload = true;
if (doingDownload && secondsSpended > DOWNLOAD_CHECKING_MIN_TIME_SPAN) {
long systemUpDuration = secondsSpended*1000;
if (mLastSystemUpTimeStamp > 0)
systemUpDuration = (now - mLastSystemUpTimeStamp);
if (DEBUG) Slog.d(TAG, "systemUpDuration:" + systemUpDuration + " secondsSpended:" + secondsSpended);
if (systemUpDuration < (secondsSpended - 2 *SYSTEM_UP_CHECK_INTERVAL)) {
if (DEBUG) Slog.d(TAG, "systemUpDuration:" + systemUpDuration + "but secondsSpended:" + secondsSpended
+ " clear download flag!!");
doingDownload = false;
}
}
//Download state changed!!
if (state.mDoingDownload != doingDownload) {
mDownloadChangeCount++;
msgHandler.sendMessage(msgHandler.obtainMessage(MSG_DOWNLOAD_STATE_CHANGED, (doingDownload?1:0), 0, state));
}
if (doingDownload) {
if (secondsSpended >= DOWNLOAD_CHECKING_MIN_TIME_SPAN) {
state.mRxBytesWhenStartEvaluated = currentRxBytes;
state.mTimeStampForRxBytes = SystemClock.elapsedRealtime();
state.mUsedTimeSliceCount++;
}
return true;
}
}
return false;
}
canBeConstrained()分析完後,再回頭看看checkAllAppStateInfo()-->notifyChanged()
private void notifyChanged() {
if (DEBUG) Slog.d(TAG, "- notifyChanged() E -");
for (int i = 0; i < mHelpers.size(); i++) {
PowerSaveHelper helper = mHelpers.get(i);
helper.applyConstrain();
}
}
BackgroundCleanHelper.java-->notifyChanged()
void applyConstrain() {
for (int index=mForceStopAppListForUsers.size()-1; index>=0; index--) {
ArrayMap<String, Integer> mForceStopAppList = mForceStopAppListForUsers.valueAt(index);
try {
for (int i=0;i<mForceStopAppList.size();i++) {
String pkgName = mForceStopAppList.keyAt(i);
int userId = mForceStopAppList.valueAt(i);
try {
//呼叫forceStopPackage清理程序
mActivityManager.forceStopPackage(pkgName, userId);
AppState appState = mAppStateInfoCollector.getAppState(pkgName, userId);
if (appState != null) {
// do some cleaning for appState
appState.clearLaunchInfo();
} else {
Slog.w(TAG, "null appState for " + pkgName + " userId:" + userId);
}
Slog.d(TAG, "force stop:" + pkgName + " userId:" + userId);
ArrayMap<String, Integer> mStoppedAppList = getStoppedAppList(userId);
// add to stopped app list
mStoppedAppList.put(pkgName, userId);
} catch (RemoteException e) {
}
}
} catch (Exception e) {
e.printStackTrace();
}
mForceStopAppList.clear();