1. 程式人生 > >Android N Idle模式分析

Android N Idle模式分析

Android 7.0加強了idle模式,就是裝置不充電且螢幕關閉情況下就會逐漸進入idle模式,這個比6.0加強了限制,也就是不考慮手機是否靜止。idle模式的影響可以檢視google官網介紹,這裡就不多說了。這邊主要介紹的是如何處理idle,如果你的應用需要用到推送等功能時,這種模式就是一個致命打擊,那麼有沒有辦法應對呢,答案當然是有了,官網推薦的是使用google自帶服務GCM,但是國內需要翻牆,所以這種辦法不太現實,那麼就頭疼了,有沒有其他辦法呢?

可以說是無意中一個發現讓我瞭解到了另一種方式,之前在測試一個應用在android N上面關於Idle模式的影響時發現其不受影響,這就覺得很奇怪了,於是經過一番測試發現好像是由於前臺

service造成的,但是又不確定,於是在好奇心的驅動下把原始碼下了下來,經過一番分析,還真是這個原因,接下來就來具體分析一下:

一、DeviceIdleController.java

framework/base/services/core/java/com/android/server/DeviceIdleController.java),這個類就是主要處理Idle模式的控制類,首先從這個類下手,下面是啟動Idle模式時的處理方法:

case MSG_REPORT_IDLE_ON_LIGHT: {
                    EventLogTags.writeDeviceIdleOnStart();
                    final boolean deepChanged;
                    final boolean lightChanged;
                    if (msg.what == MSG_REPORT_IDLE_ON) {
                        deepChanged = mLocalPowerManager.setDeviceIdleMode(true);
                        lightChanged = mLocalPowerManager.setLightDeviceIdleMode(false);
                    } else {
                        deepChanged = mLocalPowerManager.setDeviceIdleMode(false);
                        lightChanged = mLocalPowerManager.setLightDeviceIdleMode(true);
                    }
                    try {
                        mNetworkPolicyManager.setDeviceIdleMode(true);
                        mBatteryStats.noteDeviceIdleMode(msg.what == MSG_REPORT_IDLE_ON
                                ? BatteryStats.DEVICE_IDLE_MODE_DEEP
                                : BatteryStats.DEVICE_IDLE_MODE_LIGHT, null, Process.myUid());
                    } catch (RemoteException e) {
                    }
                    if (deepChanged) {
                        getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL);
                    }
                    if (lightChanged) {
                        getContext().sendBroadcastAsUser(mLightIdleIntent, UserHandle.ALL);
                    }
                    EventLogTags.writeDeviceIdleOnComplete();
                } break;


從上面可以看出,進入Idle模式主要執行三個操作

1)mLocalPowerManager.setDeviceIdleMode(true);

2)mNetworkPolicyManager.setDeviceIdleMode(true);

3)mBatteryStats.noteDeviceIdleMode(..)

我們比較關心的是網路方面的,所以接下來主要分析mNetworkPolicyManager

二、NetworkPolicyManager.java

framework/base/services/core/java/com/android/server/net/NetworkPolicyManager.java)這個類就

mNetworkPolicyManager例項物件,首先看setDeviceIdleMode函式:

public void setDeviceIdleMode(boolean enabled) {
        mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
 
        synchronized (mRulesLock) {
            if (mDeviceIdleMode != enabled) {
                mDeviceIdleMode = enabled;
                if (mSystemReady) {
                    // Device idle change means we need to rebuild rules for all
                    // known apps, so do a global refresh.
                    updateRulesForGlobalChangeLocked(false);
                }
                if (enabled) {
                    EventLogTags.writeDeviceIdleOnPhase("net");
                } else {
                    EventLogTags.writeDeviceIdleOffPhase("net");
                }
            }
        }
    }


從上面可以看出主要呼叫的函式是updateRulesForGlobalChangeLocked(false);那我們繼續跟蹤下去: 

