Android模拟多线程下载

简介: Android模拟多线程下载

本DEMO采用Executor框架来实现多线程的下载。

Executor原理:任务拆分为一些列的小任务,即Runnable,然后在提交给一个Executor执行,Executor.execute(Runnalbe) 。Executor在执行时使用内部的线程池完成操作。

本博文演示的从服务端多线程下载图片主要是通过HTTP请求头的Range,在线程池中初始化线程数,然后根据算法去计算,每个线程去下载指定Range范围的资源,每个线程现在完成后发送Message消息给主线程的handler ,当所有的线程都下载完成后,handler主动更新主线程UI。

详细代码请移步本人GITHUB


客户端核心代码

package com.turing.base.http.downHttp;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import com.apkfuns.logutils.LogUtils;
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.concurrent.Executor;
import java.util.concurrent.Executors;
/**
 * MyApp
 *
 * @author Mr.Yang on 2015-11-18  18:58.
 * @version 1.0
 *          多线程下载的实现过程:
 *          1、首先得到下载文件的长度,然后设置本地文件
 *          的长度。
 *          HttpURLConnection.getContentLength();
 *          RandomAccessFile file = new RandomAccessFile("QQWubiSetup.exe","rwd");
 *          file.setLength(filesize);//设置本地文件的长度
 *          2、根据文件长度和线程数计算每条线程下载的数据长度和下载位置。
 *          如:文件的长度为6M,线程数为3,那么,每条线程下载的数据长度为2M,每条线程开始下载的位置如上图所示。
 *          3、使用Http的Range头字段指定每条线程从文件的什么位置开始下载,下载到什么位置为止,
 *          如:指定从文件的2M位置开始下载,下载到位置(4M-1byte)为止,代码如下:
 *          con.setRequestProperty("Range", "bytes=" + 开始位置+ "-" + "结束位置");
 *          HttpURLConnection.setRequestProperty("Range","bytes=2097152-4194303");
 *          4、保存文件,使用RandomAccessFile类指定每条线程从本地文件的什么位置开始写入数据。
 *          RandomAccessFile threadfile = newRandomAccessFile("QQWubiSetup.exe ","rwd");
 *          threadfile.seek(2097152);//从文件的什么位置开始写入数据
 */
