Android原生Java应用获取Google Fit授权令牌并传输至后端以获取用户数据的实现方法
Great question—this workflow is totally feasible, and it’s actually the recommended approach for integrating Google Fit with a backend service. Let’s break down exactly how to implement it, step by step.
Absolutely. Google Fit’s OAuth 2.0 framework is designed to support this exact use case: your Android app handles the user-facing permission flow, obtains a secure authorization code (or short-lived access token), and sends it to your backend. Your backend can then exchange this code for long-term access/refresh tokens to fetch historical fitness data via the Google Fit REST API.
First, you’ll need to set up your project and credentials to authenticate with Google’s services:
- Create a new project (or use an existing one) and enable the Google Fitness API in the API Library.
- Set up OAuth 2.0 credentials:
- Create an Android client ID: Enter your app’s package name and SHA-1 fingerprint (you can grab this from Android Studio or via the
keytoolcommand). This verifies your app’s identity to Google. - Create a Web application client ID: This is critical for your backend—you’ll use it to exchange authorization codes for access/refresh tokens. Save the client ID and client secret securely (never expose the secret in your Android app!).
- Create an Android client ID: Enter your app’s package name and SHA-1 fingerprint (you can grab this from Android Studio or via the
- Define the OAuth scopes you need (e.g.,
https://www.googleapis.com/auth/fitness.activity.readfor activity data,https://www.googleapis.com/auth/fitness.body.readfor body metrics). Make sure these scopes are listed on your OAuth consent screen, and fill out all required consent details (app name, privacy policy link) since users will see this during authorization.
2.1 Add Dependencies
In your app-level build.gradle (module level), add the required Google Play Services libraries:
dependencies { implementation 'com.google.android.gms:play-services-fitness:21.0.1' implementation 'com.google.android.gms:play-services-auth:20.7.0' }
2.2 Request Runtime Permissions
For Android 10 (API 29+) and above, permissions like ACTIVITY_RECOGNITION and ACCESS_FINE_LOCATION (if you need location-based fitness data) require dynamic requests. First, add them to your AndroidManifest.xml:
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
Then, check and request these permissions before starting the Fit authorization flow:
private static final int REQUEST_PERMISSIONS = 1001; private boolean hasRequiredPermissions() { return ContextCompat.checkSelfPermission(this, Manifest.permission.ACTIVITY_RECOGNITION) == PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED; } private void requestMissingPermissions() { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACTIVITY_RECOGNITION, Manifest.permission.ACCESS_FINE_LOCATION}, REQUEST_PERMISSIONS); } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == REQUEST_PERMISSIONS) { if (hasRequiredPermissions()) { startFitAuthorizationFlow(); } else { Toast.makeText(this, "Permissions are required to access Google Fit data", Toast.LENGTH_SHORT).show(); } } }
2.3 Initiate Google Fit Authorization
Configure the Google Sign-In client with your required scopes, then trigger the authorization flow:
private GoogleSignInClient mSignInClient; private static final int REQUEST_SIGN_IN = 1002; private static final String YOUR_WEB_CLIENT_ID = "your-web-client-id-from-cloud-console"; private void initSignInClient() { List<String> fitScopes = Arrays.asList( Scopes.FITNESS_ACTIVITY_READ, Scopes.FITNESS_BODY_READ // Add other scopes as needed ); GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN) .requestScopes(new Scope(Scopes.FITNESS_ACTIVITY_READ)) .requestScopes(new Scope(Scopes.FITNESS_BODY_READ)) .requestServerAuthCode(YOUR_WEB_CLIENT_ID) // Critical for backend token exchange .build(); mSignInClient = GoogleSignIn.getClient(this, gso); } private void startFitAuthorizationFlow() { Intent signInIntent = mSignInClient.getSignInIntent(); startActivityForResult(signInIntent, REQUEST_SIGN_IN); } @Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == REQUEST_SIGN_IN) { Task<GoogleSignInAccount> task = GoogleSignIn.getSignedInAccountFromIntent(data); handleSignInResponse(task); } } private void handleSignInResponse(Task<GoogleSignInAccount> completedTask) { try { GoogleSignInAccount account = completedTask.getResult(ApiException.class); String authCode = account.getServerAuthCode(); // This is the code to send to your backend // Send the auth code to your backend via an API call (use Retrofit, OkHttp, etc.) sendAuthCodeToBackend(authCode); } catch (ApiException e) { Log.w("FitAuth", "Sign-in failed", e); Toast.makeText(this, "Google Fit authorization failed. Please try again.", Toast.LENGTH_SHORT).show(); } } // Example: Send auth code to backend with OkHttp private void sendAuthCodeToBackend(String authCode) { OkHttpClient client = new OkHttpClient(); RequestBody body = new FormBody.Builder() .add("auth_code", authCode) .build(); Request request = new Request.Builder() .url("https://your-backend-domain.com/api/exchange-fit-token") .post(body) .build(); client.newCall(request).enqueue(new Callback() { @Override public void onFailure(@NonNull Call call, @NonNull IOException e) { Log.e("BackendCall", "Failed to send auth code", e); } @Override public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException { runOnUiThread(() -> { if (response.isSuccessful()) { Toast.makeText(MainActivity.this, "Authorization successful!", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(MainActivity.this, "Backend failed to process authorization", Toast.LENGTH_SHORT).show(); } }); } }); }
Your backend will handle token exchange and Fit API calls. Here’s a high-level breakdown (using Java as an example):
3.1 Exchange Authorization Code for Tokens
Use Google’s OAuth token endpoint to swap the auth code for access/refresh tokens:
private static final String WEB_CLIENT_ID = "your-web-client-id"; private static final String WEB_CLIENT_SECRET = "your-web-client-secret"; public FitTokens exchangeAuthCodeForTokens(String authCode) throws IOException { OkHttpClient client = new OkHttpClient(); RequestBody body = new FormBody.Builder() .add("code", authCode) .add("client_id", WEB_CLIENT_ID) .add("client_secret", WEB_CLIENT_SECRET) .add("redirect_uri", "") // Leave empty for server auth code flow .add("grant_type", "authorization_code") .build(); Request request = new Request.Builder() .url("https://oauth2.googleapis.com/token") .post(body) .build(); try (Response response = client.newCall(request).execute()) { if (!response.isSuccessful()) throw new IOException("Unexpected code " + response); // Parse response into a POJO (access_token, refresh_token, expires_in) return new Gson().fromJson(response.body().string(), FitTokens.class); } } // Store these tokens securely linked to the user's account (e.g., encrypted database)
3.2 Fetch Historical Fit Data
Use the access token to call the Google Fit REST API. For example, to get daily step counts:
public String getDailyStepCounts(String accessToken, long startTimeMillis, long endTimeMillis) throws IOException { OkHttpClient client = new OkHttpClient(); String aggregateRequest = "{\n" + " \"aggregateBy\": [\n" + " {\n" + " \"dataTypeName\": \"com.google.step_count.delta\",\n" + " \"dataSourceId\": \"derived:com.google.step_count.delta:com.google.android.gms:estimated_steps\"\n" + " }\n" + " ],\n" + " \"bucketByTime\": { \"durationMillis\": 86400000 },\n" + " \"startTimeMillis\": " + startTimeMillis + ",\n" + " \"endTimeMillis\": " + endTimeMillis + "\n" + "}"; Request request = new Request.Builder() .url("https://www.googleapis.com/fitness/v1/users/me/dataset:aggregate") .addHeader("Authorization", "Bearer " + accessToken) .post(RequestBody.create(MediaType.parse("application/json"), aggregateRequest)) .build(); try (Response response = client.newCall(request).execute()) { if (!response.isSuccessful()) throw new IOException("Fit API call failed"); return response.body().string(); } }
3.3 Refresh Expired Access Tokens
Access tokens expire after ~1 hour. Use the refresh token to get a new one:
public FitTokens refreshAccessToken(String refreshToken) throws IOException { OkHttpClient client = new OkHttpClient(); RequestBody body = new FormBody.Builder() .add("refresh_token", refreshToken) .add("client_id", WEB_CLIENT_ID) .add("client_secret", WEB_CLIENT_SECRET) .add("grant_type", "refresh_token") .build(); Request request = new Request.Builder() .url("https://oauth2.googleapis.com/token") .post(body) .build(); try (Response response = client.newCall(request).execute()) { if (!response.isSuccessful()) throw new IOException("Token refresh failed"); return new Gson().fromJson(response.body().string(), FitTokens.class); } }
- Security: Never hardcode your web client secret in the Android app—keep it strictly on the backend. Using the server auth code flow (instead of getting an access token directly on mobile) avoids exposing sensitive tokens to the client side.
- Scope Minimalism: Only request the scopes you actually need. Users are more likely to grant access if you don’t ask for unnecessary permissions.
- Error Handling: Handle cases where users revoke access, tokens expire, or network calls fail. Your backend should detect invalid token errors and trigger a refresh or prompt the user to re-authorize if needed.
- Privacy Compliance: Follow Google’s API Terms of Service and applicable laws (like GDPR) when handling user fitness data—be transparent about what data you’re collecting and why.
内容的提问来源于stack exchange,提问作者Cosmin Ionuț Cîrlea

