流量控制整理

前言:

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的移动网络接口名称不同,注意区分。