Android Facebook 登录集成实战:从 SDK 配置到用户信息获取

背景

在移动应用开发中,社交登录是提升用户体验的重要功能。Facebook 登录 SDK 让用户可以用已有的 Facebook 账号快速登录应用。这篇文章分享我在 Android Studio 项目中集成 Facebook 登录 SDK 的经验,包括完整的配置流程和用户信息获取。

环境准备

创建 Facebook 应用

  1. 访问 Facebook Developers
  2. 创建新的应用
  3. 添加 Android 平台
  4. 获取应用 ID

获取密钥散列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 用于 Facebook 控制台配置
public void getKeyHash() {
try {
PackageInfo info = getPackageManager().getPackageInfo(
"com.your.package.name",
PackageManager.GET_SIGNATURES
);
for (Signature signature : info.signatures) {
MessageDigest md = MessageDigest.getInstance("SHA");
md.update(signature.toByteArray());
String keyHash = Base64.encodeToString(md.digest(), Base64.DEFAULT);
Log.d("KeyHash", keyHash);
// 将此值填入 Facebook 开发者控制台
}
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}

注意: 发布前需要使用发布密钥库重新生成密钥散列。

项目配置

1. 添加 Maven 仓库

Project build.gradle:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
buildscript {
repositories {
google()
jcenter()
mavenCentral() // Facebook SDK 需要
}
dependencies {
classpath 'com.android.tools.build:gradle:3.x.x'
}
}

allprojects {
repositories {
google()
jcenter()
mavenCentral() // 必须添加
}
}

2. 添加依赖

App module build.gradle:

1
2
3
4
5
6
dependencies {
// Facebook Login SDK
implementation 'com.facebook.android:facebook-login:[4,5)'

// 其他依赖...
}

可选 SDK 组件:

SDK 组件 依赖 说明
登录 facebook-login 基础登录功能
分享 facebook-share 内容分享
应用事件 facebook-applinks 应用链接
营销 facebook-marketing 广告追踪

3. 配置字符串资源

res/values/strings.xml:

1
2
3
4
5
6
7
<resources>
<string name="app_name">My Application</string>

<!-- Facebook 应用 ID -->
<string name="facebook_app_id" translatable="false">YOUR_APP_ID</string>
<string name="fb_login_protocol_scheme" translatable="false">fbYOUR_APP_ID</string>
</resources>

4. 配置 AndroidManifest.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.your.package">

<!-- 网络权限 -->
<uses-permission android:name="android.permission.INTERNET" />

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme">

<!-- Facebook SDK Application ID -->
<meta-data
android:name="com.facebook.sdk.ApplicationId"
android:value="@string/facebook_app_id" />

<!-- Facebook Activity -->
<activity
android:name="com.facebook.FacebookActivity"
android:configChanges="
keyboard|keyboardHidden|screenLayout|screenSize|orientation"
android:label="@string/app_name" />

<!-- Chrome Custom Tabs -->
<activity
android:name="com.facebook.CustomTabActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="@string/fb_login_protocol_scheme" />
</intent-filter>
</activity>

<!-- 主 Activity -->
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

</application>
</manifest>

实现登录功能

1. 布局文件

res/layout/activity_main.xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:padding="16dp">

<com.facebook.login.widget.LoginButton
android:id="@+id/login_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="30dp"
android:layout_marginBottom="30dp" />

<Button
android:id="@+id/logout_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Logout"
android:visibility="gone" />

<TextView
android:id="@+id/status_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="Not logged in" />

<ImageView
android:id="@+id/profile_image"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginTop="20dp" />

</LinearLayout>

2. 主 Activity 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
package com.your.package;

import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;

import com.facebook.AccessToken;
import com.facebook.CallbackManager;
import com.facebook.FacebookCallback;
import com.facebook.FacebookException;
import com.facebook.GraphRequest;
import com.facebook.GraphResponse;
import com.facebook.login.LoginManager;
import com.facebook.login.LoginResult;
import com.facebook.login.widget.LoginButton;
import com.squareup.picasso.Picasso;

import org.json.JSONException;
import org.json.JSONObject;

import java.util.Arrays;

public class MainActivity extends AppCompatActivity {

private static final String TAG = "FacebookLogin";
private static final String EMAIL = "email";
private static final String PUBLIC_PROFILE = "public_profile";

private CallbackManager callbackManager;
private LoginButton loginButton;
private Button logoutButton;
private TextView statusText;
private ImageView profileImage;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

initViews();
setupFacebookLogin();
checkLoginStatus();
}

private void initViews() {
loginButton = findViewById(R.id.login_button);
logoutButton = findViewById(R.id.logout_button);
statusText = findViewById(R.id.status_text);
profileImage = findViewById(R.id.profile_image);

// 设置权限
loginButton.setPermissions(Arrays.asList(EMAIL, PUBLIC_PROFILE));

logoutButton.setOnClickListener(v -> logout());
}

private void setupFacebookLogin() {
callbackManager = CallbackManager.Factory.create();

// 注册回调
LoginManager.getInstance().registerCallback(callbackManager,
new FacebookCallback<LoginResult>() {
@Override
public void onSuccess(LoginResult loginResult) {
Log.d(TAG, "Login successful");
handleLoginSuccess(loginResult.getAccessToken());
}

@Override
public void onCancel() {
Log.d(TAG, "Login cancelled");
Toast.makeText(MainActivity.this,
"Login cancelled", Toast.LENGTH_SHORT).show();
}

@Override
public void onError(FacebookException exception) {
Log.e(TAG, "Login error: " + exception.getMessage());
Toast.makeText(MainActivity.this,
"Login error: " + exception.getMessage(),
Toast.LENGTH_LONG).show();
}
});
}

private void handleLoginSuccess(AccessToken accessToken) {
// 获取用户信息
getUserInfo(accessToken);

// 更新 UI
updateUI(true);
}

/**
* 获取用户信息
*/
private void getUserInfo(AccessToken accessToken) {
GraphRequest request = GraphRequest.newMeRequest(
accessToken,
new GraphRequest.GraphJSONObjectCallback() {
@Override
public void onCompleted(JSONObject object, GraphResponse response) {
if (object != null) {
parseUserInfo(object);
}
}
}
);

// 设置请求参数
Bundle parameters = new Bundle();
parameters.putString("fields", "id,name,email,gender,picture.type(large)");
request.setParameters(parameters);
request.executeAsync();
}

/**
* 解析用户信息
*/
private void parseUserInfo(JSONObject object) {
try {
String id = object.optString("id");
String name = object.optString("name");
String email = object.optString("email");
String gender = object.optString("gender");

// 获取头像 URL
String pictureUrl = "";
JSONObject picture = object.optJSONObject("picture");
if (picture != null) {
JSONObject data = picture.optJSONObject("data");
if (data != null) {
pictureUrl = data.optString("url");
}
}

// 显示用户信息
String info = "Name: " + name + "\n"
+ "Email: " + email + "\n"
+ "Gender: " + gender + "\n"
+ "ID: " + id;
statusText.setText(info);

// 加载头像
if (!pictureUrl.isEmpty()) {
Picasso.get()
.load(pictureUrl)
.placeholder(R.drawable.ic_profile_placeholder)
.into(profileImage);
}

Log.d(TAG, "User info: " + info);

} catch (Exception e) {
Log.e(TAG, "Error parsing user info", e);
}
}

/**
* 返回的数据示例:
* {
* "id": "xxxxxxxx",
* "name": "John Doe",
* "email": "john@example.com",
* "gender": "male",
* "picture": {
* "data": {
* "height": 50,
* "is_silhouette": false,
* "url": "https://...",
* "width": 50
* }
* }
* }
*/

private void logout() {
LoginManager.getInstance().logOut();
updateUI(false);
Toast.makeText(this, "Logged out", Toast.LENGTH_SHORT).show();
}

private void checkLoginStatus() {
AccessToken accessToken = AccessToken.getCurrentAccessToken();
boolean isLoggedIn = accessToken != null && !accessToken.isExpired();

if (isLoggedIn) {
getUserInfo(accessToken);
}

updateUI(isLoggedIn);
}

private void updateUI(boolean isLoggedIn) {
if (isLoggedIn) {
loginButton.setVisibility(View.GONE);
logoutButton.setVisibility(View.VISIBLE);
} else {
loginButton.setVisibility(View.VISIBLE);
logoutButton.setVisibility(View.GONE);
statusText.setText("Not logged in");
profileImage.setImageResource(R.drawable.ic_profile_placeholder);
}
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
callbackManager.onActivityResult(requestCode, resultCode, data);
super.onActivityResult(requestCode, resultCode, data);
}
}

