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

Android使用Gmail API报错:unable to create directory: /tokens

解决Android端调用Gmail API时的/tokens目录创建失败问题

问题描述

使用Google Gmail API Java快速启动示例代码在Android平台上调用Gmail API发送邮件(不通过Intent),已配置Android端OAuth 2.0 Client ID,但运行时触发致命异常:

FATAL EXCEPTION: main
Process: com.nls.myapplication22, PID: 3216
java.lang.RuntimeException: Unable to start activity ComponentInfo: java.lang.RuntimeException: java.io.IOException: unable to create directory: /tokens

问题代码分析

原代码直接沿用桌面端实现,存在两个核心问题:

  1. 存储路径错误:使用相对路径"tokens"会尝试在系统根目录创建文件夹,Android应用无此权限。
  2. 授权组件不兼容:LocalServerReceiver是桌面端用于接收OAuth回调的组件,Android平台无法启动本地监听端口。

原核心代码片段:

String TOKENS_DIRECTORY_PATH = "tokens";
[...]
GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
    HTTP_TRANSPORT, JSON_FACTORY, clientSecrets, SCOPES)
    .setDataStoreFactory(new FileDataStoreFactory(new java.io.File(TOKENS_DIRECTORY_PATH)))
    .setAccessType("offline")
    .build();
LocalServerReceiver receiver = new LocalServerReceiver.Builder().setPort(8888).build();
Credential credential = new AuthorizationCodeInstalledApp(flow, receiver).authorize("user");

解决方案

1. 调整存储方式(解决目录创建权限问题)

替换桌面端的FileDataStoreFactory为Android专用的SharedPreferencesDataStoreFactory,利用应用内部存储保存OAuth令牌,无需额外权限:

// 获取应用SharedPreferences实例
SharedPreferences sharedPreferences = getApplicationContext()
    .getSharedPreferences("GmailOAuthTokens", Context.MODE_PRIVATE);
// 创建适配Android的DataStoreFactory
DataStoreFactory dataStoreFactory = new SharedPreferencesDataStoreFactory(sharedPreferences);

// 重构AuthorizationCodeFlow
GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
    HTTP_TRANSPORT, JSON_FACTORY, clientSecrets, SCOPES)
    .setDataStoreFactory(dataStoreFactory)
    .setAccessType("offline")
    .build();

2. 替换授权流程(适配Android平台)

移除LocalServerReceiver和AuthorizationCodeInstalledApp,改用Google官方的Android登录组件GoogleSignInClient完成OAuth授权:

第一步:修改Gradle依赖

替换桌面端Jetty库为Android适配的OAuth组件和登录库:

dependencies {
    [...]
    implementation("com.google.api-client:google-api-client:2.2.0")
    // 移除桌面端Jetty依赖
    // implementation("com.google.oauth-client:google-oauth-client-jetty:1.34.1")
    // 添加Android OAuth客户端库
    implementation("com.google.oauth-client:google-oauth-client-android:1.34.1")
    implementation("com.google.apis:google-api-services-gmail:v1-rev20220404-2.0.0")
    implementation("javax.mail:mail:1.4.7")
    // 添加Google登录组件
    implementation("com.google.android.gms:play-services-auth:20.7.0")
}

第二步:实现Google登录授权流程

// 定义登录请求码
private static final int RC_SIGN_IN = 1001;
private GoogleSignInClient mGoogleSignInClient;

// 初始化GoogleSignInClient
private void initGoogleSignIn() {
    GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
            .requestScopes(new Scope(GmailScopes.GMAIL_SEND)) // 请求Gmail发送权限
            .requestEmail()
            .build();
    mGoogleSignInClient = GoogleSignIn.getClient(this, gso);
}

// 启动登录流程
private void startSignIn() {
    Intent signInIntent = mGoogleSignInClient.getSignInIntent();
    startActivityForResult(signInIntent, RC_SIGN_IN);
}

// 处理登录回调
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == RC_SIGN_IN) {
        Task<GoogleSignInAccount> task = GoogleSignIn.getSignedInAccountFromIntent(data);
        handleSignInResult(task);
    }
}

// 处理登录结果,获取用于Gmail API的Credential
private void handleSignInResult(Task<GoogleSignInAccount> completedTask) {
    try {
        GoogleSignInAccount account = completedTask.getResult(ApiException.class);
        // 获取访问令牌
        String accessToken = GoogleAuthUtil.getToken(this, account.getAccount(), 
            "oauth2:" + GmailScopes.GMAIL_SEND);
        // 构建Credential
        Credential credential = new Credential.Builder(BearerToken.authorizationHeaderAccessMethod())
                .setAccessToken(accessToken)
                .setRefreshToken(account.getRefreshToken())
                .build();
        // 初始化Gmail服务
        Gmail gmailService = new Gmail.Builder(HTTP_TRANSPORT, JSON_FACTORY, credential)
                .setApplicationName("你的应用名称")
                .build();
        // 调用Gmail API发送邮件
        sendEmail(gmailService);
    } catch (ApiException | IOException e) {
        e.printStackTrace();
    }
}

// 发送邮件示例方法(需实现邮件构建逻辑)
private void sendEmail(Gmail service) throws MessagingException, IOException {
    MimeMessage mimeMessage = createEmail("收件人邮箱", "发件人邮箱", "邮件主题", "邮件内容");
    Message message = createMessageWithEmail(mimeMessage);
    service.users().messages().send("me", message).execute();
}

3. 补充权限声明

在AndroidManifest.xml中添加网络权限:

<uses-permission android:name="android.permission.INTERNET" />

问题根源总结

  1. 权限问题:桌面端代码默认在根目录创建令牌存储文件夹,Android应用无系统根目录写入权限。
  2. 组件不兼容:LocalServerReceiver是桌面端专属组件,Android无法启动本地监听端口接收OAuth回调。
  3. 依赖错误:引入了桌面端Jetty服务器依赖,Android平台无需该组件。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 16:00:42