Kotlin Android中访问lateinit var为何触发NullPointerException?
Firebase Crashlytics崩溃分析与解决方案
崩溃日志
Unable to instantiate activity ComponentInfo{com.oopsbot/org.healroot.widget.ui.MainActivity}: java.lang.NullPointerException: Attempt to invoke virtual method 'org.healroot.widget.repository.CoordinateRepository org.healroot.widget.dependency.RepositoryProvider.authenticationRepository() Caused by java.lang.NullPointerException: Attempt to invoke virtual method 'org.healroot.widget.repository.AuthenticationRepository org.healroot.widget.dependency.RepositoryProvider.authenticationRepository()' on a null object reference at org.healroot.widget.core.base.BaseActivity.<init>(BaseActivity.kt:13) at org.healroot.widget.core.base.BaseActivity.<init>(BaseActivity.kt:1) at org.healroot.widget.ui.MainActivity.<init>(MainActivity.kt:1) at java.lang.Class.newInstance(Class.java) at android.app.AppComponentFactory.instantiateActivity(AppComponentFactory.java:95) at androidx.core.app.CoreComponentFactory.instantiateActivity(CoreComponentFactory.java) at android.app.Instrumentation.newActivity(Instrumentation.java:1273) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3679) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3949) at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:106) at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135) at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2327) at android.os.Handler.dispatchMessage(Handler.java:106) at android.os.Looper.loopOnce(Looper.java:211) at android.os.Looper.loop(Looper.java:300) at android.app.ActivityThread.main(ActivityThread.java:8282) at java.lang.reflect.Method.invoke(Method.java) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:576) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1074)
相关核心代码
HealrootWidget单例类
object HealrootWidget{ private lateinit var repoProvider: RepositoryProvider private lateinit var application: Application internal fun repoProvider() = repoProvider @JvmStatic fun startWidget( application: Application, ) { initialize(application) val intent = LoadingActivity.createIntent( context, requestId, oneTimeToken ) intent.flags += Intent.FLAG_ACTIVITY_NEW_TASK context.startActivity(intent) } private fun initialize( application: Application ) { repoProvider = RepositoryProvider(context, widgetAliveObserver, environment) } }
BaseActivity抽象类(崩溃触发点)
internal abstract class BaseActivity<B : ViewBinding>() : AppCompatActivity() { private val authRepo: AuthenticationRepository = repoProvider().authenticationRepository() }
RepositoryProvider类
internal class RepositoryProvider( context: Context, widgetAliveObserver: WidgetAliveObserver, env: HealrootEnvironment ) { private val database: HealrootDatabase by lazy { val builder = Room.databaseBuilder(context, HealrootDatabase::class.java, "${env.name}_$DB_NAME") .fallbackToDestructiveMigration() val passphrase = BuildConfig.SQLITE_CIPHER_PASSPHRASE.toCharArray() val factory = SupportFactory(SQLiteDatabase.getBytes(passphrase)) builder.openHelperFactory(factory) builder.build() } private val authenticationRepository = AuthenticationRepositoryImpl( database.tokenDao(), widgetAliveObserver ) fun authenticationRepository(): AuthenticationRepository = authenticationRepository companion object { private const val DB_NAME = "database.db" } }
MainActivity的Intent创建方法
fun createIntent(context: Context, step: WidgetStep) = Intent( context, MainActivity::class.java ).apply { flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK putExtra(EXTRA_WIDGET_STEP, step) }
问题分析
崩溃核心原因是BaseActivity构造阶段,HealrootWidget中的repoProvider尚未完成初始化,调用repoProvider().authenticationRepository()时触发空指针异常。
可能的触发场景:
- 客户端绕过
HealrootWidget.startWidget(),直接启动MainActivity; - 应用进程被系统回收后,MainActivity从任务栈重建,单例
repoProvider状态丢失; FLAG_ACTIVITY_CLEAR_TOP+FLAG_ACTIVITY_NEW_TASK的Intent标记导致MainActivity跳过初始化流程直接实例化。
解决方案建议
- 延迟依赖初始化时机:将BaseActivity中的authRepo初始化移到
onCreate方法,确保初始化流程已完成:
internal abstract class BaseActivity<B : ViewBinding>() : AppCompatActivity() { private lateinit var authRepo: AuthenticationRepository override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) authRepo = repoProvider().authenticationRepository() } }
- 添加初始化校验:在
HealrootWidget.repoProvider()方法中增加非空检查,提前拦截未初始化的情况:
internal fun repoProvider(): RepositoryProvider { check(::repoProvider.isInitialized) { "HealrootWidget未初始化,请先调用startWidget方法" } return repoProvider }
处理进程重启场景:在Application的
onCreate中检查状态,必要时自动重新初始化HealrootWidget;或在BaseActivity的onCreate中校验初始化状态,未初始化则引导到启动流程。约束启动方式:通过文档或代码限制,要求客户端必须通过
HealrootWidget.startWidget()启动组件,禁止直接启动MainActivity。
内容的提问来源于stack exchange,提问作者qwertyFanatic
相关产品推荐
相关产品推荐