Private void updateRulesForGlobalChangeLocked(boolean restrictedNetworksChanged) {
        long start;
        if (LOGD) start = System.currentTimeMillis();
 
        updateRulesForDeviceIdleLocked();
        updateRulesForAppIdleLocked();
        updateRulesForRestrictPowerLocked();
        updateRulesForRestrictBackgroundLocked();
        setRestrictBackgroundLocked(mRestrictBackground);
 
        // If the set of restricted networks may have changed, re-evaluate those.
        if (restrictedNetworksChanged) {
            normalizePoliciesLocked();
            updateNetworkRulesLocked();
        }
        if (LOGD) {
            final long delta = System.currentTimeMillis() - start;
            Slog.d(TAG, "updateRulesForGlobalChangeLocked(" + restrictedNetworksChanged + ") took "
                    + delta + "ms");
        }
}


這個函式裡面前面幾個呼叫都是更新所有安裝應用rule的,主要還是setRestrictBackgroundLocked(mRestrictBackground);那我們接著來看這個函式:

private void setRestrictBackgroundLocked(boolean restrictBackground) {
        Slog.d(TAG, "setRestrictBackgroundLocked(): " + restrictBackground);
        final boolean oldRestrictBackground = mRestrictBackground;
        mRestrictBackground = restrictBackground;
        // Must whitelist foreground apps before turning data saver mode on.
        // TODO: there is no need to iterate through all apps here, just those in the foreground,
        // so it could call AM to get the UIDs of such apps, and iterate through them instead.
        updateRulesForRestrictBackgroundLocked();
        try {
            if (!mNetworkManager.setDataSaverModeEnabled(mRestrictBackground)) {
                Slog.e(TAG, "Could not change Data Saver Mode on NMS to " + mRestrictBackground);
                mRestrictBackground = oldRestrictBackground;
                // TODO: if it knew the foreground apps (see TODO above), it could call
                // updateRulesForRestrictBackgroundLocked() again to restore state.
                return;
            }
        } catch (RemoteException e) {
            // ignored; service lives in system_server
        }
        updateNotificationsLocked();
        writePolicyLocked();
    }

重點來了,我們看到裡面的註釋中出現foreground apps,這個說明Idle模式和foreground apps還是有關係的,分析程式碼可以發現主要函式是updateRulesForRestrictBackgroundLocked();那我們繼續下去,感覺快得出答案了:

