Search code examples
androidgraphreal-timeaccelerometer

My Android App is working very slowly


My app reads data from the accelerometer and plots a real time graph. I ran it on the emulator and it seems to work fine but when run on a real android device it responds very slowly, the device is Samsung GT-S6500 Android 2.3.6 (API 10).

There is a start an stop button in my app and both seem to work okay on the emulator but when app is run on the device metioned above start button runs fine but the real time graph moves very slowly and hangs stop button response time becomes very large. Any advice ?

Here is the code: MainActivity.java

package com.mycompany.falldetect;

import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;


import android.app.Activity;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.LinearLayout;


import com.telerik.widget.chart.engine.databinding.DataPointBinding;
import com.telerik.widget.chart.visualization.cartesianChart.RadCartesianChartView;
import com.telerik.widget.chart.visualization.cartesianChart.axes.CategoricalAxis;
import com.telerik.widget.chart.visualization.cartesianChart.axes.LinearAxis;
import com.telerik.widget.chart.visualization.cartesianChart.series.categorical.LineSeries;

import static android.os.Environment.getExternalStorageDirectory;


public class MainActivity extends Activity implements SensorEventListener,
    OnClickListener {
private SensorManager SensorManager;
private Sensor mAccelerometer;
private FileWriter writer;
private Button btnStart, btnStop;
private boolean started = false;
private Queue<Data> seismicActivityBuffer;
private List<Data> allSeismicActivity;
private ViewGroup chartContainer;
private RadCartesianChartView chart;
private int bufferSize;
String f = "";
int m = 0;
double prev_xyz = 9.0;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    SensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);

    mAccelerometer = SensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
    this.seismicActivityBuffer = new LinkedList<Data>();
    this.allSeismicActivity = new ArrayList<Data>();
    this.bufferSize = 100;
    // Adding points to fill the screen at initial state.
    for (int i = -this.bufferSize/1000; i < 0; i++) {
        this.seismicActivityBuffer.add(new Data(i, 0, 0, 0));
    }

    this.chartContainer = (ViewGroup) findViewById(R.id.chart_container);

    this.chart = createChart(this.seismicActivityBuffer);
    this.chartContainer.addView(this.chart);

    btnStart = (Button) findViewById(R.id.btnStart);
    btnStop = (Button) findViewById(R.id.btnStop);

    btnStart.setOnClickListener(this);
    btnStop.setOnClickListener(this);

    btnStart.setEnabled(true);
    btnStop.setEnabled(false);


}

public void onStartClick(View view) {
    SensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_NORMAL);
}

public void onStopClick(View view) {
    SensorManager.unregisterListener(this);
}

protected void onResume() {
    super.onResume();
    this.SensorManager.registerListener(this, this.mAccelerometer, SensorManager.SENSOR_DELAY_FASTEST);

}

protected void onPause() {
    super.onPause();

    if (started) {
        SensorManager.unregisterListener(this);
    }


}

@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {

}

@Override
public void onSensorChanged(SensorEvent event) {
    if (started) {
        double x = event.values[0];
        double y = event.values[1];
        double z = event.values[2];
        long timestamp = System.nanoTime();
        Data point = new Data(timestamp, x, y, z);


        //System.out.print("sesor: " + point.getTimestamp() + " " + point.getX() + " " + point.getY() + " " + point.getZ());

       // double xyz = Math.round(Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2) + Math.pow(z, 2)));
        // String s=timestamp+" "+x+" "+y+" "+z+" "+xyz+" \n";
        // f+=s;

        if (this.seismicActivityBuffer.size() > this.bufferSize) {
            this.seismicActivityBuffer.remove();
        }

        this.seismicActivityBuffer.add(point);
        //this.allSeismicActivity.add(point);

        this.chartContainer.removeAllViews();
        this.chart = createChart(seismicActivityBuffer);
        this.chartContainer.addView(chart);
        /*if(xyz!=9 && xyz!=10){
            String s=timestamp+" "+x+" "+y+" "+z+" "+xyz+" \n";
            f+=s;
        }*/
        //System.out.println("xyz: "+xyz);


        /*if(xyz <= 5){
            m++;
            prev_xyz=xyz;

        }
        if(Math.abs(prev_xyz-xyz) >= 10 ){
            Toast.makeText(getBaseContext(),"FALL DETECTED!",Toast.LENGTH_LONG).show();
            f+="FALL DETECTED!\n";
            prev_xyz=9;
            fall=true;
            m=0;
        }*/


    }

}

@Override
public void onClick(View v) {
    switch (v.getId()) {
        case R.id.btnStart:
            btnStart.setEnabled(false);
            btnStop.setEnabled(true);

            // save prev data if available
            started = true;

            this.chart = createChart(this.allSeismicActivity);
            this.chartContainer.addView(this.chart);

            break;
        case R.id.btnStop:
            stop();

            break;
        default:
            break;
    }

}


