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

Xamarin.Android实现将对象列表写入设备下载存储CSV文件的方法

在C# Xamarin.Android中导出对象列表为带表头的CSV文件(存储到下载目录)

嘿,这个需求我熟!咱们一步步来实现,保证导出的CSV带表头、每行一个对象,还能稳稳存到设备的下载目录里。我会把权限处理、CSV格式构建、文件写入这些关键点都讲清楚,避免踩坑。

1. 先搞定存储权限

Android对外部存储的访问有严格的权限控制,尤其是6.0(API 23)及以上版本需要动态申请权限,10+版本还得适配Scoped Storage。

首先在AndroidManifest.xml里声明必要权限:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!-- Android 10+ 访问公共下载目录额外需要的权限 -->
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" android:minSdkVersion="30" />

然后在代码里动态申请权限:

private async Task<bool> CheckAndRequestPermissions()
{
    var writePerm = ContextCompat.CheckSelfPermission(this, Manifest.Permission.WriteExternalStorage);
    var readPerm = ContextCompat.CheckSelfPermission(this, Manifest.Permission.ReadExternalStorage);

    var permissionsToAsk = new List<string>();
    if (writePerm != Permission.Granted) permissionsToAsk.Add(Manifest.Permission.WriteExternalStorage);
    if (readPerm != Permission.Granted) permissionsToAsk.Add(Manifest.Permission.ReadExternalStorage);

    // Android 10+ 额外处理全局存储权限
    if (Build.VERSION.SdkInt >= BuildVersionCodes.R && !Environment.IsExternalStorageManager)
    {
        permissionsToAsk.Add(Manifest.Permission.ManageExternalStorage);
    }

    if (permissionsToAsk.Any())
    {
        var result = await ActivityCompat.RequestPermissionsAsync(this, permissionsToAsk.ToArray());
        return result.All(r => r.Value == Permission.Granted);
    }
    return true;
}

2. 定义你的对象类

假设你要导出的是Person对象列表,先定义这个类(换成你自己的业务对象就行):

public class Person
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
    public string Email { get; set; }
}

3. 构建CSV内容(带表头+处理特殊字符)

要特别注意CSV的特殊字符处理! 如果字段里包含逗号、引号或者换行,直接写会导致格式错乱,必须用双引号包裹,内部的双引号还要转成两个双引号。

写个工具方法处理这个,再生成完整的CSV内容:

// 处理CSV字段的特殊字符
private string EscapeCsvValue(string value)
{
    if (string.IsNullOrEmpty(value)) return string.Empty;
    
    // 如果包含逗号、引号、换行,就用双引号包裹,内部双引号转义
    if (value.Contains(",") || value.Contains("\"") || value.Contains("\n") || value.Contains("\r"))
    {
        return $"\"{value.Replace("\"", "\"\"")}\"";
    }
    return value;
}

// 生成带表头的CSV内容
private string GenerateCsvContent(List<Person> persons)
{
    // 表头用对象的属性名
    var headers = new List<string> { nameof(Person.Id), nameof(Person.Name), nameof(Person.Age), nameof(Person.Email) };
    var csvBuilder = new StringBuilder();
    
    // 添加表头行
    csvBuilder.AppendLine(string.Join(",", headers));

    // 逐行添加对象数据
    foreach (var person in persons)
    {
        var values = new List<string>
        {
            person.Id.ToString(),
            EscapeCsvValue(person.Name),
            person.Age.ToString(),
            EscapeCsvValue(person.Email)
        };
        csvBuilder.AppendLine(string.Join(",", values));
    }

    return csvBuilder.ToString();
}

4. 写入CSV到下载目录

不同Android版本的存储策略不一样,我们要做版本适配:Android 9及以下可以直接操作文件,Android 10+推荐用MediaStore写入公共下载目录,避免Scoped Storage限制。

private async Task<bool> SaveCsvToDownloads(string csvContent, string fileName = "data.csv")
{
    try
    {
        if (Build.VERSION.SdkInt >= BuildVersionCodes.Q)
        {
            // Android 10+ 使用MediaStore写入公共下载目录
            var contentValues = new ContentValues();
            contentValues.Put(MediaStore.MediaColumns.DisplayName, fileName);
            contentValues.Put(MediaStore.MediaColumns.MimeType, "text/csv");
            contentValues.Put(MediaStore.MediaColumns.RelativePath, Environment.DirectoryDownloads);

            var uri = ContentResolver.Insert(MediaStore.Downloads.ExternalContentUri, contentValues);
            if (uri == null) return false;

            using (var outputStream = ContentResolver.OpenOutputStream(uri))
            using (var writer = new StreamWriter(outputStream))
            {
                await writer.WriteAsync(csvContent);
            }
        }
        else
        {
            // Android 9及以下直接操作文件
            var downloadsDir = Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryDownloads);
            var csvFile = new File(downloadsDir, fileName);

            using (var writer = new StreamWriter(csvFile))
            {
                await writer.WriteAsync(csvContent);
            }
        }

        Toast.MakeText(this, "CSV已成功导出到下载目录", ToastLength.Short).Show();
        return true;
    }
    catch (Exception ex)
    {
        Toast.MakeText(this, $"导出失败:{ex.Message}", ToastLength.Short).Show();
        return false;
    }
}

5. 整合起来使用

在你的Activity里,比如按钮点击事件中调用这些方法:

private async void ExportBtn_Click(object sender, EventArgs e)
{
    // 先检查权限
    var hasPermission = await CheckAndRequestPermissions();
    if (!hasPermission)
    {
        Toast.MakeText(this, "需要存储权限才能导出CSV", ToastLength.Short).Show();
        return;
    }

    // 模拟你的业务对象列表
    var personList = new List<Person>
    {
        new Person { Id = 1, Name = "张三", Age = 25, Email = "zhangsan@example.com" },
        new Person { Id = 2, Name = "李四, 带逗号", Age = 30, Email = "lisi@example.com" },
        new Person { Id = 3, Name = "王五\"带引号\"", Age = 28, Email = "wangwu@example.com" }
    };

    // 生成CSV内容
    var csvContent = GenerateCsvContent(personList);

    // 保存到下载目录
    await SaveCsvToDownloads(csvContent);
}

这样导出的CSV就会和你参考的示例一样,带表头、每行一个对象,还能在设备的下载文件夹里找到它!

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 06:59:20