You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Realm数据库与变更监听器时App偶现无报错崩溃问题求助

Android JNI层无明确堆栈偶现闪退排查记录

问题现象

近期遇到一类无法确定是框架Bug还是代码实现问题的偶现闪退,App无明确Java层报错信息就直接崩溃,在技术社区检索未找到适配解决方案,完整报错内容过长,仅展示关键开头片段:

2021-11-06 21:19:55.269 31533-31533/com.example.test A/ple.test: java_vm_ext.cc:577] JNI DETECTED ERROR IN APPLICATION: JNI NewLocalRef called with pending exception java.lang.NullPointerException: 
    java_vm_ext.cc:577] (Throwable with no stack trace)
    java_vm_ext.cc:577] 
    java_vm_ext.cc:577]     in call to NewLocalRef
    java_vm_ext.cc:577]     from void android.os.MessageQueue.nativePollOnce(long, int)

2021-11-06 21:19:55.455 31533-31533/com.example.test A/ple.test: runtime.cc:655] Runtime aborting...
    runtime.cc:655] Dumping all threads without mutator lock held
    runtime.cc:655] All threads:
    runtime.cc:655] DALVIK THREADS (40):
    runtime.cc:655] "main" prio=10 tid=1 Runnable
…

初步排查

经过一整天的定位,确认问题触发和BottomSheetDialogFragment、Realm数据库相关:

  • 首次打开BottomSheetDialogFragment调用updateContactChat方法运行正常
  • 第二次打开同一个BottomSheetDialogFragment再次调用该方法100%触发上述崩溃

对应BottomSheetDialogFragment业务代码如下:

class ChatAdd : BottomSheetDialogFragment() {

    private val appRealm = Realm.getInstance(General.secureDB())
    private var binding: FragmentChatsAddBinding? = null
    
    override fun getTheme(): Int = R.style.BottomSheetDialogTheme
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        super.onCreateView(inflater, container, savedInstanceState)
    
        binding = FragmentChatsAddBinding.inflate(inflater, container, false)
    
    
        return binding!!.root
    }
    
    override fun onStart() {
        super.onStart()
        BottomSheetBehavior.from(requireView().parent as View).apply {
            state = BottomSheetBehavior.STATE_EXPANDED
        }
    }
    
    
    
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        R.style.DialogAnimation.also { dialog!!.window?.attributes?.windowAnimations = it }
    
        binding!!.contactListContainer.layoutManager = LinearLayoutManager(activity)
        binding!!.contactListContainer.setHasFixedSize(true)
        binding!!.contactListContainer.addItemDecoration(ContactsList.ItemDecoration())
    
        showList()
    }
    
    
    private fun showList() {
    
        var contacts: RealmResults<SchemeContacts>? = null
    
        binding!!.contactListContainer.adapter = ContactsAdapter(contacts, true, object :
            ContactsAdapter.ContactItemClickListener {
            override fun onClick(contact: SchemeContacts?) {
    
                contact?.contact_id?.let {
                    CoroutineScope(Dispatchers.IO).launch {
                        try {
                            updateContactChat(it)
                        }catch(e:Exception){
                            Log.d("log", "catch error: "+e)
                        }
                    }
                }
    
            }
        })
    
    }
    
    
    private fun updateContactChat(contactId: String) {
    
        val updateRealm = Realm.getInstance(General.secureDB())
    
        updateRealm.executeTransaction { bgRealm ->
    
            val user =
                bgRealm.where<SchemeContacts>().equalTo("contact_id", contactId).findFirst()!!
            user.contact_chat = true
            user.contact_chatupdated = LocalDateTime.now().toString()
            user.contact_chatnew = 0
            user.contact_chatstatus = 0
    
        }
    
        updateRealm.close()
        dialog!!.dismiss()
    
    }
    
    
    
    override fun onDestroy() {
        super.onDestroy()
        appRealm.close()
        binding = null
    }
}

根因确认

后续定位到问题根源出在承载该BottomSheetDialogFragment的父Fragment中:父Fragment存在监听Realm数据变更的OrderedRealmCollectionChangeListener,选中联系人更新数据库后会触发该监听器更新UI,原监听器代码如下:

fun runListener(){
contacts?.addChangeListener(OrderedRealmCollectionChangeListener { contacts, changeSet ->
                if (contacts.size > 0) {
                    binding!!.notfound.root.visibility = View.GONE
                } else {
                    binding!!.notfound.root.visibility = View.VISIBLE
                }
    })
}


override fun onDestroy() {
    super.onDestroy()
    binding = null
    appRealm.close()
}

问题逻辑:

  • 首次操作时binding实例存在,运行无异常
  • Fragment销毁时仅将binding置空、关闭了Realm实例,未主动移除注册的Realm监听器,导致监听器一直持有Fragment引用无法被回收
  • 第二次打开页面时新旧两个监听器同时触发,旧监听器访问已经被置空的binding就会抛出空指针异常,该异常被JNI层捕获所以没有明确的Java调用堆栈

解决方案

主动在Fragment销毁时移除所有注册的监听器,同时增加空判断避免非法访问,修改后代码如下:

fun runListener(){
        
contacts?.addChangeListener(OrderedRealmCollectionChangeListener { 
  contacts, changeSet ->
    try {
            if(binding != null) {
                if (contacts.size > 0) {
                    binding!!.notfound.root.visibility = View.GONE
                } else {
                    binding!!.notfound.root.visibility = View.VISIBLE
                }
            }
    }catch(e:Exception){
      Log.d("error","Try Catch error: "+e.toString())
    }
  })
}
      
        
override fun onDestroy() {
  super.onDestroy()
  if(contacts != null){
        contacts!!.removeAllChangeListeners()
  }
  binding = null
  appRealm.close()
}

修改后崩溃问题完全解决。这是典型的Android资源管理不当导致的问题,核心教训是:所有注册的监听、申请的资源都要在对应生命周期结束时主动关闭回收,避免内存泄露引发的未知异常。


内容的提问来源于stack exchange,提问作者Riccoh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.27 06:15:05