CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
Android 开发入门/课时/Firebase 生态
RawBlame
课时目录12 节

Android 开发入门

  1. 01Android 开发概览
  2. 02四大组件
  3. 03传统 View UI
  4. 04Jetpack Compose
  5. 05架构模式
  6. 06依赖注入
  7. 07本地存储
  8. 08网络层
  9. 09异步与响应式
  10. 10Firebase 生态
  11. 11测试与质量
  12. 12综合练习

Firebase 生态

FCM/Crashlytics/Firestore。

30 分钟

Firebase 生态

这一课你将能做到

  1. 集成 Firebase Authentication 实现邮箱/第三方登录
  2. 使用 Cloud Messaging(FCM)接收推送通知,处理前后台消息
  3. 配置 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 项目配置

  1. 在 Firebase Console 创建项目
  2. 添加 Android 应用,包名与 build.gradle 一致
  3. 下载 google-services.json 放入 app/ 目录
  4. 添加依赖:
// 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")
    }
}

讲清楚要点

  1. FCM 消息分两种:通知消息(Notification Message,系统托盘直接显示)和数据消息(Data Message,App 自行处理)。前后台行为不同,测试时必须覆盖。

  2. Firestore 是 NoSQL 文档数据库:Collection → Document → Field,支持实时监听(Snapshot Listener)。数据模型设计要「反规范化」,避免多表 Join。

  3. Crashlytics 不只是崩溃收集:可以记录非致命异常(recordException)、设置自定义键值、关联用户 ID。这些信息在排查线上问题时至关重要。

  4. Analytics 事件要规划:不要随意打点。定义核心事件(注册、登录、完成课程、付费),命名用「动词_名词」格式,参数控制在 25 个以内。

  5. 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 中

卡点急救

  1. FCM 收不到推送:检查 google-services.json 是否最新;检查 App 是否被强制停止(部分 ROM 限制);检查通知渠道是否创建(Android 8.0+)。

  2. Firestore 写入成功但读取为空:检查是否用了 get() 而不是实时监听;检查安全规则是否允许读取;检查是否开启了离线缓存导致延迟。

  3. Crashlytics 不上传崩溃:检查是否初始化(FirebaseApp.initializeApp);检查是否在 Application.onCreate 中初始化;查看 Logcat 是否有 CrashlyticsCore 相关日志。

面试常问

  • FCM 的两种消息类型及适用场景? 通知消息:系统直接显示,适合营销推送,App 在后台时用户可见。数据消息:App 自行处理,适合静默同步、自定义 UI,前后台都触发 onMessageReceived。可混合使用。

  • Firestore 的数据模型设计原则? 反规范化(Denormalization):数据冗余存储,避免 Join。例如:用户文档里存 completedTracks 数组,而不是单独建表关联。读取次数远多于写入,以读性能优先。

  • Crashlytics 的崩溃报告包含哪些信息? 堆栈轨迹、设备信息(型号、系统版本)、App 版本、自定义键值、日志(log() 记录)、用户 ID(如果设置)。帮助定位「谁在什么设备上因为什么操作崩溃」。

自测三问

  1. 解释 FCM 通知消息和数据消息在前后台的行为差异。
  2. 设计一个 Firestore 数据模型:用户、课程、学习进度,要求支持「查询某用户所有已完成的课程」。
  3. 为什么 Analytics 事件参数要限制在 25 个以内?超出会怎样?

小结一句话

Firebase 是移动开发的「加速器」:Auth 免自建用户系统,Firestore 免搭数据库,FCM 免维护推送通道,Crashlytics 免写监控;小团队用它快速验证,大团队用它补充监控。

延伸

  • Firebase 官方文档
  • Firestore 数据建模指南
  • 下一课:测试与质量——JUnit、Espresso 与 LeakCanary。
加载本节练习…
← 上一课异步与响应式下一课 →测试与质量