• Android 网络流量监听开源项目-ConnectionClass源码分析


    很多App要做到极致的话,对网络状态的监听是很有必要的,比如在网络差的时候加载质量一般的小图,缩略图,在网络好的时候,加载高清大图,脸书的android 客户端就是这么做的,

    当然伟大的脸书也把这部分代码开源出来,今天就来带着大家分析一下脸书的这个开源代码。

    GitHub 地址https://github.com/facebook/network-connection-class

    注意这个项目下载下来以后 会报很多错误,导致很多人运行不了,大家要根据各自电脑不同的情况修改gradle脚本,才能让他运行。

    如果有人对gradle不熟悉的话 可以下载我编写好的gradle脚本 直接放在你项目的根目录下即可。

    地址在

    http://pan.baidu.com/s/1pJuxesz

    注意下载以后替换完成 把项目里为数不多的注解代码给删除即可正常运行,另外请自己查毒,中毒不要来找我~

    下面来看下源码,其实大部分的代码还是很简单的,

    首先从MainActivity 代码来看

     1  /**
     2      * AsyncTask for handling downloading and making calls to the timer.
     3      */
     4     private class DownloadImage extends AsyncTask<String, Void, Void> {
     5 
     6         @Override
     7         protected void onPreExecute() {
     8             mDeviceBandwidthSampler.startSampling();
     9             mRunningBar.setVisibility(View.VISIBLE);
    10         }
    11 
    12         @Override
    13         protected Void doInBackground(String... url) {
    14             String imageURL = url[0];
    15             try {
    16                 // Open a stream to download the image from our URL.
    17                 InputStream input = new URL(imageURL).openStream();
    18                 try {
    19                     byte[] buffer = new byte[1024];
    20 
    21                     // Do some busy waiting while the stream is open.
    22                     while (input.read(buffer) != -1) {
    23                     }
    24                 } finally {
    25                     input.close();
    26                 }
    27             } catch (IOException e) {
    28                 Log.e(TAG, "Error while downloading image.");
    29             }
    30             return null;
    31         }
    32 
    33         @Override
    34         protected void onPostExecute(Void v) {
    35             mDeviceBandwidthSampler.stopSampling();
    36             // Retry for up to 10 times until we find a ConnectionClass.
    37             if (mConnectionClass == ConnectionQuality.UNKNOWN && mTries < 10) {
    38                 mTries++;
    39                 new DownloadImage().execute(mURL);
    40             }
    41             if (!mDeviceBandwidthSampler.isSampling()) {
    42                 mRunningBar.setVisibility(View.GONE);
    43             }
    44         }
    45     }

    主要就是通过上面的 这个task 来访问一个网络上的图片,然后计算 “瞬时“流量。

    然后到DeviceBandwidthSampler来看这个方法

     1  /**
     2      * Method call to start sampling for download bandwidth.
     3      */
     4     public void startSampling() {
     5         if (mSamplingCounter.getAndIncrement() == 0) {
     6             mHandler.sendEmptyMessage(SamplingHandler.MSG_START);
     7             long lastTimeReading = SystemClock.elapsedRealtime();
     8             mLastTimeReading = lastTimeReading;
     9         }
    10     }

    这个lastTimeReading 实际上就代表这一次请求发起的时间,大家可以看到这里去了Samplinghandler,注意他是子线程的消息队列~

     1 private class SamplingHandler extends Handler {
     2         static final int MSG_START = 1;
     3         static final int MSG_STOP = 2;
     4 
     5         public SamplingHandler(Looper looper) {
     6             super(looper);
     7         }
     8 
     9         @Override
    10         public void handleMessage(Message msg) {
    11             switch (msg.what) {
    12                 case MSG_START:
    13                     addSample();
    14                     sendEmptyMessageDelayed(MSG_START, SAMPLE_TIME);
    15                     break;
    16                 case MSG_STOP:
    17                     addFinalSample();
    18                     removeMessages(MSG_START);
    19                     break;
    20                 default:
    21                     throw new IllegalArgumentException("Unknown what=" + msg.what);
    22             }
    23         }

    看11-15行,实际上就是不断的在调用add Sample这个函数,当我们那个DownloadTask 任务完成以后 就会发MSG_STOP消息。

    然后addFinalSample 这个过程就完成了(即代表本次对网络状态的监听完成)。

    好,我们就来看看这个add Sample做了什么

     1 /**
     2          * Method for polling for the change in total bytes since last update and
     3          * adding it to the BandwidthManager.
     4          */
     5         private void addSample() {
     6             long byteDiff = QTagParser.getInstance().parseDataUsageForUidAndTag(Process.myUid());
     7             synchronized (this) {
     8                 long curTimeReading = SystemClock.elapsedRealtime();
     9                 if (byteDiff != -1) {
    10                     mConnectionClassManager.addBandwidth(byteDiff, curTimeReading - mLastTimeReading);
    11                 }
    12                 mLastTimeReading = curTimeReading;
    13             }
    14         }

    这边代码也很好理解,实际上第六航,byteDiff 就是代表 获取了多少流量,单位是byte。

    然后7-12行 就是用这个byteDiff 和你算出来的时间差,这2个值,来算你的瞬时网络状态。

    第10行 就是算这个网络状态的,第10行的代码 我不准备深入分析,因为这个是FACEBOOK对网络状态的定义,

    大家在使用的时候 并不一定要按照他的来,比如他认为100k/s是网络糟糕,但是我们4g网络这么烂,可能50kb 就认为网络很好了。所以这个地方代码

    以后有时间大家要根据自己的公司业务来调整。我们主要要看byteDiff 这个值是怎么算出来的,实际上这个才是代码的精髓。

     1 /**
     2      * Reads the qtaguid file and returns a difference from the previous read.
     3      *
     4      * @param uid The target uid to read bytes downloaded for.
     5      * @return The difference between the current number of bytes downloaded and
     6      */
     7     public long parseDataUsageForUidAndTag(int uid) {
     8         // The format of each line is
     9         // idx iface acct_tag_hex uid_tag_int cnt_set rx_bytes rx_packets tx_bytes
    10         // (There are many more fields but we are not interested in them)
    11         // For us parts: 1, 2, 3 are to see if the line is relevant
    12         // and part 5 is the received bytes
    13         // (part numbers start from 0)
    14 
    15         // Permit disk reads here, as /proc/net/xt_qtaguid/stats isn't really "on
    16         // disk" and should be fast.
    17         StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
    18         try {
    19             long tagRxBytes = 0;
    20 
    21             FileInputStream fis = new FileInputStream(mPath);
    22             //sStatsReader.setFileStream(fis);
    23             // writeFileToSD(sStatsReader);
    24             sStatsReader.setFileStream(fis);
    25             byte[] buffer = sLineBuffer.get();
    26             try {
    27                 int length;
    28                 sStatsReader.readLine(buffer);
    29                 sStatsReader.skipLine(); // skip first line (headers)
    30 
    31                 int line = 2;
    32                 while ((length = sStatsReader.readLine(buffer)) != -1) {
    33                     try {
    34 
    35                         // Content is arranged in terms of:
    36                         // idx iface acct_tag_hex uid_tag_int cnt_set rx_bytes rx_packets tx_bytes tx_packets rx_tcp_bytes
    37                         // rx_tcp_packets rx_udp_bytes rx_udp_packets rx_other_bytes rx_other_packets tx_tcp_bytes tx_tcp_packets
    38                         // tx_udp_bytes tx_udp_packets tx_other_bytes tx_other_packets
    39 
    40                         // The ones we're interested in are:
    41                         // idx - ignore
    42                         // interface, filter out local interface ("lo")
    43                         // tag - ignore
    44                         // uid_tag_int, match it with the UID of interest
    45                         // cnt_set - ignore
    46                         // rx_bytes
    47                         //Log.v("burning", "buffer==" + new String(buffer));
    48                         sScanner.reset(buffer, length);
    49                         sScanner.useDelimiter(' ');
    50 
    51                         sScanner.skip();
    52                         if (sScanner.nextStringEquals("lo")) {
    53                             continue;
    54                         }
    55                         sScanner.skip();
    56                         if (sScanner.nextInt() != uid) {
    57                             continue;
    58                         }
    59                         sScanner.skip();
    60                         int rxBytes = sScanner.nextInt();
    61                         tagRxBytes += rxBytes;
    62                         line++;
    63 
    64                         // If the line is incorrectly formatted, ignore the line.
    65                     } catch (NumberFormatException e) {
    66                         Log.e(TAG, "Cannot parse byte count at line" + line + ".");
    67                         continue;
    68                     } catch (NoSuchElementException e) {
    69                         Log.e(TAG, "Invalid number of tokens on line " + line + ".");
    70                         continue;
    71                     }
    72                 }
    73             } finally {
    74                 fis.close();
    75             }
    76 
    77             if (sPreviousBytes == -1) {
    78                 sPreviousBytes = tagRxBytes;
    79                 return -1;
    80             }
    81             long diff = tagRxBytes - sPreviousBytes;
    82             sPreviousBytes = tagRxBytes;
    83             return diff;
    84 
    85         } catch (IOException e) {
    86             Log.e(TAG, "Error reading from /proc/net/xt_qtaguid/stats. Please check if this file exists.");
    87         } finally {
    88             StrictMode.setThreadPolicy(savedPolicy);
    89         }
    90 
    91         // Return -1 upon error.
    92         return -1;
    93     }

    17行-21行,实际上就是对/proc/net/xt_qtaguid/stats 这个文件进行读取,然后分析他的内容 来算出来我们的bytediff的,

    熟悉linux的同学 尤其是运维的同学 肯定对/proc/net 不陌生,后面的xt_qtaguid/stats这个路径实际上就是谷歌android

    给我们特殊的一个监听网络状态的接口文件。我这里可以给出一部分日志的结构 供大家参考。因为这个函数的内容就是对

    这个日志的解析,你能读懂这个日志 就读懂了 这个开源项目最核心的部分。

    idx哪一行就代表着文件头,下面对应的就是数据。

    可以看到iface就代表着 那个网络接口,因为我是wifi下运行的这个代码 所以肯定是wlan,acct_tag_hex 这个标记就代表socket  当然这些参数值都不重要。

    uid_tag_int 这个值 是比较重要的,很多人不明白为什么这个函数一定要先判断下uid的值,大家在这里一定要注意,对于linux系统来说 uid 就代表用户。

    而android 是单用户系统,谷歌把这个地方改造成了uid代表你的app!!!!!!!!!!!!!!所以我们在监听app网络状态的时候 你一定要判断uid

    你不能把别的app的流量也算在你自己的头上!

    然后看cnt_set 实际上着就是一个标志位 0代表前台流量 1代表后台流量罢了。然后看rx_bytes r就代表是receive tx_bytes就代表transmit所以

    就代表着 一个是收到的byte 一个是发送的byte,对于手机来说 发送的byte一般较少,我们主要关心的就是收到的byte。

    好,分析完毕以后 我们再接着看那个函数 就比较容易了。

    第29行 就是跳过文件头,因为我们的目标是获取rx_bytes

    56行 就是看如果不是自己的app 的流量 自然跳出 不会计算。

    77-83行,就是算bytediff的,注意那个sPreviousBytes 这个就是存储你上一次的流量的,初始化的时候是-1

    他是一个静态变量,

    因为我们的日志里面 rx_bytes是存储的总流量!,所以这边计算的时候要用流量差 来表示bytediff。

    到这 这个框架就分析完毕了,希望能带给大家一点启发,

    最后有的人可能要问 为什么 那个while循环 要把rx_bytes 给加起来,因为大家要注意啊 一个app

    可能有多个进程啊,我们要计算自己的app 或者某个app的 网络状态,肯定是要把他所有进程的

    流量全算进去的!所以这个地方要不断遍历!此外就是如果你真的能读懂我的这篇博客的话,又恰好会python的

    话,就可以用python和adb 来完成对你手机上app的流量测试了!很方便,再也不用装什么360来测试你app

    的流量了!

    人生苦短,何不用python!

  • 相关阅读:
    MySQL中的用户与授权
    Vim安装使用和配置
    Mysql中的explain和desc
    array_map、array_walk、array_reduce
    PHP二维数组去重(指定键名)
    git配置ssh秘钥(公钥以及私钥)windows
    在nginx上用FastCGI解析PHP
    关于token登录逻辑分析
    公有云 私有云 混合云 的区别
    使用Docker在服务器上部署Ubuntu,本地传文件到docker
  • 原文地址:https://www.cnblogs.com/punkisnotdead/p/4780351.html
Copyright © 2020-2023  润新知