Andorid Google 登录接入文档

Google 登录接入文档

一、准备工作

1.1 Google Cloud Console 后台配置

  1. 前往 Google Cloud Console 创建项目
  2. 启用 Google Sign-In API
  3. 在「凭据」中创建 OAuth 2.0 客户端 ID
    • 应用类型选择「Android」
    • 填写包名(Package Name)
    • 填写 SHA-1 证书指纹
  4. 获取 Web Client ID (用于 requestIdToken

1.2 获取 SHA-1 证书指纹

方式一:使用 Gradle 命令

bash 复制代码
# Windows
gradlew signingReport

# Mac/Linux
./gradlew signingReport

方式二:使用 keytool 命令

bash 复制代码
# Debug 签名
keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android

# Release 签名
keytool -list -v -keystore your-release-key.jks -alias your-alias

注意: 需要将 Debug 和 Release 的 SHA-1 都配置到 Google Cloud Console。


二、Gradle 依赖配置

app/build.gradle 中添加 Google Sign-In SDK 依赖:

groovy 复制代码
dependencies {
    // Google 登录
    implementation 'com.google.android.gms:play-services-auth:19.2.0'
}

最新版本依赖(推荐):

groovy 复制代码
dependencies {
    implementation 'com.google.android.gms:play-services-auth:20.7.0'
}

三、AndroidManifest.xml 配置

3.1 添加 Meta-data 配置(可选)

xml 复制代码
<application>
    <!-- Google Client ID(可选,也可在代码中配置) -->
    <meta-data
        android:name="GOOGLE_CLIENT_ID"
        android:value="你的_WEB_CLIENT_ID.apps.googleusercontent.com" />
</application>

示例

xml 复制代码
<meta-data
    android:name="GOOGLE_CLIENT_ID"
    android:value="474284414568-kehn62d39adv885i1pkh4s7tnu98iflq.apps.googleusercontent.com" />

3.2 添加网络权限

xml 复制代码
<manifest>
    <uses-permission android:name="android.permission.INTERNET" />
</manifest>

四、SDK 初始化与登录控制器

4.1 完整控制器实现

java 复制代码
package com.example.myapplication;

import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.Intent;
import android.util.Log;

import androidx.annotation.NonNull;

import com.google.android.gms.auth.api.signin.GoogleSignIn;
import com.google.android.gms.auth.api.signin.GoogleSignInAccount;
import com.google.android.gms.auth.api.signin.GoogleSignInClient;
import com.google.android.gms.auth.api.signin.GoogleSignInOptions;
import com.google.android.gms.common.api.ApiException;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;

/**
 * Google 登录控制器
 */
public class TestGoogleLoginController {

    private static final String TAG = "TestGoogleLoginController";
    
    // Google 登录客户端
    private GoogleSignInClient mGoogleSignInClient;
    
    // 请求码
    public static final int RC_SIGN_IN = 9001;
    
    // 登录类型常量
    public static final int LOGIN_TYPE = 1;  // 登录
    public static final int BIND_TYPE = 2;   // 绑定
    
    private int currentType = 0;
    private Dialog dialog;
    
    // 单例
    private static TestGoogleLoginController instance = null;

    private TestGoogleLoginController() {}

    public static TestGoogleLoginController getInstance() {
        if (instance == null) {
            synchronized (TestGoogleLoginController.class) {
                if (instance == null) {
                    instance = new TestGoogleLoginController();
                }
            }
        }
        return instance;
    }

    /**
     * 初始化 Google 登录
     * @param context 上下文
     */
    public void init(Context context) {
        Log.e(TAG, "initGoogleLogin");
        try {
            // 替换为你的 Web Client ID
            String googleClientId = "你的_WEB_CLIENT_ID.apps.googleusercontent.com";
            
            GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
                    .requestIdToken(googleClientId)  // 请求 ID Token
                    .requestEmail()                   // 请求邮箱
                    .build();
                    
            mGoogleSignInClient = GoogleSignIn.getClient(context, gso);
        } catch (Throwable e) {
            Log.e(TAG, "初始化失败: " + e.getMessage());
        }
    }

    /**
     * 发起 Google 登录
     * @param activity Activity
     * @param loginType 登录类型(LOGIN_TYPE 或 BIND_TYPE)
     * @param dialog 加载弹窗(可为 null)
     */
    public void login(Activity activity, int loginType, Dialog dialog) {
        try {
            setDialog(dialog);
            this.currentType = loginType;
            
            Intent signInIntent = mGoogleSignInClient.getSignInIntent();
            activity.startActivityForResult(signInIntent, RC_SIGN_IN);
        } catch (Throwable e) {
            Log.e(TAG, "登录失败: " + e.getMessage());
        }
    }

    /**
     * 处理登录回调(必须在 Activity.onActivityResult 中调用)
     * @param data Intent 数据
     */
    public void onActivityResult(Intent data) {
        try {
            Task<GoogleSignInAccount> task = GoogleSignIn.getSignedInAccountFromIntent(data);
            GoogleSignInAccount account = task.getResult(ApiException.class);
            
            if (account != null) {
                if (this.currentType == BIND_TYPE) {
                    // 处理绑定逻辑
                    handleBind(account);
                } else {
                    // 处理登录逻辑
                    handleLogin(account);
                }
            }
        } catch (ApiException e) {
            Log.e(TAG, "登录异常,状态码: " + e.getStatusCode());
        } catch (Throwable e) {
            Log.e(TAG, "处理回调异常: " + e.getMessage());
        }
    }

    /**
     * 处理登录成功
     */
    private void handleLogin(GoogleSignInAccount account) {
        String idToken = account.getIdToken();
        String displayName = account.getDisplayName();
        String email = account.getEmail();
        String id = account.getId();
        
        Log.i(TAG, "登录成功");
        Log.i(TAG, "ID Token: " + idToken);
        Log.i(TAG, "用户名: " + displayName);
        Log.i(TAG, "邮箱: " + email);
        Log.i(TAG, "用户ID: " + id);
        
        // 将 idToken 发送到服务器进行验证
    }

    /**
     * 处理绑定
     */
    private void handleBind(GoogleSignInAccount account) {
        String idToken = account.getIdToken();
        String displayName = account.getDisplayName();
        
        Log.i(TAG, "绑定成功");
        Log.i(TAG, "ID Token: " + idToken);
        
        // 将 idToken 发送到服务器进行绑定
    }

    /**
     * Google 登出
     * @param activity Activity
     */
    public void logout(Activity activity) {
        try {
            if (mGoogleSignInClient != null) {
                mGoogleSignInClient.signOut().addOnCompleteListener(activity,
                        new OnCompleteListener<Void>() {
                            @Override
                            public void onComplete(@NonNull Task<Void> task) {
                                Log.i(TAG, "登出成功");
                            }
                        });
            }
        } catch (Throwable e) {
            Log.e(TAG, "登出异常: " + e.getMessage());
        }
    }

    /**
     * 撤销账号访问权限
     * @param activity Activity
     */
    public void revokeAccess(Activity activity) {
        try {
            if (mGoogleSignInClient != null) {
                mGoogleSignInClient.revokeAccess().addOnCompleteListener(activity,
                        new OnCompleteListener<Void>() {
                            @Override
                            public void onComplete(@NonNull Task<Void> task) {
                                Log.i(TAG, "撤销访问成功");
                            }
                        });
            }
        } catch (Throwable e) {
            Log.e(TAG, "撤销访问异常: " + e.getMessage());
        }
    }

    /**
     * 检查是否已登录
     */
    public boolean isLoggedIn(Context context) {
        GoogleSignInAccount account = GoogleSignIn.getLastSignedInAccount(context);
        return account != null;
    }

    /**
     * 获取上次登录的账号
     */
    public GoogleSignInAccount getLastSignedInAccount(Context context) {
        return GoogleSignIn.getLastSignedInAccount(context);
    }

    public Dialog getDialog() {
        return dialog;
    }

    public void setDialog(Dialog dialog) {
        this.dialog = dialog;
    }
}

五、Activity 中集成

5.1 初始化

java 复制代码
public class MainActivity extends AppCompatActivity {
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        // 初始化 Google 登录控制器
        TestGoogleLoginController.getInstance().init(this);
    }
}

