Android 应用开发】Android 平台 HTTP网速测试 案例 API 分析

网友投稿 660 2022-05-29

转载请注明出处 : http://blog.csdn.net/shulianghan/article/details/25996817

工信部规定的网速测试标准 : 除普通网页测速采用单线程外,用户宽带接入速率测试应使用多线程(多TCP连接)HTTP下载进行测速,测试中使用的线程数量为N(N≥4)。

-- 建立连接 : 用户终端设备发起测试请求后,与测速平台建立 N 条 TCP 连接,并在每一条 TCP 连接上发送HTTP[GET]请求发起一次测试过程。

-- 请求文件 : 对每一个 HTTP[GET]请求,宽带接入速率测试平台以 HTTP 200 OK 响应,并开始传送测速文件。

-- 下载文件 : 对每一条连接,宽带接入速率测试平台持续从内存直接发送 64kByte 大小的内容。

-- 平均速率 : 从收到第 1 个 HTTP[GET]请求开始计时,宽带接入速率测试平台及客户端软件每隔 1s 统计已经发送的文件大小,计算数据平均传送速率,并在网页上或客户端中实时更新。

-- 实时速率 : 宽带接入速率测试平台同时计算每 1s 间隔内的实时数据传送速率。

-- 测量时间 : 15s 后宽带接入速率测试平台停止发送数据,计算第 5s 到第 15s 之间共计 10s 的平均速率及峰值速率,峰值速率为步骤 5)中的每秒实时速率的最大值.

一. 网速测试核心代码

从GitHub上下载的源码, 应该没有按照工信部的标准写的;

在 GitHub 上找到的网速测试的核心代码 :

-- 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 客户端接口, 该接口中封装了一系列的对象, 这些对象可以执行 处理cookie 身份验证 连接管理等 http 请求; 线程安全的客户端都是基于 该接口 的实现和配置的;

接口方法 : 执行 各种 HttpRequest, 获取连接管理实例 , 获取客户端参数;

(2) AndroidHttpClient 类

类介绍 : 该类实现了 HttpClient 接口; 该类的本质是一个 DefaultHttpClient, 为Android 进行一些合理的配置 和 注册规范, 创建该类实例的时候 使用 newInstance(String) 方法;

方法介绍 :

execute(HttpUriRequest) :

public HttpResponse execute (HttpUriRequest request)

-- 返回值 : 返回 request 的 response, 返回的是一个最终回应, 不会返回中间结果;

2. HttpUriRequest

(1) HttpUriRequest 接口

接口介绍 : 该接口实现了 HttpRequest 接口, 提供了方便的方法用于获取 request 属性, 例如 request的 uri 和 函数类型等;

方法介绍 :

-- 中断执行 : 中断 HttpRequest 的 execute()方法执行;

-- 获取uri : 获取request请求的 uri;

-- 获取方法 : 获取 request 请求的 方法, 例如 GET, POST, PUT 等;

-- 查询是否中断 : 查询是否执行了 abort()方法;

(2) HttpGet 类

类介绍 : Http 的 get 方法, 请求获取 uri 所标识的资源;

get方法 : 该方法会检索 请求地址 识别出来所有信息, 如果请求地址 引用了一个值, 这个值需要计算获得, 响应时返回的实体对应的是计算后的值;

方法特性 : getMethods 默认情况下会 遵循 http 服务器的重定向请求, 这个行为可以通过调用 setFollowRedirects(false) 关闭;

(3) HttpPost 类

类介绍 : Http 的 Post 方法, 用于请求在 uri 指定的资源后附加的新数据;

Post方法功能 :

-- 注释资源 : 给存在的资源添加注释;

-- 发送信息 : 向 公告牌, 新闻组, 邮件列表 等发送信息;

-- 数据传输 : 如 表单提交到一个数据处理程序;

-- 数据库 : 通过一个附加操作 扩展数据库;

(4) HttpHead 类

类介绍 : HEAD 方法等价于 GET 方法, 除了在响应中不能返回方法体;

元信息 : HEAD 请求 与 GET 请求 的响应的消息头中的元信息是一样的;

方法作用 : 这个方法可以用来获取 请求中的元信息, 而不会获取 请求数据;

常用用途 : 检验超文本的可用性, 可达性, 和最近的修改;

3. HttpResponse

(1) HttpResponse 接口

接口介绍 : Http响应接口, 所有类型 HTTP 响应都应该实现这个接口;

方法介绍 :

-- 获取信息实体 : 如果有可能可以通过 setEntity()方法设置;

public abstract HttpEntity getEntity ()

public abstract Locale getLocale ()

public abstract StatusLine getStatusLine ()

-- 设置响应环境 :

-- 设置状态行 :

-- 设置原因短语 : 使用原因短语更新状态行, 状态行只能被更新, 不能显示的设置 或者 在构造方法中设置;

public abstract void setReasonPhrase (String reason)

public abstract void setStatusCode (int code)

(2) BasicHttpResponse 类

类介绍 : Http 响应的基本实现, 该实现可以被修改, 该实现确保状态行的存在;

方法介绍 : 该类 实现了 HttpResponse 接口, 实现了上述接口中的所有方法;

4. StatusLine

(1) StatusLine 接口

接口介绍 : 该接口代表从 HTTP 服务器上返回的响应的状态行;

方法介绍 :

-- 获取协议版本号 : getProtocalVersion();

-- 获取原因短语 : getReasonPhrase();

-- 获取状态码 : getStatusCode();

(2) BasicStatusLine

类介绍 : HTTP 服务器响应的状态行;

方法介绍 : 实现了 StatusLine 的 3个 方法, 可以获取 协议版本号, 原因短语, 状态码;

5. HttpEntity 接口

接口介绍 : HttpEntity 可以随着 HTTP 消息发送和接收, 在一些 请求 和 响应中可以找到 HttpEntity, 这是可选的;

HttpEntity 分类 :

-- 数据流 : 内容是从数据流中获取的, 或者是在内存中生成的, 通常, 这类 实体是从连接中获取的, 并且不可重复;

-- 独立的 : 内容从内存中获取, 或者从连接 或 其它 实体中获取的, 可以重复;

-- 包装 : 从其它实体中获取的;

三. 网速测试流程

a. 创建 AndroidHttpClient : 使用 AndroidHttpClient 的 newInstance(str)方法, 创建该实例, 创建实例的时候, 传入的字符串是 包名 + 版本号, 自己组织;

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);

【Android 应用开发】Android 平台 HTTP网速测试 案例 API 分析

}

c. 创建缓冲区及相关数据 : 创建一个 byte[] 缓冲区, readLen 存储当前缓冲区读取的数据, totalBodyLen 存储所有的下载的数据个数;

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)

注意 : 网速测试时要避免与硬盘的操作, 因此不能将数据村到磁盘上, 只将数据存储到内存缓冲区中, 下一次缓冲区读取的时候, 直接将上一次的缓冲区内容覆盖擦除;

转载请注明出处 : http://blog.csdn.net/shulianghan/article/details/25996817

Android API HTTP

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Linux入门篇 —— Shell详解
下一篇:想读懂Linux内核,这一篇就足够!
相关文章