- 不受流量限制
- Settings如何实现界面?
- 填充AppEntry和State
- Network Policy
- 让Network Policy支持更多的policy
- METERED_BACKGROUND policy到底干了啥
这里记录,基于Android O,从Settings入手,介绍Android DataSave是什么,如何设置,实现原理。
不受流量限制
进入对应设置页面的步骤:设置 - 应用和通知 - 高级 - 特殊应用权限 - 不受流量限制。 如果在这里设置了允许,那么,应用可以Doze模式下不受流量限制地获得数据网络访问的权限。 可以肆无忌惮的在后台跑流量了。
Settings如何实现界面?
对应的界面类:com.android.settings.datausage.UnrestrictedDataAccess
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setAnimationAllowed(true);//允许使用动画,等待过程中转圈
setPreferenceScreen(getPreferenceManager().createPreferenceScreen(getContext()));
mApplicationsState = ApplicationsState.getInstance(// 用于获得App的基本信息,label,icon之类的
(Application) getContext().getApplicationContext());
mDataSaverBackend = new DataSaverBackend(getContext());// 用于获得DataSave的配置信息,从NetworkPolicy服务获得
mDataUsageBridge = new AppStateDataUsageBridge(mApplicationsState, this, mDataSaverBackend);// 连接上面两个部分的辅助类
mSession = mApplicationsState.newSession(this);// 建立一个异步加载应用信息的Session
mShowSystem = icicle != null && icicle.getBoolean(EXTRA_SHOW_SYSTEM);// 是否显示系统应用的配置信息
mFilter = mShowSystem ? ApplicationsState.FILTER_ALL_ENABLED // 一个自定义的过滤器,过滤系统应用
: ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER;
setHasOptionsMenu(true);
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(EXTRA_SHOW_SYSTEM, mShowSystem);// 保存界面的临时状态,当从后台恢复时能正常显示
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
setLoading(true, false); // 开始转圈圈
}
@Override
public void onResume() {
super.onResume();
mSession.resume(); // 异步加载信息通道打开
mDataUsageBridge.resume(); // 开始异步加载信息,AOSP中,这里面有个异步Bug
}
@Override
public void onExtraInfoUpdated() {
mExtraLoaded = true;
rebuild(); // 显示列表
}
假设现在所有内容都已经加载完了,也不考虑异步加载问题。看看界面如何显示?
@Override
public void onRebuildComplete(ArrayList<AppEntry> apps) {
if (getContext() == null) return;
cacheRemoveAllPrefs(getPreferenceScreen());
// 每次都替换一个Preference cache,将所有找到的preference都放进去
// 这样做的目的是不改变界面上现有的preference,只添加没有的
final int N = apps.size();
for (int i = 0; i < N; i++) {
AppEntry entry = apps.get(i);
if (!shouldAddPreference(entry)) { // 做一个简单的判断,应该Google的程序员发现了一个莫名其妙其妙的Bug而做的限制
continue;
}
String key = entry.info.packageName + "|" + entry.info.uid;
AccessPreference preference = (AccessPreference) getCachedPreference(key); //从刚才建立的Cache中拿到Preference
if (preference == null) {
preference = new AccessPreference(getPrefContext(), entry);
preference.setKey(key);
preference.setOnPreferenceChangeListener(this);
getPreferenceScreen().addPreference(preference);// 添加一行
} else {
preference.reuse(); // 重用
}
preference.setOrder(i); // 给一个顺序它,这样做的目的是,apps已经排过序,列表的位置也应该符合原来的排序
}
setLoading(false, true); // 不要转圈圈了
removeCachedPrefs(getPreferenceScreen()); // 删除缓存,因为没有必要存放了
}
填充AppEntry和State
AppEntry,这个就没什么好说的,都是些常规的获取app信息,重要是State。
public void onResume() {
super.onResume();
mSession.resume(); // 加载App信息
mDataUsageBridge.resume(); // 加载State信息
}
State的填充:
com.android.settings.datausage.AppStateDataUsageBridge#loadAllExtraInfo
@Override
protected void loadAllExtraInfo() {
ArrayList<AppEntry> apps = mAppSession.getAllApps();
final int N = apps.size();
for (int i = 0; i < N; i++) {
AppEntry app = apps.get(i);
app.extraInfo = new DataUsageState(mDataSaverBackend.isWhitelisted(app.info.uid), // 判断NetworkPolicy状态
mDataSaverBackend.isBlacklisted(app.info.uid));
}
}
private void loadWhitelist() {
if (mWhitelistInitialized) return;
for (int uid : mPolicyManager.getUidsWithPolicy(POLICY_ALLOW_METERED_BACKGROUND)) { // 这里进入Policy的世界
mUidPolicies.put(uid, POLICY_ALLOW_METERED_BACKGROUND);
}
mWhitelistInitialized = true;
}
com.android.server.net.NetworkPolicyManagerService#getUidsWithPolicy
@Override
public int[] getUidsWithPolicy(int policy) {
mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
int[] uids = new int[0];
synchronized (mUidRulesFirstLock) {
for (int i = 0; i < mUidPolicy.size(); i++) {
final int uid = mUidPolicy.keyAt(i);
final int uidPolicy = mUidPolicy.valueAt(i);
if ((policy == POLICY_NONE && uidPolicy == POLICY_NONE) ||
(uidPolicy & policy) != 0) { // 一个二进制位代表一种policy类型
uids = appendInt(uids, uid);
}
}
}
return uids;
}
Network Policy
这里看看App的 Network Policy 是如何设置的
从上面的获取过程知道,所有的policy都存放在一个数组里面:
/** Defined UID policies. */
@GuardedBy("mUidRulesFirstLock") final SparseIntArray mUidPolicy = new SparseIntArray();
谁设置了Policy?
android.net.NetworkPolicyManager#setUidPolicy
/**
* Set policy flags for specific UID.
*
* @param policy should be {@link #POLICY_NONE} or any combination of {@code POLICY_} flags,
* although it is not validated.
*/
public void setUidPolicy(int uid, int policy) {
try {
mService.setUidPolicy(uid, policy);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
private void setUidPolicyUncheckedUL(int uid, int policy, boolean persist) {
if (policy == POLICY_NONE) {
mUidPolicy.delete(uid);
} else {
mUidPolicy.put(uid, policy); // 直接put进入,没有任何的按位或,相当于只是用了一个二进制位,说白了,只支持一种policy类型,那就是POLICY_ALLOW_METERED_BACKGROUND
}
// uid policy changed, recompute rules and persist policy.
updateRulesForDataUsageRestrictionsUL(uid);
if (persist) {
synchronized (mNetworkPoliciesSecondLock) {
writePolicyAL();
}
}
}
让Network Policy支持更多的policy
那么,需要将函数的实现体修改一下。共有几个内容:
- Network Policy服务启动时,按二进制位处理各种policy
- set函数添加或功能,而不是单纯的.put进列表
- 其它辅助函数体的修改
METERED_BACKGROUND policy到底干了啥
一路跟下去,能很顺利的看到,它是取设置iptables的 bandwidth
mConnector.execute("bandwidth", suffix + chain, uid);
所谓的iptables的bandwidth,其实是用iptables设置其带宽限制。 类似的,还可以iptables的firewall。
要熟悉上述信息,需要熟悉linux/Android iptables, 以及Android Netd
下次有时间,就放一篇关于用防火墙限制应用联网的改造blog。