I'm trying to create a sample Android application using MPAndroidPlot to live-plot some random data. I'm attempting to make the application behave as follows:
Here is the code, below, I will explain the problem.
public class MainActivity extends AppCompatActivity {
private static final String TAG = "SCOPEN";
private static final int MAX_SAMPLES = 100;
private static final int BG_COLOR = 0xFF47474E;
private static final int LINE_COLOR = 0xFFFFFACD;
private float mTimeStep = 0.5f;
private float mMaxY = 10;
private float mMinY = 0;
private LineChart mChart;
private LineDataSet mDataSet;
private LineData mLineData;
@SuppressLint("SourceLockedOrientationActivity")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
setContentView(R.layout.activity_main);
// Configure graph
mChart = (LineChart) findViewById(R.id.chart);
mChart.setHardwareAccelerationEnabled(true);
mChart.setBackgroundColor(BG_COLOR);
mChart.setDescription(null);
mChart.setAutoScaleMinMaxEnabled(false);
mChart.setPinchZoom(false);
mChart.setDragEnabled(true);
mChart.setScaleEnabled(true);
mChart.setTouchEnabled(false);
// Disable legend
Legend legend = mChart.getLegend();
legend.setEnabled(false);
// Configure y-axis right
mChart.getAxisRight().setEnabled(false);
// Configure y-axis left
YAxis yAxisLeft = mChart.getAxisLeft();
yAxisLeft.setDrawGridLines(true);
yAxisLeft.setDrawAxisLine(false);
yAxisLeft.setDrawLabels(false);
yAxisLeft.setAxisMinimum(mMinY);
yAxisLeft.setAxisMaximum(mMaxY);
//yAxisLeft.enableGridDashedLine(10, 0, 0);
// Configure x-axis
XAxis xAxis = mChart.getXAxis();
xAxis.setDrawGridLines(true);
xAxis.setDrawAxisLine(false);
xAxis.setDrawLabels(false);
xAxis.setAxisMinimum(0);
xAxis.setAxisMaximum(mTimeStep * (MAX_SAMPLES - 1));
//xAxis.enableGridDashedLine(10, 10, 0);
// Configure data
mDataSet = new LineDataSet(null, null);
mDataSet.setDrawValues(false);
mDataSet.setColor(LINE_COLOR);
mDataSet.setCircleColor(LINE_COLOR);
mDataSet.setLineWidth(3f);
mDataSet.setMode(LineDataSet.Mode.CUBIC_BEZIER);
mLineData = new LineData(mDataSet);
mChart.setData(mLineData);
mChart.invalidate();
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
addEntry(new Random().nextInt(10));
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
thread.start();
}
public void addEntry(final float v) {
if (mDataSet.getEntryCount() == MAX_SAMPLES) {
mDataSet.removeFirst();
for (Entry entry : mDataSet.getValues()) {
entry.setX(entry.getX() - mTimeStep);
}
}
mDataSet.addEntry(new Entry(mDataSet.getEntryCount() * mTimeStep, v));
mDataSet.notifyDataSetChanged();
mLineData.notifyDataChanged();
mChart.notifyDataSetChanged();
mChart.invalidate();
}
}
The problem: my application crashes after a random period of time. Decreasing the time the entry-spawning thread sleeps accelerates the time until crash (< 50ms typically leads to a consistent crash within 10 seconds).
The error is:
2020-05-25 21:58:32.241 18392-18392/com.example.scopen E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.scopen, PID: 18392
java.lang.IndexOutOfBoundsException: Index: 99, Size: 100
at java.util.ArrayList.get(ArrayList.java:437)
at com.github.mikephil.charting.data.DataSet.getEntryForIndex(DataSet.java:294)
at com.github.mikephil.charting.renderer.LineChartRenderer.drawCircles(LineChartRenderer.java:667)
at com.github.mikephil.charting.renderer.LineChartRenderer.drawExtras(LineChartRenderer.java:601)
at com.github.mikephil.charting.charts.BarLineChartBase.onDraw(BarLineChartBase.java:255)
at android.view.View.draw(View.java:23190)
at android.view.View.updateDisplayListIfDirty(View.java:22065)
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:5214)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:5186)
at android.view.View.updateDisplayListIfDirty(View.java:22020)
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:5214)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:5186)
at android.view.View.updateDisplayListIfDirty(View.java:22020)
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:5214)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:5186)
at android.view.View.updateDisplayListIfDirty(View.java:22020)
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:5214)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:5186)
at android.view.View.updateDisplayListIfDirty(View.java:22020)
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:5214)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:5186)
at android.view.View.updateDisplayListIfDirty(View.java:22020)
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:5214)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:5186)
at android.view.View.updateDisplayListIfDirty(View.java:22020)
at android.view.ThreadedRenderer.updateViewTreeDisplayList(ThreadedRenderer.java:588)
at android.view.ThreadedRenderer.updateRootDisplayList(ThreadedRenderer.java:594)
at android.view.ThreadedRenderer.draw(ThreadedRenderer.java:667)
at android.view.ViewRootImpl.draw(ViewRootImpl.java:4263)
at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:4047)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:3320)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:2200)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:9065)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:999)
at android.view.Choreographer.doCallbacks(Choreographer.java:797)
at android.view.Choreographer.doFrame(Choreographer.java:732)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:984)
at android.os.Handler.handleCallback(Handler.java:883)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:237)
at android.app.ActivityThread.main(ActivityThread.java:8016)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1076)
Notice that I get an IndexOutOfBoundsException
where the attempted index access is less than the size of the ArrayList. I've tried with several different maximum sizes and the index is always size - 1. I suspect there is some sort of concurrency problem. Perhaps when mChart.invalidate()
is called, it triggers the screen to refresh. Then, there is the possibility of a state where the thread deletes a value just as the display attempts to read the last value.
I'm not java or Android expert, so I'd appreciate any help. Thanks!
I also have this issues. I find the problem that Exception is very clear that you have tried to access some index which is not available in the list. I have look into the MainActivity.java I have found the following glitch. You are trying to fetch data from a list without verifying the list(whether null or empty ).
https://www.codeproject.com/Questions/987512/How-to-fix-Unable-to-start-activity-ComponentInfo