Fix Android RAM overlay instant crash

Use getMainLooper() instead of myLooper() in Handler initialization
and add view attachment checks to prevent crashes.

Reported-by: Shadai (theonlyshadai) on Discord
Signed-off-by: Zephyron <zephyron@citron-emu.org>
This commit is contained in:
Zephyron
2025-10-08 16:59:38 +10:00
parent 4c5f12ec69
commit 2a7e6c74bd
2 changed files with 57 additions and 22 deletions

View File

@@ -526,11 +526,16 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
if (emulationViewModel.emulationStarted.value &&
!emulationViewModel.isEmulationStopping.value
) {
val perfStats = NativeLibrary.getPerfStats()
if (_binding != null) {
if (_binding != null && binding.fpsIndicatorView.isAttachedToWindow) {
val perfStats = NativeLibrary.getPerfStats()
binding.fpsIndicatorView.updateFps(perfStats[FPS].toFloat())
perfStatsUpdateHandler.postDelayed(perfStatsUpdater!!, 800)
}
} else {
// Stop the updater if emulation is stopping
if (perfStatsUpdater != null) {
perfStatsUpdateHandler.removeCallbacks(perfStatsUpdater!!)
}
perfStatsUpdateHandler.postDelayed(perfStatsUpdater!!, 800)
}
}
perfStatsUpdateHandler.post(perfStatsUpdater!!)
@@ -549,11 +554,16 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
if (emulationViewModel.emulationStarted.value &&
!emulationViewModel.isEmulationStopping.value
) {
if (_binding != null) {
if (_binding != null && binding.thermalIndicatorView.isAttachedToWindow) {
val temperature = getBatteryTemperature(requireContext())
binding.thermalIndicatorView.updateTemperature(temperature)
thermalStatsUpdateHandler.postDelayed(thermalStatsUpdater!!, 2000)
}
} else {
// Stop the updater if emulation is stopping
if (thermalStatsUpdater != null) {
thermalStatsUpdateHandler.removeCallbacks(thermalStatsUpdater!!)
}
thermalStatsUpdateHandler.postDelayed(thermalStatsUpdater!!, 2000)
}
}
thermalStatsUpdateHandler.post(thermalStatsUpdater!!)
@@ -572,10 +582,15 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
if (emulationViewModel.emulationStarted.value &&
!emulationViewModel.isEmulationStopping.value
) {
if (_binding != null) {
if (_binding != null && binding.ramMeterView.isAttachedToWindow) {
binding.ramMeterView.updateRamUsage()
ramStatsUpdateHandler.postDelayed(ramStatsUpdater!!, 1500)
}
} else {
// Stop the updater if emulation is stopping
if (ramStatsUpdater != null) {
ramStatsUpdateHandler.removeCallbacks(ramStatsUpdater!!)
}
ramStatsUpdateHandler.postDelayed(ramStatsUpdater!!, 1500)
}
}
ramStatsUpdateHandler.post(ramStatsUpdater!!)
@@ -1161,9 +1176,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
}
companion object {
private val perfStatsUpdateHandler = Handler(Looper.myLooper()!!)
private val thermalStatsUpdateHandler = Handler(Looper.myLooper()!!)
private val ramStatsUpdateHandler = Handler(Looper.myLooper()!!)
private val shaderStatsUpdateHandler = Handler(Looper.myLooper()!!)
private val perfStatsUpdateHandler = Handler(Looper.getMainLooper())
private val thermalStatsUpdateHandler = Handler(Looper.getMainLooper())
private val ramStatsUpdateHandler = Handler(Looper.getMainLooper())
private val shaderStatsUpdateHandler = Handler(Looper.getMainLooper())
}
}

View File

@@ -35,7 +35,18 @@ class RamMeterView @JvmOverloads constructor(
fun updateRamUsage() {
try {
val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
// Safety check: ensure view is attached and has valid dimensions
if (!isAttachedToWindow || width <= 0 || height <= 0) {
Log.w("RamMeter", "View not ready for update (attached: $isAttachedToWindow, width: $width, height: $height)")
return
}
val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as? ActivityManager
if (activityManager == null) {
Log.e("RamMeter", "ActivityManager service not available")
return
}
val memoryInfo = ActivityManager.MemoryInfo()
activityManager.getMemoryInfo(memoryInfo)
@@ -61,7 +72,6 @@ class RamMeterView @JvmOverloads constructor(
ramUsagePercent = 0f
usedRamMB = 0L
totalRamMB = 0L
invalidate()
}
}
@@ -92,16 +102,26 @@ class RamMeterView @JvmOverloads constructor(
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// Draw simple text-based RAM display
val usedGB = usedRamMB / 1024f
val totalGB = totalRamMB / 1024f
val ramText = if (totalGB >= 1.0f) {
"RAM: ${ramUsagePercent.roundToInt()}% (%.1fGB/%.1fGB)".format(usedGB, totalGB)
} else {
"RAM: ${ramUsagePercent.roundToInt()}% (${usedRamMB}MB/${totalRamMB}MB)"
// Safety check: ensure view has valid dimensions
if (width <= 0 || height <= 0) {
Log.w("RamMeter", "onDraw called with invalid dimensions (width: $width, height: $height)")
return
}
canvas.drawText(ramText, 8f, height - 8f, textPaint)
Log.d("RamMeter", "onDraw called - RAM: $ramText")
try {
// Draw simple text-based RAM display
val usedGB = usedRamMB / 1024f
val totalGB = totalRamMB / 1024f
val ramText = if (totalGB >= 1.0f) {
"RAM: ${ramUsagePercent.roundToInt()}% (%.1fGB/%.1fGB)".format(usedGB, totalGB)
} else {
"RAM: ${ramUsagePercent.roundToInt()}% (${usedRamMB}MB/${totalRamMB}MB)"
}
canvas.drawText(ramText, 8f, height - 8f, textPaint)
Log.d("RamMeter", "onDraw called - RAM: $ramText")
} catch (e: Exception) {
Log.e("RamMeter", "Error in onDraw", e)
}
}
}