Xamarin中Android的MainActivity如何修改文件下载路径?
嘿,你已经实现了下载功能还能正常运行,很棒!现在想把文件从应用专属的storage/emulated/0/Application/data/file/Download移到系统公共的storage/emulated/0/Download,还碰到了权限报错的问题,我来帮你捋清楚怎么解决~
一、先改路径:从应用专属到公共Download目录
你当前代码里用的ApplicationContext.GetExternalFilesDir(Android.OS.Environment.DirectoryDownloads),获取的是应用自己独占的外部存储目录,这就是文件跑到Application/data/file/Download的原因。要拿到公共的Download目录,得换个方式获取路径:
适配Android 9及以下版本
对于Android 9(API 28)及更早的系统,直接用GetExternalStoragePublicDirectory就能拿到公共Download目录,代码改成这样:
public void Downloaded() { CrossDownloadManager.Current.PathNameForDownloadedFile = new System.Func<IDownloadFile, string>(file => { // 从URL里提取文件名 string fileName = Android.Net.Uri.Parse(file.Url).Path.Split('/').Last(); // 获取系统公共Download目录 var publicDownloadDir = Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryDownloads); // 拼接最终路径 return Path.Combine(publicDownloadDir.AbsolutePath, fileName); }); }
适配Android 10及以上(Scoped Storage)
从Android 10(API 29)开始,Google推出了Scoped Storage(分区存储),GetExternalStoragePublicDirectory被废弃,而且直接写入公共目录不再受WRITE_EXTERNAL_STORAGE权限保护。这里有两种适配思路:
思路1:用MediaStore写入公共目录(Google推荐)
这种方式不需要特殊权限,直接通过MediaStore把文件插入公共Download目录,结合你的CrossDownloadManager使用的代码示例:
public void Downloaded() { CrossDownloadManager.Current.PathNameForDownloadedFile = new System.Func<IDownloadFile, string>(file => { string fileName = Android.Net.Uri.Parse(file.Url).Path.Split('/').Last(); string publicDownloadPath; if (Android.OS.Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.Q) { // Android 10+ 通过MediaStore获取公共Download的存储入口 var contentValues = new ContentValues(); contentValues.Put(MediaStore.Downloads.Columns.DisplayName, fileName); contentValues.Put(MediaStore.Downloads.Columns.MimeType, "application/octet-stream"); // 根据你的文件类型修改 contentValues.Put(MediaStore.Downloads.Columns.RelativePath, Android.OS.Environment.DirectoryDownloads); var contentUri = ContentResolver.Insert(MediaStore.Downloads.ExternalContentUri, contentValues); // 若CrossDownloadManager需要路径,可临时获取公共目录路径(但直接写入需特殊权限,更推荐MediaStream写入) publicDownloadPath = Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryDownloads).AbsolutePath; } else { publicDownloadPath = Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryDownloads).AbsolutePath; } return Path.Combine(publicDownloadPath, fileName); }); }
思路2:用系统DownloadManager(最省心)
其实如果只是要把文件下到公共Download目录,完全可以不用自己折腾路径和权限,直接用Android系统自带的DownloadManager,它会自动处理所有细节:
// 获取系统DownloadManager服务 var downloadManager = (DownloadManager)GetSystemService(Context.DownloadService); // 创建下载请求 DownloadManager.Request request = new DownloadManager.Request(Android.Net.Uri.Parse(yourFileUrl)); // 设置通知栏显示的信息 request.SetTitle("你的文件下载"); request.SetDescription("正在下载,请稍候"); // 直接指定保存到公共Download目录 request.SetDestinationInExternalPublicDir(Android.OS.Environment.DirectoryDownloads, fileName); // 下载完成后显示通知 request.SetNotificationVisibility(DownloadManager.Request.VisibilityVisibleNotifyCompleted); // 开始下载 long downloadId = downloadManager.Enqueue(request);
这种方式不需要你手动处理路径,权限也由系统自动搞定,超级省心!
二、解决权限报错的问题
你说已经加了WRITE_EXTERNAL_STORAGE权限但还是报错,这是因为Android版本不同,权限规则变了:
Android 9及以下
只需要两步:
- 在
AndroidManifest.xml里声明权限:<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> - 运行时动态请求权限(Android 6开始需要动态申请危险权限):
if (ContextCompat.CheckSelfPermission(this, Manifest.Permission.WriteExternalStorage) != Permission.Granted) { ActivityCompat.RequestPermissions(this, new string[] { Manifest.Permission.WriteExternalStorage }, 100); }
Android 10及以上
WRITE_EXTERNAL_STORAGE权限只能访问应用自己的专属目录,要写入公共Download目录,得二选一:
- 用MediaStore写入:不需要额外权限,这是Google推荐的合规方式,只是代码稍微繁琐一点。
- 申请MANAGE_EXTERNAL_STORAGE权限:这是个特殊权限,需要在
AndroidManifest.xml里添加:
然后运行时引导用户去应用设置里手动授权:<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
注意:这个权限比较敏感,Google Play对它的审核很严格,只有确实需要访问所有外部文件的应用才能使用,所以尽量优先选择MediaStore的方式。if (Android.OS.Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.R) { if (!Environment.IsExternalStorageManager) { Intent intent = new Intent(Settings.ActionManageAppAllFilesAccessPermission); intent.SetData(Android.Net.Uri.Parse("package:" + PackageName)); StartActivity(intent); } }
内容的提问来源于stack exchange,提问作者Dev Megaminds

