Android 平台 HTTP网速测试 案例 API 分析
作者 : 万境绝尘
转载请注明出处 :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请求;
-- 返回值 : 返回 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);
b. 创建 Http 请求 : 创建一个Get, Post 或者 Head 等类型的Http请求, 直接创建 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 default request = new HttpGet(urlStr); }
c. 创建缓冲区及相关数据 : 创建一个 byte[] 缓冲区, readLen 存储当前缓冲区读取的数据, totalBodyLen 存储所有的下载的数据个数;
byte[] readBuffer = new byte[HttpTask.READ_BUFFER_SIZE]; int readLen; int totalBodyLen = 0;
d. 执行 Http 请求 : 调用 HttpClient 的 execute() 方法;
HttpResponse response = httpClient.execute(request);
e. 获取响应的状态行 : 调用 响应 HttpResponse 的 getStatusLine() 方法获得;
StatusLine statusLine = response.getStatusLine();
f. 获取状态码 : 通过调用 状态行 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. 从输入流中读取数据到缓冲区 : 调用 输入流的 read(buffer)方法, 该方法返回读取的字节个数;
readLen = inputStream.read(readBuffer)
注意 : 网速测试时要避免与硬盘的操作, 因此不能将数据村到磁盘上, 只将数据存储到内存缓冲区中, 下一次缓冲区读取的时候, 直接将上一次的缓冲区内容覆盖擦除;
作者:万境绝尘
转载请注明出处:http://blog.csdn.net/shulianghan/article/details/25996817
相关推荐
在Android平台上,开发一款能够播放网络视频的播放器是一项常见的任务。这个名为"android播放器,可以播放网络视频"的项目就是基于IJK框架实现的,它不仅支持在线流媒体播放,还能处理本地视频文件,提供了丰富的...
这是"悬浮窗口"权限,自Android 6.0(API级别23)开始,应用在运行时也需要请求这一权限。 2. **悬浮窗布局**: 创建一个简单的XML布局文件,比如`float_win_layout.xml`,用于定义悬浮窗的UI元素,如TextView或...
在Android开发中,实时获取应用的网络...总的来说,这个项目为Android开发者提供了一个获取应用及设备网络速度的实践案例,通过学习和实践,我们可以提升对Android网络API的运用能力,从而优化应用程序的网络性能监控。
- **平台兼容性**:需支持多种操作系统,如iOS、Android等,涉及到的不仅是代码移植,还需针对不同平台进行深度优化。 - **性能优化**:包括硬件加速、代码优化、内存管理等,以提升浏览速度和响应时间。 - **资源...
基于风光负荷不确定性的微电网两阶段鲁棒优化模型:投资与运行成本的综合考虑及CCG算法的Matlab+Yamilp+CPLEX求解方法,考虑风光负荷的不确定性,构建了微电网两阶段鲁棒优化容量配置模型,第一阶段目标函数为微电网投资成本,第二阶段目标函数为微电网的运行成本。 采用CCG算法求解。 包含非线性项的线性化处理。 程序代码matlab+yamilp+CPLEX ,核心关键词: 微电网; 两阶段鲁棒优化; 容量配置模型; CCG算法; 线性化处理; MATLAB; YAMILP; CPLEX 用分号分隔每个关键词的结果为: 微电网; 两阶段鲁棒优化; 容量配置模型; CCG算法; 线性化处理; MATLAB; Yamilp; CPLEX,微电网鲁棒优化模型:两阶段成本最小化与线性化处理
西门子S7-200PLC在Z35摇臂钻床控制系统中的应用与组态王组态设计研究,98西门子S7-200PLC和组态王组态Z35摇臂钻床控制系统组态设计PLC设计 ,西门子S7-200PLC; 组态王组态; 摇臂钻床控制系统; PLC设计,西门子S7-200PLC与组态王Z35摇臂钻床控制系统组态设计
烟花代码编程python满屏-10.排队接水——我直接把水吸过来.py
基于三菱PLC的喷灌控制系统设计及组态画面实现,No.393 三菱PLC组态王组态画面基于PLC的喷灌控制设计灌溉控制 ,基于三菱PLC; 喷灌控制设计; 组态王组态画面; 灌溉控制。,基于三菱PLC的喷灌控制设计:组态王组态画面应用
基于Dijkstra算法的路径规划Matlab代码:读取黑白色地图并实现图像上的路径规划示例,读取黑白色地图,在图片上进行路径规划matlab代码 路径规划算法基于读图识别的Dijkstra算法 随意设置坐标,规划路径 示例如下 ,读取地图; 路径规划算法; Dijkstra算法; 坐标设置; 规划路径,基于Dijkstra算法的Matlab黑白色地图路径规划代码
融合空间特征的债券图表数据文本检测方法研究.pdf
基于粒子群优化与遗传算法的高速列车横向悬挂模糊PID控制技术研究:s函数建模与仿真分析详解文件,基于粒子群 遗传算法的高速列车横向悬挂模糊PID控制-s函数搭建。 赠word详解文件 ,基于粒子群; 遗传算法; 高速列车横向悬挂; 模糊PID控制; s函数搭建,基于遗传算法的高速列车悬挂PID模糊控制的S函数构建方案
一种基于Retinex的非线性彩色图像增强算法.pdf
基于因式分解的逆合成孔径雷达三维成像技术及序列因子分解法研究,逆合成孔径雷达三维成像,因式分解法,序列因子分解法。 此部分只做了因式分解的工作,假设散射点已经准确提取并得到散射点坐标矩阵,利用因式分解恢复目标三维结构 ,逆合成孔径雷达三维成像; 因式分解法; 序列因子分解; 散射点提取; 散射点坐标矩阵; 恢复目标三维结构。,利用因式分解恢复三维结构:逆合成孔径雷达三维成像技术
MATLAB R2021b环境下深度学习驱动的车道线检测算法研究与应用,MATLAB环境下一种基于深度学习的车道线检测方法 算法运行环境为matlab r2021b,执行基于深度学习的车道线检测。 if batch ~= numBatches lastFrameIdx = miniBatchSize*batch; else 压缩包=数据+程序 ,核心关键词如下: MATLAB环境; 深度学习; 车道线检测; 算法运行环境; 批处理; 压缩包 以上关键词用分号分隔为: MATLAB环境; 深度学习; 车道线检测; 算法运行环境r2021b; 批处理(batch~numBatches); 压缩包(数据+程序) 注意:最后一个关键词“压缩包”并不是直接从您提供的信息中提炼出来的,而是根据您提到的“压缩包=数据+程序”这一句推断出来的。如果这不是您想要的核心关键词,请告诉我,我会进行相应的调整。,MATLAB深度学习车道线检测算法压缩包
西门子S7-200PLC在物料自动称量控制系统中的应用:基于称重配料混合程序的组态设计与王设计PLC程序设计,74基西门子S7-200PLC的物料自动称量控制系统称重配料物料混合程序组态设计组态设计组态王设计plc程序设计 ,核心关键词:西门子S7-200PLC;自动称量控制系统;物料混合程序;组态设计;组态王设计;PLC程序设计。,西门子S7-200PLC物料自动称量控制系统称重配料程序组态设计
优化无人机侦查与目标搜索路径规划:对未知区域进行覆盖并精确寻靶的Matlab代码解析与实践指南。,无人机覆盖搜索路径规划 无人机搜索目标路径规划 无人机侦查路径规划 对未知区域进行覆盖搜索,并且寻找目标 matlab代码有详细注释,可快速上手。 ,核心关键词:无人机; 覆盖搜索路径规划; 搜索目标路径规划; 侦查路径规划; 未知区域覆盖搜索; MATLAB代码注释。,**无人机未知区域覆盖搜索及目标路径规划Matlab代码详解**
shp格式,可直接导入arcgis使用
基于博途软件编程的11层电梯控制系统:文档齐全,优质售后保障的智能运行体验,基于PLC的单部11层电梯控制系统,采用博途软件编写提供画面,文档(含接线图,流程图,IO分配表)及优质的后服务。 最终运行效果,详见上方演示视频 ,核心关键词:PLC控制;11层电梯;博途软件;画面编写;文档资料;接线图;流程图;IO分配表;售后服务;演示视频。,"博途软件控制下的11层电梯系统,高效运行效果详见演示视频"
"飞剪追剪程序:PLC与伺服同步控制完整指南,适合新手学习与参考,包含PLC程序、触摸屏程序及CAD电路图纸",飞剪追剪程序plc程序伺服程序 同步控制 适合新手学习参考 包含PLC程序+触摸屏程序+CAD电路图纸。 ,飞剪追剪程序; PLC程序; 伺服程序; 同步控制; 新手学习参考; PLC程序+触摸屏程序; CAD电路图纸,适合新手的飞剪追剪程序全套教程:PLC+伺服同步控制
使用Matlab编程:无迹卡尔曼滤波算法(UKF)的编写及其在电池SOC估计中的应用,噪声系数自适应优化方案的研究。,使用matlab编写m脚本,编写无迹卡尔曼滤波算法(UKF)估计电池SOC,注释清晰。 卡尔曼滤波算法(EKF)锂电池SOC估计,噪声系数自适应 Matlab ,matlab; 无迹卡尔曼滤波算法(UKF); 电池SOC估计; 注释清晰; 噪声系数自适应。,Matlab无迹卡尔曼滤波(UKF)用于电池SOC估计的代码实现