Xamarin Forms中Android端重复写入可访问外部存储的问题
解决方案:Xamarin Forms跨平台实现可访问目录文件写入
要在UWP和Android(11/12)平台实现文件自动保存到文件管理器可访问的位置(如下载文件夹),同时保持跨平台兼容性,需要使用**Xamarin依赖服务(Dependency Service)**实现平台特定的存储逻辑,核心思路是在共享代码中定义接口,各平台分别实现对应逻辑。
步骤1:定义跨平台存储接口
在共享项目中创建一个接口,统一文件操作的方法:
public interface IFileStorageService { // 获取可被文件管理器访问的文件路径(UWP返回物理路径,Android返回文件名标识) Task<string> GetPublicDataFilePathAsync(); // 向文件追加内容(支持持续写入) Task AppendTextToFileAsync(string content); }
步骤2:Android平台实现
Android 11/12采用Scoped Storage机制,不能直接访问公共目录的物理路径,需通过MediaStore写入到Downloads文件夹。
配置AndroidManifest权限
更新AndroidManifest.xml,添加必要权限:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="30" />
实现存储服务类
在Android项目中创建实现类,通过ContentResolver操作MediaStore:
[assembly: Dependency(typeof(AndroidFileStorageService))] namespace YourAppNamespace.Droid { public class AndroidFileStorageService : IFileStorageService { private readonly string _fileName = "data.txt"; private readonly string _mimeType = "text/plain"; public Task<string> GetPublicDataFilePathAsync() { // Scoped Storage下无法直接返回物理路径,返回文件名用于标识 return Task.FromResult(_fileName); } public async Task AppendTextToFileAsync(string content) { var contentValues = new ContentValues(); contentValues.Put(MediaStore.MediaColumns.DisplayName, _fileName); contentValues.Put(MediaStore.MediaColumns.MimeType, _mimeType); contentValues.Put(MediaStore.MediaColumns.RelativePath, Environment.DirectoryDownloads); var resolver = Android.App.Application.Context.ContentResolver; var fileUri = resolver.Insert(MediaStore.Downloads.ExternalContentUri, contentValues); if (fileUri == null) throw new IOException("无法在Downloads目录创建文件"); using (var outputStream = resolver.OpenOutputStream(fileUri)) using (var writer = new StreamWriter(outputStream)) { await writer.WriteLineAsync(content); await writer.FlushAsync(); } } } }
添加动态权限申请
Android 6.0+需要动态申请存储权限,在MainActivity中添加权限处理:
protected override void OnCreate(Bundle savedInstanceState) { base.OnCreate(savedInstanceState); RequestStoragePermissions(); } private void RequestStoragePermissions() { if (Build.VERSION.SdkInt >= BuildVersionCodes.M) { var permissions = new[] { Manifest.Permission.ReadExternalStorage, Manifest.Permission.WriteExternalStorage }; if (CheckSelfPermission(Manifest.Permission.WriteExternalStorage) != Permission.Granted) RequestPermissions(permissions, 100); } } public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults) { Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults); base.OnRequestPermissionsResult(requestCode, permissions, grantResults); }
步骤3:UWP平台实现
UWP直接通过KnownFolders访问Downloads文件夹,实现逻辑更简单:
[assembly: Dependency(typeof(UwpFileStorageService))] namespace YourAppNamespace.UWP { public class UwpFileStorageService : IFileStorageService { private StorageFile _dataFile; public async Task<string> GetPublicDataFilePathAsync() { var downloadsFolder = KnownFolders.DownloadsFolder; _dataFile = await downloadsFolder.CreateFileAsync("data.txt", CreationCollisionOption.OpenIfExists); return _dataFile.Path; } public async Task AppendTextToFileAsync(string content) { if (_dataFile == null) await GetPublicDataFilePathAsync(); await FileIO.AppendTextAsync(_dataFile, content + Environment.NewLine); } } }
步骤4:共享代码中调用服务
在共享项目的业务逻辑中,通过DependencyService获取平台实现,调用写入方法:
// 获取存储服务实例 var storageService = DependencyService.Get<IFileStorageService>(); // 持续写入数据示例 await storageService.AppendTextToFileAsync("蓝牙设备记录数据:" + DateTime.Now.ToString());
关键说明
- 跨平台兼容:通过依赖服务隔离平台特定代码,共享代码无需直接引用Android/UWP专属API,保留UWP兼容性。
- Android Scoped Storage:替代已废弃的
GetExternalStoragePublicDirectory,使用MediaStore写入公共Downloads目录,符合Android 11/12的存储规范。 - 持续写入:
AppendTextToFileAsync方法支持每次调用追加内容,满足记录数据的需求。
内容的提问来源于stack exchange,提问作者Trevor
相关产品推荐
相关产品推荐

