`

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网速测试工具.zip

    在Android平台上,网络速度的测试对于用户来说是至关重要的,无论是检查网络连接的质量,还是调试应用程序的性能,都需要可靠的网速测试工具。本压缩包"Android网速测试工具.zip"提供了一个全面的解决方案,包含了两...

    Android判断网速(基于Android Studio)

    首先,我们需要理解Android中的网络API,然后创建一个网络监控器来获取实时的网速信息。 1. **Android网络API** Android提供了多种API来检测和管理网络连接。`ConnectivityManager`是Android系统服务之一,它负责...

    FR1128-Android仪表盘网速测试源码_测速_仪表盘_android_

    标题中的"FR1128-Android仪表盘网速测试源码"暗示这是一个关于Android平台上的应用程序,专门用于网络速度的实时监测和显示。这个应用可能包含了一个自定义的仪表盘视图,能够以图形化的方式展示下载和上传速度,为...

    Android例子源码--炫酷的仪表盘网速测试源码

    在Android开发中,创建一个炫酷的仪表盘网速测试应用是一个吸引用户并提升用户体验的好方法。本示例源码提供了这样一个功能,它利用了Android的图形库和网络监测技术来实时显示网络速度,使用户可以直观地了解他们的...

    Android例子源码炫酷的仪表盘网速测试

    在Android开发中,创建炫酷的仪表盘网速测试是一个吸引用户注意力并提供直观网络速度反馈的有效方式。这个"Android例子源码炫酷的仪表盘网速测试"项目旨在教你如何利用Android SDK来构建这样的功能。 首先,我们...

    Android 获取当前网速质量调整网络请求

    在Android开发中,获取当前网速质量和调整网络请求是至关重要的任务,这直接影响到应用程序的用户体验和性能。本文将深入探讨这两个主题,并提供相应的实践方法。 首先,我们要了解如何在Android系统中检测网络速度...

    Android 手机网速测试工具

    最佳手机网速测试工具,首款支持5G测速的应用,全球排名第一的网络速度测试网站Speedtest.net安卓客户端,支持查看Ping值、下载速度、上传速度,实时图表显示测试结果,排除故障以分析真实网速,跟踪过往网络测试...

    联想工程师专用小工具 网速测试工具V3.90.1

    联想工程师专用小工具 网速测试工具V3.90.1联想工程师专用小工具 网速测试工具V3.90.1联想工程师专用小工具 网速测试工具V3.90.1联想工程师专用小工具 网速测试工具V3.90.1联想工程师专用小工具 网速测试工具V3.90.1...

    网速测试.rar网速测试.rar

    《全面解析:网速测试与avltool.exe工具的运用》 在互联网日益普及的今天,了解和掌握网络速度测试的方法及工具变得至关重要。无论是下载大文件、在线视频播放,还是进行远程协作,都需要一个稳定且快速的网络环境...

    基于QT的网速测试界面

    【基于QT的网速测试界面】是一个利用QT框架开发的应用程序,它专注于提供一个直观、动态的用户界面来测量网络速度。QT是一个流行的开源C++图形用户界面工具包,广泛用于跨平台应用开发,包括Windows、Linux、Mac OS...

    js简单网速测试方法完整实例

    JavaScript为网页提供了原生的网速测试能力,虽然受到浏览器环境的限制,但通过一些技巧仍然可以实现简单的网速测试功能。本文将介绍一种基于图片下载的JavaScript网速测试方法,并通过一个完整实例来展示其具体实现...

    基于QT的网速测试控件

    **基于QT的网速测试控件** 在计算机编程领域,特别是图形用户界面(GUI)开发中,QT是一个非常流行的开源框架。它提供了丰富的库和工具,使得开发者能够创建跨平台的应用程序,支持Windows、Linux、macOS等多种操作...

    网速测试HD v2.0

    网速测试HD v2.0更新时间:2015-02-05资费提示:免费版当前版本:2.0软件语言:中文软件类别:系统测试软件大小:1.53 MB适用固件:3.0及更高固件内置广告:没有广告适用平台:Android 软件介绍 网速测试是一款...

    网速测试(超好用)

    "网速测试(超好用)"这个程序显然设计用于便捷地进行此类测试。以下将详细介绍网络速度测试的相关知识点。 1. **网速测试的意义**:网络速度测试主要用来测量上传速度和下载速度,这直接影响到我们浏览网页、在线...

    网速测试工具

    网速测试工具是网络性能监测和诊断的重要辅助软件,它能够帮助用户了解自己网络的上传速度、下载速度以及网络带宽的使用情况。在信息化社会,网络速度对我们的日常生活和工作有着重大影响,无论是在线办公、游戏娱乐...

    Android网速监测demo

    在Android平台上,开发一款能够实时监测并显示网络速度的应用是一个常见的需求,这有助于用户了解他们的设备在使用数据时的表现。这个"Android网速监测demo"就是为此目的而设计的。下面我们将深入探讨这个主题,包括...

    局域网-网速测试工具.exe

    局域网-网速测试工具

Global site tag (gtag.js) - Google Analytics