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跳到文件指定位置写入
  • 进度管理:及时保存进度,支持暂停/继续

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

相关推荐
用户2018792831679 小时前
ART 内存模型:用 “手机 APP 小镇” 讲明白底层原理
android
liulangrenaaa9 小时前
Android NDK 命令规范
android
用户2018792831679 小时前
Android中的StackOverflowError与OOM:一场内存王国的冒险
android
用户20187928316710 小时前
类的回收大冒险:一场Android王国的"断舍离"故事
android
用户20187928316710 小时前
Android Class 回收原理及代码演示
android
前端 贾公子11 小时前
《Vuejs设计与实现》第 18 章(同构渲染)(上)
android·flutter
LiuYaoheng11 小时前
【Android】Android 的三种动画(帧动画、View 动画、属性动画)
android·java
苏苏码不动了11 小时前
Android Studio 虚拟机启动失败/没反应,排查原因。提供一种排查方式。
android·ide·android studio
weixin_4569042712 小时前
YOLOv11安卓目标检测App完整开发指南
android·yolo·目标检测