【Android 應(yīng)用開發(fā)】Android 平臺 HTTP網(wǎng)速測試 案例 API 分析
轉(zhuǎn)載請注明出處 :?http://blog.csdn.net/shulianghan/article/details/25996817
工信部規(guī)定的網(wǎng)速測試標(biāo)準(zhǔn) :?除普通網(wǎng)頁測速采用單線程外,用戶寬帶接入速率測試應(yīng)使用多線程(多TCP連接)HTTP下載進(jìn)行測速,測試中使用的線程數(shù)量為N(N≥4)。
-- 建立連接 : 用戶終端設(shè)備發(fā)起測試請求后,與測速平臺建立 N 條 TCP 連接,并在每一條 TCP 連接上發(fā)送HTTP[GET]請求發(fā)起一次測試過程。
-- 請求文件 : 對每一個 HTTP[GET]請求,寬帶接入速率測試平臺以 HTTP 200 OK 響應(yīng),并開始傳送測速文件。
-- 下載文件 : 對每一條連接,寬帶接入速率測試平臺持續(xù)從內(nèi)存直接發(fā)送 64kByte 大小的內(nèi)容。
-- 平均速率 : 從收到第 1 個 HTTP[GET]請求開始計時,寬帶接入速率測試平臺及客戶端軟件每隔 1s 統(tǒng)計已經(jīng)發(fā)送的文件大小,計算數(shù)據(jù)平均傳送速率,并在網(wǎng)頁上或客戶端中實(shí)時更新。
-- 實(shí)時速率 : 寬帶接入速率測試平臺同時計算每 1s 間隔內(nèi)的實(shí)時數(shù)據(jù)傳送速率。
-- 測量時間 : 15s 后寬帶接入速率測試平臺停止發(fā)送數(shù)據(jù),計算第 5s 到第 15s 之間共計 10s 的平均速率及峰值速率,峰值速率為步驟 5)中的每秒實(shí)時速率的最大值.
一. 網(wǎng)速測試核心代碼
從GitHub上下載的源碼, 應(yīng)該沒有按照工信部的標(biāo)準(zhǔn)寫的;
在 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 */
@Override
public 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 downloaded
HttpDesc task = (HttpDesc) this.measurementDesc;
String urlStr = task.url;
// TODO(Wenjie): Need to set timeout for the HTTP methods
httpClient = 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 default
request = 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 unknown
if (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_SIZE
if (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 身份驗(yàn)證 連接管理等 http 請求; 線程安全的客戶端都是基于 該接口 的實(shí)現(xiàn)和配置的;
接口方法 : 執(zhí)行 各種 HttpRequest, 獲取連接管理實(shí)例 , 獲取客戶端參數(shù);
(2) AndroidHttpClient 類
類介紹 : 該類實(shí)現(xiàn)了 HttpClient 接口; 該類的本質(zhì)是一個 DefaultHttpClient, 為Android 進(jìn)行一些合理的配置 和 注冊規(guī)范, 創(chuàng)建該類實(shí)例的時候 使用 newInstance(String) 方法;
方法介紹 :
execute(HttpUriRequest) :
public HttpResponse execute (HttpUriRequest request)
-- 返回值 : 返回 request 的 response, 返回的是一個最終回應(yīng), 不會返回中間結(jié)果;
2. HttpUriRequest
(1) HttpUriRequest 接口
接口介紹 : 該接口實(shí)現(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 所標(biāo)識的資源;
get方法 : 該方法會檢索 請求地址 識別出來所有信息, 如果請求地址 引用了一個值, 這個值需要計算獲得, 響應(yīng)時返回的實(shí)體對應(yīng)的是計算后的值;
方法特性 : getMethods 默認(rèn)情況下會 遵循 http 服務(wù)器的重定向請求, 這個行為可以通過調(diào)用 setFollowRedirects(false) 關(guān)閉;
(3) HttpPost 類
類介紹 : Http 的 Post 方法, 用于請求在 uri 指定的資源后附加的新數(shù)據(jù);
Post方法功能 :
-- 注釋資源 : 給存在的資源添加注釋;
-- 發(fā)送信息 : 向 公告牌, 新聞組, 郵件列表 等發(fā)送信息;
-- 數(shù)據(jù)傳輸 : 如 表單提交到一個數(shù)據(jù)處理程序;
-- 數(shù)據(jù)庫 : 通過一個附加操作 擴(kuò)展數(shù)據(jù)庫;
(4) HttpHead 類
類介紹 : HEAD 方法等價于 GET 方法, 除了在響應(yīng)中不能返回方法體;
元信息 : HEAD 請求 與 GET 請求 的響應(yīng)的消息頭中的元信息是一樣的;
方法作用 : 這個方法可以用來獲取 請求中的元信息, 而不會獲取 請求數(shù)據(jù);
常用用途 : 檢驗(yàn)超文本的可用性, 可達(dá)性, 和最近的修改;
3. HttpResponse
(1) HttpResponse 接口
接口介紹 : Http響應(yīng)接口, 所有類型 HTTP 響應(yīng)都應(yīng)該實(shí)現(xiàn)這個接口;
方法介紹 :
-- 獲取信息實(shí)體 : 如果有可能可以通過 setEntity()方法設(shè)置;
public abstract HttpEntity getEntity ()
public abstract Locale getLocale ()
public abstract StatusLine getStatusLine ()
-- 設(shè)置響應(yīng)環(huán)境 :
-- 設(shè)置狀態(tài)行 :
-- 設(shè)置原因短語 : 使用原因短語更新狀態(tài)行, 狀態(tài)行只能被更新, 不能顯示的設(shè)置 或者 在構(gòu)造方法中設(shè)置;
public abstract void setReasonPhrase (String reason)
public abstract void setStatusCode (int code)
(2) BasicHttpResponse 類
類介紹 : Http 響應(yīng)的基本實(shí)現(xiàn), 該實(shí)現(xiàn)可以被修改, 該實(shí)現(xiàn)確保狀態(tài)行的存在;
方法介紹 : 該類 實(shí)現(xiàn)了 HttpResponse 接口, 實(shí)現(xiàn)了上述接口中的所有方法;
4. StatusLine
(1) StatusLine 接口
接口介紹 : 該接口代表從 HTTP 服務(wù)器上返回的響應(yīng)的狀態(tài)行;
方法介紹 :
-- 獲取協(xié)議版本號 : getProtocalVersion();
-- 獲取原因短語 : getReasonPhrase();
-- 獲取狀態(tài)碼 : getStatusCode();
(2) BasicStatusLine
類介紹 : HTTP 服務(wù)器響應(yīng)的狀態(tài)行;
方法介紹 : 實(shí)現(xiàn)了 StatusLine 的 3個 方法, 可以獲取 協(xié)議版本號, 原因短語, 狀態(tài)碼;
5. HttpEntity 接口
接口介紹 : HttpEntity 可以隨著 HTTP 消息發(fā)送和接收, 在一些 請求 和 響應(yīng)中可以找到 HttpEntity, 這是可選的;
HttpEntity 分類 :
-- 數(shù)據(jù)流 : 內(nèi)容是從數(shù)據(jù)流中獲取的, 或者是在內(nèi)存中生成的, 通常, 這類 實(shí)體是從連接中獲取的, 并且不可重復(fù);
-- 獨(dú)立的 : 內(nèi)容從內(nèi)存中獲取, 或者從連接 或 其它 實(shí)體中獲取的, 可以重復(fù);
-- 包裝 : 從其它實(shí)體中獲取的;
三. 網(wǎng)速測試流程
a. 創(chuàng)建 AndroidHttpClient : 使用 AndroidHttpClient 的 newInstance(str)方法, 創(chuàng)建該實(shí)例, 創(chuàng)建實(shí)例的時候, 傳入的字符串是 包名 + 版本號, 自己組織;
AndroidHttpClient httpClient = AndroidHttpClient.newInstance(packageName + " , " + version);
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 default
request = new HttpGet(urlStr);
}
c. 創(chuàng)建緩沖區(qū)及相關(guān)數(shù)據(jù) : 創(chuàng)建一個 byte[] 緩沖區(qū), readLen 存儲當(dāng)前緩沖區(qū)讀取的數(shù)據(jù), totalBodyLen 存儲所有的下載的數(shù)據(jù)個數(shù);
byte[] readBuffer = new byte[HttpTask.READ_BUFFER_SIZE];
int readLen;
int totalBodyLen = 0;
HttpResponse response = httpClient.execute(request);
StatusLine statusLine = response.getStatusLine();
if (statusLine != null) {
statusCode = statusLine.getStatusCode();
success = (statusCode == 200);
}
HttpEntity responseEntity = response.getEntity();
originalBodyLen = responseEntity.getContentLength();
InputStream inputStream = responseEntity.getContent();
readLen = inputStream.read(readBuffer)
注意 : 網(wǎng)速測試時要避免與硬盤的操作, 因此不能將數(shù)據(jù)村到磁盤上, 只將數(shù)據(jù)存儲到內(nèi)存緩沖區(qū)中, 下一次緩沖區(qū)讀取的時候, 直接將上一次的緩沖區(qū)內(nèi)容覆蓋擦除;
轉(zhuǎn)載請注明出處?:?http://blog.csdn.net/shulianghan/article/details/25996817
Android API HTTP
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。