5.2 处理回调(必需)

java 复制代码
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    
    // Google 登录回调
    if (requestCode == TestGoogleLoginController.RC_SIGN_IN) {
        TestGoogleLoginController.getInstance().onActivityResult(data);
    }
}

5.3 登录调用

java 复制代码
// 登录
btnGoogleLogin.setOnClickListener(v -> {
    TestGoogleLoginController.getInstance().login(
        this, 
        TestGoogleLoginController.LOGIN_TYPE, 
        null
    );
});

// 绑定
btnGoogleBind.setOnClickListener(v -> {
    TestGoogleLoginController.getInstance().login(
        this, 
        TestGoogleLoginController.BIND_TYPE, 
        null
    );
});

// 登出
btnGoogleLogout.setOnClickListener(v -> {
    TestGoogleLoginController.getInstance().logout(this);
});

六、获取用户信息

登录成功后可获取的用户信息:

java 复制代码
public void getUserInfo(GoogleSignInAccount account) {
    // 用户 ID
    String id = account.getId();
    
    // 显示名称
    String displayName = account.getDisplayName();
    
    // 名字
    String givenName = account.getGivenName();
    
    // 姓氏
    String familyName = account.getFamilyName();
    
    // 邮箱
    String email = account.getEmail();
    
    // 头像 URL
    Uri photoUrl = account.getPhotoUrl();
    
    // ID Token(用于服务器验证)
    String idToken = account.getIdToken();
    
    Log.i(TAG, "用户ID: " + id);
    Log.i(TAG, "显示名称: " + displayName);
    Log.i(TAG, "邮箱: " + email);
    Log.i(TAG, "头像: " + photoUrl);
    Log.i(TAG, "ID Token: " + idToken);
}

