集成Firebase Authentication后App启动相关Activity时崩溃
问题原因
这个崩溃和Firebase Authentication集成没有关系,属于Android Activity生命周期使用错误。
从Logcat堆栈可以定位到崩溃触发点:com.example.flimer.SignUp.<init>(SignUp.kt:15),也就是SignUp Activity的实例构造阶段就抛出了空指针。Activity在构造阶段还没有完成Context绑定,也没有加载Window和布局树,此时任何依赖Context、依赖布局的方法(比如findViewById、获取主题、弹Toast等)都会因为Context为null触发崩溃。
你误以为是Firebase导致的问题,只是因为出问题的页面刚好包含Firebase相关代码。
现有代码的问题清单
- 直接触发崩溃的问题:你实际运行的SignUp代码里,存在成员变量声明时直接调用
findViewById赋值的写法(对应SignUp.kt第15行),这行代码会在Activity实例化时立刻执行,早于onCreate生命周期、早于setContentView调用,必然触发空指针。你贴出的代码把findViewById写在了onCreate里,说明运行的代码和贴出的版本不一致,优先检查类的成员属性定义。 - 输入读取逻辑错误:你在
setContentView执行后立刻读取三个输入框的文本,此时页面刚完成渲染,用户还没有任何输入操作,后续点击注册按钮时拿到的永远是空字符串,根本无法获取用户实际输入的内容。 - 表单校验逻辑错误:原校验逻辑只有当邮箱和密码同时为空时才提示,单独漏填邮箱、单独漏填密码都不会触发提示,会直接把空值传给Firebase注册接口。
- 加载弹窗逻辑错误:原
dismiss()方法每次都会新建一个Dialog实例再执行关闭,和之前弹出的加载弹窗不是同一个对象,永远无法关闭已经显示的加载框。 - 无用冗余逻辑:闪屏页里判断
Build.VERSION.SDK_INT < 16的逻辑完全无效,API 16是2012年发布的Android 4.1版本,目前线上几乎没有运行该版本以下的设备,且全屏Flag的设置应该放在setContentView之前才会生效。
修复方案
- 所有
findViewById调用必须放在setContentView执行之后,绝对不能在类成员声明位置直接调用。 - 输入框的文本内容必须在点击按钮触发注册时再读取,不要在onCreate阶段提前读取。
- 持有加载弹窗的全局实例,不要每次操作都新建Dialog对象。
- 修正表单校验的判断条件,把且逻辑改成或逻辑,覆盖所有漏填场景。
修复后的SignUp代码如下:
class SignUp : AppCompatActivity() { // 仅声明控件和对象,不做初始化 private lateinit var etEmail: EditText private lateinit var etPass: EditText private lateinit var etPass2: EditText private lateinit var btn: Button private lateinit var loadingDialog: Dialog override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_sign_up) // setContentView之后再初始化所有控件 etEmail = findViewById(R.id.et_email) etPass = findViewById(R.id.pass) etPass2 = findViewById(R.id.et_pass2) btn = findViewById(R.id.btn_create) loadingDialog = Dialog(this).apply { setContentView(R.layout.custom_dialog) setCancelable(false) } btn.setOnClickListener { // 点击时再读取用户输入的内容 val email = etEmail.text.toString().trim() val pass = etPass.text.toString().trim() val pass2 = etPass2.text.toString().trim() if (isValid(email, pass, pass2)){ loadingDialog.show() signUp(email, pass) } } } private fun isValid(email: String, pass: String, pass2: String ): Boolean{ return when { email.isEmpty() || pass.isEmpty() || pass2.isEmpty() -> { Toast.makeText(this, "请填写所有必填项", Toast.LENGTH_SHORT).show() false } pass != pass2 -> { Toast.makeText(this, "两次输入的密码不一致", Toast.LENGTH_SHORT).show() false } else -> true } } private fun signUp(email: String, pass: String){ val auth: FirebaseAuth = Firebase.auth auth.createUserWithEmailAndPassword(email,pass).addOnCompleteListener { loadingDialog.dismiss() if (it.isSuccessful){ Toast.makeText(this, "注册成功", Toast.LENGTH_SHORT).show() // 注册成功后可以跳转到主页 }else{ Toast.makeText(this, it.exception?.message ?: "注册失败,请稍后重试", Toast.LENGTH_SHORT).show() } } } }
闪屏页可以把冗余的版本判断去掉,调整全屏逻辑的位置:
class Startscreen : AppCompatActivity() { private lateinit var auth: FirebaseAuth override fun onCreate(savedInstanceState: Bundle?) { // 设置全屏要在setContentView之前调用 window.setFlags( WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN) super.onCreate(savedInstanceState) setContentView(R.layout.activity_startscreen) auth = Firebase.auth val user = auth.currentUser // 用主线程Handler,避免内存泄漏 Handler(Looper.getMainLooper()).postDelayed({ val target = if (user != null) MainActivity::class.java else SignUp::class.java startActivity(Intent(this@Startscreen, target)) finish() }, 2000) } }
内容的提问来源于stack exchange,提问作者Victor kiptoo
相关产品推荐
相关产品推荐