private void stop() {
    btnStart.setEnabled(true);
    btnStop.setEnabled(false);
    started = false;
    SensorManager.unregisterListener(this);
    chartContainer.removeAllViews();
    //openChart();
    //createExternalStorageFile(f);
}

private RadCartesianChartView createChart(Iterable<Data> dataPoints) {
    RadCartesianChartView chart = new RadCartesianChartView(this);


    // create category binding with the X coordinate of the accelerometer point
    DataPointBinding categoryBinding = new DataPointBinding() {
        @Override
        public Object getValue(Object o) throws IllegalArgumentException {
            return ((Data) o).getTimestamp();
        }
    };


    // create value binding with the X coordinate of the accelerometer point
    DataPointBinding valueBinding_x = new DataPointBinding() {
        @Override
        public Object getValue(Object o) throws IllegalArgumentException {
            return ((Data) o).getX();
        }
    };
    DataPointBinding valueBinding_y = new DataPointBinding() {
        @Override
        public Object getValue(Object o) throws IllegalArgumentException {
            return ((Data) o).getY();
        }
    };
    DataPointBinding valueBinding_z = new DataPointBinding() {
        @Override
        public Object getValue(Object o) throws IllegalArgumentException {
            return ((Data) o).getZ();
        }
    };
    LineSeries series = new LineSeries();

    series.setCategoryBinding(categoryBinding);
    series.setValueBinding(valueBinding_x);
    chart.getSeries().add(series);
    series.setData(dataPoints);
    LineSeries series2 = new LineSeries();
    series2.setCategoryBinding(categoryBinding);
    series2.setValueBinding(valueBinding_y);
    chart.getSeries().add(series2);
    series2.setData(dataPoints);
    LineSeries series3 = new LineSeries();
    series3.setCategoryBinding(categoryBinding);
    series3.setValueBinding(valueBinding_z);
    chart.getSeries().add(series3);
    series3.setData(dataPoints);

    // feed the data to the chart


    // configure the vertical axis
    LinearAxis vAxis = new LinearAxis();
    // The maximum value of the accelerometer is 20 and the minimum -20, so give a bonus 10 to the vertical axis.
    vAxis.setMaximum(20);
    vAxis.setMinimum(-20);
    chart.setVerticalAxis(vAxis);

    // configure the horizontal axis
    CategoricalAxis hAxis = new CategoricalAxis();
    hAxis.setShowLabels(false);
    chart.setHorizontalAxis(hAxis);

    return chart;
}


void createExternalStorageFile(String a) {
    // Create a path where we will place our private file on external
    // storage.
    File file = new File(getExternalStorageDirectory(), "DemoFile.txt");

    try {
        // Very simple code to copy a picture from the application's
        // resource into the external file.  Note that this code does
        // no error checking, and assumes the picture is small (does not
        // try to copy it in chunks).  Note that if external storage is
        // not currently mounted this will silently fail.

        OutputStream os = new FileOutputStream(file);

        os.write(a.getBytes());
        os.close();
    } catch (IOException e) {
        // Unable to create file, likely because external storage is
        // not currently mounted.
        Log.w("ExternalStorage", "Error writing " + file, e);
    }
}


}

Here is the logcat:

07-01 12:15:06.778 3542-3542/? I/art﹕ Not late-enabling -Xcheck:jni (already on)

07-01 12:15:07.284 3542-3554/com.mycompany.falldetect I/art﹕ Background partial concurrent mark sweep GC freed 137(46KB) AllocSpace objects, 0(0B) LOS objects, 43% free, 663KB/1175KB, paused 2.495ms total 156.875ms

07-01 12:15:07.302 3542-3554/com.mycompany.falldetect W/art﹕ Suspending all threads took: 17.061ms

07-01 12:15:08.583 3542-3542/com.mycompany.falldetect I/Choreographer﹕ Skipped 120 frames! The application may be doing too much work on its main thread.

07-01 12:15:08.860 3542-3554/com.mycompany.falldetect I/art﹕ Background sticky concurrent mark sweep GC freed 4193(196KB) AllocSpace objects, 0(0B) LOS objects, 23% free, 896KB/1175KB, paused 1.315ms total 104.895ms

07-01 12:15:09.045 3542-3542/com.mycompany.falldetect I/Choreographer﹕ Skipped 35 frames! The application may be doing too much work on its main thread.

07-01 12:15:09.247 3542-3542/com.mycompany.falldetect D/gralloc_goldfish﹕ Emulator without GPU emulation detected.

07-01 12:15:09.358 3542-3542/com.mycompany.falldetect I/Choreographer﹕ Skipped 30 frames! The application may be doing too much work on its main thread.

07-01 12:15:09.426 3542-3554/com.mycompany.falldetect I/art﹕ Background partial concurrent mark sweep GC freed 291(16KB) AllocSpace objects, 0(0B) LOS objects, 24% free, 3MB/5MB, paused 1.233ms total 153.762ms

07-01 12:15:09.739 3542-3549/com.mycompany.falldetect W/art﹕ Suspending all threads took: 19.010ms

