java网络编程之android多线程断点下载并提供下载进度(三)

转载请声明:http://blog.csdn.net/yoyo_newbie/article/details/49834419




如图,一个文件可以分n块,分别用一个线程去下载。只要知道某一个开始下载点,和某一点的下载结束点,就可以下载某一段下来。那么把所有下载好的段拼接后,就是完整的文件。这就是多线程下载文件的思路。为什要使用多线程系下载呢?在理想网络充足硬件理想好的情况下载,如果开了n线程去下载对比单线程下载,显然多线程的下载速度是单线程的 n倍快。

以下是本人封装好的demo ,本人用了线程池控制了线程并发,若想无限制在线活动线程数,自行修改。当然建议不改为好,毕竟设备支撑度始终有限

下载地址:https://github.com/Sam474850601/FileManagerDemo


视图:

activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              xmlns:tools="http://schemas.android.com/tools"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:padding="10dp"
              android:orientation="vertical"
        >

    <TextView
            android:id="@+id/tv_show"
            android:text="点击下载"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>


    <SeekBar
            android:id="@+id/seekBar"
            android:layout_marginTop="20dp"
            android:layout_gravity="center_horizontal"
            android:layout_width="match_parent" android:layout_height="wrap_content"/>
    <ScrollView
            android:layout_weight="1"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
        <TextView
                android:id="@+id/state"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"

                />
    </ScrollView>

    <Button android:layout_width="match_parent" android:layout_height="wrap_content"
            android:text="下载"
            android:onClick="download"
            />
</LinearLayout>

使用方式:

public class MainActivity extends Activity {

    //表述 下载前后情况
    private TextView tv_show;

    //下载文件进度条
    private SeekBar seekBar;

   //表述当前下载进度过程字节量
    private TextView state;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tv_show = (TextView) findViewById(R.id.tv_show);
        state = (TextView) findViewById(R.id.state);
        seekBar = (SeekBar) findViewById(R.id.seekBar);
    }

    /**
     * 下载点击按钮触发事件
     * @param view 下载按钮
     */
    public void download(View view)
    {
        tv_show.setText("准备开始下载...");
        NetManager netManager = new NetManager(this);
        final File file = new File(Environment.getExternalStorageDirectory(), "/sam/mobileqq_android.apk");
        //Q安卓安装包下载地址
        String url = "http://sqdd.myapp.com/myapp/qqteam/AndroidQQ/mobileqq_android.apk";
        //多线程下载文件,默认开4个线程。设置进程数可调用download( String url,   filePath,   threadNumber,   callback)
        netManager.download( url, file,  new NetManager.DownloadCallback() {
            @Override
            public void downloading(long progress) {
                seekBar.setProgress((int) progress); //设置进度条当前进度
                state.append("当前下载量:"+progress+"\n");
            }

            @Override
            public void onSuccess() {
                tv_show.setText("文件下载成功!");
            }

            @Override
            public void onPrepare(long filesize) {
                state.setText("文件总大小:"+filesize+"\n");
                tv_show.setText("开始下载...");
                seekBar.setMax((int) filesize);//根据文件大小设置进度条的总大小
            }

            @Override
            public void onFailure(int errorState, long fileSize, List<NetManager.FileMark> fileMarks) {
                //如果有下载出现了下载量,且出现错误后可以保存fileMarks下来, 通过调用download(fileMarks, url, file,fiesiz, callback)继续下载。
                //记录下载出错时候的原因
                String error = null;
                switch (errorState)
                {
                    case NetManager.DownloadCallback.ERROR_STATE_NOT_FOUN:
                    {
                        error = "下载地址不存在";
                    }break;
                    case NetManager.DownloadCallback.ERROR_STATE_SERVER_EXCEPTION:
                    {
                        error = "服务器配置异常无法访问";
                    }break;
                    case NetManager.DownloadCallback.ERROR_STATE_NETWORK_EXCEPTION:
                    {
                        error = "网络异常";
                    }break;
                    case NetManager.DownloadCallback.EROOR_STATE_FILE_MISS:
                    {
                        error = "文件丢失或无法创建文件";
                    }break;
                    case NetManager.DownloadCallback.ERROR_STATE_PROTOCOL_EXCEPTION:
                    {
                        error = "文件传输超出受限";
                    }break;
                    case NetManager.DownloadCallback.ERROR_STATE_OTHER:
                    {
                        error = "其他异常";
                    }break;
                }
                tv_show.setText("下载失败!原因:"+error);

            }
        });
    }

}



 

/**
 * 访问网络管理器
 * @author Sam
 */
