您的位置:首页 > 理论基础 > 计算机网络

安卓基础:HttpURLConnection实现多线程下载

2016-11-21 00:18 453 查看
一、多线程下载的原理图解





二、javaee代码实现  

public class MultiDownload {
     static int ThreadCount = 3;
     static int finishedThread = 0;
     // 确定下载地址
     static String path = "http://192.168.33.28:8080/GeePlayer_2.3.28.2726_Setup_baidu.exe";
     public static void main(String[] args) {
           // 发送get请求,请求这个地址的资源
           try {
                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();
                     File file = new File("QQPlayer.exe");
                     // 生成临时文件
                     RandomAccessFile raf = new RandomAccessFile(file, "rwd");
                     // 设置临时文件的大小
                     raf.setLength(length);
                     raf.close();
                     // 计算出每个线程应该下载多少字节
                     int size = length / ThreadCount;
                     for (int i = 0; i < ThreadCount; i++) {
                           // 计算线程下载的开始位置和结束位置
                           int startIndex = i * size;
                           int endIndex = (i + 1) * size - 1;
                           // 如果是最后一个线程,那么结束位置写死
                           if (i == ThreadCount - 1) {
                                endIndex = length - 1;
                           }
                           new DownLoadThread(startIndex, endIndex, i).start();
                     }
                }
           } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
           }
     }
}

这里我们创建了一个MultiDownload,通过HttpURLConnection框架请求资源。获取到文件的长度之后,我们首先生成了一个临时文件,用来确保用户的内存是可以放下我们请求的资源的。
然后计算出每个子线程下载的开始位置和结束位置。

class DownloadThread extends Thread{
     int threadId;
     int start;
     int end;
     public DownloadThread(int threadId, int start, int end) {
           super();
           this.threadId = threadId;
           this.start = start;
           this.end = end;
     }
     @Override
     public void run() {
           try{
           System.out.println("线程"+threadId+"的下载区间是:"+start+"------"+end);
           URL url = new URL(MultiDownload.path);
           HttpURLConnection conn = (HttpURLConnection) url.openConnection();
           conn.setReadTimeout(5000);
           conn.setRequestMethod("GET");
           conn.setConnectTimeout(5000);
           conn.setRequestProperty("Range", "bytes= "+start+"-"+end);//这一句话十分重要,只有设置了该属性才能下载指定范围的文件
           if(conn.getResponseCode()==206){   //这里是因为请求的是文件的一部分,所以返回码是206
                InputStream inputStream = conn.getInputStream();
                File file = new File("GeePlayer_2.3.28.2726_Setup_baidu.exe");
                RandomAccessFile raf = new RandomAccessFile(file, "rwd");
                raf.seek(start); //将文件指针定位到要写的位置
                byte[] b = new byte[1024];
                int len;
                while((len=inputStream.read(b))!=-1){
                     raf.write(b,0,len);
                }
                raf.close();
           }
           }catch(Exception e){
                e.printStackTrace();
           }
     }
}

这里我们定义了一个外部类,用来下载。同样的首先获取一个HttpURLConnection对象,然后设置一些基本的属性后我们又给它设置了一个非常重要的属性setRequestProperty(),传入两个参数,
表示下载的字节范围。然后当返回码为206的时候我们将文件写到工程的根目录下。这样我们的多线程下载就在Javaee上实现了。

但是这个时候我们的程序功能还是太简陋了,现在市面上的下载软件都是支持断点续传的,接下来,我们也给我们的程序加上断点续传的功能。

@Override
     public void run() {
           // 再次发送http请求,下载原文件
           try {
                
4000
File progressFile = new File(threadId + ".txt");
                // 判断进度临时文件是否存在
                if (progressFile.exists()) {
                     FileInputStream fis = new FileInputStream(progressFile);
                     BufferedReader br = new BufferedReader(new InputStreamReader(
                                fis));
                     // 从进度临时文件中读取出上一次下载的总进度,然后与原本的开始位置相加,得到新的开始位置
                     startIndex += Integer.parseInt(br.readLine());
                     fis.close();
                }
                System.out.println("线程" + threadId + "的下载区间是:" + startIndex + "---"
                           + endIndex);
                HttpURLConnection conn;
                URL url = new URL(MultiDownload.path);
                conn = (HttpURLConnection) url.openConnection();
                conn.setRequestMethod("GET");
                conn.setConnectTimeout(5000);
                conn.setReadTimeout(5000);
                // 设置本次http请求所请求的数据的区间
                conn.setRequestProperty("Range", "bytes=" + startIndex + "-"
                           + endIndex);

                // 请求部分数据,相应码是206
                if (conn.getResponseCode() == 206) {
                     // 流里此时只有1/3原文件的数据
                     InputStream is = conn.getInputStream();
                     byte[] b = new byte[1024];
                     int len = 0;
                     int total = 0;
                     // 拿到临时文件的输出流
                     File file = new File("GeePlayer_2.3.28.2726_Setup_baidu.exe");
                     RandomAccessFile raf = new RandomAccessFile(file, "rwd");
                     // 把文件的写入位置移动至startIndex
                     raf.seek(startIndex);
                     while ((len = is.read(b)) != -1) {
                           // 每次读取流里数据之后,同步把数据写入临时文件
                           raf.write(b, 0, len);
                           total += len;
                           // System.out.println("线程" + threadId + "下载了" + total);
                           // 生成一个专门用来记录下载进度的临时文件
                           RandomAccessFile progressRaf = new RandomAccessFile(
                                     progressFile, "rwd");
                           // 每次读取流里数据之后,同步把当前线程下载的总进度写入进度临时文件中
                           progressRaf.write((total + "").getBytes());
                           progressRaf.close();
                     }
                     System.out.println("线程" + threadId
                                + "下载完毕");
                     raf.close();

                     MultiDownload.finishedThread++;
                     synchronized (MultiDownload.path) {
                           if (MultiDownload.finishedThread == MultiDownload.ThreadCount) {
                                for (int i = 0; i < MultiDownload.ThreadCount; i++) {
                                     File f = new File(i + ".txt");
                                     f.delete();
                                }
                                MultiDownload.finishedThread = 0;
                           }
                     }

                }
           } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
           }
     }

这里我们自定义了一个total变量,用来存储每个子线程当前已经下载了多少字节,然后通过RandomAccessFile写入到本地临时文件,文件命名就是threadId.txt
然后我们又定义了一个静态常量finishedThread=0,当我们每个子线程执行完毕的时候就+1,当finishedThread=总共的子线程数目时,就代表所有的子线程
下载完毕,我们就可以将所有记录子线程下载字节个数的文件给删除掉了。然后将finishedThread重新置为0。  最后在线程开始的时候判断一下,如果有进度
临时文件存在,那么我们就读出上一次下载的总进度,然后与原本的开始位置相加,得出新的开始位置,这就实现了断点续传的功能。

三、将代码移植到安卓中
界面就是当我们点击按钮的时候,我们开始下载我们存在本地服务器上的GeePlayer_2.3.28.2726_Setup_baidu.exe到SD卡
所以我们首先要配置两个权限   1.联网权限   2.写SD卡权限
然后在Button的点击事件中,我们创建一个新的子线程用来请求网络,还有其他的一些细小的修改,在此就不再赘述。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Android 多线程 java ee