Android应用点击保存按钮崩溃:ViewModel构造相关InvocationTargetException问题求助
大家好,我现在在用Kotlin开发一款带底部导航的Android应用,其中ContactFragment模块需要读取系统联系人,用户通过AutoCompleteTextView选择联系人后,点击按钮就能把该联系人存入Room数据库作为紧急联系人。目前系统联系人加载和显示都正常,但选好联系人点击保存按钮时,应用直接崩溃,报错信息如下:
AndroidRuntime: java.lang.reflect.InvocationTargetException at java.lang.reflect.Constructor.newInstance0(Native Method) at java.lang.reflect.Constructor.newInstance(Constructor.java:343) at androidx.lifecycle.ViewModelProvider$AndroidViewModelFactory.create(ViewModelProvider.kt:314) at androidx.lifecycle.ViewModelProvider$AndroidViewModelFactory.create(ViewModelProvider.kt:306)
我没有使用任何依赖注入框架,下面是我的相关代码,麻烦大家帮忙看看问题出在哪:
相关代码
ContactsViewModel类
import android.app.Application import android.util.Log import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData import androidx.lifecycle.viewModelScope import com.example.sentryone.ContactDatabase import com.example.sentryone.EmergencyContact import kotlinx.coroutines.launch class ContactsViewModel (application: Application) : AndroidViewModel(application) { private val dao = ContactDatabase.getDatabase(application).contactDao() fun insert(contact: EmergencyContact) = viewModelScope.launch { Log.d("TAGviewmodel", "insert: in contact viewmodel the issue must be here ") dao.insert(contact) Log.d("TAGvieemodel", "insert: the num is inserted in db ") } val allContacts: LiveData<List<EmergencyContact>> = dao.getAll() }
EmergencyContactDao接口
import androidx.lifecycle.LiveData import androidx.room.Dao import androidx.room.Insert import androidx.room.Query @Dao interface EmergencyContactDao { @Insert suspend fun insert(contact: EmergencyContact) @Query("SELECT * FROM emergency_contacts") fun getAll(): LiveData<List<EmergencyContact>> }
ContactDatabase类
import android.content.Context import android.util.Log import androidx.room.Database import androidx.room.Room import androidx.room.RoomDatabase @Database(entities = [EmergencyContact::class], version = 1) abstract class ContactDatabase : RoomDatabase(){ abstract fun contactDao(): EmergencyContactDao companion object { @Volatile private var INSTANCE : ContactDatabase? = null fun getDatabase(context: Context): ContactDatabase { Log.d("TAG", "getDatabase: databse is about to create") return INSTANCE ?: synchronized(this) { Log.d("TAG", "getDatabase: in the process of making db ") val instance = Room.databaseBuilder( context.applicationContext, ContactDatabase::class.java, "emergency_db" ) .build() INSTANCE = instance instance } Log.d("TAG", "getDatabase: db created ") } } }
ContactFragment类
import android.content.pm.PackageManager import android.os.Bundle import android.provider.ContactsContract import androidx.fragment.app.Fragment import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.ArrayAdapter import android.widget.Toast import androidx.core.content.ContextCompat import com.example.sentryone.EmergencyContact import com.example.sentryone.databinding.FragmentContactBinding import com.example.sentryone.viewModels.ContactsViewModel import android.Manifest import android.util.Log import androidx.fragment.app.viewModels class ContactFragment : Fragment() { private var _binding : FragmentContactBinding? = null private val binding get() = _binding!! private val viewModel: ContactsViewModel by viewModels() private lateinit var contactSuggestions: List<Pair<String, String>> // name, phone override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { _binding= FragmentContactBinding.inflate(inflater,container,false) val view = binding.root return view } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { checkContactPermission() binding.btnAddContact.setOnClickListener { // Check if contactSuggestions has been initialized before using it if (!::contactSuggestions.isInitialized) { Toast.makeText(requireContext(), "Contacts not loaded yet. Please try again.", Toast.LENGTH_SHORT).show() return@setOnClickListener } val entered = binding.contactAutoComplete.text.toString() Log.d("TAG", "onViewCreated: in contact frag num is entered") val match = contactSuggestions.find { it.second.contains(entered) } if (match != null) { val emergencyContact = EmergencyContact(name = match.first, phone = match.second) viewModel.insert(emergencyContact) Toast.makeText(requireContext(), "联系人已添加", Toast.LENGTH_SHORT).show() binding.contactAutoComplete.text.clear() } else { Toast.makeText(requireContext(), "未找到匹配的联系人", Toast.LENGTH_SHORT).show() } } } private fun checkContactPermission() { if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) { requestPermissions(arrayOf(Manifest.permission.READ_CONTACTS), 100) } else { loadContacts() } } override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) if (requestCode == 100 && grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { loadContacts() } else { Toast.makeText(requireContext(), "需要联系人权限才能使用此功能", Toast.LENGTH_SHORT).show() } } private fun loadContacts() { contactSuggestions = mutableListOf() val cursor = requireContext().contentResolver.query( ContactsContract.CommonDataKinds.Phone.CONTENT_URI, arrayOf(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME, ContactsContract.CommonDataKinds.Phone.NUMBER), null, null, null ) cursor?.use { while (it.moveToNext()) { val name = it.getString(it.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)) val phone = it.getString(it.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.NUMBER)) (contactSuggestions as MutableList).add(Pair(name, phone)) } } val adapter = ArrayAdapter(requireContext(), android.R.layout.simple_dropdown_item_1line, contactSuggestions.map { "${it.first} - ${it.second}" }) binding.contactAutoComplete.setAdapter(adapter) } }
问题分析与解决建议
从报错信息来看,InvocationTargetException是反射调用时包装的内部异常,核心问题应该出在ViewModel初始化过程中,结合代码我梳理了几个可能的原因:
EmergencyContact实体类配置错误
Room要求实体类必须标注@Entity注解并指定表名,同时要有主键字段。如果你的实体类缺少这些配置,Room在创建数据库时会抛出异常,导致ViewModel初始化失败。建议检查实体类,正确配置示例如下:import androidx.room.Entity import androidx.room.PrimaryKey @Entity(tableName = "emergency_contacts") data class EmergencyContact( @PrimaryKey(autoGenerate = true) val id: Int = 0, val name: String, val phone: String )ContactDatabase方法逻辑错误
getDatabase方法中,Log.d("TAG", "getDatabase: db created ")这行代码在return语句之后,永远不会执行,虽然不直接导致崩溃,但建议调整到synchronized块内部,保证逻辑合理性:fun getDatabase(context: Context): ContactDatabase { Log.d("TAG", "getDatabase: databse is about to create") return INSTANCE ?: synchronized(this) { Log.d("TAG", "getDatabase: in the process of making db ") val instance = Room.databaseBuilder( context.applicationContext, ContactDatabase::class.java, "emergency_db" ) .build() INSTANCE = instance Log.d("TAG", "getDatabase: db created ") instance } }添加异常捕获定位具体错误
可以在Fragment的保存按钮点击事件中添加异常捕获,直接查看崩溃的具体原因:if (match != null) { val emergencyContact = EmergencyContact(name = match.first, phone = match.second) try { viewModel.insert(emergencyContact) Toast.makeText(requireContext(), "联系人已添加", Toast.LENGTH_SHORT).show() binding.contactAutoComplete.text.clear() } catch (e: Exception) { Log.e("TAG", "添加联系人失败: ${e.message}", e) Toast.makeText(requireContext(), "添加失败: ${e.message}", Toast.LENGTH_SHORT).show() } }确认Room依赖配置正确
检查Module级别的build.gradle是否添加了完整的Room依赖:dependencies { def room_version = "2.5.0" implementation "androidx.room:room-runtime:$room_version" kapt "androidx.room:room-compiler:$room_version" implementation "androidx.room:room-ktx:$room_version" }
按照以上步骤排查后,应该能解决崩溃问题。
内容来源于stack exchange

