使用MPAndroidChart绘制安卓实时加速度折线图时触发NegativeArraySizeException
MPAndroidChart实时折线图展示线性加速度抛出NegativeArraySizeException异常
我想用MPAndroidChart制作实时折线图展示安卓手机的线性加速度,但运行时出现了NegativeArraySizeException异常。
实现代码
package com.example.graph_example import android.content.Context import android.graphics.Color import android.hardware.Sensor import android.hardware.SensorEvent import android.hardware.SensorEventListener import android.hardware.SensorManager import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import com.github.mikephil.charting.charts.LineChart import com.github.mikephil.charting.components.XAxis import com.github.mikephil.charting.data.Entry import com.github.mikephil.charting.data.LineData import com.github.mikephil.charting.data.LineDataSet import java.util.Calendar class MainActivity : AppCompatActivity(), SensorEventListener { private lateinit var sensorManager: SensorManager private lateinit var linearAccelerationSensor: Sensor private lateinit var chart : LineChart private lateinit var dataSetX: LineDataSet private lateinit var dataSetY: LineDataSet private lateinit var dataSetZ: LineDataSet private var lastX = 0.0f private var lastY = 0.0f private var lastZ = 0.0f private var timestamp: Long = 0L override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager linearAccelerationSensor = sensorManager.getDefaultSensor(Sensor.TYPE_LINEAR_ACCELERATION) chart = findViewById(R.id.line_chart) dataSetX = LineDataSet(mutableListOf(), "X") dataSetX.color = Color.RED dataSetX.lineWidth = 2.5f dataSetX.setDrawValues(false) dataSetX.mode = LineDataSet.Mode.CUBIC_BEZIER dataSetY = LineDataSet(mutableListOf(), "Y") dataSetY.color = Color.GREEN dataSetY.lineWidth = 2.5f dataSetY.setDrawValues(false) dataSetY.mode = LineDataSet.Mode.CUBIC_BEZIER dataSetZ = LineDataSet(mutableListOf(), "Z") dataSetZ.color = Color.BLUE dataSetZ.lineWidth = 2.5f dataSetZ.setDrawValues(false) dataSetZ.mode = LineDataSet.Mode.CUBIC_BEZIER chart.data = LineData(dataSetX, dataSetY, dataSetZ) chart.description.isEnabled = false chart.legend.isEnabled = false chart.setNoDataText("Waiting for data...") chart.setNoDataTextColor(Color.BLACK) chart.setTouchEnabled(false) chart.isDragEnabled = false chart.setScaleEnabled(false) chart.setPinchZoom(false) chart.setDrawGridBackground(false) chart.axisRight.isEnabled = false chart.xAxis.position = XAxis.XAxisPosition.BOTTOM chart.xAxis.setDrawAxisLine(false) } override fun onResume() { super.onResume() sensorManager.registerListener(this, linearAccelerationSensor, SensorManager.SENSOR_DELAY_GAME) } override fun onPause() { super.onPause() sensorManager.unregisterListener(this) } override fun onSensorChanged(event: SensorEvent?) { if (event != null) { val x = event.values[0] val y = event.values[1] val z = event.values[2] val currentTime = Calendar.getInstance().timeInMillis if (timestamp != 0L) { val dt = ((currentTime - timestamp) / 1000.0f) val dx = x * dt val dy = y * dt val dz = z * dt // 更新累计值 lastX += dx lastY += dy lastZ += dz // 添加新数据点,X轴用累计值,Y轴用当前加速度 val pointX = Entry(lastX, x) val pointY = Entry(lastY, y) val pointZ = Entry(lastZ, z) dataSetX.addEntry(pointX) dataSetY.addEntry(pointY) dataSetZ.addEntry(pointZ) // 通知图表更新 chart.data.notifyDataChanged() chart.notifyDataSetChanged() // 限制X轴显示范围 chart.setVisibleXRangeMaximum(20.0f) chart.moveViewToX(lastX) } // 记录当前时间作为下一次的基准 timestamp = currentTime } } override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) { // Do nothing } }
错误日志
2023-01-02 22:13:27.370 13697-13697/com.example.graph_example E/AndroidRuntime: FATAL EXCEPTION: main Process: com.example.graph_example, PID: 13697 java.lang.NegativeArraySizeException: -12 at com.github.mikephil.charting.utils.Transformer.generateTransformedValuesLine(Transformer.java:178) at com.github.mikephil.charting.renderer.LineChartRenderer.drawValues(LineChartRenderer.java:549) at com.github.mikephil.charting.charts.BarLineChartBase.onDraw(BarLineChartBase.java:278) at android.view.View.draw(View.java:22350) at android.view.View.updateDisplayListIfDirty(View.java:21226) at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4500) at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4473) at android.view.View.updateDisplayListIfDirty(View.java:21186) at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4500) at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4473) at android.view.View.updateDisplayListIfDirty(View.java:21186) at android.view.View.draw(View.java:22081) at android.view.ViewGroup.drawChild(ViewGroup.java:4516) at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4277) at android.view.View.updateDisplayListIfDirty(View.java:21217) at android.view.View.draw(View.java:22081) at android.view.ViewGroup.drawChild(ViewGroup.java:4516) at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4277) at android.view.View.updateDisplayListIfDirty(View.java:21217) at android.view.View.draw(View.java:22081) at android.view.ViewGroup.drawChild(ViewGroup.java:4516) at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4277) at android.view.View.updateDisplayListIfDirty(View.java:21217) at android.view.View.draw(View.java:22081) at android.view.ViewGroup.drawChild(ViewGroup.java:4516) at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4277) at android.view.View.draw(View.java:22353) at com.android.internal.policy.DecorView.draw(DecorView.java:806) at android.view.View.updateDisplayListIfDirty(View.java:21226) at android.view.ThreadedRenderer.updateViewTreeDisplayList(ThreadedRenderer.java:559) at android.view.ThreadedRenderer.updateRootDisplayList(ThreadedRenderer.java:565) at android.view.ThreadedRenderer.draw(ThreadedRenderer.java:642) at android.view.ViewRootImpl.draw(ViewRootImpl.java:4101) at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:3828) at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:3099) at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1952) at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:8171) at android.view.Choreographer$CallbackRecord.run(Choreographer.java:972) at android.view.Choreographer.doCallbacks(Choreographer.java:796) at android.view.Choreographer.doFrame(Choreographer.java:731) at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:957) at android.os.Handler.handleCallback(Handler.java:938) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loop(Looper.java:223) at android.app.ActivityThread.main(ActivityThread.java:7656) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
问题原因及修复方案
原因分析
- LineDataSet初始化错误:初始化时传入
null而非空列表,MPAndroidChart内部处理空数据集时可能出现计算异常,导致数组大小为负。 - 第一次传感器事件的逻辑错误:初始
timestamp为0时,第一次执行累计值计算会得到异常大的值,后续Entry的X轴值可能导致范围计算出错;同时原代码添加数据点后才更新累计值,逻辑顺序颠倒。 - Entry的X轴值无序:如果X轴值出现非递增情况,MPAndroidChart渲染时计算显示范围会触发NegativeArraySizeException。
修复步骤
- 初始化LineDataSet时传入空MutableList:将
LineDataSet(null, "X")改为LineDataSet(mutableListOf(), "X"),确保数据集初始状态合法。 - 调整传感器事件处理逻辑:
- 第一次事件仅记录
timestamp,不添加数据点; - 后续事件先计算时间间隔
dt,更新累计值lastX/Y/Z,再基于累计值创建Entry添加到数据集; - 使用
chart.data.notifyDataChanged()和chart.notifyDataSetChanged()正确通知图表更新。
- 第一次事件仅记录
- 统一时间戳类型:将
timestamp改为Long类型,避免Double类型的精度问题。
内容的提问来源于stack exchange,提问作者이건웅
相关产品推荐
相关产品推荐

