Android单例初始化疑问:是否应继承Application类在onCreate中初始化?
首先明确告诉你:继承Application类并在其onCreate()中初始化单例是完全正确的做法,Android文档提到"通常无需继承Application"是因为大部分场景下确实不需要,但你的情况正好是需要继承的典型场景之一。
为什么这个方案可行?
Android应用进程启动时,系统会优先创建Application实例并调用它的onCreate()方法,这个过程发生在任何Activity、Service等组件启动之前。也就是说,不管你的应用重启后直接进入哪个Activity,单例的初始化代码都会提前执行,从根源上避免了"未初始化就调用"的崩溃问题。
具体实现步骤
- 创建自定义Application类:
class MyApplication : Application() { override fun onCreate() { super.onCreate() // 在这里完成单例的初始化,传入ApplicationContext MySingleton.init(applicationContext) } }
- 在
AndroidManifest.xml中配置这个自定义Application:
<application android:name=".MyApplication" android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <!-- 你的Activity等组件配置 --> </application>
替代方案:用ContentProvider初始化(无需继承Application)
如果你确实不想继承Application,还可以借助ContentProvider的特性来完成初始化——ContentProvider的onCreate()会在Application的onCreate()之前被调用,同样能保证初始化时机足够早。
实现示例:
class SingletonInitProvider : ContentProvider() { override fun onCreate(): Boolean { context?.let { ctx -> MySingleton.init(ctx) } return true } // 以下方法只需返回默认值即可,无需实际实现 override fun query(uri: Uri, projection: Array<String>?, selection: String?, selectionArgs: Array<String>?, sortOrder: String?): Cursor? = null override fun getType(uri: Uri): String? = null override fun insert(uri: Uri, values: ContentValues?): Uri? = null override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int = 0 override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<String>?): Int = 0 }
然后在Manifest中注册这个Provider:
<provider android:name=".SingletonInitProvider" android:authorities="${applicationId}.singleton_init_provider" android:exported="false" />
你的崩溃原因分析
从你提供的崩溃日志可以明确看到问题:
E/AndroidRuntime: FATAL EXCEPTION: main Process: com.loser, PID: 32117 java.lang.RuntimeException: Unable to resume activity {com.loser/com.loser.Activity2}: java.lang.RuntimeException: Call init() first. at android.app.ActivityThread.performResumeActivity(ActivityThread.java:3454) at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:3494) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2757) at android.app.ActivityThread.-wrap12(ActivityThread.java) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1496) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loop(Looper.java:154) at android.app.ActivityThread.main(ActivityThread.java:6186) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:889) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:779) Caused by: java.lang.RuntimeException: Call init() first. at com.loser.MySingleton$Companion.getInstance(MySingleton.kt:44) at com.loser.Activity2.onResume(Activity2.kt:93) at android.app.Instrumentation.callActivityOnResume(Instrumentation.java:1270) at android.app.Activity.performResume(Activity.java:6788) at android.app.ActivityThread.performResumeActivity(ActivityThread.java:3431) at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:3494) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2757) at android.app.ActivityThread.-wrap12(ActivityThread.java) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1496) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loop(Looper.java:154) at android.app.ActivityThread.main(ActivityThread.java:6186) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:889) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:779)
当你的应用被系统后台回收后,用户再次打开时,Android会恢复之前的Activity栈,直接启动Activity2,而此时MainActivity的onCreate()并没有被执行,导致单例没有完成初始化,所以在Activity2的onResume()中调用单例时触发了Call init() first的异常。
综上,最直观且推荐的方案就是继承Application来完成单例初始化,这个方案简单易懂,也符合Android的组件生命周期规范。
内容的提问来源于stack exchange,提问作者Damn Vegetables

