Firebase 生态
这一课你将能做到
- 集成 Firebase Authentication 实现邮箱/第三方登录
- 使用 Cloud Messaging(FCM)接收推送通知,处理前后台消息
- 配置 Crashlytics 监控崩溃,用 Analytics 追踪用户行为
为什么要学
Firebase 是 Google 的「移动后端即服务」(MBaaS),把服务器、数据库、推送、监控都打包好了。对于独立开发者或小团队,Firebase 意味着「零后端上线」。即使公司有自己的后端,Crashlytics 和 Analytics 也几乎是标配。
人话:Firebase 就像「移动开发的瑞士军刀」。Auth 管登录,Firestore 管数据,FCM 管推送,Crashlytics 管崩溃,Analytics 管统计。不用自己搭服务器,不用买数据库,Google 全包了。
面试中,「FCM 消息类型」、「Firestore 数据模型」、「Crashlytics 原理」是高频考点。
先建立图像
┌─────────────────────────────────────────┐
│ Firebase 核心服务 │
│ │
│ ┌─────────┐ ┌─────────┐ ┌────────┐ │
│ │ Auth │ │Firestore│ │ FCM │ │
│ │ 登录 │ │ 数据库 │ │ 推送 │ │
│ │ 🔐 │ │ 🗄️ │ │ 📨 │ │
│ └────┬────┘ └────┬────┘ └───┬────┘ │
│ │ │ │ │
│ ┌────┴────────────┴───────────┴────┐ │
│ │ 你的 Android App │ │
│ └──────────────────────────────────┘ │
│ ↑ ↑ ↑ │
│ ┌────┴────┐ ┌────┴────┐ ┌───┴────┐ │
│ │Crashlytics│ │Analytics│ │ Remote │ │
│ │ 崩溃监控 │ │ 统计 │ │ Config │ │
│ │ 💥 │ │ 📊 │ │ ⚙️ │ │
│ └─────────┘ └─────────┘ └────────┘ │
└─────────────────────────────────────────┘
跟着做
步骤 1:Firebase 项目配置
- 在 Firebase Console 创建项目
- 添加 Android 应用,包名与
build.gradle 一致
- 下载
google-services.json 放入 app/ 目录
- 添加依赖:
// build.gradle.kts (项目级)
plugins {
id("com.google.gms.google-services") version "4.4.2" apply false
}
// build.gradle.kts (app)
plugins {
id("com.google.gms.google-services")
}
dependencies {
// Firebase BOM(管理版本)
implementation(platform("com.google.firebase:firebase-bom:33.7.0"))
// 按需添加
implementation("com.google.firebase:firebase-auth-ktx")
implementation("com.google.firebase:firebase-firestore-ktx")
implementation("com.google.firebase:firebase-messaging-ktx")
implementation("com.google.firebase:firebase-crashlytics-ktx")
implementation("com.google.firebase:firebase-analytics-ktx")
}
步骤 2:Firebase Authentication
@Singleton
class AuthRepository @Inject constructor() {
private val auth = Firebase.auth
val currentUser: Flow<FirebaseUser?> = callbackFlow {
val listener = FirebaseAuth.AuthStateListener { firebaseAuth ->
trySend(firebaseAuth.currentUser)
}
auth.addAuthStateListener(listener)
awaitClose { auth.removeAuthStateListener(listener) }
}
// 邮箱登录
suspend fun signInWithEmail(email: String, password: String): Result<FirebaseUser> {
return try {
val result = auth.signInWithEmailAndPassword(email, password).await()
Result.success(result.user!!)
} catch (e: Exception) {
Result.failure(e)
}
}
// 邮箱注册
suspend fun signUpWithEmail(email: String, password: String): Result<FirebaseUser> {
return try {
val result = auth.createUserWithEmailAndPassword(email, password).await()
Result.success(result.user!!)
} catch (e: Exception) {
Result.failure(e)
}
}
// Google 登录
suspend fun signInWithGoogle(idToken: String): Result<FirebaseUser> {
return try {
val credential = GoogleAuthProvider.getCredential(idToken, null)
val result = auth.signInWithCredential(credential).await()
Result.success(result.user!!)
} catch (e: Exception) {
Result.failure(e)
}
}
fun signOut() {
auth.signOut()
}
}
// Compose 登录页面
@Composable
fun LoginScreen(
viewModel: AuthViewModel = hiltViewModel()
) {
var email by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") }
val authState by viewModel.authState.collectAsState()
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.Center
) {
OutlinedTextField(
value = email,
onValueChange = { email = it },
label = { Text("邮箱") },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(8.dp))
OutlinedTextField(
value = password,
onValueChange = { password = it },
label = { Text("密码") },
visualTransformation = PasswordVisualTransformation(),
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(16.dp))
Button(
onClick = { viewModel.signIn(email, password) },
modifier = Modifier.fillMaxWidth()
) {
Text("登录")
}
TextButton(
onClick = { viewModel.signUp(email, password) },
modifier = Modifier.fillMaxWidth()
) {
Text("注册新账号")
}
when (authState) {
is AuthState.Loading -> CircularProgressIndicator()
is AuthState.Error -> Text(
text = (authState as AuthState.Error).message,
color = MaterialTheme.colorScheme.error
)
is AuthState.Success -> {
// 导航到主页
}
else -> {}
}
}
}
步骤 3:Cloud Messaging(FCM)推送
// 1. 获取 Token
class FcmTokenManager @Inject constructor() {
suspend fun getToken(): String? = suspendCancellableCoroutine { cont ->
Firebase.messaging.token
.addOnCompleteListener { task ->
if (task.isSuccessful) {
cont.resume(task.result)
} else {
cont.resume(null)
}
}
}
}
// 2. 自定义 MessagingService
class MyFirebaseMessagingService : FirebaseMessagingService() {
override fun onNewToken(token: String) {
super.onNewToken(token)
// 发送 Token 到服务器,用于定向推送
Log.d("FCM", "New token: $token")
}
override fun onMessageReceived(message: RemoteMessage) {
super.onMessageReceived(message)
// 前台时收到消息,需要手动显示通知
val title = message.notification?.title ?: message.data["title"]
val body = message.notification?.body ?: message.data["body"]
if (title != null || body != null) {
showNotification(title ?: "", body ?: "")
}
// 数据消息处理
if (message.data.isNotEmpty()) {
val trackSlug = message.data["track_slug"]
// 处理点击跳转逻辑
}
}
private fun showNotification(title: String, body: String) {
val channelId = "default_channel"
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE)
as NotificationManager
// Android 8.0+ 需要通知渠道
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
channelId,
"默认通知",
NotificationManager.IMPORTANCE_HIGH
)
notificationManager.createNotificationChannel(channel)
}
val intent = Intent(this, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
}
val pendingIntent = PendingIntent.getActivity(
this, 0, intent,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
val notification = NotificationCompat.Builder(this, channelId)
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle(title)
.setContentText(body)
.setAutoCancel(true)
.setContentIntent(pendingIntent)
.build()
notificationManager.notify(System.currentTimeMillis().toInt(), notification)
}
}
// AndroidManifest.xml 中声明
<service
android:name=".MyFirebaseMessagingService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
步骤 4:Firestore 实时数据库
@Singleton
class FirestoreRepository @Inject constructor() {
private val db = Firebase.firestore
// 保存用户进度
suspend fun saveProgress(
userId: String,
trackSlug: String,
progress: Int
): Result<Unit> = try {
db.collection("users")
.document(userId)
.collection("progress")
.document(trackSlug)
.set(
mapOf(
"progress" to progress,
"updatedAt" to FieldValue.serverTimestamp()
)
)
.await()
Result.success(Unit)
} catch (e: Exception) {
Result.failure(e)
}
// 实时监听进度变化
fun observeProgress(userId: String): Flow<Map<String, Int>> = callbackFlow {
val listener = db.collection("users")
.document(userId)
.collection("progress")
.addSnapshotListener { snapshot, error ->
if (error != null) {
close(error)
return@addSnapshotListener
}
val progressMap = snapshot?.documents?.associate { doc ->
doc.id to (doc.getLong("progress")?.toInt() ?: 0)
} ?: emptyMap()
trySend(progressMap)
}
awaitClose { listener.remove() }
}
// 批量查询
suspend fun getCompletedTracks(userId: String): List<String> = try {
val snapshot = db.collection("users")
.document(userId)
.collection("progress")
.whereEqualTo("progress", 100)
.get()
.await()
snapshot.documents.map { it.id }
} catch (e: Exception) {
emptyList()
}
}
步骤 5:Crashlytics 与 Analytics
@Singleton
class AnalyticsManager @Inject constructor() {
private val analytics = Firebase.analytics
private val crashlytics = Firebase.crashlytics
// 自定义事件
fun logTrackOpened(trackSlug: String, trackTitle: String) {
analytics.logEvent("track_opened") {
param("track_slug", trackSlug)
param("track_title", trackTitle)
param("timestamp", System.currentTimeMillis())
}
}
fun logLessonCompleted(trackSlug: String, lessonSlug: String, duration: Long) {
analytics.logEvent("lesson_completed") {
param("track_slug", trackSlug)
param("lesson_slug", lessonSlug)
param("duration_seconds", duration)
}
}
// 设置用户属性
fun setUserProperties(userId: String, level: String) {
analytics.setUserId(userId)
analytics.setUserProperty("user_level", level)
}
// 记录非致命错误
fun logError(throwable: Throwable, context: String) {
crashlytics.log("Context: $context")
crashlytics.recordException(throwable)
}
// 设置自定义键值,崩溃时上传
fun setCustomKey(key: String, value: String) {
crashlytics.setCustomKey(key, value)
}
// 测试崩溃(Debug 用)
fun testCrash() {
throw RuntimeException("Test Crash from Firebase")
}
}
讲清楚要点
-
FCM 消息分两种:通知消息(Notification Message,系统托盘直接显示)和数据消息(Data Message,App 自行处理)。前后台行为不同,测试时必须覆盖。
-
Firestore 是 NoSQL 文档数据库:Collection → Document → Field,支持实时监听(Snapshot Listener)。数据模型设计要「反规范化」,避免多表 Join。
-
Crashlytics 不只是崩溃收集:可以记录非致命异常(recordException)、设置自定义键值、关联用户 ID。这些信息在排查线上问题时至关重要。
-
Analytics 事件要规划:不要随意打点。定义核心事件(注册、登录、完成课程、付费),命名用「动词_名词」格式,参数控制在 25 个以内。
-
Firebase 免费额度有限:Spark 计划有每日限额。Firestore 读写、FCM 消息、Auth 用户都有限制。超出后自动升级到 Blaze(按量付费),要监控用量。
你可能会懵的地方
-
FCM Token 会变吗?
会。App 重装、清除数据、系统恢复、Token 过期都会变。必须在 onNewToken 时上报服务器。
-
Firestore 离线支持怎么工作?
默认开启。写操作先写本地缓存,网络恢复后同步。读操作优先返回缓存,同时后台更新。可配置 Settings.Builder.setPersistenceEnabled。
-
Crashlytics 能捕获所有崩溃吗?
能捕获 Java/Kotlin 层崩溃。Native 崩溃(C/C++)需要 NDK 集成。ANR 需要额外配置。
-
Analytics 和 Crashlytics 能同时用吗?
可以,且推荐。Analytics 追踪用户行为路径,Crashlytics 追踪异常。结合使用能回答「用户在崩溃前做了什么」。
-
Firebase 在国内能用吗?
需要 Google Play 服务,国内用户可能无法使用。国内项目考虑替代方案:个推/极光(推送)、Bugly(崩溃)、GrowingIO(统计)。
错法对照
| 错法 | 问题 | 正解 |
|---|
| 在 MainThread 直接 await() Firestore | 阻塞 UI | 在 Repository 中用 suspend,ViewModel 调度 |
| 忽略 FCM 数据消息 | 前后台行为不一致 | 统一在 onMessageReceived 处理 |
| Analytics 事件参数过多 | 超出 25 个限制,被丢弃 | 精简参数,用用户属性补充 |
| 不处理 FCM Token 刷新 | 推送送达率下降 | onNewToken 时上报服务器 |
在生产环境用 testCrash() | 污染崩溃数据 | 包在 BuildConfig.DEBUG 中 |
卡点急救
-
FCM 收不到推送:检查 google-services.json 是否最新;检查 App 是否被强制停止(部分 ROM 限制);检查通知渠道是否创建(Android 8.0+)。
-
Firestore 写入成功但读取为空:检查是否用了 get() 而不是实时监听;检查安全规则是否允许读取;检查是否开启了离线缓存导致延迟。
-
Crashlytics 不上传崩溃:检查是否初始化(FirebaseApp.initializeApp);检查是否在 Application.onCreate 中初始化;查看 Logcat 是否有 CrashlyticsCore 相关日志。
面试常问
-
FCM 的两种消息类型及适用场景?
通知消息:系统直接显示,适合营销推送,App 在后台时用户可见。数据消息:App 自行处理,适合静默同步、自定义 UI,前后台都触发 onMessageReceived。可混合使用。
-
Firestore 的数据模型设计原则?
反规范化(Denormalization):数据冗余存储,避免 Join。例如:用户文档里存 completedTracks 数组,而不是单独建表关联。读取次数远多于写入,以读性能优先。
-
Crashlytics 的崩溃报告包含哪些信息?
堆栈轨迹、设备信息(型号、系统版本)、App 版本、自定义键值、日志(log() 记录)、用户 ID(如果设置)。帮助定位「谁在什么设备上因为什么操作崩溃」。
自测三问
- 解释 FCM 通知消息和数据消息在前后台的行为差异。
- 设计一个 Firestore 数据模型:用户、课程、学习进度,要求支持「查询某用户所有已完成的课程」。
- 为什么 Analytics 事件参数要限制在 25 个以内?超出会怎样?
小结一句话
Firebase 是移动开发的「加速器」:Auth 免自建用户系统,Firestore 免搭数据库,FCM 免维护推送通道,Crashlytics 免写监控;小团队用它快速验证,大团队用它补充监控。
延伸