求助:解决ContactHelper中java.lang.NullPointerException崩溃问题
Android应用NullPointerException崩溃修复方案
我们的Android应用偶尔发生崩溃,Firebase显示崩溃信息为:Fatal Exception: java.lang.NullPointerException,报错提示phoneCursor.getString(…) must not be null。该崩溃由ContactHelperClass中的getContactInformation方法引发,相关代码如下:
fun getContactInformation(activity: Activity, data: Intent, completion: (Buyer) -> Unit) { val buyer = Buyer() val contentUri = data.data val contentResolver = activity.contentResolver val cursor = contentResolver.query(contentUri!!, null, null, null, null) if (cursor != null) { if (cursor.moveToFirst()) { val contactId = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts._ID)) if (cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME) > 0) { buyer.firstName = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME)) } if (cursor.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER) > 0) { val phoneCursor = contentResolver.query( ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = ?", arrayOf(contactId), null ) if (phoneCursor != null) { if (phoneCursor.count > 1) { val values = ArrayList<ContactBuyer>() while (phoneCursor.moveToNext()) { val contact = ContactBuyer() contact.phoneNumber = phoneCursor.getString( phoneCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER) ) try { when (phoneCursor.getString( phoneCursor.getColumnIndex( ContactsContract.CommonDataKinds.Phone.TYPE ) ).toInt()) { ContactsContract.CommonDataKinds.Phone.TYPE_HOME -> { contact.name = R.string.buyer_select_phone_number_type_home.localized() } ContactsContract.CommonDataKinds.Phone.TYPE_WORK -> { contact.name = R.string.buyer_select_phone_number_type_work.localized() } ContactsContract.CommonDataKinds.Phone.TYPE_FAX_HOME, ContactsContract.CommonDataKinds.Phone.TYPE_FAX_WORK -> { contact.name = R.string.buyer_select_phone_number_type_fax.localized() } ContactsContract.CommonDataKinds.Phone.TYPE_OTHER -> { contact.name = R.string.shared_other.localized() } ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE -> { contact.name = R.string.buyer_select_phone_number_type_mobile.localized() } } } catch (ex: Throwable) { contact.name = R.string.shared_other.localized() } values.add((contact)) } BuyerMultiNumberDialog().showDialog( values, object : BuyerMultiNumberInterface { override fun onSelectedNumber(phoneNumber: String) { try { parsePhoneNumberFromContacts( activity, phoneNumber ) { extractedCountryCode, nationalNumber -> buyer.cellPhone = java.lang.Long.valueOf(nationalNumber) .toString() buyer.countryCode = "+$extractedCountryCode" } phoneCursor.close() cursor.close() completion(buyer) } catch (e: Exception) { } } }) } else { var phoneNumber = "" while (phoneCursor.moveToNext()) { phoneNumber = phoneCursor.getString( phoneCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER) ) } try { parsePhoneNumberFromContacts( activity, phoneNumber ) { extractedCountryCode, nationalNumber -> buyer.cellPhone = java.lang.Long.valueOf(nationalNumber).toString() buyer.countryCode = "+$extractedCountryCode" } phoneCursor.close() cursor.close() completion(buyer) } catch (e: Exception) { } } } } } } }
完整堆栈跟踪信息如下:
Fatal Exception: java.lang.NullPointerException: phoneCursor.getString( … ) must not be null at com.**.business.helpers.utils.ContactHelper.getContactInformation(ContactHelper.kt:71) at com.***.ui.layout.modules.shareListing.dialogs.ShareListingSendDialog.resultLauncher$lambda-12(ShareListingSendDialog.kt:734) at com.***.ui.layout.modules.shareListing.dialogs.ShareListingSendDialog.$r8$lambda$9BI94Y5eKf3mim33AzjWutfS-bk() at com.***.ui.layout.modules.shareListing.dialogs.ShareListingSendDialog$$ExternalSyntheticLambda8.onActivityResult(:4) at androidx.activity.result.ActivityResultRegistry$1.onStateChanged(ActivityResultRegistry.java:149) at androidx.lifecycle.LifecycleRegistry$ObserverWithState.dispatchEvent(LifecycleRegistry.java:360) at androidx.lifecycle.LifecycleRegistry.forwardPass(LifecycleRegistry.java:271) at androidx.lifecycle.LifecycleRegistry.sync(LifecycleRegistry.java:313) at androidx.lifecycle.LifecycleRegistry.moveToState(LifecycleRegistry.java:151) at androidx.lifecycle.LifecycleRegistry.handleLifecycleEvent(LifecycleRegistry.java:134) at androidx.fragment.app.Fragment.performStart(Fragment.java:3026) at androidx.fragment.app.FragmentStateManager.start(FragmentStateManager.java:589) at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:300) at androidx.fragment.app.FragmentStore.moveToExpectedState(FragmentStore.java:112) at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1647) at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:3128) at androidx.fragment.app.FragmentManager.dispatchStart(FragmentManager.java:3079) at androidx.fragment.app.FragmentController.dispatchStart(FragmentController.java:262) at androidx.fragment.app.FragmentActivity.onStart(FragmentActivity.java:510) at androidx.appcompat.app.AppCompatActivity.onStart(AppCompatActivity.java:248) at com.***.ui.layout.modules.listing.activities.listingDetail.ActivityListingDetail.onStart(ActivityListingDetail.kt:469) at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1433) at android.app.Activity.performStart(Activity.java:7986) at android.app.ActivityThread.handleStartActivity(ActivityThread.java:3677) at android.app.servertransaction.TransactionExecutor.performLifecycleSequence(TransactionExecutor.java:221) at android.app.servertransaction.TransactionExecutor.cycleToPath(TransactionExecutor.java:201) at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:173) at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:97) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2267) at android.os.Handler.dispatchMessage(Handler.java:107) at android.os.Looper.loop(Looper.java:237) at android.app.ActivityThread.main(ActivityThread.java:8167) at java.lang.reflect.Method.invoke(Method.java) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:496) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1100)
问题分析
崩溃的核心原因是:调用phoneCursor.getString()读取联系人电话号码或类型字段时返回了null,但代码直接将null赋值给了非空类型变量(比如contact.phoneNumber可能被定义为非空String),触发了NullPointerException。这种情况通常出现在联系人条目存在但电话号码字段为空,或者系统Contacts数据库中存在异常数据的场景。
修复方案
1. 给getString()添加null安全处理
直接在调用getString()时使用?:操作符,为null返回值提供默认值,避免赋值给非空变量:
// 获取电话号码时添加默认值 contact.phoneNumber = phoneCursor.getString( phoneCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER) ) ?: "" // 获取电话类型时先处理null,再转换类型 val typeStr = phoneCursor.getString( phoneCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.TYPE) ) when (typeStr?.toInt() ?: ContactsContract.CommonDataKinds.Phone.TYPE_OTHER) { // 原类型判断逻辑不变 }
2. 提前获取列索引,优化性能与稳定性
避免在循环中重复调用getColumnIndex(),提前获取一次列索引,减少冗余操作:
if (phoneCursor != null) { val numberColumnIndex = phoneCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER) val typeColumnIndex = phoneCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.TYPE) if (phoneCursor.count > 1) { val values = ArrayList<ContactBuyer>() while (phoneCursor.moveToNext()) { val contact = ContactBuyer() contact.phoneNumber = phoneCursor.getString(numberColumnIndex) ?: "" // 处理类型逻辑... } // 弹窗逻辑... } else { var phoneNumber = "" while (phoneCursor.moveToNext()) { phoneNumber = phoneCursor.getString(numberColumnIndex) ?: "" } // 解析号码逻辑... } }
3. 过滤空电话号码记录
如果电话号码为空,直接跳过该条记录,避免无效数据进入后续流程:
while (phoneCursor.moveToNext()) { val phoneNumber = phoneCursor.getString(numberColumnIndex) ?: continue val contact = ContactBuyer() contact.phoneNumber = phoneNumber // 处理类型逻辑... values.add(contact) }
4. 完善异常处理与日志记录
原代码中的空catch块无法定位问题,添加日志记录便于后续排查:
catch (e: Exception) { Log.e("ContactHelper", "解析联系人电话号码失败", e) }
5. 使用use函数自动关闭Cursor
利用Kotlin的use函数自动管理Cursor资源,避免忘记关闭导致的资源泄漏:
contentResolver.query(contentUri!!, null, null, null, null)?.use { cursor -> if (cursor.moveToFirst()) { // 原cursor处理逻辑 contentResolver.query( ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = ?", arrayOf(contactId), null )?.use { phoneCursor -> // 原phoneCursor处理逻辑 } } }
使用use后无需手动调用cursor.close()和phoneCursor.close(),代码更简洁安全。
内容的提问来源于stack exchange,提问作者VETLance
相关产品推荐
相关产品推荐

