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

Xamarin Android如何用按钮从URL下载APK?WebClient下载报错求助

在Xamarin Android中实现按钮点击下载APK + 解决WebClient报错问题

我来帮你搞定这个需求,顺便排查下你遇到的WebClient报错问题~

一、你的WebClient报错到底为啥?

你碰到的System.Net.WebException,大概率是这几个坑导致的:

  • 主线程跑网络请求:Android 4.0之后就禁止在UI主线程执行同步网络操作了,你直接在按钮点击事件里调用DownloadFile(同步方法),不报错才怪。
  • 路径硬编码错了:@"j:\storage\emulated\legacy\Download\"这不是Android设备的有效存储路径,Android的存储路径得用系统API获取,不能瞎写。
  • 权限没给够:没申请网络权限或者存储写入权限,导致要么连不上网,要么写不了文件。

二、正确的实现步骤,一步一步来

1. 先把必要权限加上

打开项目里的Properties/AndroidManifest.xml,添加这些权限:

<!-- 网络权限,必须有 -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- Android 12及以下的存储写入权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<!-- Android 11+允许安装未知来源应用的权限 -->
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<!-- Android 13+访问下载目录需要的权限 -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" android:maxSdkVersion="33" />

2. 换掉WebClient,用HttpClient异步下载

WebClient是比较老的API了,现在更推荐用HttpClient做异步请求,既不会阻塞UI,也更稳定。另外Android 6.0+还要动态申请权限,不能只在Manifest里写。

3. 完整的按钮点击事件代码

假设你的按钮叫btnDownloadApk,在Activity里这么写:

先定义权限请求的常量:

private const int RequestPermissionsCode = 100;
// 需要申请的权限数组
private readonly string[] _requiredPermissions = new[]
{
    Android.Manifest.Permission.Internet,
    Android.Manifest.Permission.WriteExternalStorage
};

然后写按钮点击的逻辑:

btnDownloadApk.Click += async (sender, e) =>
{
    // 先检查权限有没有拿到
    if (ContextCompat.CheckSelfPermission(this, Android.Manifest.Permission.WriteExternalStorage) != Permission.Granted ||
        ContextCompat.CheckSelfPermission(this, Android.Manifest.Permission.Internet) != Permission.Granted)
    {
        // 没权限就弹框申请
        ActivityCompat.RequestPermissions(this, _requiredPermissions, RequestPermissionsCode);
        return;
    }

    try
    {
        // 获取系统的下载目录,这才是正确的路径
        var downloadDir = Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryDownloads);
        var apkFilePath = System.IO.Path.Combine(downloadDir.AbsolutePath, "my_update.apk");

        // 用HttpClient异步下载APK
        using (var httpClient = new HttpClient())
        {
            // 替换成你的APK下载URL
            using (var response = await httpClient.GetAsync("https://your-domain.com/your-app.apk"))
            {
                response.EnsureSuccessStatusCode(); // 确保请求成功,失败会抛异常
                // 把下载的流写入本地文件
                using (var stream = await response.Content.ReadAsStreamAsync())
                {
                    using (var fileStream = new System.IO.FileStream(apkFilePath, System.IO.FileMode.Create))
                    {
                        await stream.CopyToAsync(fileStream);
                    }
                }
            }
        }

        // 下载完了,直接启动安装
        InstallApk(apkFilePath);
        Toast.MakeText(this, "下载完成,开始安装", ToastLength.Short).Show();
    }
    catch (Exception ex)
    {
        // 出错了给用户提示
        Toast.MakeText(this, $"下载失败:{ex.Message}", ToastLength.Long).Show();
    }
};

4. 实现APK安装的方法

Android 7.0之后不能直接用文件路径启动安装,得用FileProvider,所以还要加这个方法:

private void InstallApk(string filePath)
{
    var apkFile = new Java.IO.File(filePath);
    // 这里的${PackageName}要换成你的应用包名,或者直接写死比如"com.yourcompany.yourapp"
    var apkUri = Android.Support.V4.Content.FileProvider.GetUriForFile(this, $"{PackageName}.fileprovider", apkFile);

    var installIntent = new Intent(Intent.ActionView);
    installIntent.SetDataAndType(apkUri, "application/vnd.android.package-archive");
    installIntent.AddFlags(ActivityFlags.GrantReadUriPermission); // 给安装程序读取权限
    installIntent.AddFlags(ActivityFlags.NewTask); // 新开任务启动安装

    StartActivity(installIntent);
}

5. 配置FileProvider(必须!)

在AndroidManifest.xml的<application>标签里加这段:

<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>

然后在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="external_files" path="." />
</paths>

三、几个要注意的小细节

  • Android 13+适配:如果你的目标SDK是33及以上,存储权限规则变了,建议用应用私有存储或者MediaStore来保存APK,避免权限问题。
  • 异步操作:所有网络和文件操作都要用async/await,别搞同步操作,不然会卡UI甚至触发ANR。
  • 异常处理:一定要捕获异常,给用户清晰的提示,别让程序崩了。

内容的提问来源于stack exchange,提问作者DonAlex1897

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.13 08:15:11