一个优秀的升级体验需要具备两点:快(下载稳、速度高)、兼容性好(全系统版本流畅安装)。
本文将带你利用强大的 OkDownload 库实现多任务、多线程、分片断点续传下载,并全方位适配 Android 6.0 至 Android 14+ 的 APK 安装流程。
第一部分:基于 OkDownload 的高效下载方案
流媒体和传统文件下载库(如 DownloadManager)在面对大文件或弱网环境时,经常出现下载中断、速度慢的问题。OkDownload 是目前移动端顶级的下载框架,天然支持多线程、分片和断点续传。
1. 核心依赖引入
首先,在 build.gradle 中引入 OkDownload 的核心库及扩展组件:
arduino
dependencies {
// OkDownload 核心库
implementation 'com.liulishuo.okdownload:okdownload:1.0.7'
// 提供 SQLite 数据库支持,用于持久化断点信息
implementation 'com.liulishuo.okdownload:sqlite:1.0.7'
// 可选:利用 OkHttp 作为底层网络连接器
implementation 'com.liulishuo.okdownload:okhttp:1.0.7'
}
2. 初始化配置
在自定义的 Application 中进行初始化,建议结合 OkHttp 以获得更好的网络性能和连接池复用:
scala
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
// 初始化 OkDownload,配置下载引擎
OkDownload.Builder builder = new OkDownload.Builder(this);
OkDownload.setSingletonInstance(builder.build());
}
}
3. 构建多线程分片下载任务
OkDownload 会根据文件大小自动进行分片(Block),并分配多线程并行下载。我们只需要配置好 DownloadTask 即可。
typescript
public class DownloadManager {
private DownloadTask downloadTask;
public void startDownload(Context context, String url, String fileName, DownloadListener listener) {
// 1. 定义下载路径 (建议放在沙盒缓存目录,避免公共权限问题)
File parentFile = context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS);
// 2. 构建下载任务
downloadTask = new DownloadTask.Builder(url, parentFile)
.setFilename(fileName)
.setPassIfAlreadyCompleted(true) // 如果已下载完成,直接跳过
.setConnectionCount(3) // 核心:设置多线程分片并发数
.setMinIntervalMillisForPreProgress(200) // 进度回调间隔
.build();
// 3. 异步执行任务
downloadTask.enqueue(listener);
}
public void cancelDownload() {
if (downloadTask != null) {
downloadTask.cancel(); // 取消下载,OkDownload 会自动保存断点进度
}
}
}
4. 监听下载进度与断点续传
实现 DownloadListener4WithSpeed(带下载速度计算的监听器),在下载完成后自动触发安装:
less
DownloadListener listener = new DownloadListener4WithSpeed() {
@Override
public void taskStart(@NonNull DownloadTask task) {
// 下载开始(会自动读取本地数据库的断点,继续下载)
}
@Override
public void connectEnd(@NonNull DownloadTask task, int blockCount, int responseCode, @NonNull Map<String, List<String>> responseHeaderFields) {
// 连接结束,blockCount 表示当前分片数
}
@Override
public void infoReady(@NonNull DownloadTask task, @NonNull BreakpointInfo info, boolean fromBreakpoint, @NonNull Listener4SpeedAssistExtend.Listener4SpeedModel model) {
// info 包含了文件总大小及断点状态。fromBreakpoint 为 true 表示触发了断点续传
}
@Override
public void progressBlock(@NonNull DownloadTask task, int blockIndex, long currentBlockOffset, @NonNull SpeedCalculator blockSpeed) {
// 单个分片的进度
}
@Override
public void progress(@NonNull DownloadTask task, long currentOffset, @NonNull SpeedCalculator taskSpeed) {
// 整体进度回调
long totalLength = task.getInfo().getTotalLength();
int progress = (int) ((float) currentOffset / totalLength * 100);
String speed = taskSpeed.speed(); // 获取当前下载速度,如 "1.2 MB/s"
Log.d("Download", "进度: " + progress + "%, 速度: " + speed);
}
@Override
public void taskEnd(@NonNull DownloadTask task, @NonNull EndCause cause, @Nullable Exception realCause, @NonNull SpeedCalculator taskSpeed) {
if (cause == EndCause.COMPLETED) {
// 下载完成,获取最终的文件对象
File apkFile = task.getFile();
Log.d("Download", "下载完成,准备安装: " + apkFile.getAbsolutePath());
// TODO: 执行下一阶段:安装 APK
} else if (cause == EndCause.CANCELED) {
Log.d("Download", "下载暂停/取消");
} else {
Log.e("Download", "下载失败: " + realCause.getMessage());
}
}
};
第二部分:全版本 Android 系统 APK 安装适配
下载完成后,真正的挑战在于 Android 系统的版本碎片化。从 Android 6.0 到当前的 Android 14+,Google 对应用安装权限和隐私进行了层层加锁。以下是核心适配演进和完整解决方案。
1. 权限与配置准备
① AndroidManifest.xml 配置
首先,在清单文件中声明必要权限与 FileProvider。
xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 适配 Android 8.0 必须的未知来源应用安装权限 -->
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<!-- 适配 Android 14 必须的前台服务权限(如果你将下载放在了 Service 中) -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<application ...>
<!-- 适配 Android 7.0+ 必须的 FileProvider -->
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application>
</manifest>
② 创建 res/xml/file_paths.xml
用于映射我们在 OkDownload 中指定的 getExternalFilesDir 路径:
xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 对应 context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS) -->
<external-files-path name="download_files" path="Download/" />
</paths>
2. 核心安装逻辑:全版本适配代码
编写一个统一的工具类 ApkInstaller,涵盖从 Android 6.0 到 Android 14+ 的所有适配细节:
scss
public class ApkInstaller {
public static void installApk(Context context, File apkFile) {
if (apkFile == null || !apkFile.exists()) {
Toast.makeText(context, "APK文件不存在", Toast.LENGTH_SHORT).show();
return;
}
// 1. 适配 Android 8.0+ (Oreo) 未知来源安装权限
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
boolean hasInstallPermission = context.getPackageManager().canRequestPackageInstalls();
if (!hasInstallPermission) {
// 引导用户去开启未知来源安装权限
Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES);
intent.setData(Uri.parse("package:" + context.getPackageName()));
if (context instanceof Activity) {
((Activity) context).startActivityForResult(intent, 10086);
} else {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
Toast.makeText(context, "请先开启允许安装未知来源应用权限", Toast.LENGTH_LONG).show();
return;
}
}
// 2. 构建安装 Intent
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// 3. 适配 Android 7.0+ (Nougat) FileProvider 机制
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
// 临时赋予读取权限
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
// 通过 FileProvider 生成 content:// 类型的 Uri
Uri apkUri = FileProvider.getUriForFile(context,
context.getPackageName() + ".fileprovider", apkFile);
intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
// 4. 针对 Android 14 (API 34+) 的特殊检查
// Android 14 引入了最低安装版本限制(Target SDK 23以下直接拦截),
// 虽然无法通过代码绕过,但通过异常捕获可以避免引发 Crash,并提示用户。
} else {
// Android 6.0 及以下,直接使用 file:// 类型的 Uri
intent.setDataAndType(Uri.fromFile(apkFile), "application/vnd.android.package-archive");
}
// 5. 启动系统安装器
try {
context.startActivity(intent);
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(context, "解析包失败或系统拒绝安装: " + e.getMessage(), Toast.LENGTH_LONG).show();
}
}
}
避坑指南与专家建议
- Android 14 针对 TargetSDK 的限制:Android 14 开始,系统全面禁止安装 targetSdkVersion < 23(Android 6.0以下)的旧应用。确保你升级后新版 APK 的 targetSdkVersion 大于等于 23,否则即使下载完成,系统也会报 INSTALL_FAILED_DEPRECATED_SDK_VERSION 错误。
- 下载服务的生命周期(Android 13/14+ 适配):若将 OkDownload 放在后台 Service 中运行,在 Android 13/14 中必须为其申请 FOREGROUND_SERVICE_DATA_SYNC 类型的通知栏前台服务权限,并弹出通知,否则 Service 会在后台被系统直接 Kill 掉。
- 分片数的动态选择:通过 setConnectionCount(int) 设置多线程分片数时,不宜过多。一般建议 3~5 个线程。过多的线程会导致服务器视其为恶意请求而熔断,或者频繁的线程上下文切换反而降低低端机的下载效率。
总结
通过 OkDownload 我们可以实现非常高效、抗网络波动的分片多线程下载。结合 FileProvider 适配 和 未知来源权限动态引导,即可打造出一套坚如磐石、体验平滑的 Android 自动化 App 升级引擎。