Android基础篇-多线程下载(二)

效果图
这里写图片描述

LOG效果图:
这里写图片描述

多线程下载
如果基础不怎么好的读者,请先去了解我的第一篇多线程的博客代码:请点击这里
多线程下载同一个文件:
1. 好处:可以提升下载速度,但是并不是你开启下载的线程越多速度就达到很快,因为最终的线程下载速度还是取决于你的带宽,开启多个线程只能让你和别人同时下载时,你的速度是可以比别人更快的

2.分析问题:我一般解决问题都是首先将复杂的问题拆分成许多简单的问题,这里我也是这样,那么我将带你们走进我解决办法的思路中。

    2.1问题一:既然是多个线程去下载同一个文件,我们必须划分好每个线程的任务,不然每个线程都做同一件事,多线程就没意义,
      解决问题一:通过所在的线程计算出每个线程下载的开始位置与结束位置,每个线程下载的大小等于:总的大小/线程数 ,**当然最后一个线程例外,他必须下载到文件的结尾**,所以这里我们可以很快得到两个公式:
      开始位置=(线程数-1)*(总的大小/线程数)
      结束位置=线程数 *(总的大小/线程数)
      2.2 问题二:我们必须一下载就应该在SD卡目录或者下载目录生成一个同等大小的文件,因为如果不这样做,你下着下着和可能内存不够而停止或者异常,假设下一个好大的文件下了几个小时突然一下子系统告诉你内存不够,请重新下载之内的,你不是会崩溃,所以我们这里提前创建好一个同等大小的文件
          解决问题二:点击下载我们就得到文件的大小,同时创建一个同等大小的文件:
          //得到文件的长度
                           int length=conn.getContentLength();
          //使用RandomAccessFile 创建文件,因为他有一个setLength()可以提前固定好文件长度
          2.3问题三:进度条的更新
          解决它那就简单了,首先设置进度条的最大值为文件大小,然后只要在下载的时候将下载的长度时事去发送message通知进度条更新就好了
          2.4问题四:怎么让每个线程在计算好的位置开始下载并且在结束位置结束
          解决办法:在网路通信HttpURLConnection 中有一个 conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex)方法;
          其中第一个参数是一个Key,第二个参数就是bytes=开始位置-结束位置,最后将计算好的位置放在相应的位置,下载就会自动从该指定位置开始下载了

基本解决了这四个问题,这篇博客就可以自己敲了

布局代码:

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/coordinator_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <android.support.design.widget.TextInputLayout
        android:id="@+id/textInputLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <EditText
            android:hint="URL"
            android:id="@+id/et_name"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

    </android.support.design.widget.TextInputLayout>
    <android.support.design.widget.TextInputLayout
        android:id="@+id/textInputLayout1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <EditText
            android:hint="线程数"
            android:id="@+id/et_thread"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

    </android.support.design.widget.TextInputLayout>

    <ProgressBar
        style="?android:attr/progressBarStyleHorizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/progressBar"
        android:indeterminate="false"/>

    <TextView
        android:id="@+id/tv_progress"
        android:textSize="20sp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
    <Button
        android:onClick="startDownload"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="开始下载"
        android:id="@+id/button"/>

</LinearLayout>

MainActivity:

import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


class MainActivity extends AppCompatActivity{

