如何在Xamarin Forms中从Uri下载图片并保存至系统图库?
嘿,要在Xamarin.Forms里实现从Uri下载图片并保存到系统图库,核心得靠依赖服务(Dependency Service)——毕竟访问系统相册是平台专属操作,共享项目没法直接搞定。我给你一步步捋清楚怎么做:
整体思路
Xamarin.Forms是跨平台框架,这类和系统底层交互的功能,得用「共享项目定义接口 + 各平台实现具体逻辑」的方式:先在共享项目里定义保存图片的接口,然后分别给Android、iOS写平台专属的实现,最后在共享项目里调用接口完成下载+保存流程。
第一步:在共享项目中定义接口
先写一个统一的接口,用来规范保存图片的方法:
public interface ISaveImageService { // 传入图片字节数组和文件名,返回是否保存成功 Task<bool> SaveImageToGallery(byte[] imageBytes, string imageFileName); }
再写一个从Uri下载图片的工具方法,把远程图片转成字节数组:
private async Task<byte[]> DownloadImageFromUri(string imageUri) { using (var httpClient = new HttpClient()) { try { return await httpClient.GetByteArrayAsync(imageUri); } catch (Exception ex) { Console.WriteLine($"下载图片出错:{ex.Message}"); // 这里可以根据需求加用户提示,比如弹个Alert return null; } } }
第二步:Android平台实现
先配置权限
在AndroidManifest.xml里添加存储权限:
<!-- Android 9及以下需要 --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <!-- Android 13及以上需要 --> <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
注意:Android 6.0及以上还要动态申请权限,可以用Permissions.RequestAsync<Permissions.StorageWrite>()来处理。
写接口实现类
创建一个实现ISaveImageService的类,记得加Dependency标记让Xamarin识别:
[assembly: Dependency(typeof(SaveImageServiceAndroid))] namespace YourAppName.Droid.Services { public class SaveImageServiceAndroid : ISaveImageService { public async Task<bool> SaveImageToGallery(byte[] imageBytes, string imageFileName) { try { // 确保文件名有合法后缀 if (!imageFileName.EndsWith(".jpg") && !imageFileName.EndsWith(".png")) { imageFileName += ".jpg"; } // 适配Android 10+的Scoped Storage if (Build.VERSION.SdkInt >= BuildVersionCodes.Q) { var values = new ContentValues(); values.Put(MediaStore.Images.Media.Columns.DisplayName, imageFileName); values.Put(MediaStore.Images.Media.Columns.MimeType, "image/jpeg"); values.Put(MediaStore.Images.Media.Columns.RelativePath, Environment.DirectoryPictures); var uri = Android.App.Application.Context.ContentResolver.Insert(MediaStore.Images.Media.ExternalContentUri, values); using (var stream = Android.App.Application.Context.ContentResolver.OpenOutputStream(uri)) { await stream.WriteAsync(imageBytes, 0, imageBytes.Length); } // 通知系统图库刷新,否则刚保存的图片可能看不到 MediaScannerConnection.ScanFile(Android.App.Application.Context, new[] { uri.Path }, null, null); } else { // 旧版本Android的存储方式 var picturesDir = Environment.GetExternalStoragePublicDirectory(Environment.DirectoryPictures); var imageFile = new File(picturesDir, imageFileName); using (var stream = new FileStream(imageFile.Path, FileMode.Create)) { await stream.WriteAsync(imageBytes, 0, imageBytes.Length); } MediaScannerConnection.ScanFile(Android.App.Application.Context, new[] { imageFile.Path }, null, null); } return true; } catch (Exception ex) { Console.WriteLine($"Android保存图片失败:{ex.Message}"); return false; } } } }
第三步:iOS平台实现
先配置权限
在Info.plist里添加相册访问权限描述(必须加,否则App会崩溃):
<key>NSPhotoLibraryAddUsageDescription</key> <string>需要访问相册来保存下载的图片</string>
写接口实现类
同样创建实现类并标记Dependency:
[assembly: Dependency(typeof(SaveImageServiceiOS))] namespace YourAppName.iOS.Services { public class SaveImageServiceiOS : ISaveImageService { public async Task<bool> SaveImageToGallery(byte[] imageBytes, string imageFileName) { try { await Task.Run(() => { var imageData = NSData.FromArray(imageBytes); var uiImage = UIImage.LoadFromData(imageData); if (uiImage == null) { throw new Exception("无法将字节数组转换为图片"); } // 保存到相册 uiImage.SaveToPhotosAlbum((img, error) => { if (error != null) { throw new Exception($"iOS保存图片失败:{error.LocalizedDescription}"); } }); }); return true; } catch (Exception ex) { Console.WriteLine($"iOS保存图片失败:{ex.Message}"); return false; } } } }
第四步:在共享项目中调用
比如在按钮点击事件里完成整个流程:
private async void SaveImageBtn_Clicked(object sender, EventArgs e) { string targetUri = "https://example.com/your-image.jpg"; // 替换成你的图片Uri string saveFileName = "MyDownloadedImage"; // 自定义保存的文件名 // 1. 下载图片 byte[] imageBytes = await DownloadImageFromUri(targetUri); if (imageBytes == null) { await DisplayAlert("提示", "图片下载失败", "确定"); return; } // 2. 获取平台服务并保存 var saveService = DependencyService.Get<ISaveImageService>(); bool isSuccess = await saveService.SaveImageToGallery(imageBytes, saveFileName); // 3. 给用户反馈 if (isSuccess) { await DisplayAlert("成功", "图片已保存到系统图库", "确定"); } else { await DisplayAlert("失败", "图片保存出错,请稍后重试", "确定"); } }
额外注意点
- 权限处理:Android 6+要动态申请存储权限,iOS必须在Info.plist里写清楚权限用途,否则会被App Store拒绝。
- 文件名要避免特殊字符,比如斜杠、问号等,防止保存失败。
- 可以给下载过程加个加载动画,提升用户体验。
内容的提问来源于stack exchange,提问作者Giorge Caique
相关产品推荐
相关产品推荐

