多线程下载与断点续传

多线程下载和断点续传用HttpUrlConnection和OkHttp都可以实现
这里说的是用OkHttp来实现的
以下是代码,多线程下载,暂停开始,断点续传,进度条显示

   public class testActivity extends Activity {
    private String name="aaa";
    private int pwd=123;
    private final String getUrl="http://139.129.110.99:8901/test?name="+name+"&pwd="+pwd;
    private final String postUrl="http://139.129.110.99:8901/posttest";
    private final String sourceUrl="http://api.jisuapi.com/news/get?channel=头条&start=0&num=10&appkey=e06ac19faa18ef4a";
    public final String fieUrl="http://139.129.110.99:81/witraffic_app.rar";
    private ProgressBar progressBar,progressBar1,progressBar2,progressBar3;
    private OkHttpClient client=new OkHttpClient();
    private Call call;
    //设置线程的数量
    private final static  int threadCount=3;
    //正在下载的线程
    private int runningThread;
    //设置下载的文件的路径
    public String filePath;
    //总的下载进度
    public int totalCount=0;
    //文件总大小
    public long total;
    //判断是否正在下载
    public Boolean isRunning=false;
    private Button button;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);
        progressBar=(ProgressBar)findViewById(R.id.progress);
        button=(Button)findViewById(R.id.btn);
        runningThread=threadCount;
        //创建下载的文件
        File file=new File(Environment.getExternalStorageDirectory(),"aaa");
        file.mkdirs();
        File file1=new File(file,"aaa.rar");
        try {
            file1.createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    //点击这个按钮的时候开始下载或者继续下载,对应button的点击事件
    public void click1(View v)
    {
        if(!isRunning) {
            //这次请求就为了获取文件长度
            Request request = new Request.Builder()
                    .url(fieUrl)
                    .tag("a")
                    .build();
            call = client.newCall(request);
            call.enqueue(new Callback() {
                @Override
                public void onFailure(Call call, IOException e) {
                    Log.i("test", e.getMessage());
                }

                @Override
                public void onResponse(Call call, Response response) throws IOException {
                    //1 获取到文件的大小
                    total = response.body().contentLength();
                    //设置总大小
                    progressBar.setMax((int) total);
                    //2 创建和服务器大小一样的文件,同时为他分配和服务器上文件大小一样的空间
                    //支持对文件随机读写的类,第一个参数传入文件的地址,第二个参数是模式,rwd代表同步到底层设备
                    //随机读写的意思是可以从任何的位置开始读写
                    filePath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/aaa/aaa.rar";
                    RandomAccessFile file = new RandomAccessFile(filePath, "rwd");
                    //设置文件的大小
                    file.setLength(total);
                    Log.i("test", "线程" + total);
                    //3 计算每个线程下载的大小和开始的长度
                /*
                计算方法说明:
                文件下载的起点是从0开始的,所以如果一个文件共10000bit,那么下载的位置就是0~9999
                首先用总大小除以线程数量,这两者都是整型,做除法后得到的也是整型
                得到的就是处理最后一个线程以外每个线程需要下载的长度
                文件的地址是从0开始的,所以第一个线程下载的位置就是0*blockSize~1*blockSize-1
                第二个就是1*blockSize~2*blockSize-1
                ......
                第n个就是(n-1)*blockSize~n*blockSize-1
                .....
                最后一个线程下载区间就是,假设一共三个线程
                2*blockSize~文件末尾
                 */
                /*
                所以第一个线程的开始位置为0,结束位置就是1*blockSize-1
                第二个线程开始位置为1*blockSize,结束位置就是2*blockSize-1
                ....
                第n个线程开始位置就是(n-1)*blockSize,结束位置就是n*blockSize-1
                假设一共三个线程,最后一个线程开始位置就是,2*blockSize~(文件总长度-1)
                 */
                    //3.1 算出除了最后一个线程外每个线程下载的大小
                    long blockSize = total / threadCount;
                    Log.i("test", "" + blockSize);
                    //3.2 开多个线程进行下载
                    for (int i = 0; i < threadCount; i++) {
                        //每个线程的开始位置
                        long startIndex = i * blockSize;
                        //每个线程的结束位置
                        long endIndex = (i + 1) * blockSize - 1;
                        //如果是最后一个线程,那么结束位置是文件的末尾
                        if (i == (threadCount - 1)) {
                            endIndex = total - 1;
                        }
                        Log.i("test", "线程" + i + "开始位置" + startIndex + "结束位置:" + endIndex);
                        //开启线程开始下载,单写一个DownLoadThread类,直接传入参数开始下载
                        new DownLoadThread(startIndex, endIndex, i).start();

                    }
                    isRunning = true;

                }
            });
            button.setText("暂停");

        }
        else
        {
            CancelTag("a");
            button.setText("开始");
            isRunning=false;
        }
    }
    //下载文件的线程的代码
    public class DownLoadThread extends Thread
    {
        public long startIndex;
        public long endIndex;
        public long threadId;
        public DownLoadThread(long startIndex,long endIndex,long threadId)
        {
            this.startIndex=startIndex;
            this.endIndex=endIndex;
            this.threadId=threadId;
        }

        @Override
        public void run() {
            //创建存放开始位置的文件
            final File startIndexFile=new File(Environment.getExternalStorageDirectory()+"/aaa",threadId+".txt");
            if(!startIndexFile.exists())
            {
                try {
                    startIndexFile.createNewFile();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            //判断是第一次下载还是断点后继续下载
            try {
                FileInputStream inputStream=new FileInputStream(startIndexFile);
                BufferedReader reader=new BufferedReader(new InputStreamReader(inputStream));
                String currentIndex=reader.readLine();
                if(currentIndex!=null)
                {
                    startIndex=Integer.parseInt(currentIndex);
                    Log.i("test","线程"+threadId+"开始位置:"+startIndex+"结束位置:"+endIndex);
                }
                inputStream.close();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }catch (IOException e)
            {
                e.printStackTrace();
            }
            //这个判断很重要,在结束的时候暂停很可能导致开始位置大于结束位置
            if(startIndex<=endIndex)
            {
                 //这里开始请求数据,每个线程请求的都是部分数据,所以要设置一个头
                //这个头的名为Range,参数是下载的起止位置,格式为"bytes=开始位置-结束位置"
                //这时返回的状态码为206,206代表请求部分数据
                //同时设置一个tag标签,用于暂停下载时使用
                final Request request=new Request.Builder()
                        .url(fieUrl)
                        .tag("a")
                        .addHeader("Range","bytes="+startIndex+"-"+endIndex)
                        .build();
                Call call=client.newCall(request);
                call.enqueue(new Callback() {
                    @Override
                    public void onFailure(Call call, IOException e) {

                    }

                    @Override
                    public void onResponse(Call call, Response response) throws IOException {
                        //这里开始处理接收文件的逻辑
                        //记录该线程当前下载的大小
                        long currentCount=0;
                        //记录当前的位置
                        long currentPosition=0;
                        //首先再拿到之前创建的空文件
                        RandomAccessFile file=new RandomAccessFile(filePath,"rwd");
                        //设置开始写入的位置
                        file.seek(startIndex);
                        int len=-1;
                        InputStream inputStream=response.body().byteStream();
                        byte[] bytes=new byte[1024*1024];
                        while((len=inputStream.read(bytes))!=-1)
                        {
                            //RandomAccessFile类同样有write方法,写入文件,相当于输出流,也需要关闭
                            file.write(bytes,0,len);
                            //当前线程下载的总大小
                            currentCount+=len;
                            //如果下载暂停了以后新的下载位置
                            currentPosition=startIndex+currentCount;
                            //用RandomAccessFile来记录位置是因为它可以用rwd模式将数据同步到底层设备,
                            //避免数据同步不上
                            RandomAccessFile raf=new RandomAccessFile(startIndexFile,"rwd");
                            raf.write(String.valueOf(currentPosition).getBytes());
                            raf.close();
                            //设置下载进度,progress可以直接在子线程更新UI
                            synchronized (DownLoadThread.class)
                            {
                                totalCount+=len;
                                progressBar.setProgress(totalCount);
                                if(totalCount==total)
                                {
                                    runOnUiThread(new Runnable() {
                                        @Override
                                        public void run() {
                                            Toast.makeText(testActivity.this,"下载完毕",Toast.LENGTH_SHORT).show();
                                        }
                                    });
                                }
                            }

                        }
                        //关闭RandomAccessFile
                        file.close();
                        Log.i("test","线程"+threadId+"下载完毕");
                        //每次下载完毕一个线程runningThread就减一,当runningThread为0时,代表所有线程都下载完毕
                        //为DownLoadThread在操作runningThread--时加锁,在多个线程同时操作runningThread--时保证先后顺序
                        synchronized (DownLoadThread.class)
                        {
                            runningThread--;
                            //当所有线程都下载完毕时,删除存储开始位置的文件
                            if(runningThread==0)
                            {
                                for(int i=0;i

你可能感兴趣的:(多线程下载与断点续传)