【Android 应用开发】Android 平台 HTTP网速测试 案例 API 分析
作者 : 萬境絕塵??
轉載請注明出處 :?http://blog.csdn.net/shulianghan/article/details/25996817
工信部規(guī)定的網(wǎng)速測試標準 :?除普通網(wǎng)頁測速采用單線程外,用戶寬帶接入速率測試應使用多線程(多TCP連接)HTTP下載進行測速,測試中使用的線程數(shù)量為N(N≥4)。
-- 建立連接 : 用戶終端設備發(fā)起測試請求后,與測速平臺建立 N 條 TCP 連接,并在每一條 TCP 連接上發(fā)送HTTP[GET]請求發(fā)起一次測試過程。
-- 請求文件 : 對每一個 HTTP[GET]請求,寬帶接入速率測試平臺以 HTTP 200 OK 響應,并開始傳送測速文件。
-- 下載文件 : 對每一條連接,寬帶接入速率測試平臺持續(xù)從內存直接發(fā)送 64kByte 大小的內容。
-- 平均速率 : 從收到第 1 個 HTTP[GET]請求開始計時,寬帶接入速率測試平臺及客戶端軟件每隔 1s 統(tǒng)計已經(jīng)發(fā)送的文件大小,計算數(shù)據(jù)平均傳送速率,并在網(wǎng)頁上或客戶端中實時更新。
-- 實時速率 : 寬帶接入速率測試平臺同時計算每 1s 間隔內的實時數(shù)據(jù)傳送速率。
-- 測量時間 : 15s 后寬帶接入速率測試平臺停止發(fā)送數(shù)據(jù),計算第 5s 到第 15s 之間共計 10s 的平均速率及峰值速率,峰值速率為步驟 5)中的每秒實時速率的最大值.
一. 網(wǎng)速測試核心代碼
從GitHub上下載的源碼, 應該沒有按照工信部的標準寫的;
在 GitHub 上找到的網(wǎng)速測試的核心代碼 :?
-- GitHub 地址 :?https://github.com/Mobiperf/Speedometer.git ;
/** Runs the HTTP measurement task. Will acquire power lock to ensure wifi is not turned off */@Overridepublic MeasurementResult call() throws MeasurementError {int statusCode = HttpTask.DEFAULT_STATUS_CODE;long duration = 0;long originalHeadersLen = 0;long originalBodyLen;String headers = null;ByteBuffer body = ByteBuffer.allocate(HttpTask.MAX_BODY_SIZE_TO_UPLOAD);boolean success = false;String errorMsg = "";InputStream inputStream = null;try {// set the download URL, a URL that points to a file on the Internet// this is the file to be downloadedHttpDesc task = (HttpDesc) this.measurementDesc;String urlStr = task.url;// TODO(Wenjie): Need to set timeout for the HTTP methodshttpClient = AndroidHttpClient.newInstance(Util.prepareUserAgent(this.parent));HttpRequestBase request = null;if (task.method.compareToIgnoreCase("head") == 0) {request = new HttpHead(urlStr);} else if (task.method.compareToIgnoreCase("get") == 0) {request = new HttpGet(urlStr);} else if (task.method.compareToIgnoreCase("post") == 0) {request = new HttpPost(urlStr);HttpPost postRequest = (HttpPost) request;postRequest.setEntity(new StringEntity(task.body));} else {// Use GET by defaultrequest = new HttpGet(urlStr);}if (task.headers != null && task.headers.trim().length() > 0) {for (String headerLine : task.headers.split("\r\n")) {String tokens[] = headerLine.split(":");if (tokens.length == 2) {request.addHeader(tokens[0], tokens[1]);} else {throw new MeasurementError("Incorrect header line: " + headerLine);}}}byte[] readBuffer = new byte[HttpTask.READ_BUFFER_SIZE];int readLen; int totalBodyLen = 0;long startTime = System.currentTimeMillis();HttpResponse response = httpClient.execute(request);/* TODO(Wenjie): HttpClient does not automatically handle the following codes* 301 Moved Permanently. HttpStatus.SC_MOVED_PERMANENTLY* 302 Moved Temporarily. HttpStatus.SC_MOVED_TEMPORARILY* 303 See Other. HttpStatus.SC_SEE_OTHER* 307 Temporary Redirect. HttpStatus.SC_TEMPORARY_REDIRECT* * We may want to fetch instead from the redirected page. */StatusLine statusLine = response.getStatusLine();if (statusLine != null) {statusCode = statusLine.getStatusCode();success = (statusCode == 200);}/* For HttpClient to work properly, we still want to consume the entire response even if* the status code is not 200 */HttpEntity responseEntity = response.getEntity(); originalBodyLen = responseEntity.getContentLength();long expectedResponseLen = HttpTask.MAX_HTTP_RESPONSE_SIZE;// getContentLength() returns negative number if body length is unknownif (originalBodyLen > 0) {expectedResponseLen = originalBodyLen;}if (responseEntity != null) {inputStream = responseEntity.getContent();while ((readLen = inputStream.read(readBuffer)) > 0 && totalBodyLen <= HttpTask.MAX_HTTP_RESPONSE_SIZE) {totalBodyLen += readLen;// Fill in the body to report up to MAX_BODY_SIZEif (body.remaining() > 0) {int putLen = body.remaining() < readLen ? body.remaining() : readLen; body.put(readBuffer, 0, putLen);}this.progress = (int) (100 * totalBodyLen / expectedResponseLen);this.progress = Math.min(Config.MAX_PROGRESS_BAR_VALUE, progress);broadcastProgressForUser(this.progress);}duration = System.currentTimeMillis() - startTime;}Header[] responseHeaders = response.getAllHeaders();if (responseHeaders != null) {headers = "";for (Header hdr : responseHeaders) {/** TODO(Wenjie): There can be preceding and trailing white spaces in* each header field. I cannot find internal methods that return the* number of bytes in a header. The solution here assumes the encoding* is one byte per character.*/originalHeadersLen += hdr.toString().length();headers += hdr.toString() + "\r\n";}}PhoneUtils phoneUtils = PhoneUtils.getPhoneUtils();MeasurementResult result = new MeasurementResult(phoneUtils.getDeviceInfo().deviceId,phoneUtils.getDeviceProperty(), HttpTask.TYPE, System.currentTimeMillis() * 1000,success, this.measurementDesc);result.addResult("code", statusCode);if (success) {result.addResult("time_ms", duration);result.addResult("headers_len", originalHeadersLen);result.addResult("body_len", totalBodyLen);result.addResult("headers", headers);result.addResult("body", Base64.encodeToString(body.array(), Base64.DEFAULT));}Log.i(SpeedometerApp.TAG, MeasurementJsonConvertor.toJsonString(result));return result; } catch (MalformedURLException e) {errorMsg += e.getMessage() + "\n";Log.e(SpeedometerApp.TAG, e.getMessage());} catch (IOException e) {errorMsg += e.getMessage() + "\n";Log.e(SpeedometerApp.TAG, e.getMessage());} finally {if (inputStream != null) {try {inputStream.close();} catch (IOException e) {Log.e(SpeedometerApp.TAG, "Fails to close the input stream from the HTTP response");}}if (httpClient != null) {httpClient.close();}}throw new MeasurementError("Cannot get result from HTTP measurement because " + errorMsg);}
二. 分析源碼中用到的 API?
1. HttpClient
(1) HttpClient 接口
接口介紹 : 這是一個 http 客戶端接口, 該接口中封裝了一系列的對象, 這些對象可以執(zhí)行 處理cookie 身份驗證 連接管理等 http 請求; 線程安全的客戶端都是基于 該接口 的實現(xiàn)和配置的;
接口方法 : 執(zhí)行 各種 HttpRequest, 獲取連接管理實例 , 獲取客戶端參數(shù);?
(2) AndroidHttpClient 類
類介紹 : 該類實現(xiàn)了 HttpClient 接口; 該類的本質是一個 DefaultHttpClient, 為Android 進行一些合理的配置 和 注冊規(guī)范, 創(chuàng)建該類實例的時候 使用 newInstance(String) 方法;
方法介紹 :?
execute(HttpUriRequest) :?
public HttpResponse execute (HttpUriRequest request)-- 作用 : 使用默認的上下文對象執(zhí)行 request請求;
-- 返回值 : 返回 request 的 response, 返回的是一個最終回應, 不會返回中間結果;
2. HttpUriRequest
(1) HttpUriRequest 接口
接口介紹 : 該接口實現(xiàn)了 HttpRequest 接口, 提供了方便的方法用于獲取 request 屬性, 例如 request的 uri 和 函數(shù)類型等;
方法介紹 :?
-- 中斷執(zhí)行 : 中斷 HttpRequest 的 execute()方法執(zhí)行;
-- 獲取uri : 獲取request請求的 uri;
-- 獲取方法 : 獲取 request 請求的 方法, 例如 GET, POST, PUT 等;
-- 查詢是否中斷 : 查詢是否執(zhí)行了 abort()方法;
(2) HttpGet 類
類介紹 : Http 的 get 方法, 請求獲取 uri 所標識的資源;
get方法 : 該方法會檢索 請求地址 識別出來所有信息, 如果請求地址 引用了一個值, 這個值需要計算獲得, 響應時返回的實體對應的是計算后的值;
方法特性 : getMethods 默認情況下會 遵循 http 服務器的重定向請求, 這個行為可以通過調用 setFollowRedirects(false) 關閉;
(3) HttpPost 類
類介紹 : Http 的 Post 方法, 用于請求在 uri 指定的資源后附加的新數(shù)據(jù);
Post方法功能 :?
-- 注釋資源 : 給存在的資源添加注釋;
-- 發(fā)送信息 : 向 公告牌, 新聞組, 郵件列表 等發(fā)送信息;
-- 數(shù)據(jù)傳輸 : 如 表單提交到一個數(shù)據(jù)處理程序;
-- 數(shù)據(jù)庫 : 通過一個附加操作 擴展數(shù)據(jù)庫;
(4) HttpHead 類
類介紹 : HEAD 方法等價于 GET 方法, 除了在響應中不能返回方法體;
元信息 : HEAD 請求 與 GET 請求 的響應的消息頭中的元信息是一樣的;
方法作用 : 這個方法可以用來獲取 請求中的元信息, 而不會獲取 請求數(shù)據(jù);?
常用用途 : 檢驗超文本的可用性, 可達性, 和最近的修改;
3. HttpResponse?
(1) HttpResponse 接口
接口介紹 : Http響應接口, 所有類型 HTTP 響應都應該實現(xiàn)這個接口;
方法介紹 :?
-- 獲取信息實體 : 如果有可能可以通過 setEntity()方法設置;
public abstract HttpEntity getEntity ()-- 獲取響應環(huán)境 : 根據(jù)環(huán)境確定 響應碼對應的原因;
public abstract Locale getLocale ()-- 獲取狀態(tài)行 : 獲取響應的狀態(tài)行
public abstract StatusLine getStatusLine ()-- 設置響應實體 :?
-- 設置響應環(huán)境 :?
-- 設置狀態(tài)行 :?
-- 設置原因短語 : 使用原因短語更新狀態(tài)行, 狀態(tài)行只能被更新, 不能顯示的設置 或者 在構造方法中設置;?
public abstract void setReasonPhrase (String reason)-- 設置狀態(tài)碼 : 更新狀態(tài)碼, 狀態(tài)碼只能更新, 不能顯示的設置 或者在構造方法中設置;
public abstract void setStatusCode (int code)
(2) BasicHttpResponse 類
類介紹 : Http 響應的基本實現(xiàn), 該實現(xiàn)可以被修改, 該實現(xiàn)確保狀態(tài)行的存在;
方法介紹 : 該類 實現(xiàn)了 HttpResponse 接口, 實現(xiàn)了上述接口中的所有方法;
4. StatusLine
(1) StatusLine 接口
接口介紹 : 該接口代表從 HTTP 服務器上返回的響應的狀態(tài)行;
方法介紹 :?
-- 獲取協(xié)議版本號 : getProtocalVersion();
-- 獲取原因短語 : getReasonPhrase();
-- 獲取狀態(tài)碼 : getStatusCode();
(2) BasicStatusLine
類介紹 : HTTP 服務器響應的狀態(tài)行;
方法介紹 : 實現(xiàn)了 StatusLine 的 3個 方法, 可以獲取 協(xié)議版本號, 原因短語, 狀態(tài)碼;
5. HttpEntity 接口
接口介紹 : HttpEntity 可以隨著 HTTP 消息發(fā)送和接收, 在一些 請求 和 響應中可以找到 HttpEntity, 這是可選的;
HttpEntity 分類 :?
-- 數(shù)據(jù)流 : 內容是從數(shù)據(jù)流中獲取的, 或者是在內存中生成的, 通常, 這類 實體是從連接中獲取的, 并且不可重復;
-- 獨立的 : 內容從內存中獲取, 或者從連接 或 其它 實體中獲取的, 可以重復;
-- 包裝 : 從其它實體中獲取的;
三. 網(wǎng)速測試流程
a. 創(chuàng)建 AndroidHttpClient : 使用 AndroidHttpClient 的 newInstance(str)方法, 創(chuàng)建該實例, 創(chuàng)建實例的時候, 傳入的字符串是 包名 + 版本號, 自己組織;
AndroidHttpClient httpClient = AndroidHttpClient.newInstance(packageName + " , " + version);
b. 創(chuàng)建 Http 請求 : 創(chuàng)建一個Get, Post 或者 Head 等類型的Http請求, 直接創(chuàng)建 HttpGet(url) 對象即可;
HttpRequestBase request = null;if (task.method.compareToIgnoreCase("head") == 0) {request = new HttpHead(urlStr);} else if (task.method.compareToIgnoreCase("get") == 0) {request = new HttpGet(urlStr);} else if (task.method.compareToIgnoreCase("post") == 0) {request = new HttpPost(urlStr);HttpPost postRequest = (HttpPost) request;postRequest.setEntity(new StringEntity(task.body));} else {// Use GET by defaultrequest = new HttpGet(urlStr);}
c. 創(chuàng)建緩沖區(qū)及相關數(shù)據(jù) : 創(chuàng)建一個 byte[] 緩沖區(qū), readLen 存儲當前緩沖區(qū)讀取的數(shù)據(jù), totalBodyLen 存儲所有的下載的數(shù)據(jù)個數(shù);
byte[] readBuffer = new byte[HttpTask.READ_BUFFER_SIZE];int readLen; int totalBodyLen = 0;
d. 執(zhí)行 Http 請求 : 調用 HttpClient 的 execute() 方法;
HttpResponse response = httpClient.execute(request);
e. 獲取響應的狀態(tài)行 : 調用 響應 HttpResponse 的 getStatusLine() 方法獲得;
StatusLine statusLine = response.getStatusLine();
f. 獲取狀態(tài)碼 : 通過調用 狀態(tài)行 statusLine 的 getStatusCode() 方法獲得;
if (statusLine != null) {statusCode = statusLine.getStatusCode();success = (statusCode == 200);}
g. 獲取響應實體 : 調用 響應 HttpResponse 的 getEntity() 方法獲得;
HttpEntity responseEntity = response.getEntity();
h. 獲取文件長度 : 調用 響應實體的 HttpEntity 的 getContentLength() 方法;
originalBodyLen = responseEntity.getContentLength();
i. 獲取輸入流 : 調用 響應實體 HttpEntity 的 getContent() 方法;
InputStream inputStream = responseEntity.getContent();
j. 從輸入流中讀取數(shù)據(jù)到緩沖區(qū) : 調用 輸入流的 read(buffer)方法, 該方法返回讀取的字節(jié)個數(shù);
readLen = inputStream.read(readBuffer)
注意 : 網(wǎng)速測試時要避免與硬盤的操作, 因此不能將數(shù)據(jù)村到磁盤上, 只將數(shù)據(jù)存儲到內存緩沖區(qū)中, 下一次緩沖區(qū)讀取的時候, 直接將上一次的緩沖區(qū)內容覆蓋擦除;
作者?:?萬境絕塵??
轉載請注明出處?:?http://blog.csdn.net/shulianghan/article/details/25996817
總結
以上是生活随笔為你收集整理的【Android 应用开发】Android 平台 HTTP网速测试 案例 API 分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【Android 应用开发】Androi
- 下一篇: 【Android 系统开发】Androi