使用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
相关产品推荐
相关产品推荐

