Xamarin Forms无token.json时如何编程获取Google Drive API凭证
问题根因
FileDataStore是Google .NET客户端库为桌面/控制台场景设计的默认凭证存储实现,未做任何移动端适配,在Xamarin.Android环境下运行存在两个不可绕过的兼容问题:
- 路径处理逻辑存在bug:传入完整的
token.json文件路径后,其内部逻辑会将该路径识别为文件夹,额外拼接一层和用户、授权范围相关的子路径与文件名,直接导致路径错乱,这也是测试中出现文件找不到、文件已存在类错误的核心原因 - 文件IO逻辑不匹配安卓沙箱规则:默认实现的文件创建、读写权限逻辑未适配安卓应用私有目录的访问规范,会触发只读、权限拒绝类错误
另外原代码中使用.Result阻塞异步方法,在移动端UI线程调用时极易引发死锁,属于不推荐的写法。
解决方案
弃用默认的FileDataStore,自行实现IDataStore接口对接Xamarin.Forms跨平台合法存储路径,绕开默认实现的兼容问题。
1. 实现移动端适配的自定义IDataStore
using Google.Apis.Json; using Google.Apis.Util.Store; using System.IO; using System.Threading.Tasks; using Xamarin.Essentials; public class MobileFileDataStore : IDataStore { private readonly string _appDataDir; public MobileFileDataStore() { // 自动映射到各平台应用私有目录,安卓API24及以上无需额外申请存储权限即可读写 _appDataDir = FileSystem.AppDataDirectory; if (!Directory.Exists(_appDataDir)) { Directory.CreateDirectory(_appDataDir); } } public Task ClearAsync() { if (Directory.Exists(_appDataDir)) { Directory.Delete(_appDataDir, true); Directory.CreateDirectory(_appDataDir); } return Task.CompletedTask; } public Task DeleteAsync<T>(string key) { var targetFile = Path.Combine(_appDataDir, $"{key}.json"); if (File.Exists(targetFile)) File.Delete(targetFile); return Task.CompletedTask; } public Task<T> GetAsync<T>(string key) { var targetFile = Path.Combine(_appDataDir, $"{key}.json"); if (!File.Exists(targetFile)) return Task.FromResult(default(T)); using var stream = File.OpenRead(targetFile); return Task.FromResult(NewtonsoftJsonSerializer.Instance.Deserialize<T>(stream)); } public Task StoreAsync<T>(string key, T value) { var targetFile = Path.Combine(_appDataDir, $"{key}.json"); if (File.Exists(targetFile)) File.Delete(targetFile); using var stream = File.Create(targetFile); NewtonsoftJsonSerializer.Instance.Serialize(stream, value); return Task.CompletedTask; } }
若项目未引用Xamarin.Essentials,可手动替换
FileSystem.AppDataDirectory为Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),效果一致。
2. 修改授权调用逻辑
将原同步阻塞写法改为异步调用,替换默认FileDataStore为自定义实现:
// 方法标记为async,避免阻塞 public static async Task DriveCreateFolder(string credjson) { try { byte[] byteArray = Encoding.ASCII.GetBytes(credjson); UserCredential credential; using (var stream = new MemoryStream(byteArray)) { // 替换为自定义存储实现 credential = await GoogleWebAuthorizationBroker.AuthorizeAsync( GoogleClientSecrets.FromStream(stream).Secrets, Scopes, "user", CancellationToken.None, new MobileFileDataStore()); } // 以下Drive服务初始化、文件夹创建逻辑保持原有逻辑即可 var service = new DriveService(new BaseClientService.Initializer { HttpClientInitializer = credential, ApplicationName = ApplicationName }); var fileMetadata = new Google.Apis.Drive.v3.Data.File() { Name = "MyAppFolder", MimeType = "application/vnd.google-apps.folder" }; var request = service.Files.Create(fileMetadata); request.Fields = "id"; var file = request.Execute(); Console.WriteLine("Folder ID: " + file.Id); ApplicationFolder = file; } catch (AggregateException) { Console.WriteLine("Credential load failed"); } catch (Exception e) { Console.WriteLine($"Operation error: {e.Message}"); throw; } }
3. 安卓端必要配置
在Android项目的AndroidManifest.xml中添加网络权限,否则授权流程无法访问Google服务:
<uses-permission android:name="android.permission.INTERNET" />
同时确认Google Cloud控制台中OAuth客户端的配置:选择「已安装的应用」类型,填写的安卓包名、签名证书SHA1值与测试包完全匹配,否则会出现授权被拒绝的问题。
测试报错原因对应说明
- 测试1:传入相对路径
token.json时,安卓默认工作目录为只读系统分区,触发只读文件系统错误 - 测试2/3:默认FileDataStore将传入的
token.json文件路径识别为文件夹,尝试在该路径下创建子文件时,因父路径实际是文件而非目录,触发文件找不到、访问拒绝错误 - 测试4:默认FileDataStore写文件时未做存在性兼容处理,发现目标路径已有同名文件直接抛出异常,属于实现逻辑bug。
内容的提问来源于stack exchange,提问作者James Dsouza
相关产品推荐
相关产品推荐

