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!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM