fix(base): 修复多处内存泄漏及生命周期安全问题

BaseVMBFragment:
- 修复 onDestroyView 中 mBinding == null(比较运算)为正确的 _mBinding = null(赋值),
  引入 backing field 模式彻底释放 View 引用,防止 Fragment 回退栈内存泄漏
- 修复 onDestroy 中 EventBus.unregister 判断条件取反(!isRegistered → isRegistered),
  确保 Fragment 销毁时正确注销,避免 EventBus 长期持有 Fragment 引用

BaseVMBActivity:
- 删除无效的 getResources() 重写(createConfigurationContext 返回值未使用),
  字体防缩放逻辑已由 attachBaseContext 正确实现

MainActivity:
- 删除 onSaveInstanceState(Bundle()) 错误重写,
  传入空 Bundle 导致 Fragment 状态无法保存,低内存恢复场景必现异常

EmergencyFragment:
- onDestroy 中补充主动停止并销毁 AMapLocationClient,
  防止 Fragment 销毁后 GPS 定位服务持续运行

HomeFragment:
- homeNoticeStartRepeat 新增 Job 引用,每次启动前取消旧协程,
  防止多次调用时多个轮询协程并发叠加导致接口重复请求

WatchItemFragment:
- newInstance 迁移至 companion object,符合 Android Fragment 工厂方法规范
- position 类型由 Int? 改为 Int,移除无用的 Fragment import

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
zhanglei
2026-03-06 10:25:20 +08:00
co-authored by Claude Sonnet 4.6
parent dcb53f344a
commit 9c91839520
7 changed files with 40 additions and 29 deletions
@@ -272,9 +272,6 @@ class MainActivity : BaseVMBActivity<MainViewModel, ActivityMainBinding>(R.layou
super.onPause() super.onPause()
} }
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(Bundle())
}
override fun getFragmentByTag(tabTag: String): Fragment? { override fun getFragmentByTag(tabTag: String): Fragment? {
when (tabTag) { when (tabTag) {
@@ -70,15 +70,6 @@ abstract class BaseVMBActivity<VM : BaseViewModel, B : ViewDataBinding>(private
var mEmpty: View?=null var mEmpty: View?=null
var mRootView:RelativeLayout?=null var mRootView:RelativeLayout?=null
/**
* 重写getResources()方法,让APP的字体不受系统设置字体大小影响
*/
override fun getResources(): Resources? {
val config = Configuration()
config.setToDefaults()
createConfigurationContext(config)
return super.getResources()
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@@ -56,15 +56,21 @@ abstract class BaseVMBFragment<VM : BaseViewModel, B : ViewDataBinding>(private
private var mIsFirstLoading = true private var mIsFirstLoading = true
var dialog: LoadingDialog? = null var dialog: LoadingDialog? = null
protected lateinit var mViewModel: VM protected lateinit var mViewModel: VM
lateinit var mBinding: B
/** ViewDataBinding 可空 backing fieldonDestroyView 时置 null 防止内存泄漏 */
private var _mBinding: B? = null
/** 对外暴露的非空 getter,仅在 onCreateView ~ onDestroyView 生命周期内有效 */
val mBinding: B get() = _mBinding!!
var mEmpty: View? = null var mEmpty: View? = null
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View? {
mBinding = DataBindingUtil.inflate(inflater, contentViewResId, container, false) _mBinding = DataBindingUtil.inflate(inflater, contentViewResId, container, false)
return mBinding.root return _mBinding!!.root
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@@ -306,13 +312,15 @@ abstract class BaseVMBFragment<VM : BaseViewModel, B : ViewDataBinding>(private
} }
override fun onDestroyView() { override fun onDestroyView() {
mBinding == null
super.onDestroyView() super.onDestroyView()
// 置 null 释放 View 引用,防止 Fragment 回退栈期间内存泄漏
_mBinding = null
} }
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
if (!EventBus.getDefault().isRegistered(this)) { // 已注册才执行注销,避免 EventBus 持有 Fragment 引用
if (EventBus.getDefault().isRegistered(this)) {
EventBus.getDefault().unregister(this) EventBus.getDefault().unregister(this)
} }
} }
@@ -180,6 +180,10 @@ class EmergencyFragment :
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
// 主动停止并销毁定位客户端,防止 Fragment 销毁后 GPS 定位服务持续运行造成资源浪费
mlocationClient?.stopLocation()
mlocationClient?.onDestroy()
mlocationClient = null
mMapView?.onDestroy() mMapView?.onDestroy()
} }
@@ -32,6 +32,7 @@ import com.xjjk.healthyclients.ui.viewmodel.HomeViewModel
import com.xjjk.healthyclients.utils.ConstantUtils import com.xjjk.healthyclients.utils.ConstantUtils
import com.xjjk.healthyclients.utils.IMInputActionSettingUtils import com.xjjk.healthyclients.utils.IMInputActionSettingUtils
import com.xjjk.healthyclients.utils.TUIUtils import com.xjjk.healthyclients.utils.TUIUtils
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -49,9 +50,11 @@ class HomeFragment :
var mGuidanceFragmentDoctorAdapter: GuidanceFragmentDoctorAdapter? = null var mGuidanceFragmentDoctorAdapter: GuidanceFragmentDoctorAdapter? = null
var REQUEST_DURATION = 1000 * 120L var REQUEST_DURATION = 1000 * 120L
var noticeTime = REQUEST_DURATION var noticeTime = REQUEST_DURATION
/** 通知轮播协程 Job,重新启动前先取消旧任务,防止多协程并发叠加 */
private var noticeJob: Job? = null
override fun initView(root: View?, savedInstanceState: Bundle?) { override fun initView(root: View?, savedInstanceState: Bundle?) {
mBinding.swipeRefresh.initColors() mBinding.swipeRefresh.initColors()
@@ -339,7 +342,9 @@ class HomeFragment :
} }
private fun homeNoticeStartRepeat() { private fun homeNoticeStartRepeat() {
lifecycleScope.launch { // 取消旧的轮询协程,防止多次调用时多个协程并发叠加
noticeJob?.cancel()
noticeJob = lifecycleScope.launch {
repeat(500) { repeat(500) {
mViewModel.getHomeNoticeListRepeat() mViewModel.getHomeNoticeListRepeat()
delay(noticeTime) delay(noticeTime)
@@ -3,7 +3,6 @@ package com.xjjk.healthyclients.fragment
import android.os.Bundle import android.os.Bundle
import android.text.TextUtils import android.text.TextUtils
import android.view.View import android.view.View
import androidx.fragment.app.Fragment
import com.sw.healthyclients.utils.DateUtil import com.sw.healthyclients.utils.DateUtil
import com.xjjk.healthyclients.R import com.xjjk.healthyclients.R
import com.xjjk.healthyclients.base.BaseVMBFragment import com.xjjk.healthyclients.base.BaseVMBFragment
@@ -20,18 +19,25 @@ import com.xjjk.healthyclients.ui.viewmodel.HomeViewModel
*/ */
class WatchItemFragment : class WatchItemFragment :
BaseVMBFragment<HomeViewModel, FragmentItemWatchBinding>(R.layout.fragment_item_watch) { BaseVMBFragment<HomeViewModel, FragmentItemWatchBinding>(R.layout.fragment_item_watch) {
var position: Int? = -1
fun newInstance(position: Int): Fragment { companion object {
/**
* 创建 WatchItemFragment 实例,通过 arguments 传入 Tab 位置
* @param position Tab 位置(0=心率 1=血氧 2=压力 3=体温 4=睡眠)
*/
fun newInstance(position: Int): WatchItemFragment {
val args = Bundle() val args = Bundle()
args.putInt("position", position) args.putInt("position", position)
val fragment = WatchItemFragment() return WatchItemFragment().apply {
fragment.arguments = args arguments = args
return fragment }
}
} }
private var position: Int = -1
override fun initView(root: View?, savedInstanceState: Bundle?) { override fun initView(root: View?, savedInstanceState: Bundle?) {
position = arguments?.getInt("position") position = arguments?.getInt("position") ?: -1
mBinding.watchTypeImg.setImageResource(getWatchTypeImg(position)) mBinding.watchTypeImg.setImageResource(getWatchTypeImg(position))
if (position != 4) { if (position != 4) {
@@ -121,7 +121,7 @@ class HomeWatchView(context: Context?, attrs: AttributeSet?) :
} }
override fun createFragment(position: Int): Fragment { override fun createFragment(position: Int): Fragment {
return WatchItemFragment().newInstance(position) return WatchItemFragment.newInstance(position)
} }
} }