    //记录已经下载的长度
    int hasDownloadLength=0;
    Button button;
    TextView tv_progress;
    EditText textInputEditText,textInputEditText1;
    ProgressBar progressBar;
    //SD卡路径
    String SDpath= Environment.getExternalStorageDirectory()+"";
    //SD卡不存在错误
    final int SDPATH_ERROR=1;
    //服务器错误
    final int SERVICE_ERROR=2;
    //URL错误
    final int URL_ERROR=3;
    //Thread错误
    final int THREAD_ERROR=4;
    //已经下载的长度
    final int HASLENGTH=5;
    //URL
   // String path="http://ddd1.pc6.com/soft/explorer.exe";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        button= (Button) findViewById(R.id.button);
        textInputEditText= (EditText) findViewById(R.id.et_name);
        textInputEditText1= (EditText) findViewById(R.id.et_thread);
        tv_progress= (TextView) findViewById(R.id.tv_progress);
        progressBar= (ProgressBar) findViewById(R.id.progressBar);
        progressBar.setProgress(0);
    }

    /**
     * 按钮的点击事件
     * 点击创建文件,开始下载
     * @param view
     */
    public void startDownload(View view){
        button.setClickable(false);
        //首先创建一个文件,然后下载
        createSameFileSize();
    }

    /**
     * 切割URL得到下载应用的名字
     */
    public String splitURL(String url){
        //得到 / 最后出现的位置,
        int index=url.lastIndexOf("/");
        Log.i("splitURL",index+"");
        //截取 / 后面的字符串
        String appName=url.substring(index+1,url.length());
        Log.i("splitURL",appName);
        return  appName;
    }
    private Handler handler=new Handler(){
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case SDPATH_ERROR:
                    Toast.makeText(MainActivity.this,"SD卡路径错误",Toast.LENGTH_SHORT).show();
                    break;
                case SERVICE_ERROR:
                    Toast.makeText(MainActivity.this,"服务器错误",Toast.LENGTH_SHORT).show();
                    break;
                case URL_ERROR:
                    Toast.makeText(MainActivity.this,"URL为空",Toast.LENGTH_SHORT).show();
                    break;
                case THREAD_ERROR:
                    Toast.makeText(MainActivity.this,"线程数不规范",Toast.LENGTH_SHORT).show();
                    break;
                case HASLENGTH:
                    progressBar.setProgress(msg.getData().getInt("size"));
                    float temp = (float) progressBar.getProgress()
                            / (float) progressBar.getMax();

                    int progress = (int) (temp * 100);
                    if (progress == 100) {
                        Toast.makeText(MainActivity.this, "下载完成!", Toast.LENGTH_LONG).show();
                    }
                    tv_progress.setText("下载进度:" + progress + " %");
                    break;
            }
        }
    };


    /**
     * 创建一个空的下载文件
     * synchronized保证下载之前必定会先创建文件
     * 给线程分配开始下载位置   结束下载位置
     */
    public  synchronized void  createSameFileSize(){
        new Thread(){
            @Override
            public void run() {
               if (Environment.getExternalStorageState()!=null){
                   try {
                       //得到下载的应用名称
                       String inputPath=textInputEditText.getEditableText().toString();
                       if (inputPath==null||inputPath.equals("")){
                           handler.obtainMessage(URL_ERROR);
                       }else{
                           String appName=splitURL(inputPath);
                           URL url=new URL(inputPath);
                           HttpURLConnection conn= (HttpURLConnection) url.openConnection();
                           conn.setRequestMethod("GET");
                           conn.setConnectTimeout(5000);
                           //200表示ok
                           if (conn.getResponseCode()==200){
                               //得到文件的长度
                               int length=conn.getContentLength();
                               progressBar.setMax(length);
                               Log.i("文件大小:","文件大小:"+length);
                               //在SDpath目录下声明一个appName的文件,此时还没有创建文件
                               File file=new File(SDpath,appName);
                               if (file.exists()){
                                   file.delete();
                               }
                               //创建文件,rwd表示文件可读可写,一旦更新立即写入
                               RandomAccessFile raf=new RandomAccessFile(file,"rwd");
                               raf.setLength(length);
                               //获取线程数
                               String threadCount=textInputEditText1.getEditableText().toString();
                               //正则表达式,判断是不是数字
                               Pattern p = Pattern.compile("[0-9]*");
                               Matcher m = p.matcher(threadCount);
                               if (threadCount==null||threadCount.equals("")||(!m.matches())){
                                   handler.obtainMessage(THREAD_ERROR);
                               }else{
                                   int threadCount1=Integer.parseInt(threadCount);
                                   //平均每个线程下载的长度
                                   int blockSize=length/threadCount1;
                                   //计算每个线程下载的起始位置和结束位置
                                   for (int threadID=1;threadID<=threadCount1;threadID++){
                                       //通过公式计算出起始位置
                                       int startIndex=(threadID-1)*blockSize;
                                       //通过公式计算出结束位置
                                       int endIndex=threadID*blockSize-1;
                                       if (threadCount1==threadID){
                                           endIndex=length;
                                       }
                                       Log.i("下载位置:",threadID+":"+startIndex+"--->"+endIndex);
                                       new DownloadThread(threadID,startIndex,endIndex,inputPath).start();
                                   }
                                   Log.i("raf","创建文件完成");
                                   //完成后断开连接
                                   conn.disconnect();
                               }
                           }else{
                               handler.obtainMessage(SERVICE_ERROR);
                           }
                       }

                   } catch (MalformedURLException e) {
                       e.printStackTrace();
                   } catch (IOException e) {
                       e.printStackTrace();
                   }
               }else{
                   handler.obtainMessage(SDPATH_ERROR);
               }
            }
        }.start();
    }

    /**
     * 下载类
     */
    public class DownloadThread extends Thread{
        int threadId;//线程ID
        int startIndex;//下载起始位置
        int endIndex;//下载结束位置
        String path;//下载路径
        public DownloadThread(int threadId, int startIndex, int endIndex, String path) {
            this.threadId = threadId;
            this.startIndex = startIndex;
            this.endIndex = endIndex;
            this.path = path;
        }

        @Override
        public void run() {
            //得到下载的应用名称
            URL url= null;
            try {
                url = new URL(path);
                HttpURLConnection conn= (HttpURLConnection) url.openConnection();
                conn.setRequestMethod("GET");
                //重要:请求服务器下载指定位置的文件
                conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);
                conn.setConnectTimeout(5000);
                //200 ok 下载全部资源
                //206 ok  下载部分资源
                int code=conn.getResponseCode();
                Log.i("code","code:"+code);
                //已经设置了指定请求,所以这里是指定位置的输出流
                InputStream is=conn.getInputStream();
                //得到下载的应用名称
                String inputPath=textInputEditText.getEditableText().toString();
                String appName=splitURL(inputPath);
                File file=new File(SDpath,appName);
                RandomAccessFile raf=new RandomAccessFile(file,"rwd");
                //随机写文件的时候,从哪个位置开始写
                raf.seek(startIndex);
                int length=0;
                byte[] buf=new byte[1024];
                while((length=is.read(buf))!=-1){
                    raf.write(buf,0,length);
                    hasDownloadLength+=length;
                    Message message=new Message();
                    message.what=HASLENGTH;
                    message.getData().putInt("size",hasDownloadLength);
                    handler.sendMessage(message);
                }
                is.close();
                raf.close();
                Log.i("下载结束",threadId+"下载结束");
            } catch (MalformedURLException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
    }
}

有问题欢迎提出

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

有头发的猿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值