Android Settings 应用二 获取应用消耗的流量

了解这一块也是因为有需求要获取指定应用所消耗的流量。在Android中,流量消耗主要分为手机卡和WIFI,在Settings中,也有统计流量的使用情况。经了解,Settings中是通过Loader去加载,Loader的原理就不多说了,很多大神的分析都很赞,这里仅介绍下该方法是怎么获取到流量的,其中会附带介绍Settings关于流量统计的源码分析思路。

一、入口

Settings中有关流量统计的功能是在com.android.settings.datausage包路径下,其中DataUsageSummary是流量使用情况的入口。其中有两个方法:

    /**
     * 添加Mobile流量统计
     * @param subId
     */
    private void addMobileSection(int subId) {
        TemplatePreferenceCategory category = (TemplatePreferenceCategory)
                inflatePreferences(R.xml.data_usage_cellular);
        category.setTemplate(getNetworkTemplate(subId), subId, services);
        category.pushTemplates(services);
    }

    /**
     * 添加Wifi流量统计
     */
    private void addWifiSection() {
        TemplatePreferenceCategory category = (TemplatePreferenceCategory)
                inflatePreferences(R.xml.data_usage_wifi);
        category.setTemplate(NetworkTemplate.buildTemplateWifiWildcard(), 0, services);
    }

这两个方法分别是添加Mobile和Wifi的流量统计,顺着两份xml文件查找下去,可以发现两个流量统计的入口都是指向DataUsageList。而此处setTemplate方法传入的两个Template会在后面区分是Mobile还是Wifi时用到。

//Mobile
NetworkTemplate.buildTemplateMobileAll(
                services.mTelephonyManager.getSubscriberId(subscriptionId));
//Wifi
NetworkTemplate.buildTemplateWifiWildcard()

二、功能实现

找到具体的入口后,便可以分析具体的功能实现。由于我们只关心流量数据的变化,在抛去繁琐的UI初始化以后,我们可以发现跟数据变化有关的两个方法:updateDetailData()和bindStats()。下面利用注释分析下这两个方法的主要功能:

    /**
     * Update details based on {@link #mChart} inspection range depending on
     * current mode. Updates {@link #mAdapter} with sorted list
     * of applications data usage.
     */
    private void updateDetailData() {
        if (LOGD) Log.d(TAG, "updateDetailData()");
        //该参数主要用于指定查询的时间范围
        final long start;
        final long end;
        ...
        ...//此处省略了start,和end处理逻辑。
        ...
        
        final long now = System.currentTimeMillis();
        final Context context = getActivity();
        NetworkStatsHistory.Entry entry = null;
        if (mChartData != null) {
            entry = mChartData.network.getValues(start, end, now, null);
        }
        //初始化Loader
        // kick off loader for detailed stats
        getLoaderManager().restartLoader(LOADER_SUMMARY,
                SummaryForAllUidLoader.buildArgs(mTemplate, start, end), mSummaryCallbacks);
        //显示总的消耗量
        final long totalBytes = entry != null ? entry.rxBytes + entry.txBytes : 0;
        final String totalPhrase = Formatter.formatFileSize(context, totalBytes);
        mUsageAmount.setTitle(getString(R.string.data_used_template, totalPhrase));
        if (mShowDataUsage) {
            final int summaryRes = R.string.data_usage_total_during_range;
            final String rangePhrase = Utils.formatDateRange(context, mSelectLeft, mSelectRight);
            mUsageSummary.setSummary(getString(summaryRes, totalPhrase, rangePhrase));
        }
    }

Loader终于出现了,通过LoaderManager进行管理,其中restartLoader需要三个参数,其中一个参数由SummaryForAllUidLoader生成。该类位于com.android.settingslib.net路径下,SummaryForAllUidLoader是继承自AsyncTaskLoader实现异步加载。该类的buildArgs方法将之前提到的Template、start、end分装成Bundle格式。第二个参数LoaderCallbacks<NetworkStats>,该类是与Loader处理过程息息相关的,具体原理可以参照Loader相关的博客。