进阶功能

1. 自定义登录按钮

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
public class CustomLoginActivity extends AppCompatActivity {

private CallbackManager callbackManager;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_custom_login);

callbackManager = CallbackManager.Factory.create();

// 自定义按钮
Button customLoginButton = findViewById(R.id.custom_login_button);
customLoginButton.setOnClickListener(v -> {
// 检查网络
if (!isNetworkAvailable()) {
Toast.makeText(this, "No network connection",
Toast.LENGTH_SHORT).show();
return;
}

// 启动登录
LoginManager.getInstance().logInWithReadPermissions(
this,
Arrays.asList("email", "public_profile")
);
});

// 注册回调
LoginManager.getInstance().registerCallback(callbackManager,
new FacebookCallback<LoginResult>() {
@Override
public void onSuccess(LoginResult loginResult) {
// 处理成功
}

@Override
public void onCancel() {
// 处理取消
}

@Override
public void onError(FacebookException error) {
// 处理错误
}
});
}

private boolean isNetworkAvailable() {
ConnectivityManager cm = (ConnectivityManager)
getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = cm.getActiveNetworkInfo();
return networkInfo != null && networkInfo.isConnected();
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
callbackManager.onActivityResult(requestCode, resultCode, data);
super.onActivityResult(requestCode, resultCode, data);
}
}

