Xamarin.Forms使用DownloadManager下载文件失败如何解决?
问题根因
你写的最简调用代码无法生效,核心是CrossDownloadManager插件并非零配置即可使用,你缺失了安卓端必要的权限声明、运行时权限申请、全局实例持有、存储路径配置环节,且未做安卓版本兼容的APK安装逻辑。
落地解决步骤
1. 补全安卓项目基础配置
打开安卓项目根目录下的AndroidManifest.xml文件,先在<manifest>节点下添加必要权限:
<!-- 网络访问权限 --> <uses-permission android:name="android.permission.INTERNET" /> <!-- 存储读写权限 --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <!-- 安装APK权限(安卓8.0以上必须) --> <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
如果项目目标版本为Android 10(API 29)及以上,需要在<application>节点追加属性android:requestLegacyExternalStorage="true",否则插件无法写入公共存储目录;如果测试用的下载链接是HTTP非加密协议,还需要追加android:usesCleartextTraffic="true"允许明文网络请求。
安卓6.0(API 23)以上不能仅靠静态声明权限,需要在触发下载前动态申请存储权限,直接用Xamarin.Essentials内置的权限API即可,无需手写原生回调。
2. 修正下载逻辑代码
你当前代码有两个致命问题:一是下载实例被定义为方法局部变量,方法执行完会被GC回收直接中断下载;二是没有指定存储路径、绑定状态监听,既没法确定文件保存位置,也没法定位失败原因。
修正后的可运行代码如下:
// 定义为页面/服务的全局字段,避免被GC回收 private IDownloadFile _currentDownloadTask; async void StartGameApkDownload(string downloadUrl) { // 先申请存储权限 var permissionStatus = await Permissions.CheckStatusAsync<Permissions.StorageWrite>(); if (permissionStatus != PermissionStatus.Granted) { permissionStatus = await Permissions.RequestAsync<Permissions.StorageWrite>(); if (permissionStatus != PermissionStatus.Granted) { // 权限被拒绝时弹提示引导用户手动开启即可 return; } } var downloadManager = CrossDownloadManager.Current; // 指定APK保存到公共下载目录下的游戏启动器文件夹,避免临时目录被系统清理 string saveDir = Path.Combine( Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryDownloads).AbsolutePath, "GameLauncher" ); Directory.CreateDirectory(saveDir); string apkSavePath = Path.Combine(saveDir, "game_install.apk"); _currentDownloadTask = downloadManager.CreateDownloadFile(downloadUrl, new DownloadManagerFileOptions { PathName = apkSavePath }); // 绑定下载状态、进度监听,方便排查问题 downloadManager.CollectionChanged += (sender, e) => { if (e.NewItems == null) return; foreach (var item in e.NewItems) { if (item is not IDownloadFile trackFile) continue; trackFile.PropertyChanged += (s, args) => { switch (args.PropertyName) { case nameof(IDownloadFile.Status): if (trackFile.Status == DownloadFileStatus.COMPLETED) { // 下载完成触发安装 InstallApk(apkSavePath); } else if (trackFile.Status == DownloadFileStatus.FAILED) { // 打印错误信息定位问题 Console.WriteLine($"下载失败,错误码:{trackFile.Error},错误详情:{trackFile.ErrorMessage}"); } break; case nameof(IDownloadFile.BytesDownloaded): // 此处可绑定UI更新下载进度条 double progress = (double)trackFile.BytesDownloaded / trackFile.TotalBytesExpectedToWrite; Console.WriteLine($"当前下载进度:{progress:P}"); break; } }; } }; // 第二个参数传true表示如果文件已存在则覆盖 downloadManager.Start(_currentDownloadTask, true); }
部分新版本插件需要手动初始化,在安卓项目MainActivity.cs的OnCreate方法中追加一行即可:
CrossDownloadManager.Current = new DownloadManagerImplementation();
3. 适配APK安装逻辑
安卓7.0(API 24)以上禁止直接通过文件路径暴露Uri给安装程序,必须配置FileProvider,否则下载完成后无法调起安装:
- 在安卓项目
Resources/xml目录下新建file_paths.xml文件(xml目录不存在就手动创建),内容如下:
<?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <external-path name="game_apk_path" path="Download/GameLauncher" /> </paths>
- 在
AndroidManifest.xml的<application>节点内追加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>
- 实现安装方法:
void InstallApk(string apkFullPath) { Java.IO.File apkFile = new Java.IO.File(apkFullPath); Intent installIntent = new Intent(Intent.ActionView); installIntent.AddFlags(ActivityFlags.GrantReadUriPermission); installIntent.AddFlags(ActivityFlags.NewTask); Android.Net.Uri apkUri; if (Build.VERSION.SdkInt >= BuildVersionCodes.N) { apkUri = FileProvider.GetUriForFile( Android.App.Application.Context, $"{Android.App.Application.Context.PackageName}.fileprovider", apkFile ); } else { apkUri = Android.Net.Uri.FromFile(apkFile); } installIntent.SetDataAndType(apkUri, "application/vnd.android.package-archive"); Android.App.Application.Context.StartActivity(installIntent); }
常见问题排查
- 如果下载无反应,先看输出窗口的
trackFile.ErrorMessage日志,90%的问题会直接写明是权限、网络还是路径错误 - 不要在调试时把下载链接替换成无效的本地地址,确保测试设备能正常访问APK下载地址
- 版本校验逻辑可以放在下载前执行,请求服务端接口拿到最新版本号、APK下载地址、文件MD5值,下载完成后比对本地文件MD5和服务端返回值一致再触发安装,避免下载到损坏的安装包。
内容的提问来源于stack exchange,提问作者Draas
相关产品推荐
相关产品推荐