private final LoaderCallbacks<NetworkStats> mSummaryCallbacks = new LoaderCallbacks<
            NetworkStats>() {
        @Override
        public Loader<NetworkStats> onCreateLoader(int id, Bundle args) {
            //创建异步加载的Loader,这里的mStatsSession是指的是INetworkStatsSession,是获取流量的主要入口
            return new SummaryForAllUidLoader(getActivity(), mStatsSession, args);
        }

        @Override
        public void onLoadFinished(Loader<NetworkStats> loader, NetworkStats data) {            //加载结束后会调用该方法,用于更新数据源
            final int[] restrictedUids = services.mPolicyManager.getUidsWithPolicy(
                    POLICY_REJECT_METERED_BACKGROUND);
            bindStats(data, restrictedUids);
            updateEmptyVisible();
        }

        @Override
        public void onLoaderReset(Loader<NetworkStats> loader) {
            //当restartLoader时,会调用该方法,用于重置数据源
            bindStats(null, new int[0]);
            updateEmptyVisible();
        }
    };

查看SummaryForAllUidLoader,可以发现,获取流量的主要方法还是通过INetworkStatsSession.getSummaryForAllUid()这个方法来获取的,此处我们可以展开联想直接操作INetworkStatsSession是否就可以获取到流量统计结果了?

onLoadFinished中的NetworkStats就是我们要获取的流量数据,而且是所有应用的流量数据。在加载结束后会调用bindStats()来处理数据,该方法不做详细介绍,我们只要获取到NetworkStats就可以用自己的逻辑来处理流量数据。接下来介绍下本人封装的方法,用于获取全部应用的流量数据:

private static final int LOADER_SUMMARY = 3;

INetworkStatsSession mSession ;
PackageManager pm = null;
NetworkStats mStats = null;

