Android 网络策略管理
工作中遇到一个问题,App在后台运行获取当前网络链接状态,网络是BLOCKED状态。跟踪下这个状态的产生。
问题出现在Android7,我看的源码是Android 12.
获取当前网络状态
ConnectivityManager mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
NetworkInfo info = mConnectivityManager.getActiveNetworkInfo();
NetworkInfo里面关于连接状态的分类为:
public enum DetailedState {
/** Ready to start data connection setup. /
IDLE,
/* Searching for an available access point. /
SCANNING,
/* Currently setting up data connection. /
CONNECTING,
/* Network link established, performing authentication. /
AUTHENTICATING,
/* Awaiting response from DHCP server in order to assign IP address information. /
OBTAINING_IPADDR,
/* IP traffic should be available. /
CONNECTED,
/* IP traffic is suspended /
SUSPENDED,
/* Currently tearing down data connection. /
DISCONNECTING,
/* IP traffic not available. /
DISCONNECTED,
/* Attempt to connect failed. /
FAILED,
/* Access to this network is blocked. /
BLOCKED,
/* Link has poor connectivity. /
VERIFYING_POOR_LINK,
/* Checking if network is a captive portal */
CAPTIVE_PORTAL_CHECK
}
看下这个状态的生成:
ConnectivityService.java
public NetworkInfo getActiveNetworkInfo() {
enforceAccessPermission();
final int uid = mDeps.getCallingUid();
final NetworkAgentInfo nai = getNetworkAgentInfoForUid(uid);
if (nai == null) return null;
final NetworkInfo networkInfo = getFilteredNetworkInfo(nai, uid, false);
maybeLogBlockedNetworkInfo(networkInfo, uid);
return networkInfo;
}
final NetworkInfo networkInfo = getFilteredNetworkInfo(nai, uid, false); 这个里面就会判断是否BLOCKED。
@NonNull
private NetworkInfo filterNetworkInfo(@NonNull NetworkInfo networkInfo, int type,
@NonNull NetworkCapabilities nc, int uid, boolean ignoreBlocked) {
final NetworkInfo filtered = new NetworkInfo(networkInfo);
// Many legacy types (e.g,. TYPE_MOBILE_HIPRI) are not actually a property of the network
// but only exists if an app asks about them or requests them. Ensure the requesting app
// gets the type it asks for.
filtered.setType(type);
if (isNetworkWithCapabilitiesBlocked(nc, uid, ignoreBlocked)) {
filtered.setDetailedState(DetailedState.BLOCKED, null /* reason */,
null /* extraInfo */);
}
filterForLegacyLockdown(filtered);
return filtered;
}
isNetworkWithCapabilitiesBlocked就是最终判断入口。
private boolean isNetworkWithCapabilitiesBlocked(@Nullable final NetworkCapabilities nc,
final int uid, final boolean ignoreBlocked) {
// Networks aren't blocked when ignoring blocked status
if (ignoreBlocked) {
return false;
}
if (isUidBlockedByVpn(uid, mVpnBlockedUidRanges)) return true;
final long ident = Binder.clearCallingIdentity();
try {
final boolean metered = nc == null ? true : nc.isMetered();
return mPolicyManager.isUidNetworkingBlocked(uid, metered);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
ignoreBlocked是false。所以会继续往下。
具体的实现就是在NetworkPolicyManagerService。
NetworkPolicyManagerService
static boolean isUidNetworkingBlockedInternal(int uid, int uidRules, boolean isNetworkMetered,
boolean isBackgroundRestricted, @Nullable NetworkPolicyLogger logger) {
final int reason;
// Networks are never blocked for system components
if (isSystem(uid)) {
reason = NTWK_ALLOWED_SYSTEM;
} else if (hasRule(uidRules, RULE_REJECT_RESTRICTED_MODE)) {
reason = NTWK_BLOCKED_RESTRICTED_MODE;
} else if (hasRule(uidRules, RULE_REJECT_ALL)) {
reason = NTWK_BLOCKED_POWER;
} else if (!isNetworkMetered) {
reason = NTWK_ALLOWED_NON_METERED;
} else if (hasRule(uidRules, RULE_REJECT_METERED)) {
reason = NTWK_BLOCKED_DENYLIST;
} else if (hasRule(uidRules, RULE_ALLOW_METERED)) {
reason = NTWK_ALLOWED_ALLOWLIST;
} else if (hasRule(uidRules, RULE_TEMPORARY_ALLOW_METERED)) {
reason = NTWK_ALLOWED_TMP_ALLOWLIST;
} else if (isBackgroundRestricted) {
reason = NTWK_BLOCKED_BG_RESTRICT;
} else {
reason = NTWK_ALLOWED_DEFAULT;
}
final boolean blocked;
switch(reason) {
case NTWK_ALLOWED_DEFAULT:
case NTWK_ALLOWED_NON_METERED:
case NTWK_ALLOWED_TMP_ALLOWLIST:
case NTWK_ALLOWED_ALLOWLIST:
case NTWK_ALLOWED_SYSTEM:
blocked = false;
break;
case NTWK_BLOCKED_RESTRICTED_MODE:
case NTWK_BLOCKED_POWER:
case NTWK_BLOCKED_DENYLIST:
case NTWK_BLOCKED_BG_RESTRICT:
blocked = true;
break;
default:
throw new IllegalArgumentException();
}
if (logger != null) {
logger.networkBlocked(uid, reason);
}
return blocked;
}
这里是取出一个保存的状态,一直比较状态。
根据顺序形成优先级判断。这里会说明是否blocked和原因是什么,但是在设置给networkInfo的时候reason直接给的null,所以上层app只知道blocked,但是不知道原因。
根据上面的代码可以知道:1system app是永远不会blocked。
private static boolean isSystem(int uid) {
return uid < Process.FIRST_APPLICATION_UID;// public static final int FIRST_APPLICATION_UID = 10000;
}
然后我的情况是Wi-Fi,不是计费网络,所以
if (!isNetworkMetered) {
reason = NTWK_ALLOWED_NON_METERED;
}
这段代码以后判断不用看了。那么就是之前的几个判断:
NTWK_BLOCKED_RESTRICTED_MODE,NTWK_BLOCKED_POWER,对于规则的判断就是RULE_REJECT_RESTRICTED_MODE,RULE_REJECT_ALL。
那么问题来了,这里的rule是怎么来的?
synchronized (mUidRulesFirstLock) {
uidRules = mUidRules.get(uid, RULE_NONE);
isBackgroundRestricted = mRestrictBackground;
}
那么mUidRules又是怎么生成,怎么变化的?
服务初始化
SystemServer里面会调用到下面这个,在之前的版本是在本类的systemReady里面初始化。
private void initService(CountDownLatch initCompleteSignal) {
......
}
这部分比较长。不直接贴,我下面分开贴。
1.PowerManager
updatePowerSaveWhitelistUL();//白名单
mPowerManagerInternal.registerLowPowerModeObserver(
new PowerManagerInternal.LowPowerModeListener() {
@Override
public int getServiceType() {
return ServiceType.NETWORK_FIREWALL;
}
@Override
public void onLowPowerModeChanged(PowerSaveState result) {
final boolean enabled = result.batterySaverEnabled;
if (LOGD) {
Slog.d(TAG, "onLowPowerModeChanged(" + enabled + ")");
}
synchronized (mUidRulesFirstLock) {
if (mRestrictPower != enabled) {
mRestrictPower = enabled;
updateRulesForRestrictPowerUL();
}
}
}
});
mPowerManagerInternal.registerLowPowerModeObserver(
new PowerManagerInternal.LowPowerModeListener() {
@Override
public int getServiceType() {
return ServiceType.DATA_SAVER;
}
@Override
public void onLowPowerModeChanged(PowerSaveState result) {
synchronized (mUidRulesFirstLock) {
updateRestrictBackgroundByLowPowerModeUL(result);
}
}
});
2.ams uid
mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
try {
final int changes = ActivityManager.UID_OBSERVER_PROCSTATE
| ActivityManager.UID_OBSERVER_GONE
| ActivityManager.UID_OBSERVER_CAPABILITY;
mActivityManager.registerUidObserver(mUidObserver, changes,
NetworkPolicyManager.FOREGROUND_THRESHOLD_STATE, "android");
mNetworkManager.registerObserver(mAlertObserver);
} catch (RemoteException e) {
// ignored; both services live in system_server
}
3.一堆广播接收器
// listen for changes to power save allowlist
final IntentFilter whitelistFilter = new IntentFilter(
PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED);
mContext.registerReceiver(mPowerSaveWhitelistReceiver, whitelistFilter, null, mHandler);
// watch for network interfaces to be claimed
final IntentFilter connFilter = new IntentFilter(CONNECTIVITY_ACTION);
mContext.registerReceiver(mConnReceiver, connFilter, NETWORK_STACK, mHandler);
// listen for package changes to update policy
final IntentFilter packageFilter = new IntentFilter();
packageFilter.addAction(ACTION_PACKAGE_ADDED);
packageFilter.addDataScheme("package");
mContext.registerReceiver(mPackageReceiver, packageFilter, null, mHandler);
// listen for UID changes to update policy
mContext.registerReceiver(
mUidRemovedReceiver, new IntentFilter(ACTION_UID_REMOVED), null, mHandler);
// listen for user changes to update policy
final IntentFilter userFilter = new IntentFilter();
userFilter.addAction(ACTION_USER_ADDED);
userFilter.addAction(ACTION_USER_REMOVED);
mContext.registerReceiver(mUserReceiver, userFilter, null, mHandler);
// listen for stats update events
final IntentFilter statsFilter = new IntentFilter(ACTION_NETWORK_STATS_UPDATED);
mContext.registerReceiver(
mStatsReceiver, statsFilter, READ_NETWORK_USAGE_HISTORY, mHandler);
// listen for restrict background changes from notifications
final IntentFilter allowFilter = new IntentFilter(ACTION_ALLOW_BACKGROUND);
mContext.registerReceiver(mAllowReceiver, allowFilter, MANAGE_NETWORK_POLICY, mHandler);
// Listen for snooze from notifications
mContext.registerReceiver(mSnoozeReceiver,
new IntentFilter(ACTION_SNOOZE_WARNING), MANAGE_NETWORK_POLICY, mHandler);
mContext.registerReceiver(mSnoozeReceiver,
new IntentFilter(ACTION_SNOOZE_RAPID), MANAGE_NETWORK_POLICY, mHandler);
// listen for configured wifi networks to be loaded
final IntentFilter wifiFilter =
new IntentFilter(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
mContext.registerReceiver(mWifiReceiver, wifiFilter, null, mHandler);
// listen for carrier config changes to update data cycle information
final IntentFilter carrierConfigFilter = new IntentFilter(
ACTION_CARRIER_CONFIG_CHANGED);
mContext.registerReceiver(mCarrierConfigReceiver, carrierConfigFilter, null, mHandler);
这部分直接看源码注视就好了。
4.ConnManager
// listen for meteredness changes
mConnManager.registerNetworkCallback(
new NetworkRequest.Builder().build(), mNetworkCallback);
5.AppStandby
mAppStandby.addListener(new NetPolicyAppIdleStateChangeListener());
synchronized (mUidRulesFirstLock) {
updateRulesForAppIdleParoleUL();//把防火墙里面的数据同步下
}
上面的代码是12的,7的这部分是
mUsageStats.addAppIdleStateChangeListener(new AppIdleStateChangeListener());
6.SubscriptionManager
// Listen for subscriber changes
mContext.getSystemService(SubscriptionManager.class).addOnSubscriptionsChangedListener(
new HandlerExecutor(mHandler),
new OnSubscriptionsChangedListener() {
@Override
public void onSubscriptionsChanged() {
updateNetworksInternal();
}
});
综上,就是监听一系列网络变化,用户变化,app变化和app空闲的判断,低电量和省电等。这里可以参考android官方:低电耗模式和应用待机模式。
AppStandby
这部分怎么来的,本期不关注,直接看回调。NetPolicyAppIdleStateChangeListener。
abstract static class AppIdleStateChangeListener {
/** Callback to inform listeners that the idle state has changed to a new bucket. */
public abstract void onAppIdleStateChanged(String packageName, @UserIdInt int userId,
boolean idle, int bucket, int reason);
/**
* Callback to inform listeners that the parole state has changed. This means apps are
* allowed to do work even if they're idle or in a low bucket.
*/
public void onParoleStateChanged(boolean isParoleOn) {
// No-op by default
}
/**
* Optional callback to inform the listener that the app has transitioned into
* an active state due to user interaction.
*/
public void onUserInteractionStarted(String packageName, @UserIdInt int userId) {
// No-op by default
}
}
onAppIdleStateChanged具体实现就在下面:
public void onAppIdleStateChanged(String packageName, int userId, boolean idle, int bucket,
int reason) {
try {
final int uid = mContext.getPackageManager().getPackageUidAsUser(packageName,
PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
synchronized (mUidRulesFirstLock) {
mLogger.appIdleStateChanged(uid, idle);
updateRuleForAppIdleUL(uid);
updateRulesForPowerRestrictionsUL(uid);
}
} catch (NameNotFoundException nnfe) {
}
}
最后执行到: updateRulesForPowerRestrictionsULInner(uid, oldUidRules, isUidIdle),这里就更新rule。
final boolean restrictMode = isUidIdle || mRestrictPower || mDeviceIdleMode;
final boolean isForeground = isUidForegroundOnRestrictPowerUL(uid);
final boolean isWhitelisted = isWhitelistedFromPowerSaveUL(uid, mDeviceIdleMode);
if (isForeground) {
if (restrictMode) {
newUidRules |= RULE_ALLOW_ALL;
}
} else if (restrictMode) {
newUidRules |= isWhitelisted ? RULE_ALLOW_ALL : RULE_REJECT_ALL;
}
restrictMode 根据我的情况,这个肯定是true。
isUidForegroundOnRestrictPowerUL 这里,是不是比前台服务优先级更高:
public static final int FOREGROUND_THRESHOLD_STATE =
ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
procState <= FOREGROUND_THRESHOLD_STATE
基本断定是false,因为我没有启动前台。
isWhitelistedFromPowerSaveUL
private boolean isWhitelistedFromPowerSaveUL(int uid, boolean deviceIdleMode) {
final int appId = UserHandle.getAppId(uid);
boolean isWhitelisted = mPowerSaveTempWhitelistAppIds.get(appId)
|| mPowerSaveWhitelistAppIds.get(appId);
if (!deviceIdleMode) {
isWhitelisted = isWhitelisted || isWhitelistedFromPowerSaveExceptIdleUL(uid);
}
return isWhitelisted;
}
是否在白名单中,false。
所以newUidRules=RULE_REJECT_ALL。
OK,状态确认。那么回到最开始的判断,状态是:NTWK_BLOCKED_POWER,blocked。结束。
然后为啥这个状态会影响网络访问:
updateRuleForAppIdleUL(uid);
if (!mPowerSaveTempWhitelistAppIds.get(appId) && isUidIdle(uid)
&& !isUidForegroundOnRestrictPowerUL(uid)) {
setUidFirewallRule(FIREWALL_CHAIN_STANDBY, uid, FIREWALL_RULE_DENY);
if (LOGD) Log.d(TAG, "updateRuleForAppIdleUL DENY " + uid);
}
网络在这里被关了。
以上,完毕。如有纰漏,请大家斧正。谢谢