很多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!