07-01 12:15:13.154 3542-3542/com.mycompany.falldetect I/Choreographer﹕ Skipped 33 frames! The application may be doing too much work on its main thread.

07-01 12:15:13.516 3542-3542/com.mycompany.falldetect I/Choreographer﹕ Skipped 36 frames! The application may be doing too much work on its main thread.

07-01 12:15:13.901 3542-3542/com.mycompany.falldetect I/Choreographer﹕ Skipped 37 frames! The application may be doing too much work on its main thread.

07-01 12:15:20.126 3542-3549/com.mycompany.falldetect W/art﹕ Suspending all threads took: 51.394ms

07-01 12:15:20.140 3542-3542/com.mycompany.falldetect I/Choreographer﹕ Skipped 38 frames! The application may be doing too much work on its main thread.

07-01 12:15:20.585 3542-3549/com.mycompany.falldetect W/art﹕ Suspending all threads took: 17.665ms

07-01 12:15:20.656 3542-3554/com.mycompany.falldetect I/art﹕ Background sticky concurrent mark sweep GC freed 8627(235KB) AllocSpace objects, 0(0B) LOS objects, 0% free, 7MB/7MB, paused 1.020ms total 124.402ms

07-01 12:15:22.087 3542-3549/com.mycompany.falldetect W/art﹕ Suspending all threads took: 27.697ms

07-01 12:15:22.122 3542-3554/com.mycompany.falldetect W/art﹕ Suspending all threads took: 29.438ms

07-01 12:15:23.703 3542-3554/com.mycompany.falldetect W/art﹕ Suspending all threads took: 30.292ms

07-01 12:15:24.505 3542-3554/com.mycompany.falldetect I/art﹕ Background partial concurrent mark sweep GC freed 1195(43KB) AllocSpace objects, 1(3MB) LOS objects, 25% free, 3MB/5MB, paused 1.126ms total 116.397ms

07-01 12:15:25.263 3542-3554/com.mycompany.falldetect W/art﹕ Suspending all threads took: 38.968ms

07-01 12:18:45.820 3542-3554/com.mycompany.falldetect I/art﹕ Background partial concurrent mark sweep GC freed 1244(45KB) AllocSpace objects, 1(3MB) LOS objects, 25% free, 3MB/5MB, paused 31.610ms total 38.187ms


Solution

  • ok, so you are doing a lot of updates on the graph in:

    public void onSensorChanged(SensorEvent event) {
    

    that is a lot of code, to run in a very short amount of time, my best guess is that the phone is simply not fast enough to do all of those widget updates, before the next sensor data triggers another event.

    The accellerometer is a highly sensitive device, and thus produces a lot of noise. This means that this event will be triggered roughly 100 times per second (or more)

    As a result, this means that your event handler must be faster than 1/100 seconds, otherwise you'll build up an event queue that will continue to grow, forever, and slow down the app even further as the queue builds up..

    A solution is simple:

    let the event handler, simply push the data onto a data array / list / vector..

    let another part of the code, (timed event or main loop) pull the data from the array / list / vector, and update the graph, at a much lower frequency, i.e. 2 times a second (if in a loop, add a small sleep in the loop, to allow the scheduler to do it's job).

    This will allow the event handler, to complete before the next sensor event is triggered.

    edit: more help (I am not a java programmer, so this may not even be runable code, but you should get the idea of what I'm trying to convey):

    @Override
    public void onSensorChanged(SensorEvent event) {
        if (!started) {
            return;
        }
    
        double x = event.values[0];
        double y = event.values[1];
        double z = event.values[2];
        long timestamp = System.nanoTime();
        Data point = new Data(timestamp, x, y, z);
    
        //add to list, that can be accessed by another loop
        if (this.seismicActivityBuffer.size() > this.bufferSize) {
            this.seismicActivityBuffer.remove();
        }
        this.seismicActivityBuffer.add(point);
    
        //this takes too long to do in this eventhandler (says Henrik)
        //so another loop must update the chart, using the seismicActivityBuffer
        //
        //this.chartContainer.removeAllViews();
        //this.chart = createChart(seismicActivityBuffer);
        //
        //this.chartContainer.addView(chart);
    
    }
    
    public void onStartClick(View view) {
        SensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_NORMAL);
        this.doMainLoop = 1;
    }
    
    public void onStopClick(View view) {
        SensorManager.unregisterListener(this);
        this.doMainLoop = 0;
    }
    
    private void stop() {
        //all the stuff you do now + this:
        this.doMainLoop = -1;
    }
    
    private void mainChartLoop()
    {
        while(this.doMainLoop > -1)
        {
          if(this.doMainLoop > 0)
          {
             this.chartContainer.removeAllViews();
             this.chart = createChart(this.seismicActivityBuffer); 
             this.chartContainer.addView(chart);
          }
        }
    }
    
    public void onCreate(Bundle savedInstanceState) {
        // all your stuff + this:
        this.mainChartLoop();
    }