2. 获取好友列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private void getFriends(AccessToken accessToken) {
GraphRequest request = GraphRequest.newMyFriendsRequest(
accessToken,
new GraphRequest.GraphJSONArrayCallback() {
@Override
public void onCompleted(JSONArray objects, GraphResponse response) {
if (objects != null) {
for (int i = 0; i < objects.length(); i++) {
try {
JSONObject friend = objects.getJSONObject(i);
String id = friend.getString("id");
String name = friend.getString("name");
Log.d(TAG, "Friend: " + name + " (" + id + ")");
} catch (JSONException e) {
e.printStackTrace();
}
}
}
}
}
);
request.executeAsync();
}

3. 分享功能

1
2
3
4
5
6
7
8
9
10
11
import com.facebook.share.model.ShareLinkContent;
import com.facebook.share.widget.ShareDialog;

private void shareContent() {
ShareLinkContent content = new ShareLinkContent.Builder()
.setContentUrl(Uri.parse("https://yourwebsite.com"))
.setQuote("Check out this awesome app!")
.build();

ShareDialog.show(this, content);
}

常见问题

1. 登录失败,Key Hash 错误

解决方案:

1
2
3
4
5
# 生成 Release Key Hash
keytool -exportcert -alias YOUR_RELEASE_KEY_ALIAS \
-keystore YOUR_RELEASE_KEY_PATH | \
openssl sha1 -binary | \
openssl base64

在 Facebook 开发者控制台添加所有环境的 Key Hash。

2. 无法获取 Email

原因: 用户可能未授权邮箱权限,或邮箱未验证。

解决方案:

1
2
3
4
5
6
7
8
9
10
11
12
private void getUserInfo(AccessToken accessToken) {
GraphRequest request = GraphRequest.newMeRequest(accessToken,
(object, response) -> {
String email = object.optString("email");
if (email.isEmpty()) {
// 邮箱为空,引导用户完成邮箱验证
Toast.makeText(this,
"Please verify your email on Facebook",
Toast.LENGTH_LONG).show();
}
});
}

3. 权限被拒绝

检查清单:

  • AndroidManifest.xml 配置正确
  • Facebook 应用已发布
  • 应用 ID 正确
  • Key Hash 已配置
  • 测试用户已添加(开发模式)

最佳实践

1. 安全存储 AccessToken

1
2
3
4
5
6
7
8
9
// 不要将 AccessToken 发送到服务器验证
// 使用服务端 API 验证用户身份
public class FacebookAuth {

public void authenticateWithServer(String accessToken) {
// 发送到服务器验证
// 服务器调用 Facebook Graph API 验证 token
}
}

2. 处理权限变更

1
2
3
4
5
6
7
8
// 检查权限
AccessToken accessToken = AccessToken.getCurrentAccessToken();
if (!accessToken.getPermissions().contains("email")) {
// 请求额外权限
LoginManager.getInstance().logInWithReadPermissions(
this, Arrays.asList("email")
);
}

3. 测试策略

环境 Key Hash Facebook 应用模式
开发 Debug Key Hash 开发模式
测试 Debug + Test Key Hash 开发模式
生产 Release Key Hash 上线模式

小结

Facebook 登录集成要点:

  1. 配置正确:应用 ID、Key Hash、权限配置
  2. 用户体验:自定义按钮、清晰的权限说明
  3. 错误处理:网络、权限、登录失败等场景
  4. 安全性:不在客户端泄露敏感信息

按照这篇文章的步骤,可以实现完整的 Facebook 登录功能,为用户提供便捷的社交登录体验。