Android断点续传原理:小明的"读书笔记"故事

有趣的比喻故事

想象一下,小明正在抄写一本很厚的书《Android开发秘籍》。这本书有1000页,他不可能一次性抄完。

故事场景:

  1. 普通下载:就像小明每次都是从第1页开始抄,即使昨天已经抄到第500页了

  2. 断点续传:聪明的小明会:

    • 在书签上记录"已抄到第500页"
    • 下次继续从第501页开始抄
    • 即使中途被打断,也不会白费功夫

技术原理解析

核心概念

java 复制代码
// 就像小明的读书笔记
public class DownloadInfo {
    private String fileUrl;      // 书的地址
    private String savePath;     // 笔记本位置
    private long totalSize;      // 书的总页数:1000页
    private long downloadedSize; // 已抄页数:500页
    private boolean isPaused;    // 是否暂停
}

HTTP协议支持

服务器就像图书管理员,支持"从指定位置阅读":

java 复制代码
// 告诉管理员:"请从第501页开始给我书的内容"
connection.setRequestProperty("Range", "bytes=" + downloadedSize + "-");

完整代码实现

1. 下载信息管理(小明的书签)

java 复制代码
public class DownloadRecord {
    private String url;
    private String filePath;
    private long totalSize;
    private long currentSize;
    
    // 保存读书进度
    public void saveProgress() {
        SharedPreferences sp = context.getSharedPreferences("download_progress", Context.MODE_PRIVATE);
        sp.edit().putLong(url + "_current", currentSize)
                 .putLong(url + "_total", totalSize)
                 .apply();
    }
    
    // 读取读书进度
    public void loadProgress() {
        SharedPreferences sp = context.getSharedPreferences("download_progress", Context.MODE_PRIVATE);
        currentSize = sp.getLong(url + "_current", 0);
        totalSize = sp.getLong(url + "_total", 0);
    }
}

2. 智能下载器(聪明的小明)

java 复制代码
public class SmartDownloader {
    private DownloadRecord record;
    private boolean isPaused = false;
    
    public void startDownload(String url, String savePath) {
        new Thread(() -> {
            try {
                HttpURLConnection connection = createConnection(url);
                
                // 关键步骤1:检查这本书能不能跳着读
                boolean supportBreakpoint = checkBreakpointSupport(connection);
                
                // 关键步骤2:设置从哪开始读
                if (supportBreakpoint && record.getCurrentSize() > 0) {
                    connection.setRequestProperty("Range", "bytes=" + record.getCurrentSize() + "-");
                }
                
                // 关键步骤3:开始读书并记录进度
                downloadContent(connection, savePath);
                
            } catch (Exception e) {
                e.printStackTrace();
            }
        }).start();
    }
    
    private void downloadContent(HttpURLConnection connection, String savePath) {
        try (InputStream input = connection.getInputStream();
             RandomAccessFile output = new RandomAccessFile(savePath, "rw")) {
            
            // 跳到上次写到的位置继续写
            output.seek(record.getCurrentSize());
            
            byte[] buffer = new byte[8192]; // 每次读8KB
            int bytesRead;
            
            while ((bytesRead = input.read(buffer)) != -1 && !isPaused) {
                // 写入笔记本
                output.write(buffer, 0, bytesRead);
                
                // 更新进度
                record.setCurrentSize(record.getCurrentSize() + bytesRead);
                record.saveProgress(); // 及时保存书签
                
                // 通知UI更新进度条
                updateProgressUI(record.getCurrentSize(), record.getTotalSize());
            }
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public void pause() {
        isPaused = true;
        record.saveProgress(); // 暂停时一定要保存书签
    }
    
    public void resume() {
        isPaused = false;
        startDownload(record.getUrl(), record.getFilePath());
    }
}

3. 服务器能力检测(询问图书管理员)

java 复制代码
private boolean checkBreakpointSupport(HttpURLConnection connection) {
    try {
        // 方法1:检查Accept-Ranges头
        String acceptRanges = connection.getHeaderField("Accept-Ranges");
        if ("bytes".equals(acceptRanges)) {
            return true;
        }
        
        // 方法2:检查Content-Range头(更可靠)
        String contentRange = connection.getHeaderField("Content-Range");
        if (contentRange != null && contentRange.startsWith("bytes")) {
            return true;
        }
        
        // 方法3:直接尝试Range请求
        connection.setRequestProperty("Range", "bytes=0-0");
        if (connection.getResponseCode() == 206) { // 206表示部分内容
            return true;
        }
        
    } catch (Exception e) {
        e.printStackTrace();
    }
    return false;
}

时序图:整个调用过程

高级特性:多线程断点续传

就像小明请了3个朋友一起抄书:

java 复制代码
public class MultiThreadDownloader {
    private static final int THREAD_COUNT = 3;
    
    public void multiThreadDownload(String url, String savePath) {
        // 1. 获取文件总大小
        long fileSize = getFileSize(url);
        long blockSize = fileSize / THREAD_COUNT;
        
        // 2. 为每个朋友分配任务
        for (int i = 0; i < THREAD_COUNT; i++) {
            long startPos = i * blockSize;
            long endPos = (i == THREAD_COUNT - 1) ? fileSize - 1 : (i + 1) * blockSize - 1;
            
            new DownloadThread(i, url, savePath, startPos, endPos).start();
        }
    }
    
    class DownloadThread extends Thread {
        private int threadId;
        private String url;
        private String savePath;
        private long startPos;
        private long endPos;
        
        @Override
        public void run() {
            // 每个线程独立记录自己的进度
            downloadBlock(threadId, url, savePath, startPos, endPos);
        }
    }
}

实际应用技巧

1. 进度保存策略

java 复制代码
// 不要每次写入都保存,太频繁影响性能
private void smartSaveProgress() {
    long currentTime = System.currentTimeMillis();
    if (currentTime - lastSaveTime > 1000) { // 每秒保存一次
        record.saveProgress();
        lastSaveTime = currentTime;
    }
}

2. 异常处理

java 复制代码
private void downloadWithRetry() {
    int retryCount = 0;
    while (retryCount < MAX_RETRY) {
        try {
            downloadContent();
            break;
        } catch (IOException e) {
            retryCount++;
            if (retryCount == MAX_RETRY) {
                // 通知用户网络异常
                notifyNetworkError();
            } else {
                // 等待后重试
                SystemClock.sleep(2000);
            }
        }
    }
}

总结

断点续传就像聪明的读书方法:

  • 记录进度:用SharedPreferences或数据库记录下载位置
  • Range请求:告诉服务器"我从哪里开始下载"
  • 随机写入:用RandomAccessFile跳到文件指定位置写入
  • 进度管理:及时保存进度,支持暂停/继续

这样即使下载过程中网络中断、手机关机,也能从中断的地方继续,大大提升用户体验!

相关推荐
千里马学框架20 小时前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台20 小时前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone20 小时前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc21 小时前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo1 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077001 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼1 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone1 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen1 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone1 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui