You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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,否则下载完成后无法调起安装:

  1. 在安卓项目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>
  1. 在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>
  1. 实现安装方法:
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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 04:57:10