Android应用开发-网络编程(二)(重制版)

Apache HttpClient框架

GET方式请求提交数据

  1. 创建一个HttpClient

HttpClient hc = new DefaultHttpClient();

  2. 创建一个HttpGet,要提交给服务器的数据已经拼接在path中

HttpGet hg = new HttpGet(path);

  3. 使用HttpClient对象发送GET请求,建立连接,返回响应头对象

HttpResponse hr = hc.execute(hg);

  4. 拿到响应头中的状态行,获取状态码,如果为200则说明请求成功

if(hr.getStatusLine().getStatusCode() == 200){
    // 拿到响应头中实体里的内容,其实就是服务器返回的输入流
    InputStream is = hr.getEntity().getContent();
    String text = Utils.getTextFromStream(is);
}

POST方式请求提交数据

  1. 创建一个HttpClient

HttpClient hc = new DefaultHttpClient();

  2. 创建一个HttpPost,构造方法的参数就是网址

HttpPost hp = new HttpPost(path);

  3. 往HttpPost对象里放入要提交给服务器的数据

// 要提交的数据以键值对的形式封装在BasicNameValuePair对象中
BasicNameValuePair bnvp = new BasicNameValuePair("name", name);
BasicNameValuePair bnvp2 = new BasicNameValuePair("pass", pass);
List<NameValuePair> parameters = new ArrayList<NameValuePair>();
parameters.add(bnvp);
parameters.add(bnvp2);
// 要提交的数据都已经在集合中了,把集合传给实体对象,并指定进行URL编码的码表
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(parameters, "utf-8");
// 设置POST请求对象的实体,其实就是把要提交的数据封装至POST请求的输出流中
hp.setEntity(entity);

  4. 使用HttpClient对象发送POST请求,建立连接,返回响应头对象

HttpResponse hr = hc.execute(hp);

  5. 拿到响应头中的状态行,获取状态码,如果为200则说明请求成功

if(hr.getStatusLine().getStatusCode() == 200){
    // 拿到响应头中实体里的内容,其实就是服务器返回的输入流
    InputStream is = hr.getEntity().getContent();
    String text = Utils.getTextFromStream(is);
}

android-async-http框架(基于Apache HttpClient框架封装)

android-async-http框架是一个异步的HttpClient框架,不需要我们自己创建子线程,框架会为我们创建子线程去执行网络的交互

GET方式请求提交数据

  1.创建异步HttpClient

AsyncHttpClient ahc = new AsyncHttpClient();

  2. 发送GET请求提交数据,提交的数据拼接在path上

ahc.get(path, new MyResponseHandler());

POST方式请求提交数据

  1. 创建异步HttpClient

AsyncHttpClient ahc = new AsyncHttpClient();

  2. 把要提交的数据封装至RequestParams

RequestParams params = new RequestParams();
params.add("name", name);
params.add("pass", pass);

  3. 发送POST请求提交数据

ahc.post(path, params, new MyResponseHandler());

响应处理器AsyncHttpResponseHandler

class MyResponseHandler extends AsyncHttpResponseHandler{

        // 请求服务器成功时(响应码是200)回调此方法
        @Override
        public void onSuccess(int statusCode, Header[] headers,
                byte[] responseBody) {
            // responseBody的内容就是服务器返回的数据
            Toast.makeText(MainActivity.this, new String(responseBody,"GBK"), Toast.LENGTH_SHORT).show();

        }

        // Http请求失败(返回码不为200),系统回调此方法
        @Override
        public void onFailure(int statusCode, Header[] headers,
                byte[] responseBody, Throwable error) {
            Toast.makeText(MainActivity.this, new String(responseBody,"GBK"), Toast.LENGTH_SHORT).show();

        }

    }

多线程下载

