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
相关产品推荐
相关产品推荐