public class DownLoad {
    // 创建线程池,指定大小
    private Executor threadPool = Executors.newFixedThreadPool(3);
    public void downLoadFile(String url, Handler handler) {
        try {
            // 根据URL实例化URL
            URL httpUrl = new URL(url);
            // 打开连接
            HttpURLConnection conn = (HttpURLConnection) httpUrl.openConnection();
            // 设置属性
            conn.setRequestMethod("GET");
            conn.setReadTimeout(1000 * 5);
            // 因为要通过线程池-多线程下载文件,所以应该先获取总长度,然后分配每个线程的下载大小
            int count = conn.getContentLength();
            // 假设通过三个线程下载,计算每个线程的下载大小
            int block = count / 3;
            // 遍历 计算出每个线程获取内容的起始位置
            // 获取文件名称
            String fileName = getFileName(url);
            LogUtils.d("fileName:" + fileName);
            // 指定文件的下载地址,加载到sd卡中
            File parent = Environment.getExternalStorageDirectory();
            File downFile = new File(parent, fileName);
            for (int i = 0; i < 3; i++) {
                // 算法解释 假设文件的大小为11个字节,我们通过线程池开启3个线程,这样还剩余2个字节,在最后的线程里处理
                // 第一个线程 从0开始获取到2
                // 第二个线程 从3开始获取到5
                // 第三个线程 从6到10
                long start = i * block;
                long end = (i + 1) * block - 1;
                if (i == 2) {
                    end = count;
                }
                LogUtils.d("start:" + start + ",end" + end);
                // 实例化Runnable对象
                LogUtils.d("getAbsolutePath:" + downFile.getAbsolutePath());
                DownLoadRunnnable downLoadRunnnable = new DownLoadRunnnable(url, downFile.getAbsolutePath(), start, end, handler);
                // 提交到线程池
                threadPool.execute(downLoadRunnnable);
                LogUtils.d("execute成功");
            }
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    /**
     * 根据URL的后缀 获取要获取的图片文件名称
     * http://localhost:8080/HttpService/girl.jpg
     */
    public String getFileName(String url) {
        // 获取最后一个/ 截取文件名称
        return url.substring(url.lastIndexOf("/") + 1);
    }
    /**
     * DownLoadRunnnable对象
     */
    static class DownLoadRunnnable implements Runnable {
        private String url;
        private String fileName;
        private long start;
        private long end;
        private Handler handler;
        // 构造函数
        public DownLoadRunnnable(String url, String fileName, long start, long end, Handler handler) {
            this.url = url;
            this.fileName = fileName;
            this.start = start;
            this.end = end;
            this.handler = handler;
        }
        @Override
        public void run() {
            try {
                URL downLoadURL = new URL(url);
                HttpURLConnection conn = (HttpURLConnection) downLoadURL.openConnection();
                conn.setReadTimeout(1000 * 5);
                conn.setRequestMethod("GET");
                //使用Http的Range头字段指定每条线程从文件的什么位置开始下载,下载到什么位置为止,这里指定从开始到结束
                conn.setRequestProperty("Range", "bytes=" + start + "-" + end);
                // 保存文件
                /**public RandomAccessFile(File file, String mode) throws FileNotFoundException
                 创建从中读取和向其中写入(可选)的随机访问文件流,该文件由 File 参数指定。
                 将创建一个新的 FileDescriptor 对象来表示此文件的连接。
                 mode 参数指定用以打开文件的访问模式。允许的值及其含意为:
                 值         含意
                 "r"    以只读方式打开。调用结果对象的任何 write 方法都将导致抛出 IOException。
                 "rw"   打开以便读取和写入。如果该文件尚不存在,则尝试创建该文件。
                 "rws"  打开以便读取和写入,对于 "rw",还要求对文件的内容或元数据的每个更新都同步写入到底层存储设备。
                 "rwd"  打开以便读取和写入,对于 "rw",还要求对文件内容的每个更新都同步写入到底层存储设备。
                 **/
                RandomAccessFile accessFile = new RandomAccessFile(new File(fileName), "rwd");
                //从文件的什么位置开始写入数据
                accessFile.seek(start);
                // 获取输入流
                InputStream ins = conn.getInputStream();
                // 设置缓冲区域
                byte[] b = new byte[1024 * 4];
                int len;
                // 循环读取写入accessFile
                while ((len = ins.read(b)) != -1) {
                    accessFile.write(b, 0, len);
                }
                LogUtils.d("write 成功");
                // 关闭流
                if (accessFile != null) {
                    accessFile.close();
                }
                if (ins != null) {
                    ins.close();
                }
                // 发送消息通知主线程完成
                Message message = new Message();
                message.what = 1 ;
                handler.sendMessage(message);
                LogUtils.d("message发送成功");
            } catch (MalformedURLException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
相关文章
|
28天前
|
Java 程序员 开发工具
Android|修复阿里云播放器下载不回调的问题
虽然 GC 带来了很多便利,但在实际编码时,我们也需要注意对象的生命周期管理,该存活的存活,该释放的释放,避免因为 GC 导致的问题。
32 2
|
1月前
|
调度 Android开发 开发者
构建高效Android应用:探究Kotlin多线程优化策略
【10月更文挑战第11天】本文探讨了如何在Kotlin中实现高效的多线程方案,特别是在Android应用开发中。通过介绍Kotlin协程的基础知识、异步数据加载的实际案例,以及合理使用不同调度器的方法,帮助开发者提升应用性能和用户体验。
49 4
|
2月前
|
Java 数据库 Android开发
一个Android App最少有几个线程?实现多线程的方式有哪些?
本文介绍了Android多线程编程的重要性及其实现方法,涵盖了基本概念、常见线程类型(如主线程、工作线程)以及多种多线程实现方式(如`Thread`、`HandlerThread`、`Executors`、Kotlin协程等)。通过合理的多线程管理,可大幅提升应用性能和用户体验。
131 15
一个Android App最少有几个线程?实现多线程的方式有哪些?
|
2月前
|
Java Android开发 UED
🧠Android多线程与异步编程实战!告别卡顿,让应用响应如丝般顺滑!🧵
在Android开发中,为应对复杂应用场景和繁重计算任务,多线程与异步编程成为保证UI流畅性的关键。本文将介绍Android中的多线程基础,包括Thread、Handler、Looper、AsyncTask及ExecutorService等,并通过示例代码展示其实用性。AsyncTask适用于简单后台操作,而ExecutorService则能更好地管理复杂并发任务。合理运用这些技术,可显著提升应用性能和用户体验,避免内存泄漏和线程安全问题,确保UI更新顺畅。
97 5
|
2月前
|
Java 数据库 Android开发
一个Android App最少有几个线程?实现多线程的方式有哪些?
本文介绍了Android应用开发中的多线程编程,涵盖基本概念、常见实现方式及最佳实践。主要内容包括主线程与工作线程的作用、多线程的多种实现方法(如 `Thread`、`HandlerThread`、`Executors` 和 Kotlin 协程),以及如何避免内存泄漏和合理使用线程池。通过有效的多线程管理,可以显著提升应用性能和用户体验。
73 10
|
2月前
|
API Android开发 iOS开发
安卓与iOS开发中的线程管理对比
【9月更文挑战第12天】在移动应用的世界中,安卓和iOS平台各自拥有庞大的用户群体。开发者们在这两个平台上构建应用时,线程管理是他们必须面对的关键挑战之一。本文将深入探讨两大平台在线程管理方面的异同,通过直观的代码示例,揭示它们各自的设计理念和实现方式,帮助读者更好地理解如何在安卓与iOS开发中高效地处理多线程任务。
|
2月前
|
Java Android开发 开发者
安卓应用开发中的线程管理优化技巧
【9月更文挑战第10天】在安卓开发的海洋里,线程管理犹如航行的风帆,掌握好它,能让应用乘风破浪,反之则可能遭遇性能的暗礁。本文将通过浅显易懂的语言和生动的比喻,带你探索如何优雅地处理安卓中的线程问题,从基础的线程创建到高级的线程池运用,让你的应用运行更加流畅。
|
3月前
|
存储 监控 数据库
Android经典实战之OkDownload的文件分段下载及合成原理
本文介绍了 OkDownload,一个高效的 Android 下载引擎,支持多线程下载、断点续传等功能。文章详细描述了文件分段下载及合成原理,包括任务创建、断点续传、并行下载等步骤,并展示了如何通过多种机制保证下载的稳定性和完整性。
118 0
|
3月前
|
安全 网络安全 数据安全/隐私保护
网络安全与信息安全:关于网络安全漏洞、加密技术、安全意识等方面的知识分享安卓与iOS开发中的线程管理比较
【8月更文挑战第30天】本文将探讨网络安全与信息安全的重要性,并分享关于网络安全漏洞、加密技术和安全意识的知识。我们将了解常见的网络攻击类型和防御策略,以及如何通过加密技术和提高安全意识来保护个人和组织的信息安全。