服务器CPU分配给每个线程的时间片相同,服务器带宽平均分配给每个线程,所以客户端开启的线程越多,就能抢占到更多的服务器资源。实际上并不是客户端并发的下载线程越多,程序的下载速度就越快,因为当客户端开启太多的并发线程之后,应用程序需要维护每条线程的开销、线程同步的开销,这些开销反而会导致下载速度降低;并且无论开多少个线程抢占服务器资源,下载带宽也不会超过客户端的物理带宽

主线程首先发送Http GET请求确定每个线程下载哪部分数据

  • 获取资源文件总大小,然后创建大小一致的临时文件

    String path = "http://dldir1.qq.com/music/clntupate/QQMusic.apk";
    URL url = new URL(path);
    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    conn.setRequestMethod("GET");
    conn.setConnectTimeout(5000);
    conn.setReadTimeout(5000);
    if(conn.getResponseCode() == 200){
        int length = conn.getContentLength();  // 获得服务器流中数据的长度
        RandomAccessFile raf = new RandomAccessFile(getFileName(path), "rwd");// 创建一个临时文件存储下载的数据
        raf.setLength(length);    // 设置临时文件的大小
        raf.close();
  • 计算每个线程下载多少数据

    int blockSize = length / THREAD_COUNT;
  • 计算每个线程下载数据的开始位置和结束位置,然后开启下载子线程
    for(int id = 0; id < threadCount; id++){
        // 计算每个线程下载数据的开始位置和结束位置
        int startIndex = id * blockSize;
        int endIndex = (id+1) * blockSize - 1;
        // 如果是最后一个线程,结束位置为资源文件的结尾
        if(id == threadCount - 1){
            endIndex = length - 1;
        }
        // 开启线程,按照计算出来的开始结束位置开始下载数据
        new DownLoadThread(startIndex, endIndex, id).start();
    }

每个下载线程再次发送Http GET请求,请求自己负责下载的那部分数据

  • 请求自己负责的那部分数据,同步写入到临时文件相应的位置

    String path = "http://dldir1.qq.com/music/clntupate/QQMusic.apk";
    URL url = new URL(path);
    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    conn.setRequestMethod("GET");
    conn.setConnectTimeout(5000);
    conn.setReadTimeout(5000);
    conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);// 设置本次Http请求的数据区间
    conn.connect();
    //请求部分数据,成功的响应码是206
    if(conn.getResponseCode() == 206){
        InputStream is = conn.getInputStream();    // 流里此时只有1/ThreadCount资源文件里的数据
        RandomAccessFile raf = new RandomAccessFile(getFileName(path), "rwd");
        raf.seek(startIndex);    // 把文件的写入位置移动至startIndex
        byte[] b = new byte[1024];
        int len;
        while((len = is.read(b)) != -1){
            raf.write(b, 0, len);
        }
        raf.close();
    }

带断点续传的多线程下载

  • 每个下载线程定义了一个threadProgress成员变量记录当前线程下载的进度,线程在往资源临时文件中写入数据的同时记录下threadProgress,并存入进度缓存文件

    while((len = is.read(b)) != -1){
        raf.write(b, 0, len);
        threadProgress += len;
        RandomAccessFile progressRaf = new RandomAccessFile(progressFile, "rwd");
        progressRaf.write((threadProgress + "").getBytes());
        progressRaf.close();
    }
  • 下次下载开始时,先读取缓存文件中的值,得到的值就是该线程新的开始位置
    FileInputStream fis = new FileInputStream(progressFile);
    BufferedReader br = new BufferedReader(new InputStreamReader(fis));
    threadProgress = Integer.parseInt(br.readLine());// 从进度临时文件中读取出上一次下载的总进度
    startIndex += threadProgress;         // 与原本的开始位置相加,得到新的开始位置,完成断点续传
    fis.close();

 

  • 所有线程都下载完毕之后,删除缓存文件

    finishedThread++;
    if(finishedThread == threadCount){
        for(int i = 0; i < threadCount; i++){
            File f = new File(target, i + ".txt");
            f.delete();
        }
    }

手机版的断点续传多线程下载器