private void updateRulesForRestrictBackgroundLocked() {
        final PackageManager pm = mContext.getPackageManager();
 
        // update rules for all installed applications
        final List<UserInfo> users = mUserManager.getUsers();
        final List<ApplicationInfo> apps = pm.getInstalledApplications(
                PackageManager.MATCH_UNINSTALLED_PACKAGES | PackageManager.MATCH_DISABLED_COMPONENTS
                        | PackageManager.MATCH_DIRECT_BOOT_AWARE
                        | PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
 
        final int usersSize = users.size();
        final int appsSize = apps.size();
        for (int i = 0; i < usersSize; i++) {
            final UserInfo user = users.get(i);
            for (int j = 0; j < appsSize; j++) {
                final ApplicationInfo app = apps.get(j);
                final int uid = UserHandle.getUid(user.id, app.uid);
                updateRulesForDataUsageRestrictionsLocked(uid);
                updateRulesForPowerRestrictionsLocked(uid);
            }
        }
    }


這個函式比較容易分析,主要就是兩個函式呼叫,這兩個函式都是用來更新rule的,所以基本流程是一樣的,我們分析一個就行了,這裡選擇updateRulesForPowerRestrictionsLocked(uid);

private void updateRulesForPowerRestrictionsLocked(int uid) {
        if (!isUidValidForBlacklistRules(uid)) {
            if (LOGD) Slog.d(TAG, "no need to update restrict power rules for uid " + uid);
            return;
        }
 
        final boolean isIdle = isUidIdle(uid);
        final boolean restrictMode = isIdle || mRestrictPower || mDeviceIdleMode;
        final int uidPolicy = mUidPolicy.get(uid, POLICY_NONE);
        final int oldUidRules = mUidRules.get(uid, RULE_NONE);
        final boolean isForeground = isUidForegroundOnRestrictPowerLocked(uid);
 
        final boolean isWhitelisted = isWhitelistedBatterySaverLocked(uid);
        final int oldRule = oldUidRules & MASK_ALL_NETWORKS;
        int newRule = RULE_NONE;
 
        // First step: define the new rule based on user restrictions and foreground state.
 
        // NOTE: if statements below could be inlined, but it's easier to understand the logic
        // by considering the foreground and non-foreground states.
        if (isForeground) {
            if (restrictMode) {
                newRule = RULE_ALLOW_ALL;
            }
        } else if (restrictMode) {
            newRule = isWhitelisted ? RULE_ALLOW_ALL : RULE_REJECT_ALL;
        }
 
        final int newUidRules = (oldUidRules & MASK_METERED_NETWORKS) | newRule;
 
        if (LOGV) {
            Log.v(TAG, "updateRulesForNonMeteredNetworksLocked(" + uid + ")"
                    + ", isIdle: " + isIdle
                    + ", mRestrictPower: " + mRestrictPower
                    + ", mDeviceIdleMode: " + mDeviceIdleMode
                    + ", isForeground=" + isForeground
                    + ", isWhitelisted=" + isWhitelisted
                    + ", oldRule=" + uidRulesToString(oldRule)
                    + ", newRule=" + uidRulesToString(newRule)
                    + ", newUidRules=" + uidRulesToString(newUidRules)
                    + ", oldUidRules=" + uidRulesToString(oldUidRules));
        }
 
        if (newUidRules == RULE_NONE) {
            mUidRules.delete(uid);
        } else {
            mUidRules.put(uid, newUidRules);
        }
 
        // Second step: notify listeners if state changed.
        if (newRule != oldRule) {
            if (newRule == RULE_NONE || (newRule & RULE_ALLOW_ALL) != 0) {
                if (LOGV) Log.v(TAG, "Allowing non-metered access for UID " + uid);
            } else if ((newRule & RULE_REJECT_ALL) != 0) {
                if (LOGV) Log.v(TAG, "Rejecting non-metered access for UID " + uid);
            } else {
                // All scenarios should have been covered above
                Log.wtf(TAG, "Unexpected change of non-metered UID state for " + uid
                        + ": foreground=" + isForeground
                        + ", whitelisted=" + isWhitelisted
                        + ", newRule=" + uidRulesToString(newUidRules)
                        + ", oldRule=" + uidRulesToString(oldUidRules));
            }
            mHandler.obtainMessage(MSG_RULES_CHANGED, uid, newUidRules).sendToTarget();
        }
    }


這邊我們發現出現了一個變數 final boolean isForeground = isUidForegroundOnRestrictPowerLocked(uid);那這個變數什麼時候為true呢,我們來分析一下這個函式:

private boolean isUidForegroundOnRestrictPowerLocked(int uid) {
        final int procState = mUidState.get(uid, ActivityManager.PROCESS_STATE_CACHED_EMPTY);
        return isProcStateAllowedWhileIdleOrPowerSaveMode(procState);
    }
 static boolean isProcStateAllowedWhileIdleOrPowerSaveMode(int procState) {
        return procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
    }

答案揭曉了,當前的程序優先順序至少是前臺service時就為true,好的,我們回到原來的程式碼,繼續看下去我們又發現

if (isForeground) {
            if (restrictMode) {
                newRule = RULE_ALLOW_ALL;
            }
        }


RULE_ALLOW_ALL一看就是允許了該應用訪問網路的許可權,後面程式碼就是傳送廣播通知rule變化,這裡就不多說了。

三、總結

經過上面的分析,我們大致明白了,如果把應用優先順序提高到前臺service及以上,那麼idle模式就不會限制你訪問網路等,這樣就多了一種方法來繞過idle模式影響,其實想一下也應該知道,就像播放音樂時,即使你螢幕關閉,也不應該把你的音樂停掉,不然肯定被使用者罵死,好了,分析就到這裡了~~