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

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

相关推荐
robotx2 小时前
安卓线程相关
android
消失的旧时光-19432 小时前
Android 面试高频:JSON 文件、大数据存储与断电安全(从原理到工程实践)
android·面试·json
dalancon3 小时前
VSYNC 信号流程分析 (Android 14)
android
dalancon3 小时前
VSYNC 信号完整流程2
android
dalancon3 小时前
SurfaceFlinger 上帧后 releaseBuffer 完整流程分析
android
用户69371750013844 小时前
不卷AI速度,我卷自己的从容——北京程序员手记
android·前端·人工智能
程序员Android5 小时前
Android 刷新一帧流程trace拆解
android
墨狂之逸才5 小时前
解决 Android/Gradle 编译报错:Comparison method violates its general contract!
android
阿明的小蝴蝶6 小时前
记一次Gradle环境的编译问题与解决
android·前端·gradle
汪海游龙6 小时前
开源项目 Trending AI 招募 Google Play 内测人员(12 名)
android·github