用进度条显示下载进度

  • 拿到下载文件总大小时,设置进度条的最大值

    pb.setMax(length);    // 设置进度条的最大值
  • 进度条需要显示所有线程的整体下载进度,所以各条线程每下载一次,就要把新下载的长度加入进度条
    • 定义一个int全局变量,记录三条线程的总下载长度

      int totalProgress;
    • 刷新进度条
      while((len = is.read(b)) != -1){
          raf.write(b, 0, len);
          // 每次读取流里数据之后,把每次读取数据的长度显示至进度条
          totalProgress += len;
          pb.setProgress(totalProgress);
  • 每次断点下载时,从新的开始位置开始下载,进度条也要从新的位置开始显示,在读取缓存文件获取新的下载开始位置时,也要处理进度条进度
    FileInputStream fis = new FileInputStream(progressFile);
    BufferedReader br = new BufferedReader(new InputStreamReader(fis));
    // 从进度临时文件中读取出上一次下载的总进度
    // 然后与原本的开始位置相加,得到新的开始位置,完成断点续传
    threadProgress = Integer.parseInt(br.readLine());
    startIndex += threadProgress;
    // 把上次多线程下载的总进度显示至进度条
    totalProgress += threadProgress;
    pb.setProgress(totalProgress);

添加文本框显示百分比进度

tv.setText((long) pb.getProgress() * 100 / pb.getMax() + "%");// 文本进度与进度条是同步的,转换成long类型防止整型溢出

使用开源框架xUtils下载文件

开源框架xUtils是基于原来的开源框架afinal开发的,主要有四大模块,其中HttpUtils模块支持断点续传,随时停止下载任务,开始任务

  1. 创建HttpUtils对象

    HttpUtils http = new HttpUtils();
  2. 下载文件
    http.download(path,     // 下载地址
            target,         // 下载数据保存的路径和文件名
            true,           // 是否支持断点续传
            true, // 如果请求地址中没有文件名,则文件名在响应头中,下载完成后自动重命名
            new RequestCallBack<File>() {// 侦听下载状态
    
        // 下载成功后回调
        @Override
        public void onSuccess(ResponseInfo<File> responseInfo) {
            Toast.makeText(MainActivity.this, responseInfo.result.getPath(), Toast.LENGTH_SHORT).show();
        }
    
        // 下载失败时回调,比如文件已经下载、没有网络权限、文件访问不到,方法传入一个字符串s告知失败原因
        @Override
        public void onFailure(HttpException e, String s) {
            tv_failure.setText(s);
        }
    
        // 在下载过程中不断的调用,用于刷新进度条
        @Override
        public void onLoading(long total, long current, boolean isUploading) {
            super.onLoading(total, current, isUploading);
            pb.setMax((int) total);      // 设置进度条总长度
            pb.setProgress((int) current);// 设置进度条当前进度
            tv_progress.setText(current * 100 / total + "%");// 文本进度
        }
    });
时间: 2024-10-20 11:46:59

Android应用开发-网络编程(二)(重制版)的相关文章

Android应用开发-网络编程(一)(重制版)

网络图片查看器 1. 确定图片的网址 2. 发送http请求 URL url = new URL(address); // 获取客户端和服务器的连接对象,此时还没有建立连接 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); // 设置请求方法,注意必须大写 conn.setRequestMethod("GET"); // 设置连接和读取超时 conn.setConnectTimeout(5000); c

iOS网络编程开发—网络编程基础

iOS网络编程开发—网络编程基础 一.网络编程 1.简单说明 在移动互联网时代,移动应用的特征有: (1)几乎所有应用都需要用到网络,比如QQ.微博.网易新闻.优酷.百度地图 (2)只有通过网络跟外界进行数据交互.数据更新,应用才能保持新鲜.活力 (3)如果没有了网络,也就缺少了数据变化,无论外观多么华丽,终将变成一潭死水 移动网络应用 = 良好的UI + 良好的用户体验 + 实时更新的数据 新闻:网易新闻.新浪新闻.搜狐新闻.腾讯新闻 视频:优酷.百度视频.搜狐视频.爱奇艺视频 音乐:QQ音乐

Android之Http网络编程(一)

Android应用作为一个客户端程序绝大部分都是需要进行网络请求和访问的,而http通信是一种比较常见并常用的通信方式. 在Android中http网络编程中有两种实现方式,一种是使用HttpURLConnection,另一种就是使用HttpClient. 这两种实现方式的大体过程都是: Android客户端向服务器发出请求. 服务端接收请求并响应. 服务端返回数据给客户端. 在Http通信中有POST和GET两种方式,其不同之处在于GET方式可以获得静态页面,同时可以将请求参数放在URL字符串

Android之Http网络编程(三)

在前面两篇博客<Android之Http网络编程(一)>.<Android之Http网络编程(二)>中,简单的介绍了对网页的请求和客户端与服务端的简单的参数交互.那么,这一篇博客就来认识一下Android客户端获取服务端返回的数据. 大家都知道客户端与服务端的交互大体过程如下: Android客户端向服务器发出请求. 服务端接收请求并响应. 服务端返回数据给客户端. 对于Android客户端来说,最重要的也就莫过于获取服务端返回的数据来展示了. 那么,首先我们要知道服务端返回的数据

winform网络编程(二)

mnesia在频繁操作数据的过程可能会报错:** WARNING ** Mnesia is overloaded: {dump_log, write_threshold},可以看出,mnesia应该是过载了.这个警告在mnesia dump操作会发生这个问题,表类型为disc_only_copies .disc_copies都可能会发生. 如何重现这个问题,例子的场景是多个进程同时在不断地mnesia:dirty_write/2 mnesia过载分析 1.抛出警告是在mnesia 增加dump

安卓第八天笔记--网络编程二

安卓第八天笔记--网络编程二 1.网络图片查看器 /** * 网络图片查看器 * 1.获取输入的URL地址,判断是否为空 * 2.建立子线程,获取URl对象new URL(path) * 3.打开连接获取HttpURLConnection conn = (HttpURLConnection) url.openConnection(); * 4.设置连接超时时间conn.setConnectionTimeOut(5000)毫秒 * 5.设置请求方式setRequestMethod * GET或者P

ios开发 网络编程浅析(一)

iphone包含了很多框架和库,从底层的套接字到不同层次的封装,可以方便地给程序添加网络功能. (1)BSD套接字.最底层的套接字,这是Unix网络开发常用的API.如果从其他系统移植程序,而程序用的是BSD套接字,那么网络部分可以继续使用这些API. (2)CFNetwork framework .CFNetwork 也是比较底层的, 是对BSD套接字的一个扩展 .它是一个C语言的库,它是基于BSD套接字,提供了对网络协议的抽象.这些抽象使得用户更容易地操作套接字.处理网络的各种连接..它集成

Linux网络编程(二)

服务套和客户机的信息函数 1.字节转换函数 在网络上面有着许多类型的机器,这些机器在表示数据的字节顺序是不同的, 比如i386芯片是低字节在内存地址的低端,高字节在高端,而alpha芯片却相反. 为了统一起来,在Linux下面,有专门的字节转换函数. unsigned long int htonl(unsigned long int hostlong)     unsigned short int htons(unisgned short int hostshort)     unsigned

Android 自定义控件开发入门(二)

上一次我们讲了一堆实现自定义控件的理论基础,列举了View类一些可以重写的方法,我们对这些方法的重写是我们继承View类来派生自定义控件的关键 我通过一个最简单的例子给大家展示了这一个过程,无论是多么复杂的自定义控件,思路总是这样子的,但是因为我们仅仅重写了onDraw方法使得大家觉得怪怪的,作为一个控件,我们居然还要为了他的实现为其增加麻烦的监听,这就不能叫做控件了. 下面再给大家介绍一个经常重写的方法法:publicboolean onTouchEvent (MotionEvent even