七、服务器端验证

7.1 验证 ID Token

将客户端获取的 idToken 发送到服务器,服务器使用 Google API 验证:

Node.js 示例

javascript 复制代码
const {OAuth2Client} = require('google-auth-library');
const client = new OAuth2Client(CLIENT_ID);

async function verify(token) {
    const ticket = await client.verifyIdToken({
        idToken: token,
        audience: CLIENT_ID,
    });
    const payload = ticket.getPayload();
    const userid = payload['sub'];
    // 验证成功,返回用户信息
    return payload;
}

Java 示例

java 复制代码
import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier;

GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder(transport, jsonFactory)
    .setAudience(Collections.singletonList(CLIENT_ID))
    .build();

GoogleIdToken idToken = verifier.verify(tokenString);
if (idToken != null) {
    Payload payload = idToken.getPayload();
    String userId = payload.getSubject();
    String email = payload.getEmail();
    // 验证成功
}

八、错误码说明

状态码 常量 说明
7 NETWORK_ERROR 网络错误
10 DEVELOPER_ERROR 配置错误(SHA-1 或包名不匹配)
12500 SIGN_IN_CANCELLED 用户取消登录
12501 SIGN_IN_CURRENTLY_IN_PROGRESS 登录正在进行中
12502 SIGN_IN_FAILED 登录失败

九、常见问题

Q1: 错误码 10 (DEVELOPER_ERROR)

原因:SHA-1 证书指纹或包名配置错误

解决方案

  1. 确保 Google Cloud Console 中配置的包名与 build.gradle 中的 applicationId 一致
  2. 确保 SHA-1 指纹正确(Debug 和 Release 都需要配置)
  3. 使用 requestIdToken() 时必须使用 Web Client ID,不是 Android Client ID

Q2: 无法获取 ID Token

原因 :未正确配置 requestIdToken()

解决方案

java 复制代码
GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
        .requestIdToken(WEB_CLIENT_ID)  // 必须是 Web Client ID
        .requestEmail()
        .build();

Q3: 登录后立即显示已登出

原因:可能是模拟器或 Google Play Services 版本问题

解决方案

  1. 使用真机测试
  2. 更新 Google Play Services 到最新版本

Q4: 混淆配置

proguard-rules.pro 中添加:

proguard 复制代码
# Google Play Services
-keep class com.google.android.gms.** { *; }
-keep class com.google.android.gms.auth.** { *; }
-keepattributes Signature

Q5: 静默登录(免登录)

使用 silentSignIn() 实现静默登录:

java 复制代码
public void silentSignIn(Context context) {
    GoogleSignInAccount account = GoogleSignIn.getLastSignedInAccount(context);
    if (account != null) {
        // 已有登录记录,直接使用
        handleLogin(account);
    } else {
        // 尝试静默登录
        mGoogleSignInClient.silentSignIn()
            .addOnCompleteListener(task -> {
                if (task.isSuccessful()) {
                    GoogleSignInAccount signInAccount = task.getResult();
                    handleLogin(signInAccount);
                } else {
                    // 静默登录失败,需要用户手动登录
                }
            });
    }
}

十、参考链接

相关推荐
黄林晴3 小时前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab15 小时前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿18 小时前
Android MediaPlayer 笔记
android
Jony_19 小时前
Android 启动优化方案
android
阿巴斯甜19 小时前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇19 小时前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_1 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android
_小马快跑_1 天前
Kotlin | 从SparseArray、ArrayMap的set操作符看类型检查的不同
android
_小马快跑_1 天前
Android | 为什么有了ArrayMap还要再设计SparseArray?
android