get和post请求(HttpUrlConnection、HttpClient、AsyncHttpClient)多线程下载,断点续传

自定义控件:封装网络图片加载功能

/* 根据提供的 urlStr 自动联网下载图片,并显示出来 */
public void setImageUrl(final String urlStr) {
    new Thread() {
        public void run() {

            try {
                // 1. 创建 Url
                URL url = new URL(urlStr);
                // 2. 获取网络连接对象
                HttpURLConnection connection = (HttpURLConnection) url.openConnection();
                // 3. 设置连接参数
                connection.setConnectTimeout(5000);
                connection.setRequestMethod("GET");

                // 4. 获取服务器连接状态码
                int code = connection.getResponseCode();
                if (code == 200) {
                    // 5. 连接成功获取服务器返回值
                    InputStream in = connection.getInputStream();

                    // 6. 将服务器返回值转换成图片
                    Bitmap bitmap = BitmapFactory.decodeStream(in);

                    // 将图片显示出来
                    Message msg = Message.obtain();
                    msg.obj = bitmap;
                    handler.sendMessage(msg);

                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        };
    }.start();
}

Handler handler = new Handler(){
    public void handleMessage(android.os.Message msg) {
        // 在主线程更新图片
        Bitmap bitmap = (Bitmap) msg.obj;
        setImageBitmap(bitmap);// 当前类就是要显示图片的控件
    };
};
<!-- 自定义控件要使用完整的包名来引用 -->
<com.itheima.netpictureviewer.HeimaSmartImageView
    android:id="@+id/iv_content"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:src="@drawable/ic_launcher" />

网页上实现 get 和 post 登陆的区别

get 提交的参数在 URL 里,post 提交的参数在请求体里

  • 两者传递的数据大小也有区别
    • get 在 IE浏览器上限制1KB,在 Http 协议 4KB
    • post 以数据流的形式传输,没有数据大小限制
  • LocalHost 和127.0.0.1
    • 代表了当前设备
    • 在 window 浏览器访问,代表了当前的笔记本电脑
    • 在 Android 里访问,代表了模拟器或者手机
    • 如果在 Android 项目的代码里使用 localhost ,会导致这个代码去 模拟器里寻找服务器,肯定会连接失败

使用 HttpUrlConnection 实现 get 请求登陆

    /** 使用 get 请求将账号密码发送给服务器,并获取服务器返回的结果 */
    public void click1(View view) {
        // 获取用户输入的账号密码
        final String username = et_username.getText().toString();
        final String password = et_password.getText().toString();

        // 去除空字符串"  abc  " -- > abc  
//      username = username.trim();

        // 验证空字符串
        if (TextUtils.isEmpty(username) || TextUtils.isEmpty(password)) {
            Toast.makeText(MainActivity.this, "账号密码不能为空", 0).show();
            return;// 如果账号密码为空,则不再往下处理
        }

        // 提交账号密码到服务器,并获取服务器的返回值
        new Thread() {
            public void run() {
                // [GET 请求] 将请求参数拼接到 URL 地址上
                String urlStr = "http://192.168.67.62:8080/web/LoginServlet?username="+username+"&password="+password;

                try {
                    // 1. 创建 Url 
                    URL url = new URL(urlStr);
                    // 2. 获取网络连接对象
                    HttpURLConnection connection = (HttpURLConnection) url.openConnection();
                    // 3. 设置连接参数
                    connection.setConnectTimeout(5000);
                    connection.setRequestMethod("GET");

                    // 4. 获取服务器连接状态码
                    int code = connection.getResponseCode();
                    if (code == 200) {
                        // 5. 连接成功获取服务器返回值
                        InputStream in = connection.getInputStream();

                        // 6. 将服务器返回值转换成字符串
                        String result = StreamUtils.readStream(in);
//                      System.out.println(result);

                        // 7. 使用吐司来显示返回结果
                        showToast(result);

                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

        }.start();
    }

使用 HttpUrlConnection 实现 post 请求登陆

// [POST 请求] URL 地址上不包含请求参数
String urlStr = "http://192.168.67.62:8080/web/LoginServlet";
String body = "username="+username+"&password="+password;

try {
    // 1. 创建 Url 
    URL url = new URL(urlStr);
    // 2. 获取网络连接对象
    HttpURLConnection connection = (HttpURLConnection) url.openConnection();
    // 3. 设置连接参数
    connection.setConnectTimeout(5000);
    connection.setRequestMethod("POST");// [POST 请求]需要是使用 POST 方式请求

    //[POST 请求] 填充请求体数据
    connection.setDoOutput(true);// 打开输出开关
    OutputStream out = connection.getOutputStream();// 获取请求体的输出流
    out.write(body.getBytes());// 将请求体的数据以二进制的形式发送到服务器
    out.close();

    // 4. 获取服务器连接状态码
  • 封装 Toast 方法
    /** 在主线程弹出吐司 */ 
    private void showToast(final String result) {
        runOnUiThread(new Runnable() {
            public void run() {
                Toast.makeText(MainActivity.this, "服务器返回:"+result, 0).show();
            }
        });
    };

乱码问题的解决

获取服务器的数据乱码。提交给服务器的数据乱码。

  • 获取到服务器的数据是乱码

    • 服务器发送的编码和客户端解析的编码不一致
    • 解决方式就是:服务器发送UTF-8,客户端就可以直接解析。服务器按照默认的GBK发送,客户端按照 GBK 解析。
  • 提交给服务器的数据乱码

    // [处理乱码] 
    String encodeusername = URLEncoder.encode(username, "UTF-8");

使用 HttpClient 实现 get 和 post 提交数据

  • HttpClient 是封装了Http请求的工具包。在 Android2.3 被谷歌工程师引入到了 Android SDK。在Android6.0 被移除了。
  • 要求能够看懂课堂的代码,进公司之后能够项目的代码。
  • get请求
    /** 使用 get 请求将账号密码发送给服务器,并获取服务器返回的结果 */
    public void click1(View view) {
        // 获取用户输入的账号密码
        final String username = et_username.getText().toString();
        final String password = et_password.getText().toString();

        // 去除空字符串"  abc  " -- > abc  
//      username = username.trim();

        // 验证空字符串
        if (TextUtils.isEmpty(username) || TextUtils.isEmpty(password)) {
            Toast.makeText(MainActivity.this, "账号密码不能为空", 0).show();
            return;// 如果账号密码为空,则不再往下处理
        }

        // 提交账号密码到服务器,并获取服务器的返回值
        new Thread() {
            public void run() {

                try {
                    // [处理乱码] 
                    String encodeusername = URLEncoder.encode(username, "UTF-8");
                    String encodepassword = URLEncoder.encode(password, "UTF-8");
                    // [GET 请求] 将请求参数拼接到 URL 地址上
                    String urlStr = "http://192.168.67.62:8080/web/LoginServlet?username="+encodeusername+"&password="+encodepassword;

                    // 1. 创建网络连接对象
                    DefaultHttpClient client = new DefaultHttpClient();
                    // 2. 创建请求参数
                    HttpGet get= new HttpGet(urlStr);
                    // 3. 连接服务器,获取返回值
                    HttpResponse response = client.execute(get);

                    // 4. 获取服务器连接状态码
                    int code = response.getStatusLine().getStatusCode();
                    if (code == 200) {
                        // 5. 连接成功获取服务器返回值
                        InputStream in = response.getEntity().getContent();

                        // 6. 将服务器返回值转换成字符串
                        String result = StreamUtils.readStream(in);
//                      System.out.println(result);

                        // 7. 使用吐司来显示返回结果
                        showToast(result);

                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

        }.start();
    }
  • post 请求
    /** 使用 post 请求将账号密码发送给服务器,并获取服务器返回的结果 */
    public void click2(View view) {
        // 获取用户输入的账号密码
        final String username = et_username.getText().toString();
        final String password = et_password.getText().toString();

        // 去除空字符串"  abc  " -- > abc  
//      username = username.trim();

        // 验证空字符串
        if (TextUtils.isEmpty(username) || TextUtils.isEmpty(password)) {
            Toast.makeText(MainActivity.this, "账号密码不能为空", 0).show();
            return;// 如果账号密码为空,则不再往下处理
        }

        // 提交账号密码到服务器,并获取服务器的返回值
        new Thread() {
            public void run() {

                try {
                    // [处理乱码] 
                    String encodeusername = URLEncoder.encode(username, "UTF-8");
                    String encodepassword = URLEncoder.encode(password, "UTF-8");

                    // [POST 请求] URL 地址上不包含请求参数
                    String urlStr = "http://192.168.67.62:8080/web/LoginServlet";
//                  String body = "username="+encodeusername+"&password="+encodepassword;

                    // 1. 创建网络连接对象
                    DefaultHttpClient client = new DefaultHttpClient();

                    // 2. 创建连接参数
                    //[POST 请求] 填充请求体数据
                    HttpPost post = new HttpPost(urlStr);
                    // 创建表单数据列表
                    List<BasicNameValuePair> pairs = new ArrayList<BasicNameValuePair>();
                    pairs.add(new BasicNameValuePair("username", encodeusername));
                    pairs.add(new BasicNameValuePair("password", encodepassword));

                    // 创建表单实体
                    UrlEncodedFormEntity entity = new UrlEncodedFormEntity(pairs );
                    // 设置post的请求体
                    post.setEntity(entity );

                    // 3. 发起请求
                    HttpResponse response = client.execute(post);

                    // 4. 获取服务器连接状态码
                    int code = response.getStatusLine().getStatusCode();
                    if (code == 200) {
                        // 5. 连接成功获取服务器返回值
                        InputStream in = response.getEntity().getContent();

                        // 6. 将服务器返回值转换成字符串
                        String result = StreamUtils.readStream(in);
//                      System.out.println(result);

                        // 7. 使用吐司来显示返回结果
                        showToast(result);

                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

        }.start();
    }

使用 AsyncHttpClient 开源项目实现 get 和 post 提交数据

  • 封装了开启子线程和Handler的操作
  • get请求
    /** 使用 get 请求将账号密码发送给服务器,并获取服务器返回的结果 */
    public void click1(View view) {
        // 获取用户输入的账号密码
        final String username = et_username.getText().toString();
        final String password = et_password.getText().toString();

        // [GET 请求] 将请求参数拼接到 URL 地址上
        String urlStr = "http://192.168.67.62:8080/web/LoginServlet?username="+username+"&password="+password;

        AsyncHttpClient client = new AsyncHttpClient();
        client.get(urlStr, new AsyncHttpResponseHandler() {

            @Override
            // 当服务器连接成功之后的回调方法
            public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) {
                System.out.println("thread="+Thread.currentThread());
                try {
                    String result = new String(responseBody,"GBK");
                    Toast.makeText(MainActivity.this, "服务器说了:"+result, 0).show();

                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

            @Override
            // 当连接失败时的回调方法
            public void onFailure(int statusCode, Header[] headers,
                    byte[] responseBody, Throwable error) {

            }
        });
    }
  • post 请求
    /** 使用 post 请求将账号密码发送给服务器,并获取服务器返回的结果 */
    public void click2(View view) {
        // 获取用户输入的账号密码
        final String username = et_username.getText().toString();
        final String password = et_password.getText().toString();

        String urlStr = "http://192.168.67.62:8080/web/LoginServlet";
//      String body = "username="+username+"&password="+password;

        // 创建网络工具对象
        AsyncHttpClient client = new AsyncHttpClient();
        // 填充请求体
        Map<String, String> bodys = new HashMap<String, String>();
        bodys.put("username", username);
        bodys.put("password", password);
        RequestParams params = new RequestParams(bodys);

        // 发起请求
        client.post(urlStr, params, new AsyncHttpResponseHandler() {

            @Override
            // 请求数据成功后被回调,在主线程执行
            public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) {

                try {
                    Toast.makeText(MainActivity.this, new String(responseBody,"GBK"), 0).show();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

            @Override
            // 请求数据失败后被回调,在主线程执行
            public void onFailure(int statusCode, Header[] headers,
                    byte[] responseBody, Throwable error) {

            }
        });
    }

JavaSE 实现多线程下载

加快文件下载速度。但是线程太多不停切换CPU时间片反而性能下降。同时影响服务器性能。并且网络上限受带宽影响 3-5 个线程

  • 处理下载的逻辑:包工头带小弟分配任务

    • 开启子线程获取服务器文件大小
    • 在客户端创建一个相同大小的文件
    • 计算各个线程的下载区间
    • 开启下载子线程
  • RandomAccessFile

    • 创建的文件可以从任意位置开始读写

    • 构造方法里 mode 的作用

    • “r” 以只读方式打开。调用结果对象的任何 write 方法都将导致抛出 IOException。

    • “rw” 打开以便读取和写入。如果该文件尚不存在,则尝试创建该文件。

    • “rws” 打开以便读取和写入,对于 “rw”,还要求对文件的内容或元数据的每个更新都同步写入到底层存储设备。

    • “rwd” 打开以便读取和写入,对于 “rw”,还要求对文件内容的每个更新都同步写入到底层存储设备。

  • 计算各个线程的下载区间

    • 线程1 0 ~ 2 | 0 ~ 1 * 区块大小 - 1
    • 线程23 ~ 5 | 1 * 区块大小 ~ 2 区块大小 -1
    • 线程36 ~ 9 | n * 区块大小 ~ (n + 1)* 区块大小 - 1
    • 最后一个线程 | n * 区块大小 ~ 文件大小 - 1
    • 区块大小 = 文件大小 / 线程数
  • 设置请求头

    • conn.setRequestProperty(“Range”,”bytes=”+startIndex+”-“+endIndex);
    • 如果返回 206 说明可以下载这个一区块的文件,如果416说明请求头设置的区块有问题

JavaSE 实现断点续传

下载中断后,下次打开可以从中断的位置继续下载

package com.itheima.mulitdownload;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;

public class MulitDownload {

    public static void main(String[] args) {
        startDownload();
    }

    static String urlStr = "http://192.168.67.62:8080/feiq.exe";
    static int threadCount = 3;
    static int runningTheadCount  = threadCount;

    /**
      下载服务器文件。
        - 开启子线程获取服务器文件大小
        - 在客户端创建一个相同大小的文件
        - 计算各个线程的下载区间
        - 开启下载子线程
     */
    private static void startDownload() {
        new Thread() {
            public void run() {

                try {
                    // 1. 创建 Url 
                    URL url = new URL(urlStr);
                    // 2. 获取网络连接对象
                    HttpURLConnection connection = (HttpURLConnection) url.openConnection();
                    // 3. 设置连接参数
                    connection.setConnectTimeout(5000);
                    connection.setRequestMethod("GET");

                    // 4. 获取服务器连接状态码
                    int code = connection.getResponseCode();
                    if (code == 200) {
                        // 5. 获取文件大小
                        int contentLength = connection.getContentLength();
                        System.out.println("contentLength="+contentLength);

                        System.out.println("MulitDownload.run,getFileName="+getFileName(urlStr));
                        // 6. 创建与服务器相同大小的文件
                        RandomAccessFile raf = new RandomAccessFile(getFileName(urlStr), "rwd");
                        raf.setLength(contentLength);
                        raf.close();


//                      线程1 0 ~ 2 |    0 ~ 1 * 区块大小 - 1
//                      线程2 3 ~ 5 |    1 * 区块大小 ~ 2 区块大小 -1
//                      线程3 6 ~ 9 | n * 区块大小 ~ (n + 1)* 区块大小 - 1
//                      最后一个线程| n * 区块大小 ~ 文件大小 - 1 
//                      区块大小 = 文件大小 / 线程数
                        // 7. 计算各个线程的下载区间
                        int blockSize = contentLength / threadCount;// 计算区块大小
                        for (int i = 0; i < threadCount; i++) {
                            int startIndex = i * blockSize;
                            int endIndex = 0;
                            if (i != threadCount - 1) {
                                // 不是最后一个线程
                                endIndex = (i + 1) * blockSize -1;
                            } else {
                                // 最后一个线程
                                endIndex = contentLength - 1; // 存疑!!!!!
                            }
//                          System.out.println("MulitDownload.run,start="+startIndex+";end="+endIndex+";i="+i);

                            // 开启下载线程
                            new XiaoDiThread(startIndex, endIndex, i).start();
                        }

                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            };
        }.start();
    }

    private static class XiaoDiThread extends Thread{

        int startIndex;
        int endIndex;
        int id;

        public XiaoDiThread(int startIndex, int endIndex, int id) {
            this.startIndex = startIndex;
            this.endIndex = endIndex;
            this.id = id;
            System.out.println("startIndex="+startIndex+";endIndex="+endIndex+";id="+id);
        }

        @Override
        /*根据包工头分配的任务,下载一个文件区块*/
        public void run() {

            try {

                // 7.2 断点续传,读取上一次的下载位置,并且更新下载区块的起始位置
                File indexFile = new File(getFileName(urlStr)+id+".txt");
                if (indexFile .exists()) {
                    // 存在断点文件,读取断点
                    BufferedReader reader = new BufferedReader(new FileReader(indexFile)); 
                    String indexStr = reader.readLine();// 读取上一次的断点信息
                    reader.close();// 关闭流

                    startIndex = Integer.valueOf(indexStr);
                }
                System.out.println("startIndex="+startIndex+";id="+id);

                // 1. 创建 Url
                URL url = new URL(urlStr);
                // 2. 获取网络连接对象
                HttpURLConnection connection = (HttpURLConnection) url.openConnection();
                // 3. 设置连接参数
                connection.setConnectTimeout(5000);
                connection.setRequestMethod("GET");
                // 在请求头里设置请求的文件区块:Range bytes=0-100
                connection.setRequestProperty("Range","bytes="+startIndex+"-"+endIndex);

                // 4. 获取服务器连接状态码
                int code = connection.getResponseCode();
//              System.out.println("code="+code);
                if (code == 206) {
                    // 5. 连接成功获取服务器返回值
                    InputStream in = connection.getInputStream();

                    // 6. 将输入流的数据保存到随机文件里
                    RandomAccessFile raf = new RandomAccessFile(getFileName(urlStr), "rwd");
                    raf.seek(startIndex);// 跳转到下载的起始位置

                    int len  = 0;
                    byte[] buffer = new byte[1024];
                    int total = 0;
                    while ((len = in.read(buffer))!= -1) {
                        raf.write(buffer, 0, len);

                        // 7.1 断点续传,保存下载进度
                        total = total + len;
                        RandomAccessFile indexRaf = new RandomAccessFile(getFileName(urlStr)+id+".txt", "rwd");
                        indexRaf.write(String.valueOf(startIndex + total).getBytes());
                        indexRaf.close();
                    }
                    in.close();
                    raf.close();

                    System.out.println("下载完毕:"+id);

                    // 所有线程下载结束之后,要删除断点的进度文件
                    synchronized (XiaoDiThread.class) {
                        runningTheadCount--;

                        if (runningTheadCount == 0) {
                            // 所有线程都已经下载结束,删除所有进度文件
                            for (int i = 0; i < threadCount; i++) {
                                File deleteFile = new File(getFileName(urlStr)+i+".txt");
                                deleteFile.delete();
                            }
                        }
                    }

                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    }

    // http://192.168.67.62:8080/feiq.exe --> feiq.exe
    protected static String getFileName(String urlStr) {
        int index = urlStr.lastIndexOf("/");
        return urlStr.substring(index+1, urlStr.length());
    }
}

将断点续传移植到 Android 上

下载时显示各个线程的下载进度

package com.itheima.mulitdownload;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

import android.app.Activity;
import android.os.Bundle;
import android.os.Environment;
import android.view.View;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.Toast;

public class MainActivity extends Activity {

    private EditText et_url;
    private EditText et_threadcount;
    private LinearLayout ll_progress;
    private List<ProgressBar> progressBarList;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        et_url = (EditText) findViewById(R.id.et_url);
        et_threadcount = (EditText) findViewById(R.id.et_thread_count);
        ll_progress = (LinearLayout) findViewById(R.id.ll_progress);

        progressBarList = new ArrayList<ProgressBar>();
    }

    // 获取用户输入的地址和线程数,在界面上显示相同数量的进度条。开启子线程下载文件
    public void click(View view) {
        if (runningTheadCount !=0) {
            Toast.makeText(MainActivity.this, "有下载正在进行", 0).show();
            return;
        }

        // 获取用户数据
        urlStr = et_url.getText().toString();
        String threadCountStr = et_threadcount.getText().toString();
        threadCount = Integer.parseInt(threadCountStr);
        runningTheadCount  = threadCount;

        // 生成与线程数一样多的进度条
        ll_progress.removeAllViews();
        progressBarList.clear();
        for (int i = 0; i < threadCount; i++) {
            ProgressBar progressBar = (ProgressBar) View.inflate(MainActivity.this, R.layout.progress_item, null);

            ll_progress.addView(progressBar);
            progressBarList.add(progressBar);
        }

        startDownload();
    }


    String urlStr ;
    int threadCount ;
    int runningTheadCount;

    /**
      下载服务器文件。
        - 开启子线程获取服务器文件大小
        - 在客户端创建一个相同大小的文件
        - 计算各个线程的下载区间
        - 开启下载子线程
     */
    private void startDownload() {
        new Thread() {
            public void run() {

                try {
                    // 1. 创建 Url 
                    URL url = new URL(urlStr);
                    // 2. 获取网络连接对象
                    HttpURLConnection connection = (HttpURLConnection) url.openConnection();
                    // 3. 设置连接参数
                    connection.setConnectTimeout(5000);
                    connection.setRequestMethod("GET");

                    // 4. 获取服务器连接状态码
                    int code = connection.getResponseCode();
                    if (code == 200) {
                        // 5. 获取文件大小
                        int contentLength = connection.getContentLength();
                        System.out.println("contentLength="+contentLength);

                        System.out.println("MulitDownload.run,getFileName="+getFileName(urlStr));
                        // 6. 创建与服务器相同大小的文件
                        RandomAccessFile raf = new RandomAccessFile(getFileName(urlStr), "rw");
                        raf.setLength(contentLength);
                        raf.close();


//                      线程1 0 ~ 2 |    0 ~ 1 * 区块大小 - 1
//                      线程2 3 ~ 5 |    1 * 区块大小 ~ 2 区块大小 -1
//                      线程3 6 ~ 9 | n * 区块大小 ~ (n + 1)* 区块大小 - 1
//                      最后一个线程| n * 区块大小 ~ 文件大小 - 1 
//                      区块大小 = 文件大小 / 线程数
                        // 7. 计算各个线程的下载区间
                        int blockSize = contentLength / threadCount;// 计算区块大小
                        for (int i = 0; i < threadCount; i++) {
                            int startIndex = i * blockSize;
                            int endIndex = 0;
                            if (i != threadCount - 1) {
                                // 不是最后一个线程
                                endIndex = (i + 1) * blockSize -1;
                            } else {
                                // 最后一个线程
                                endIndex = contentLength - 1; // 存疑!!!!!
                            }
//                          System.out.println("MulitDownload.run,start="+startIndex+";end="+endIndex+";i="+i);

                            // 开启下载线程
                            new XiaoDiThread(startIndex, endIndex, i).start();
                        }

                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            };
        }.start();
    }

    private class XiaoDiThread extends Thread{

        int startIndex;
        int endIndex;
        int id;

        public XiaoDiThread(int startIndex, int endIndex, int id) {
            this.startIndex = startIndex;
            this.endIndex = endIndex;
            this.id = id;
            System.out.println("startIndex="+startIndex+";endIndex="+endIndex+";id="+id);
        }

        @Override
        /*根据包工头分配的任务,下载一个文件区块*/
        public void run() {

            try {
                // [断点续传的进度更新]计算上一次下载的大小
                int lastProgress = 0;

                // 7.2 断点续传,读取上一次的下载位置,并且更新下载区块的起始位置
                File indexFile = new File(getFileName(urlStr)+id+".txt");
                if (indexFile .exists()) {
                    // 存在断点文件,读取断点
                    BufferedReader reader = new BufferedReader(new FileReader(indexFile)); 
                    String indexStr = reader.readLine();// 读取上一次的断点信息
                    reader.close();// 关闭流
                    int breakPoint = Integer.valueOf(indexStr);
                    lastProgress = breakPoint - startIndex;// 计算上一次已经下载的文件大小

                    startIndex = breakPoint;
                }
                System.out.println("startIndex="+startIndex+";id="+id);

                // 1. 创建 Url
                URL url = new URL(urlStr);
                // 2. 获取网络连接对象
                HttpURLConnection connection = (HttpURLConnection) url.openConnection();
                // 3. 设置连接参数
                connection.setConnectTimeout(5000);
                connection.setRequestMethod("GET");
                // 在请求头里设置请求的文件区块:Range bytes=0-100
                connection.setRequestProperty("Range","bytes="+startIndex+"-"+endIndex);

                // 4. 获取服务器连接状态码
                int code = connection.getResponseCode();
//              System.out.println("code="+code);
                if (code == 206) {
                    // 5. 连接成功获取服务器返回值
                    InputStream in = connection.getInputStream();

                    // 6. 将输入流的数据保存到随机文件里
                    RandomAccessFile raf = new RandomAccessFile(getFileName(urlStr), "rwd");
                    raf.seek(startIndex);// 跳转到下载的起始位置

                    int len  = 0;
                    byte[] buffer = new byte[500];
                    int total = 0;
                    while ((len = in.read(buffer))!= -1) {
                        raf.write(buffer, 0, len);

                        // 7.1 断点续传,保存下载进度
                        total = total + len;
                        RandomAccessFile indexRaf = new RandomAccessFile(getFileName(urlStr)+id+".txt", "rwd");
                        indexRaf.write(String.valueOf(startIndex + total).getBytes());
                        indexRaf.close();

                        // [断点续传的进度更新]更新进度条
                        int max = endIndex - startIndex; // 计算最大值
                        int progress = total + lastProgress;// 当前线程已经下载的大小

                        // 更新进度条。进度条允许在子线程操作。
                        ProgressBar progressBar = progressBarList.get(id);
                        progressBar.setMax(max);
                        progressBar.setProgress(progress);

                    }
                    in.close();
                    raf.close();

                    System.out.println("下载完毕:"+id);

                    // 所有线程下载结束之后,要删除断点的进度文件
                    synchronized (XiaoDiThread.class) {
                        runningTheadCount--;

                        if (runningTheadCount == 0) {
                            // 所有线程都已经下载结束,删除所有进度文件
                            for (int i = 0; i < threadCount; i++) {
                                File deleteFile = new File(getFileName(urlStr)+i+".txt");
                                deleteFile.delete();
                            }
                        }
                    }

                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    }

    // http://192.168.67.62:8080/feiq.exe --> feiq.exe
    protected static String getFileName(String urlStr) {
        int index = urlStr.lastIndexOf("/");
        String externalPath = Environment.getExternalStorageDirectory().getAbsolutePath(); 
        return externalPath +"/"+ urlStr.substring(index+1, urlStr.length());
    }
}
<ProgressBar
  android:id="@+id/progress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?android:attr/progressBarStyleHorizontal"
android:orientation="vertical"
android:max="100"
android:progress="40" />

开源项目实现多线程下载

xUtils的使用

package com.itheima.xutils;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

import android.app.Activity;
import android.os.Bundle;
import android.os.Environment;
import android.view.View;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.Toast;
import com.itheima.xutils.R;
import com.lidroid.xutils.HttpUtils;
import com.lidroid.xutils.exception.HttpException;
import com.lidroid.xutils.http.ResponseInfo;
import com.lidroid.xutils.http.callback.RequestCallBack;

public class MainActivity extends Activity {

    private EditText et_url;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        et_url = (EditText) findViewById(R.id.et_url);
        progressBar = (ProgressBar) findViewById(R.id.progress);
    }

    // 获取用户输入的地址和线程数,在界面上显示相同数量的进度条。开启子线程下载文件
    public void click(View view) {

        // 获取用户数据
        urlStr = et_url.getText().toString();

        // 开启下载
        HttpUtils utils = new HttpUtils();
        utils.download(urlStr, getFileName(urlStr), true, new RequestCallBack<File>() {

            @Override
            // 文件下载成功
            public void onSuccess(ResponseInfo<File> responseInfo) {
                Toast.makeText(MainActivity.this, "下载完成", 0).show();
            }

            @Override
            // 文件下载失败
            public void onFailure(HttpException error, String msg) {
                Toast.makeText(MainActivity.this, "下载失败:"+msg, 0).show();
            }

            @Override
            public void onLoading(long total, long current, boolean isUploading) {
                super.onLoading(total, current, isUploading);
                progressBar.setMax((int) total);
                progressBar.setProgress((int) current);
            }
        });

    }


    String urlStr ;
    private ProgressBar progressBar;

    // http://192.168.67.62:8080/feiq.exe --> feiq.exe
    protected static String getFileName(String urlStr) {
        int index = urlStr.lastIndexOf("/");
        String externalPath = Environment.getExternalStorageDirectory().getAbsolutePath(); 
        return externalPath +"/"+ urlStr.substring(index+1, urlStr.length());
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值