android-async-http文件下载最佳实践:Range请求与进度监听

android-async-http文件下载最佳实践:Range请求与进度监听

【免费下载链接】android-async-http 【免费下载链接】android-async-http 项目地址: https://gitcode.com/gh_mirrors/and/android-async-http

你还在为大文件下载导致的应用卡顿、网络不稳定时的重复下载而烦恼吗?本文将通过实际案例,教你如何利用android-async-http实现断点续传和实时进度监听,解决Android开发中文件下载的常见痛点。读完本文,你将掌握:Range请求分块下载、断点续传实现、下载进度实时更新的完整方案。

核心类与工作原理

RangeFileAsyncHttpResponseHandler

android-async-http提供了RangeFileAsyncHttpResponseHandler专门处理分块下载,其核心机制是:

  1. 通过HTTP Range头指定请求字节范围
  2. 将响应数据直接写入本地文件
  3. 支持断点续传,自动从已下载位置继续

分块下载流程图

mermaid

分块下载实现步骤

1. 检查服务器支持情况

在开始分块下载前,需要先验证服务器是否支持Range请求:

// 发送HEAD请求获取文件信息
client.head(this, URL, null, new TextHttpResponseHandler() {
    @Override
    public void onSuccess(int statusCode, Header[] headers, String responseString) {
        boolean supportsRange = false;
        long fileSize = -1;
        
        // 解析响应头信息
        for (Header header : headers) {
            if ("Accept-Ranges".equals(header.getName())) {
                supportsRange = true;
            } else if ("Content-Length".equals(header.getName())) {
                fileSize = Long.parseLong(header.getValue());
            }
        }
        
        if (supportsRange && fileSize > 0) {
            // 服务器支持Range请求,开始分块下载
            startChunkedDownload(fileSize);
        } else {
            // 服务器不支持Range请求,使用普通下载
            startNormalDownload();
        }
    }
});

2. 实现分块下载逻辑

使用RangeFileAsyncHttpResponseHandler实现分块下载:

private static final int CHUNK_SIZE = 10240; // 10KB分块大小

private void startChunkedDownload(long totalFileSize) {
    File downloadFile = new File(getCacheDir(), "downloaded_file.jpg");
    
    RangeFileAsyncHttpResponseHandler responseHandler = new RangeFileAsyncHttpResponseHandler(downloadFile) {
        @Override
        public void onSuccess(int statusCode, Header[] headers, File file) {
            // 检查是否下载完成
            if (file.length() < totalFileSize) {
                // 未完成,继续下载下一块
                sendNextRangeRequest();
            } else {
                // 下载完成
                runOnUiThread(() -> Toast.makeText(context, "下载完成", Toast.LENGTH_SHORT).show());
            }
        }
        
        @Override
        public void onFailure(int statusCode, Header[] headers, Throwable e, File file) {
            Log.e("Download", "下载失败: " + e.getMessage());
        }
        
        @Override
        public void updateRequestHeaders(HttpUriRequest uriRequest) {
            super.updateRequestHeaders(uriRequest);
            // 设置Range请求头,从上次下载位置继续
            long currentSize = downloadFile.length();
            uriRequest.setHeader("Range", "bytes=" + currentSize + "-" + (currentSize + CHUNK_SIZE - 1));
        }
    };
    
    // 执行下载请求
    AsyncHttpClient client = new AsyncHttpClient();
    client.get(downloadUrl, responseHandler);
}

断点续传完整案例

ResumeDownloadSample实现

ResumeDownloadSample演示了如何实现断点续传功能:

public class ResumeDownloadSample extends SampleParentActivity {
    private File downloadTarget;
    
    private File getDownloadTarget() {
        try {
            if (downloadTarget == null) {
                // 创建临时文件存储下载内容
                downloadTarget = File.createTempFile("download_", "_resume", getCacheDir());
            }
        } catch (IOException e) {
            Log.e("ResumeDownload", "创建临时文件失败", e);
        }
        return downloadTarget;
    }
    
    @Override
    public ResponseHandlerInterface getResponseHandler() {
        return new RangeFileAsyncHttpResponseHandler(getDownloadTarget()) {
            @Override
            public void onSuccess(int statusCode, Header[] headers, File file) {
                addView(getColoredView(LIGHTGREEN, "下载进度: " + file.length() + " bytes"));
            }
            
            @Override
            public void onFailure(int statusCode, Header[] headers, Throwable throwable, File file) {
                addView(getColoredView(RED, "下载中断: " + file.length() + " bytes"));
            }
        };
    }
    
    @Override
    public String getDefaultHeaders() {
        return "Range=bytes=10-20"; // 自定义起始下载位置
    }
}

下载进度监听实现

实时进度更新

通过重写RangeFileAsyncHttpResponseHandler的onProgress方法实现进度监听:

