获取字符串资源主机名时为何抛出NullPointerException?
问题现象
启动应用初始化偏好设置时,调用getDefaultHostName()方法触发NullPointerException,导致应用启动失败。
报错堆栈
Process: com.elto.date, PID: 31344
java.lang.RuntimeException: 无法创建应用com.elto.date.lightDark.MyApp: java.lang.NullPointerException
at android.app.ActivityThread.handleBindApplication(ActivityThread.java:7109)
at android.app.ActivityThread.access$1600(ActivityThread.java:271)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2134)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loopOnce(Looper.java:210)
at android.os.Looper.loop(Looper.java:299)
at android.app.ActivityThread.main(ActivityThread.java:8319)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:556)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1038)Caused by: java.lang.NullPointerException
at com.elto.date.lightDark.MyApp.getDefaultHostName(MyApp.kt:240)
at com.elto.date.lightDark.MyApp.onCreate(MyApp.kt:141)
at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1212)
at android.app.ActivityThread.handleBindApplication(ActivityThread.java:7103)
... 其余栈帧省略
代码问题分析
核心原因
在MyApp类中,你定义了一个私有变量sContext并直接初始化为null:
private val sContext: MyApp? = null
而getDefaultHostName()方法中使用!!强制解包这个null变量,直接触发空指针异常:
fun getDefaultHostName(): String { return sContext!!.resources .getString(if (isEmulator()) R.string.emulator_host_name else R.string.default_host_name) }
冗余代码问题
实际上你的MyApp类的companion object中已经维护了一个instance变量,并且在onCreate()方法中已经赋值为当前应用实例:
override fun onCreate() { super<Application>.onCreate() instance = this // ... 其余代码 }
自己定义的sContext完全是冗余且未被正确初始化的变量。
修复方案
步骤1:移除冗余的sContext变量
删除MyApp类中定义的private val sContext: MyApp? = null。
步骤2:修改getDefaultHostName()方法
直接使用当前MyApp实例(this)或者companion object中的instance来获取资源:
fun getDefaultHostName(): String { return resources.getString(if (isEmulator()) R.string.emulator_host_name else R.string.default_host_name) }
如果需要在静态场景调用,也可以用:
fun getDefaultHostName(): String { return instance.resources.getString(if (isEmulator()) R.string.emulator_host_name else R.string.default_host_name) }
步骤3:修复getAppContext()方法
如果需要提供全局上下文获取方法,直接返回instance或者this:
fun getAppContext(): Context { return instance }
或者非静态版本:
fun getAppContext(): Context { return this }
修复后的偏好设置初始化代码
原有逻辑无需改动,因为getDefaultHostName()已经能正确获取资源:
val pref = PreferenceManager.getDefaultSharedPreferences(this) if (TextUtils.isEmpty(pref.getString(Utils.PREFS_HOST_NAME, null))) { // 未找到偏好设置,保存默认值 val editor = pref.edit() editor.putString(Utils.PREFS_HOST_NAME, getDefaultHostName()) editor.putBoolean(Utils.PREFS_USE_TLS, getDefaultTLS()) editor.apply() }
额外建议
- 避免使用
!!强制解包可空变量,推荐用?.安全调用结合?:空合并运算符,降低空指针风险。 - 全局上下文尽量通过
Application的instance获取,不要额外定义冗余变量,减少维护成本和初始化错误。
内容的提问来源于stack exchange,提问作者Mahmoud Nabil

