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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 03:40:43