public final class NetManager {

    private Handler handler;
    public NetManager( Context context) {
        handler = new Handler(context.getMainLooper());
    }

    /**
     * 多线程下载文件,默认开4个线程。
     * @param url 文件下载地址
     * @param filePath 文件保存的路径
     *  @param callback  下载回调
     */
    public void download(String url, String filePath, DownloadCallback callback) {

        download(url, filePath,4, callback);
    }

    /**
     * 多线程下载文件,默认开4个线程。
     * @param url 文件下载地址
     * @param filePath 文件保存的路径
     * @param callback  下载过程或结束回调
     */
    public void download(String url, File filePath, DownloadCallback callback) {

        download(url, filePath.getAbsolutePath(), callback);
    }


    /**
     * 多线程下载文件
     * @param url          文件下载地址
     * @param filePath      预设文件下载后的路径
     * @param threadNumber  设置的执行的线程数目
     * @param callback  下载过程或结束回调
     */
    public synchronized void download(final String url, final String filePath, final int threadNumber, final DownloadCallback callback) {
        new Thread() {
            @Override
            public void run() {
                InputStream inputStream = null;
                RandomAccessFile randomAccessFile = null;
                Integer errorState = -1;
                long fileSize = 0;
                try {
                    HttpURLConnection urlConnection = (HttpURLConnection) new URL(url).openConnection();
                    urlConnection.setRequestMethod("GET");
                    urlConnection.setConnectTimeout(10 * 1000);
                    urlConnection.setDoInput(true);
                    File file = new File(filePath);
                    if (!file.getParentFile().exists())
                        file.getParentFile().mkdir();
                    if (file.exists())
                        file.delete();
                    file.createNewFile();
                    int responseCode = urlConnection.getResponseCode();
                    if (responseCode >= 200 && responseCode < 300) {
                        fileSize = urlConnection.getContentLength();//获取文件大小
                        randomAccessFile = new RandomAccessFile(file, "rwd");
                        randomAccessFile.setLength(fileSize);//设置文件大小
                        randomAccessFile.close();//设置文件大小后关闭
                        long value = fileSize % threadNumber;
                        boolean isDivision = 0 == value;//是否整除
                        long unit = isDivision ? fileSize / threadNumber : (fileSize - value) / threadNumber;
                        int block = isDivision ? threadNumber : threadNumber + 1; //如果不整除,分多一块处理
                        List<FileMark> fileMarks = new ArrayList<FileMark>();
                        for (int i = 0; i < block; i++) {
                            FileMark fileMark = new FileMark();
                            fileMark.startMark = i * unit;//开始下载位置
                            long enValue = (i + 1) * unit - 1;
                            fileMark.endMark = i != block - 1?enValue:fileSize;
                            fileMarks.add(fileMark);
                        }
                        download(fileMarks, url, file, fileSize, callback);
                    } else {
                        errorState = DownloadCallback.ERROR_STATE_SERVER_EXCEPTION;
                    }
                } catch (MalformedURLException e) {
                    errorState = DownloadCallback.ERROR_STATE_NOT_FOUN;
                } catch (FileNotFoundException e) {
                    errorState = DownloadCallback.EROOR_STATE_FILE_MISS;
                } catch (ProtocolException e) {
                    errorState = DownloadCallback.ERROR_STATE_PROTOCOL_EXCEPTION;
                } catch (IOException e) {
                    errorState = DownloadCallback.ERROR_STATE_NETWORK_EXCEPTION;
                } catch (Exception ex) {
                    errorState = DownloadCallback.ERROR_STATE_OTHER;
                } finally {
                    if (null != randomAccessFile) {
                        try {
                            randomAccessFile.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                    if (null != inputStream) {
                        try {
                            inputStream.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                    if (-1 != errorState) {
                        final int es = errorState;
                        final long size = fileSize;
                        handler.post(new Runnable() {
                            @Override
                            public void run() {
                                callback.onFailure(es, size, null);
                            }
                        });
                    }
                }
            }
        }.start();
    }


    private class CloseThread {
        int threadDistroyNum=1;
        long downloadQuantity;
        int errorState  = -1;
        boolean hasException;
    }

    /**
     * 多线程下载文件, 可以通过传入FileMark下载。
     * @param fileMarks  需要下载文件段落集合
     * @param url 下载路径
     * @param file 下载文件
     * @param fileSize 文件总长度
     * @param callback 下载过程或结束回调
     */
    public synchronized void download(List<FileMark> fileMarks, String url, File file,
                                      final long fileSize,  final DownloadCallback callback)
    {
        handler.post(new Runnable() {
            @Override
            public void run() {
                callback.onPrepare(fileSize);
            }
        });
        CloseThread ct = new CloseThread();
        List<FileMark> remindFileMarks = new ArrayList<FileMark>();
        long remindQuantity = 0;
        for (FileMark fileMark : fileMarks) {
            long value =  fileMark.endMark - fileMark.startMark+1;
            remindQuantity += value;
        }
        ct.downloadQuantity = fileSize - remindQuantity+1;
        for (FileMark fileMark : fileMarks) {
            DownloadFileThread thread = new DownloadFileThread(url, file, fileMark, fileMarks.size(), ct,
                    remindFileMarks, fileSize, callback);
            ThreadManager.getPool().execute(thread);
        }
    }

    /**
     * 线程池管理器
     */
    public static  class ThreadManager {
        /**
         * 最大队列长度
         */
        private static final int MAX_QUEUE_LENGTH = 128;

        /**
         * 常驻内在线程数
         */
        private static final int ALIVE_THREAD_SIZE = 5;

        /**
         * 最大活动线程数
         */
        private static final int MAX_THREAD_SIZE = 15;

        /**
         * 线程空置多长时间销毁
         */
        private static final int THREAD_ALIVE_SECONDS = 60;

        /**
         * 线程池
         */
        private static final ThreadPoolExecutor threadPool = new ThreadPoolExecutor(ALIVE_THREAD_SIZE,
                MAX_THREAD_SIZE, THREAD_ALIVE_SECONDS, TimeUnit.SECONDS,
                new ArrayBlockingQueue<Runnable>(MAX_QUEUE_LENGTH),
                new ThreadPoolExecutor.DiscardOldestPolicy());

        /**
         * 取得线程池实例
         *
         * @return
         */
        public static final ThreadPoolExecutor getPool() {
            return threadPool;
        }

    }

    /**
     * 下载执行回调
     */
    public interface DownloadCallback {
        /**
         * 下载地址不存在
         */
        public static final int ERROR_STATE_NOT_FOUN = 0x01;


        /**
         * 服务器配置异常无法访问
         */
        public static final int ERROR_STATE_SERVER_EXCEPTION = 0x02;

        /**
         * 网络异常
         */
        public static final int ERROR_STATE_NETWORK_EXCEPTION = 0x03;


        /**
         * 文件丢失或无法创建文件
         */
        public static final int EROOR_STATE_FILE_MISS = 0x04;


        /**
         *  文件传输超出受限
         */
        public static final int ERROR_STATE_PROTOCOL_EXCEPTION = 0x05;

        /**
         * 其他异常
         */
        public static final int ERROR_STATE_OTHER = 0x06;


        /**
         * 下载时候,进度情况
         *
         * @param progress  当前下载量
         *
         */
        void downloading(long progress);

        /**
         * 下载成功
         */
        void onSuccess();


        /**
         * 下载之前回调
         * @param filesize 文件长度
         */
        void onPrepare(long filesize);



        /**
         * 下载过程出现错误
         *
         * @param errorState  下载错误情况
         * @param fileMarks  剩余为下载的部分
         * @param fileSize   总文件大小
         */
        void onFailure(int errorState, long fileSize, List<FileMark> fileMarks);
    }

    /**
     * 文件下载的位置信息
     */
    public static class FileMark {
        /**
         * 开始下载的长度
         */
        public  long startMark;

        /**
         *  终止下载的长度
         */
        public  long endMark;

        @Override
        public String toString() {
            return "FileMark{" +
                    "startMark=" + startMark +
                    ", endMark=" + endMark +
                    '}';
        }
    }


    /**
     * 下载文件块线程
     */
    private class DownloadFileThread extends Thread {
        private String url;//下载地址
        private File file;//文件路径
        private FileMark fileMark;//文件下载块信息
        private DownloadCallback callback;
        private CloseThread ct;//线程结束数目
        private int totalThread;//总线程数目
        private long fileSize;
        private List<FileMark> remindFileMarks;

        public DownloadFileThread(String url, File file, FileMark fileMark, int totalThread, CloseThread ct,
                                  List<FileMark> remindFileMarks, long fileSize, DownloadCallback callback) {
            this.url = url;
            this.file = file;
            this.fileMark = fileMark;
            this.totalThread = totalThread;
            this.ct = ct;
            this.callback = callback;
            this.fileSize = fileSize;
            this.remindFileMarks = remindFileMarks;
        }


        @Override
        public void run() {
            InputStream inputStream = null;
            RandomAccessFile randomAccessFile = null;
            long currentDownQuantity = 0;
            try {
                HttpURLConnection urlConnection = (HttpURLConnection) new URL(url).openConnection();
                urlConnection.setRequestMethod("GET");
                urlConnection.setConnectTimeout(10 * 1000);
                urlConnection.setReadTimeout(10*1000);
                urlConnection.setRequestProperty("Range", "bytes=" + fileMark.startMark + "-" + fileMark.endMark);
                int responseCode = urlConnection.getResponseCode();
                if (responseCode >= 200 && responseCode < 300) {
                    inputStream = urlConnection.getInputStream();
                    int len = 0;
                    byte[] data = new byte[1024 * 8];
                    randomAccessFile = new RandomAccessFile(file, "rwd");
                    randomAccessFile.seek(fileMark.startMark);//设置往文件写入部分
                    while (-1 != (len = inputStream.read(data))) {
                        currentDownQuantity += len;
                        randomAccessFile.write(data, 0, len);
                        final long  value =(ct.downloadQuantity = ct.downloadQuantity + len);
                        handler.post(new Runnable() {
                            @Override
                            public void run() {
                                callback.downloading(value);
                            }
                        });
                    }
                } else {
                    ct.errorState = DownloadCallback.ERROR_STATE_SERVER_EXCEPTION;
                }
            } catch (MalformedURLException e) {
                ct.errorState  = DownloadCallback.ERROR_STATE_NOT_FOUN;
            } catch (FileNotFoundException e) {
                ct.errorState  = DownloadCallback.EROOR_STATE_FILE_MISS;
            } catch (ProtocolException e) {
                ct.errorState  = DownloadCallback.ERROR_STATE_PROTOCOL_EXCEPTION;
            } catch (IOException e) {
                ct.errorState  = DownloadCallback.ERROR_STATE_NETWORK_EXCEPTION;
            } catch (Exception ex) {
                ct.errorState  = DownloadCallback.ERROR_STATE_OTHER;
            } finally {
                if (null != randomAccessFile) {
                    try {
                        randomAccessFile.close();
                    } catch (IOException e) {

                    }
                }
                if (null != inputStream) {
                    try {
                        inputStream.close();
                    } catch (IOException e) {

                    }
                }
                if (-1 !=  ct.errorState )
                    ct.hasException =true;
                synchronized (ct)
                {
                    if( ct.threadDistroyNum++ == totalThread)
                    {
                        fileMark.startMark += currentDownQuantity;
                        if(ct.hasException)
                        {
                            remindFileMarks.add(fileMark);
                        }
                        handler.post(new Runnable() {
                            @Override
                            public void run() {
                                if (remindFileMarks.isEmpty()) {
                                    callback.onSuccess();
                                } else {
                                    callback.onFailure( ct.errorState , fileSize, remindFileMarks);
                                }
                            }
                        });
                    }
                }
            }
        }
    }






}
















  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java多线程断点续传下载可以使用Java中的线程池和RandomAccessFile类实现。具体步骤如下: 1. 创建一个线程池,线程数量可以根据需要调整。 ``` ExecutorService executorService = Executors.newFixedThreadPool(threadCount); ``` 2. 获取文件大小和已经下载的字节数,计算出每个线程需要下载的字节数。 ``` URL url = new URL(fileUrl); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); long fileSize = connection.getContentLength(); long threadSize = fileSize / threadCount; ``` 3. 创建一个RandomAccessFile对象,指定文件名和读写模式。 ``` RandomAccessFile randomAccessFile = new RandomAccessFile(fileName, "rw"); ``` 4. 为每个线程创建一个DownloadThread对象,指定线程编号、起始位置和结束位置。 ``` for (int i = 0; i < threadCount; i++) { long start = i * threadSize; long end = (i == threadCount - 1) ? fileSize - 1 : start + threadSize - 1; DownloadThread thread = new DownloadThread(fileUrl, randomAccessFile, start, end); executorService.execute(thread); } ``` 5. 在DownloadThread中使用HttpURLConnection下载文件,使用RandomAccessFile写入文件。 ``` URL url = new URL(fileUrl); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setRequestProperty("Range", "bytes=" + start + "-" + end); InputStream inputStream = connection.getInputStream(); byte[] buffer = new byte[1024]; int length; while ((length = inputStream.read(buffer)) != -1) { randomAccessFile.write(buffer, 0, length); } inputStream.close(); ``` 6. 在程序关闭时,关闭RandomAccessFile和线程池。 ``` randomAccessFile.close(); executorService.shutdown(); ``` 这样就可以实现Java多线程断点续传下载了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值