public Map<Integer, DataUsageWifi> forLoaderManager(NetworkTemplate template, long start, long end){
        //用于获取INetworkStatsSession
        INetworkStatsService mService
                = INetworkStatsService.Stub.asInterface(ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
        try {
            mSession = mService.openSession();
            if (mSession == null) {
                Log.e(TAG, "getDataUsageWifi: the session is null" );
                return null;
            }
            mStats = null;
            ((Activity)mContext).getLoaderManager().restartLoader(LOADER_SUMMARY,
                    SummaryForAllUidLoader.buildArgs(template, start, end), mSummaryCallbacks);
            //下面是我自己实现的数据处理逻辑,由于需要实现同步返回,所以利用while去控制,如有更好的方法,欢迎指教
            int time = 50;
            while(true){
                if(mStats != null)
                    break;
                if(time-- < 0)
                    break;
                Thread.sleep(50);
            }
            if(mStats == null){
                Log.e(TAG, "getDataUsageWifi: get the netword data usage failed" );
                return null;
            }
            pm = mContext.getPackageManager();
            Map<Integer, DataUsageWifi> map = new HashMap<>();
            int size = mStats.size();
            NetworkStats.Entry entity = null;
            for(int i=0; i<size; i++){
                entity = mStats.getValues(i, entity);
                if(map.containsKey(entity.uid)){
                    //uid相同,则将值相加
                    DataUsageWifi info = map.get(entity.uid);
                    info.rxByte += entity.rxBytes;
                    info.txByte += entity.txBytes;
                    map.put(entity.uid, info);
                }else{
                    String name = pm.getNameForUid(entity.uid);
                    map.put(entity.uid, new DataUsageWifi(entity.uid, name, entity.rxBytes, entity.txBytes));
//                    Log.d(TAG, "onLoadFinished: name="+ name);
//                    Log.d(TAG, "onLoadFinished: uid="+ entity.uid);
//                    Log.d(TAG, "onLoadFinished: set="+ entity.set);
//                    Log.d(TAG, "onLoadFinished: rxByte="+ entity.rxBytes);
//                    Log.d(TAG, "onLoadFinished: txByte="+ entity.txBytes);
                }
            }
            return map;
        } catch (RemoteException e) {
            e.printStackTrace();
            return null;
        } catch (InterruptedException e) {
            e.printStackTrace();
            return null;
        }
    }

private final LoaderManager.LoaderCallbacks<NetworkStats> mSummaryCallbacks = new LoaderManager.LoaderCallbacks<
            NetworkStats>() {
        @Override
        public Loader<NetworkStats> onCreateLoader(int id, Bundle args) {
            Log.d(TAG, "onCreateLoader: id="+ id);
            return new SummaryForAllUidLoader(mContext, mSession, args);
        }

        @Override
        public void onLoadFinished(Loader<NetworkStats> loader, NetworkStats data) {
            Log.d(TAG, "onLoadFinished: loader="+ loader+ ", data="+ data);
            mStats = data;
        }

        @Override
        public void onLoaderReset(Loader<NetworkStats> loader) {
            Log.d(TAG, "onLoaderReset: loader="+ loader);
        }

        private void updateEmptyVisible() {
            Log.d(TAG, "updateEmptyVisible: executed");
        }
    };

三、另一种更直接的方式

由于需要提供Service方式的SDK,而Loader是Activity和Fragment中才有的,所以上述的方法无法实现。通过上述的分析知道数据源其实是从Context.NETWORK_STATS_SERVICE这个服务获取的。那是否可以直接通过操作服务来获取到数据呢。所以就有了另外一种直接的获取方式来实现我的功能:

public Map<Integer, DataUsageWifi> forNetworkStatsManager(int type, long start, long end){
        Log.d(TAG, "getAllDataUsage: start="+ start+ ", end="+ end);
        //用于获取应用UID
        PackageManager pm = (PackageManager) mContext.getPackageManager();
        if(pm == null){
            Log.e(TAG, "getAllDataUsage: get the telephone manager failed" );
            return null;
        }
        //用于获取subscriberId
        TelephonyManager tm = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
        if(pm == null){
            Log.e(TAG, "getAllDataUsage: get the telephone manager failed" );
            return null;
        }
        //用于获取流量数据
        NetworkStatsManager nsm = (NetworkStatsManager) mContext.getSystemService(Context.NETWORK_STATS_SERVICE);
        if(nsm == null){
            Log.e(TAG, "getAllDataUsage: get the network stats manager failed");
            return null;
        }
        List<ApplicationInfo> list = pm.getInstalledApplications(PackageManager.GET_ACTIVITIES);
        try {
            Map<Integer, DataUsageWifi> result = getDataUsageByUid(nsm, tm, type, start, end);
            if(result == null)return null;
            for (ApplicationInfo info: list) {
                DataUsageWifi data = result.get(info.uid);
                if(data == null)
                    continue;
                data.packageName = info.packageName;
            }
            return result;
        } catch (RemoteException e) {
            e.printStackTrace();
            return null;
        }
    }

    private Map<Integer, DataUsageWifi> getDataUsageByUid(NetworkStatsManager nsm, TelephonyManager tm,
                                           int type, long start, long end) throws RemoteException {
        Log.d(TAG, "listDataUsageByUid: executed");
        // 获取subscriberId
        String subId = tm.getSubscriberId();
        if((type==ConnectivityManager.TYPE_MOBILE) && (subId==null)){
            Log.e(TAG, "listDataUsageByUid: get sub id fail" );
            return null;
        }
        android.app.usage.NetworkStats summaryStats;
        android.app.usage.NetworkStats.Bucket summaryBucket
                = new android.app.usage.NetworkStats.Bucket();
        Map<Integer, DataUsageWifi> map = new HashMap<>();
        //此处的type用于决定是获取Mobile通道的流量数据还是获取Wifi通道的流量数据
        //ConnectivityManager.TYPE_MOBILE
        //ConnectivityManager.TYPE_WIFI
        summaryStats = nsm.querySummary(type, subId, start, end);
        do {
            summaryStats.getNextBucket(summaryBucket);
            int summaryUid = summaryBucket.getUid();
            long summaryRx = summaryBucket.getRxBytes();
            long summaryTx = summaryBucket.getTxBytes();
            Log.i(TAG, "uid:" + summaryBucket.getUid() + " rx:" + summaryBucket.getRxBytes() +
                    " tx:" + summaryBucket.getTxBytes());
            DataUsageWifi info = new DataUsageWifi(summaryUid, "", summaryRx, summaryTx);
            map.put(summaryUid, info);
        } while (summaryStats.hasNextBucket());
        return map;
    }

NetworkStatsManager还有其他几个方法,可以通过UID去获取指定应用的流量,实现起来更加方便。至于性能方面有没有差异,还没有仔细去研究,下一步将会去探讨这些,以及Context.NETWORK_STATS_SERVICE中的这个服务到底都做了哪些活。

刚开始学习总结工作内容,想到什么写什么,感觉很乱咯。稍后会将Demo的源码上传,有需要的朋友可以下载运行看看,同样需要系统签名,具体操作可以操考应用耗电量那一篇的方式进行操作。

 

 

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值