fix: add comprehensive try-catch logging for crash diagnosis
Build APK / build (push) Failing after 4s

- Added try-catch wrappers around all lifecycle callbacks
- Write crash stack traces to external storage file (sensTools_crash.log)
- Added Log.d/TAG throughout initialization flow
This commit is contained in:
茂之钳
2026-05-27 07:11:32 +00:00
parent 2b0d46440c
commit c643217217
@@ -4,9 +4,9 @@ import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.os.Build
import android.os.Bundle
import android.os.IBinder
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@@ -25,9 +25,14 @@ import com.senstools.presentation.ui.charts.GyroChartManager
import com.senstools.presentation.ui.charts.MagnetometerBarManager
import com.senstools.presentation.viewmodel.DashboardViewModel
import kotlinx.coroutines.launch
import java.io.FileWriter
class DashboardFragment : Fragment() {
companion object {
private const val TAG = "DashboardFragment"
}
private var _binding: FragmentDashboardBinding? = null
private val binding get() = _binding!!
@@ -49,19 +54,29 @@ class DashboardFragment : Fragment() {
private val connection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
val binder = service as DataCollectionService.LocalBinder
collectionService = binder.getService()
serviceBound = true
try {
Log.d(TAG, "onServiceConnected")
val binder = service as DataCollectionService.LocalBinder
collectionService = binder.getService()
serviceBound = true
// Must startService first to make the service a foreground started service
// (required by Android 12+ for startForeground to work)
requireContext().startService(Intent(requireContext(), DataCollectionService::class.java))
// Must startService first to make the service a foreground started service
// (required by Android 12+ for startForeground to work)
val intent = Intent(requireContext(), DataCollectionService::class.java)
requireContext().startService(intent)
Log.d(TAG, "startService called")
// Auto-start collection as soon as service is bound
collectionService?.startCollection()
// Auto-start collection as soon as service is bound
collectionService?.startCollection()
Log.d(TAG, "startCollection called")
// Observe service flows
observeServiceData()
// Observe service flows
observeServiceData()
Log.d(TAG, "observeServiceData called")
} catch (e: Exception) {
Log.e(TAG, "Error in onServiceConnected", e)
writeCrashLog("onServiceConnected", e)
}
}
override fun onServiceDisconnected(name: ComponentName?) {
@@ -75,26 +90,42 @@ class DashboardFragment : Fragment() {
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentDashboardBinding.inflate(inflater, container, false)
try {
_binding = FragmentDashboardBinding.inflate(inflater, container, false)
} catch (e: Exception) {
Log.e(TAG, "Error inflating layout", e)
writeCrashLog("onCreateView", e)
throw e
}
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
try {
repository = DataRepository(requireContext())
ekfAttitude = EKFAttitude()
repository = DataRepository(requireContext())
ekfAttitude = EKFAttitude()
setupCharts()
setupObservers()
setupListeners()
bindService()
setupCharts()
setupObservers()
setupListeners()
bindService()
Log.d(TAG, "onViewCreated complete")
} catch (e: Exception) {
Log.e(TAG, "Error in onViewCreated", e)
writeCrashLog("onViewCreated", e)
}
}
override fun onResume() {
super.onResume()
if (!serviceBound) {
bindService()
try {
if (!serviceBound) {
bindService()
}
} catch (e: Exception) {
Log.e(TAG, "Error in onResume", e)
writeCrashLog("onResume", e)
}
}
@@ -109,137 +140,166 @@ class DashboardFragment : Fragment() {
}
private fun setupCharts() {
accelChartManager = AccelChartManager(binding.chartAccel)
gyroChartManager = GyroChartManager(binding.chartGyro)
magnetometerManager = MagnetometerBarManager(binding.chartMagnetometer)
try {
accelChartManager = AccelChartManager(binding.chartAccel)
gyroChartManager = GyroChartManager(binding.chartGyro)
magnetometerManager = MagnetometerBarManager(binding.chartMagnetometer)
} catch (e: Exception) {
Log.e(TAG, "Error setting up charts", e)
writeCrashLog("setupCharts", e)
}
}
private fun bindService() {
val intent = Intent(requireContext(), DataCollectionService::class.java)
requireContext().bindService(intent, connection, Context.BIND_AUTO_CREATE)
try {
val intent = Intent(requireContext(), DataCollectionService::class.java)
requireContext().bindService(intent, connection, Context.BIND_AUTO_CREATE)
Log.d(TAG, "bindService called")
} catch (e: Exception) {
Log.e(TAG, "Error binding service", e)
writeCrashLog("bindService", e)
}
}
private fun observeServiceData() {
val service = collectionService ?: return
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
launch {
service.currentGPSData.collect { gps ->
viewModel.updateGPSData(gps)
try {
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
launch {
service.currentGPSData.collect { gps ->
viewModel.updateGPSData(gps)
}
}
}
launch {
service.currentSensorData.collect { sensor ->
viewModel.updateSensorData(sensor)
launch {
service.currentSensorData.collect { sensor ->
viewModel.updateSensorData(sensor)
// EKF update
if (!ekfInitialized) {
ekfAttitude.initializeFromSensors(
// EKF update
if (!ekfInitialized) {
ekfAttitude.initializeFromSensors(
sensor.accelerometer,
sensor.magnetometer
)
ekfInitialized = true
}
ekfAttitude.predict(
sensor.gyroscope,
sensor.timestamp
)
ekfAttitude.update(
sensor.accelerometer,
sensor.magnetometer
)
ekfInitialized = true
}
ekfAttitude.predict(
sensor.gyroscope,
sensor.timestamp
)
ekfAttitude.update(
sensor.accelerometer,
sensor.magnetometer
)
val attitude = ekfAttitude.getAttitude()
viewModel.updateAttitudeData(attitude)
val attitude = ekfAttitude.getAttitude()
viewModel.updateAttitudeData(attitude)
// Update charts
accelChartManager.addData(
sensor.accelerometer[0],
sensor.accelerometer[1],
sensor.accelerometer[2]
)
gyroChartManager.addData(
sensor.gyroscope[0],
sensor.gyroscope[1],
sensor.gyroscope[2]
)
magnetometerManager.updateData(
sensor.magnetometer[0],
sensor.magnetometer[1],
sensor.magnetometer[2]
)
// Update charts
accelChartManager.addData(
sensor.accelerometer[0],
sensor.accelerometer[1],
sensor.accelerometer[2]
)
gyroChartManager.addData(
sensor.gyroscope[0],
sensor.gyroscope[1],
sensor.gyroscope[2]
)
magnetometerManager.updateData(
sensor.magnetometer[0],
sensor.magnetometer[1],
sensor.magnetometer[2]
)
// If recording, buffer the sensor data
if (viewModel.isRecording.value == true && recordingFile != null) {
repository.appendSensorData(sensor, recordingFile!!)
// If recording, buffer the sensor data
if (viewModel.isRecording.value == true && recordingFile != null) {
repository.appendSensorData(sensor, recordingFile!!)
}
}
}
}
}
} catch (e: Exception) {
Log.e(TAG, "Error in observeServiceData", e)
writeCrashLog("observeServiceData", e)
}
}
private fun setupObservers() {
viewModel.gpsData.observe(viewLifecycleOwner) { gps ->
binding.apply {
tvLongitude.text = String.format("%.8f", gps.longitude)
tvLatitude.text = String.format("%.8f", gps.latitude)
tvAltitude.text = String.format("%.2f m", gps.altitude)
tvSpeed.text = String.format("%.2f m/s", gps.speed)
tvBearing.text = String.format("%.2f°", gps.bearing)
tvSatellites.text = gps.satellites.toString()
try {
viewModel.gpsData.observe(viewLifecycleOwner) { gps ->
binding.apply {
tvLongitude.text = String.format("%.8f", gps.longitude)
tvLatitude.text = String.format("%.8f", gps.latitude)
tvAltitude.text = String.format("%.2f m", gps.altitude)
tvSpeed.text = String.format("%.2f m/s", gps.speed)
tvBearing.text = String.format("%.2f°", gps.bearing)
tvSatellites.text = gps.satellites.toString()
// Also buffer GPS data during recording
if (viewModel.isRecording.value == true && recordingFile != null) {
repository.appendGPSData(gps, recordingFile!!)
}
}
}
}
viewModel.sensorData.observe(viewLifecycleOwner) { sensor ->
binding.apply {
tvAccelX.text = String.format("%.2f", sensor.accelerometer[0])
tvAccelY.text = String.format("%.2f", sensor.accelerometer[1])
tvAccelZ.text = String.format("%.2f", sensor.accelerometer[2])
tvGyroX.text = String.format("%.2f", sensor.gyroscope[0])
tvGyroY.text = String.format("%.2f", sensor.gyroscope[1])
tvGyroZ.text = String.format("%.2f", sensor.gyroscope[2])
tvMagX.text = String.format("%.2f", sensor.magnetometer[0])
tvMagY.text = String.format("%.2f", sensor.magnetometer[1])
tvMagZ.text = String.format("%.2f", sensor.magnetometer[2])
tvPressure.text = String.format("%.2f m", sensor.pressureAltitude)
tvTemperature.text = String.format("%.2f°C", sensor.temperature)
tvHumidity.text = String.format("%.1f%%", sensor.humidity)
viewModel.sensorData.observe(viewLifecycleOwner) { sensor ->
binding.apply {
tvAccelX.text = String.format("%.2f", sensor.accelerometer[0])
tvAccelY.text = String.format("%.2f", sensor.accelerometer[1])
tvAccelZ.text = String.format("%.2f", sensor.accelerometer[2])
tvGyroX.text = String.format("%.2f", sensor.gyroscope[0])
tvGyroY.text = String.format("%.2f", sensor.gyroscope[1])
tvGyroZ.text = String.format("%.2f", sensor.gyroscope[2])
tvMagX.text = String.format("%.2f", sensor.magnetometer[0])
tvMagY.text = String.format("%.2f", sensor.magnetometer[1])
tvMagZ.text = String.format("%.2f", sensor.magnetometer[2])
tvPressure.text = String.format("%.2f m", sensor.pressureAltitude)
tvTemperature.text = String.format("%.2f°C", sensor.temperature)
tvHumidity.text = String.format("%.1f%%", sensor.humidity)
}
}
}
viewModel.attitudeData.observe(viewLifecycleOwner) { attitude ->
binding.apply {
artificialHorizon.setAttitude(attitude.pitch, attitude.roll)
tvPitch.text = String.format("%.2f°", attitude.pitch)
tvRoll.text = String.format("%.2f°", attitude.roll)
tvYaw.text = String.format("%.2f°", attitude.yaw)
viewModel.attitudeData.observe(viewLifecycleOwner) { attitude ->
binding.apply {
artificialHorizon.setAttitude(attitude.pitch, attitude.roll)
tvPitch.text = String.format("%.2f°", attitude.pitch)
tvRoll.text = String.format("%.2f°", attitude.roll)
tvYaw.text = String.format("%.2f°", attitude.yaw)
}
}
}
viewModel.isRecording.observe(viewLifecycleOwner) { isRecording ->
binding.apply {
btnRecord.isEnabled = !isRecording
btnStopRecording.isEnabled = isRecording
tvRecordingStatus.text = if (isRecording) "Recording..." else "Idle"
viewModel.isRecording.observe(viewLifecycleOwner) { isRecording ->
binding.apply {
btnRecord.isEnabled = !isRecording
btnStopRecording.isEnabled = isRecording
tvRecordingStatus.text = if (isRecording) "Recording..." else "Idle"
}
}
} catch (e: Exception) {
Log.e(TAG, "Error in setupObservers", e)
writeCrashLog("setupObservers", e)
}
}
private fun setupListeners() {
binding.btnRecord.setOnClickListener {
startRecording()
try {
startRecording()
} catch (e: Exception) {
Log.e(TAG, "Error in btnRecord click", e)
writeCrashLog("btnRecord", e)
}
}
binding.btnStopRecording.setOnClickListener {
stopRecording()
try {
stopRecording()
} catch (e: Exception) {
Log.e(TAG, "Error in btnStop click", e)
writeCrashLog("btnStop", e)
}
}
}
@@ -260,6 +320,22 @@ class DashboardFragment : Fragment() {
Toast.makeText(requireContext(), "Recording saved", Toast.LENGTH_SHORT).show()
}
private fun writeCrashLog(location: String, e: Exception) {
try {
val logFile = java.io.File(
requireContext().getExternalFilesDir(null),
"sensTools_crash.log"
)
FileWriter(logFile, true).use { writer ->
writer.appendLine("=== Crash at $location ===")
writer.appendLine("Time: ${System.currentTimeMillis()}")
writer.appendLine("Exception: ${e.javaClass.name}: ${e.message}")
e.stackTrace?.forEach { writer.appendLine(" at $it") }
writer.appendLine()
}
} catch (_: Exception) { }
}
override fun onDestroyView() {
super.onDestroyView()
if (serviceBound) {