@Override
public void onProgress(long bytesWritten, long totalSize) {
    super.onProgress(bytesWritten, totalSize);
    // 计算下载进度百分比
    int progress = (int) (bytesWritten * 100 / totalSize);
    // 在UI线程更新进度条
    runOnUiThread(() -> progressBar.setProgress(progress));
    Log.d("Download", "进度: " + progress + "%");
}

进度监听完整代码

结合分块下载的进度监听实现:

RangeFileAsyncHttpResponseHandler responseHandler = new RangeFileAsyncHttpResponseHandler(downloadFile) {
    // ... 其他重写方法 ...
    
    @Override
    public void onProgress(long bytesWritten, long totalSize) {
        super.onProgress(bytesWritten, totalSize);
        // 注意:totalSize是当前分块的大小,不是整个文件的大小
        long downloadedSoFar = downloadFile.length() + bytesWritten;
        int overallProgress = (int) (downloadedSoFar * 100 / totalFileSize);
        updateProgressUI(overallProgress);
    }
    
    private void updateProgressUI(int progress) {
        runOnUiThread(() -> {
            progressBar.setProgress(progress);
            progressText.setText(progress + "%");
        });
    }
};

高级配置与优化

1. 分块大小优化

分块大小(CHUNK_SIZE)的设置需要平衡:

  • 太小:增加网络请求次数
  • 太大:单次请求时间过长,影响用户体验

建议设置为10KB-1MB,具体取决于应用场景:

// 根据网络类型动态调整分块大小
private int getOptimalChunkSize(NetworkInfo networkInfo) {
    if (networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
        return 1024 * 1024; // WiFi环境下使用1MB分块
    } else {
        return 10 * 1024; // 移动网络下使用10KB分块
    }
}

2. 超时与重试策略

配置下载超时和重试机制:

AsyncHttpClient client = new AsyncHttpClient();
client.setTimeout(30000); // 30秒超时
client.setMaxRetriesAndTimeout(3, 5000); // 最多重试3次,每次间隔5秒

// 设置自定义重试处理器
client.setRetryHandler(new RetryHandler() {
    @Override
    public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
        // 仅在网络异常时重试
        return executionCount <= 3 && (exception instanceof SocketException || exception instanceof ConnectException);
    }
});

完整示例代码

RangeResponseSample提供了完整的分块下载实现,核心代码如下:

public class RangeResponseSample extends GetSample {
    private static final int CHUNK_SIZE = 10240; // 10KB分块
    private File file;
    private long fileSize = -1;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        try {
            // 创建临时文件
            file = File.createTempFile("temp_", "_handled", getCacheDir());
        } catch (IOException e) {
            Log.e("RangeDownload", "创建临时文件失败", e);
        }
    }
    
    @Override
    public ResponseHandlerInterface getResponseHandler() {
        return new RangeFileAsyncHttpResponseHandler(file) {
            @Override
            public void onSuccess(int statusCode, Header[] headers, File file) {
                if (fileSize < 1) {
                    // 解析文件总大小
                    for (Header header : headers) {
                        if ("Content-Length".equals(header.getName())) {
                            fileSize = Long.parseLong(header.getValue());
                        }
                    }
                }
                
                // 检查是否需要继续下载
                if (file.length() < fileSize) {
                    sendNextRangeRequest();
                } else {
                    Toast.makeText(RangeResponseSample.this, "下载完成", Toast.LENGTH_SHORT).show();
                }
            }
            
            @Override
            public void updateRequestHeaders(HttpUriRequest uriRequest) {
                super.updateRequestHeaders(uriRequest);
                long currentSize = file.length();
                uriRequest.setHeader("Range", "bytes=" + currentSize + "-" + (currentSize + CHUNK_SIZE - 1));
            }
        };
    }
}

注意事项与最佳实践

  1. 权限申请:确保在AndroidManifest.xml中添加网络和存储权限:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
  1. 主线程处理:所有UI更新必须在主线程执行,使用runOnUiThread或Handler

  2. 临时文件管理:下载完成后及时清理临时文件,参考RangeResponseSample的onDestroy方法:

@Override
protected void onDestroy() {
    super.onDestroy();
    // 移除临时文件
    if (file != null && !file.delete()) {
        Log.e("Download", "无法删除临时文件: " + file.getAbsolutePath());
    }
}
  1. 服务器兼容性检查:在使用Range请求前,务必验证服务器是否支持该功能

总结与扩展

通过本文介绍的方法,你可以实现高效可靠的文件下载功能,支持断点续传和进度监听。android-async-http还提供了更多高级功能:

建议结合官方示例sample目录下的完整代码,进一步探索更多高级用法。你还可以扩展实现:多任务下载队列、后台下载服务、下载完成通知等功能,打造更完善的下载体验。

如果觉得本文对你有帮助,欢迎点赞收藏,关注作者获取更多Android网络编程实践技巧!下一篇将介绍android-async-http的缓存策略与性能优化。

【免费下载链接】android-async-http 【免费下载链接】android-async-http 项目地址: https://gitcode.com/gh_mirrors/and/android-async-http

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值