前言:
Android的流量控制的话应该是分为3个部分:1.界面层,2.framework层,3.iptables层
界面层通过networkpolicyeditor将设置好的policy(包含流量上限limit,警告warning,以及周期cycleday)写(write)到framework层中,framework层中将对应的policy再写到iptables层。
wifi和移动数据的控制——通过在iptables中的INPUT和OUTPUT链上添加firewall链,在firewall链中添加wifi和mobile链,然后对应的规则添加在这两条链上。流程也是界面-》framework-》netd-》iptables。
流量统计是通过networkstatsservice每隔30分钟从/proc/目录下拉取当时对应的流量使用情况,再统计在/data/目录下。
界面层:
- 界面的加载
界面层的主要代码在settings中,路径是source/packages/apps/settings/ . 界面部分的内容多在DataUsageSummary.java这个类中。一般会根据产品的要求修改一些东西。
代码分下流程就是:
在onresume的时候:因为networkstatsservice每隔30分钟拉取数据更新,但是进入界面后就需要最新的数据,所以一开始就需要拉取最新的数据。在doinbackground中。拉取到数据之后就可以更新界面updatebody();
@Override
public void onResume() {
super.onResume();
...
// kick off background task to update stats
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
try {
// wait a few seconds before kicking off
Thread.sleep(0 * DateUtils.SECOND_IN_MILLIS);
mStatsService.forceUpdate();
} catch (InterruptedException e) {
} catch (RemoteException e) {
}
return null;
}
@Override
protected void onPostExecute(Void result) {
if (isAdded()) {
updateBody();
}
if(usageLimitEnabled){
mDataLimit.setEnabled(true);
mDataLimitValue.setEnabled(true);
mDataLimitEditorLayout.setClickable(true);
mCycleView.setClickable(true);
}
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
...
}
在updatebody方法中,其实就是更新现有的数据:更新现有数据的chart图表。在callback中更新summary。
private void updateBody() {
...
getLoaderManager().restartLoader(LOADER_CHART_DATA,
ChartDataLoader.buildArgs(mTemplate, mCurrentApp), mChartDataCallbacks);
...
}
更新单个应用详细信息:
private final LoaderCallbacks<ChartData> mChartDataCallbacks = new LoaderCallbacks<
ChartData>() {
...
@Override
public void onLoadFinished(Loader<ChartData> loader, ChartData data) {
....
// calcuate policy cycles based on available data
updatePolicy(true);
updateAppDetail();
....
}
...
};
private void updateDetailData() {
// kick off loader for detailed stats
getLoaderManager().restartLoader(LOADER_SUMMARY,
SummaryForAllUidLoader.buildArgs(mTemplate, start, end), mSummaryCallbacks);
}
至此,界面所有的信息就加载出来了。
通过两个loader来加载图表以及应用使用流量的详细信息。
- 设置上限警告以及周期
如下的设置流量警告的代码:直接使用policyeditor设置警告值
private void setPolicyWarningBytes(long warningBytes) {
if (LOGD) Log.d(TAG, "setPolicyWarningBytes()");
mPolicyEditor.setPolicyWarningBytes(mTemplate, warningBytes);
}
// networkpolicyeditor.java
public void setPolicyWarningBytes(NetworkTemplate template, long warningBytes) {
final NetworkPolicy policy = getOrCreatePolicy(template);
policy.warningBytes = warningBytes;
policy.inferred = false;
policy.clearSnooze();
writeAsync();
}
public void write(NetworkPolicy[] policies) {
mPolicyManager.setNetworkPolicies(policies);
}
其实就是将修改过的policy设置到framework中。
基本到了这边,界面上的东西就结束了。
framework层
framework层主要关注的是networkpolicymanagerservice和networkstatsservice这两个,第一个和策略有关,第二个和统计有关。networkpolicymanager和networkstatsservice都通过networkmanagementservice与netd进行串联。这个地方就不讨论这些了。
networkpolicymanagerservice中添加了:实时网速功能,每日流量提醒,移动数据和wifi单个应用控制。
实时网速功能其实就是通过获取到3秒时间的total值取平均获取到的:
realtimeSpeedHandler = new Handler(realtimelooper){
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case 0:{
String formatespeed;
NetworkInfo info = ((ConnectivityManager)mContext
.getSystemService(Context.CONNECTIVITY_SERVICE)).getActiveNetworkInfo();
cur_KB_rx = TrafficStats.getTotalRxBytes()-old_KB_rx;
old_KB_rx = TrafficStats.getTotalRxBytes();
cur_KB_tx = TrafficStats.getTotalTxBytes()-old_KB_tx;
old_KB_tx = TrafficStats.getTotalTxBytes();// 获取到总的流量
formatespeed = formatRealtimeSpeed(mContext, (cur_KB_tx+cur_KB_rx)/3,false);//计算出平均值
Log.i(TAG, "real time onreceive...cur_kb:" + formatespeed);
//send the data to statusbar
Intent sIntent = new Intent(Intent.ACTION_UPDATE_REALTIME_SPEED);
if(info==null || !(System.currentTimeMillis()-lastNetworkShowSpeedTime>2000 && System.currentTimeMillis()-lastNetworkShowSpeedTime <4000)){
sIntent.putExtra("realtime_speed", "");
}else{
sIntent.putExtra("realtime_speed", formatespeed);
}
mContext.sendBroadcast(sIntent);
if(!realtimeSpeedHandler.hasMessages(0))
this.sendEmptyMessageDelayed(0,3000);
lastNetworkShowSpeedTime = System.currentTimeMillis();
break;
}
}
}
};
每日流量提醒的功能和流量警告功能相似,就不再多做赘述。
单个应用的流量控制功能:在关机时本地保存数据,开机时读取到内存中,同时写入到iptables中,如果内存中数据有变化的时候,同时写出到本地文件中。
void writePolicyLocked() {
...
// write all known uid policies
for (int i = 0; i < mUidPolicy.size(); i++) {
final int uid = mUidPolicy.keyAt(i);
final int policy = mUidPolicy.valueAt(i);
// skip writing empty policies
if (policy == POLICY_NONE) continue;
out.startTag(null, TAG_UID_POLICY);
writeIntAttribute(out, ATTR_UID, uid);
writeIntAttribute(out, ATTR_POLICY, policy);
out.endTag(null, TAG_UID_POLICY);
}
//write all known wifi policies
for(int i=0;i<mUidWifiPolicy.size();i++){
final int uid = mUidWifiPolicy.keyAt(i);
final int policy = mUidWifiPolicy.valueAt(i);
// skip writing empty policies
if (policy == POLICY_NONE) continue;
out.startTag(null,TAG_UID_WIFI_POLICY);
writeIntAttribute(out, ATTR_UID, uid);
writeIntAttribute(out, ATTR_WIFI_POLICY, policy);
out.endTag(null, TAG_UID_WIFI_POLICY);
}
...
}
设置的操作也在其中:
private void setUidWifiPolicyUncheckedLocked(int uid, int networkType, boolean allow,boolean persist) {
int policy = allow ? 1:0;
mUidWifiPolicy.put(uid, policy);//保存到内存,以便序列化
// uid policy changed, recompute rules and persist policy.
try {
//通过networkmanagementservice设置对应的规则
mNetworkManager.setFirewallUidChainRule(uid,networkType,allow);
} catch (IllegalStateException e) {
Log.wtf(TAG, "problem setting uid rules", e);
} catch (RemoteException e) {
// ignored; service lives in system_server
}
if (persist) {
writePolicyLocked();
}
}
networkmanagement中的操作:
private void setFirewallUidRuleInternal(int chain, int uid, int rule) {
synchronized (mQuotaLock) {
SparseIntArray uidFirewallRules = getUidFirewallRules(chain);
final int oldUidFirewallRule = uidFirewallRules.get(uid, FIREWALL_RULE_DEFAULT);
if (DBG) {
Slog.d(TAG, "oldRule = " + oldUidFirewallRule
+ ", newRule=" + rule + " for uid=" + uid);
}
if (oldUidFirewallRule == rule) {
if (DBG) Slog.d(TAG, "!!!!! Skipping change");
// TODO: eventually consider throwing
return;
}
try {
String ruleName = getFirewallRuleName(chain, rule);
String oldRuleName = getFirewallRuleName(chain, oldUidFirewallRule);
if (rule == NetworkPolicyManager.FIREWALL_RULE_DEFAULT) {
uidFirewallRules.delete(uid);
} else {
uidFirewallRules.put(uid, rule);
}
//通过nativedaemonconnector来与netd进行连接
if (!ruleName.equals(oldRuleName)) {
mConnector.execute("firewall", "set_uid_rule", getFirewallChainName(chain), uid,
ruleName);
}
} catch (NativeDaemonConnectorException e) {
throw e.rethrowAsParcelableException();
}
}
}
当然了,nativedaemonconnector和netd连接的东西以及原理你应该都知道,我就不讲了,只讲下业务逻辑方面的把。
netd部分
netd部分的代码不多,可以看一下
http://diana.devops.letv.com/#/c/319950/
http://diana.devops.letv.com/#/c/319951/
这两笔提交,高通和mtk的移动网络接口名称不同,注意区分。