0

I do a step counter.

I read in a one book a method, how to calculate user's steps using an accelerometer. I have Samsung Grand Premium (2014) which has only an accelerometer, so why I am designing the algorithm.

The problem is I need to visualize some results in a graphview. I created 3 classes: Accelerometer, Hub and MainScreen.

In the Accelerometer.class, I get the new value of the X,Y,Z in a certain period of time. After that I transmit data to Hub. The method IncomingData() receives data from Accelerometer. Then it has to analyze in the future, but now it just receives. This method also counts Samples. If we have 10 seconds window analysis, every point is known every 20ms. Therefore, the max of the samples is 10 000 ms / 20 ms = 500 samples. When the current sample == MaxSamples, I represent data in the graph calling the setDataGraph in the MainScreen activity.

Normally it works fine. But if I am waiting some of the time, f.e. 5 minutes, and using my own profiler I can see that the DrawTime increases, and the problem I don't know why, and how I can fix it. It works, but it is going to eat a lot of resources, and the graph starts lagging.

I want to use GraphView because it is the View, and it can be easily implemented in the layout.

If you have any idea please Help Me.

Thank you.

Accelerometer.class()

import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;

public class Accelerometer   implements SensorEventListener{

    private String ACCELEROMETER_NAME = "ACCELEROMETER";
    private double[] XYZ = new double[3];

    private SensorManager AclManager;

    private Hub hub;
    private MainScreen mainScreen;
    private int ACCELEROMETER_DELAY =  SensorManager.SENSOR_DELAY_GAME;//SENSOR_DELAY_GAME

    Accelerometer(Hub hub, SensorManager Manager, Sensor SensorType){
        this.hub = hub;
        this.mainScreen = mainScreen;
        AclManager = Manager;
        AclManager.registerListener(this, SensorType, ACCELEROMETER_DELAY); //SENSOR_DELAY_GAME = 20 ms
    }



    @Override
    public void onSensorChanged(SensorEvent event) {
        if(event.sensor.getType() == Sensor.TYPE_ACCELEROMETER)
        for(int i = 0 ; i < event.values.length; i++){
            XYZ[i] =  event.values[i];
        }
        Hub.IncomingData(ACCELEROMETER_NAME,XYZ,ACCELEROMETER_DELAY);

//        mainScreen.test_view.setText(XYZ[0]+" "+XYZ[1]+" "+XYZ[2]+" ");
    }

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

    }


}

Hub.class()

import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorManager;
import android.os.Bundle;

import androidx.appcompat.app.AppCompatActivity;

import com.google.android.gms.common.util.ArrayUtils;

import java.util.ArrayList;
import java.util.stream.Stream;

public class Hub
         {

    private static SensorManager StepsManager;
    private Sensor  AccelerometerSensor;
    private Sensor  StepsCounterSensor;
    private Sensor  StepsDetectorSensor;

    private double StepsFromReboot = 0;
    private double StepsDetectorCurrentDate = 0;
    private static double StepsAccelerometerCurrentDate = 0;


    private Accelerometer acl;

    private static double[] AccelerometerCoords = new double[3];
    private static double AccelerometerVectorLength = 0;
    private static double AccelerometerVectorLengthPrevious = AccelerometerVectorLength;
    private static double VectorsDiff = AccelerometerVectorLength - AccelerometerVectorLengthPrevious;
    private static final int STEP_THRESHOLD = 6;

    private static boolean DateStarted = true;

             private ArrayList<Boolean> AreSensorsPresented = new ArrayList<Boolean>(3){
        {
            add(false);
            add(false);
            add(false);
        }
    };

    private ArrayList<Sensor> SensorsList = new ArrayList<Sensor>(3);

    private ArrayList<String> SensorsNames = new ArrayList<String>(3){
        {
            add("Accelerometer sensor");
            add("Steps counter sensor");
            add("Steps detector sensor");
        }
    };


    public void CheckSensorsAvailability(){
        for(int i = 0; i < SensorsList.size(); i++){
            if(SensorsList.get(i) == null){
                AreSensorsPresented.set(i,false);
            }
            else{
                AreSensorsPresented.set(i,true);
            }
        }
    }

    public  void TypeSensorsInfo(){
        System.out.println("\nThe sensors are available:\n");
        for(int i = 0; i < AreSensorsPresented.size(); i++){
            System.out.println(SensorsNames.get(i)+": is "+(AreSensorsPresented.get(i) ? "presented":"unavailable"));
        }
    }

    private static final int WindowAnalyzeSec = 10  * 1000;
    public  static int MaxSamples = 500;
    public static int Samples = 0;
    public static ArrayList<Double> ValueHistory = new  ArrayList<Double>(MaxSamples);
    public static ArrayList<Double> Time = new ArrayList<>(MaxSamples);

    public static long TimeStart;
    public static long TimeEnd;


             public static void IncomingData(String From, double[] values, int DelayType){
        switch (From){
            case "ACCELEROMETER":
            {
                if (Samples == 0) TimeStart = System.currentTimeMillis();
                //there comes x,y,z coordinates
                AccelerometerCoords[0] = values[0];
                AccelerometerCoords[1] = values[1];
                AccelerometerCoords[2] = values[2];
                AccelerometerVectorLength = Math.sqrt(Math.pow(AccelerometerCoords[0],2) + Math.pow(AccelerometerCoords[1],2) + Math.pow(AccelerometerCoords[2],2));
                VectorsDiff = AccelerometerVectorLength - AccelerometerVectorLengthPrevious;

                if(VectorsDiff>STEP_THRESHOLD){
                    StepsAccelerometerCurrentDate+=1;
                    if(DateStarted){StepsAccelerometerCurrentDate = 0; DateStarted = false;}
                    MainScreen.test_view.setText(
                            "Steps from Accelerometer are: " + StepsAccelerometerCurrentDate +
                            "\n\n"+AccelerometerVectorLength+"\nprev: " +AccelerometerVectorLengthPrevious  +"\ndiff: "+VectorsDiff+'^'
                    );
                }
                else{
                    String Info = MainScreen.test_view.getText().toString();
                    Info = Info.substring(0,Info.indexOf('^')+1);
                    Info += "\n\nCurrent difference:\nVectors difference: " + VectorsDiff + "\nX: " + AccelerometerCoords[0] + "\nY: " +  AccelerometerCoords[1] + "\nZ: " +  AccelerometerCoords[2] +"\nSamples are "+Samples;
                    MainScreen.test_view.setText(Info);
                }
                MaxSamplesCount(DelayType);
                if (Samples >= MaxSamples){
                    ThisMainScreen.setDataGraph(Time,ValueHistory);
                    ValueHistory.clear();
                    Samples = 0;
                    TimeEnd  = System.currentTimeMillis() - TimeStart;
                    System.out.println("TimeEnd: "+TimeEnd/1000+" s");
                    System.out.println("MaxSamples: "+MaxSamples+"\n\n");
                }
                else{
                       ++Samples;
                       ValueHistory.add(AccelerometerVectorLength);
                }
            }
            AccelerometerVectorLengthPrevious = AccelerometerVectorLength;
            break;
        }
    }

    private static MainScreen ThisMainScreen;


    public Hub(MainScreen ThisMainScreen){
        this.ThisMainScreen = ThisMainScreen;
        StepsManager = (SensorManager) ThisMainScreen.getSystemService(Context.SENSOR_SERVICE);
        AccelerometerSensor = StepsManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
        StepsCounterSensor = StepsManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER);
        StepsDetectorSensor = StepsManager.getDefaultSensor(Sensor.TYPE_STEP_DETECTOR);
        SensorsList.add(AccelerometerSensor);
        SensorsList.add(StepsCounterSensor);
        SensorsList.add(StepsDetectorSensor);
        CheckSensorsAvailability();
        acl = new Accelerometer(Hub.this,StepsManager,AccelerometerSensor);
        SetTimeValues();

    }

    public static float getDelayInMs(int DelayType){
        /*
        SENSOR_DELAY_FASTEST: delay = 0; (200Hz)
        SENSOR_DELAY_GAME: delay = 20000;(20ms)
        SENSOR_DELAY_UI: delay = 66667; (66,667 ms)
        SENSOR_DELAY_NORMAL: delay = 200000; (200ms)
        */
        float delay = -1f;
        switch (DelayType){
            case SensorManager.SENSOR_DELAY_FASTEST: {
                delay = 1/200f*1000f; //5
                break;
            }
            case SensorManager.SENSOR_DELAY_GAME:{
                delay = 20f;
                break;
            }
            case SensorManager.SENSOR_DELAY_UI:{
                delay = 66.667f;
                break;
            }
            case SensorManager.SENSOR_DELAY_NORMAL: {
                delay = 200f;
                break;
            }
        }
        return delay;
    }


 private static void MaxSamplesCount(int DelayType){
     //updates every SENSOR_DELAY_GAME = 20 ms
     //freq = 1/20ms = 50Hz
     //window is = 10s = 10 000 ms
     //N = 10 000/20 = 500
     float UpdateEveryMs = getDelayInMs(DelayType);
     MaxSamples =  (int) Math.ceil((float)WindowAnalyzeSec/UpdateEveryMs);
//     SetTimeValues();
 }


private static void SetTimeValues(){
        Time.clear();
        double step = ((double) WindowAnalyzeSec/1000 - 0)/(double) MaxSamples;
        for (int i = 0; i < MaxSamples; i++){
            Time.add(i*step);
        }
}





    public ArrayList<Sensor> getSensorsList(){
        return SensorsList;
    }

    public ArrayList<String> getSensorsNames(){
        return SensorsNames;
    }

    public ArrayList<Boolean> getSensorAvaialble(){
        return AreSensorsPresented;
    }



}


MainScreen.class()

import android.graphics.Canvas;
import android.graphics.Color;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.os.Bundle;
import android.text.Html;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import androidx.appcompat.app.AppCompatActivity;

import com.jjoe64.graphview.GraphView;
import com.jjoe64.graphview.series.DataPoint;
import com.jjoe64.graphview.series.LineGraphSeries;

import java.util.ArrayList;
import java.util.Collections;

public class MainScreen extends AppCompatActivity implements SensorEventListener {

    public static GraphView graph;
    private Hub hub;
    private Button check;
    public static TextView test_view;

    private ArrayList<String> sNames;
    private ArrayList<Sensor> sList;
    private ArrayList<Boolean> sAvailable;


    public static double GetMax(double[] arr) {
        double max = arr[0];
        for (int i = 0; i < arr.length; i++) {
            if (arr[i] > max) {
                max = arr[i];
            }
        }
        return max;
    }

    public double GetMin(double[] arr) {
        double min = arr[0];
        for (int i = 0; i < arr.length; i++) {
            if (arr[i] < min) {
                min = arr[i];
            }
        }
        return min;
    }

//    static LineGraphSeries<DataPoint>series  = new LineGraphSeries<DataPoint>();
//    static LineGraphSeries<DataPoint>series2  = new LineGraphSeries<DataPoint>();


    public static LineGraphSeries<DataPoint> series = new LineGraphSeries<>();

    public static void setDataGraph(ArrayList<Double> time, ArrayList<Double> magnitude) {
        long TimerStart = System.currentTimeMillis();
//        graph.removeAllSeries();

        series.resetData(new DataPoint[]{});
        for (int i = 0; i < magnitude.size(); i++) {
            try {
                series.appendData(new DataPoint(time.get(i), magnitude.get(i)), true, magnitude.size());
            } catch (Exception e) {
                System.out.println("EXCEPTION IS : " + e.getMessage());
            }
        }
        graph.addSeries(series);


        double magmax = Collections.max(magnitude) + 2; // Math.ceil(GetMax(magnitude))+2;//extra space
        graph.getViewport().setMaxY(magmax);
        long TimerEnd = System.currentTimeMillis() - TimerStart;
        System.out.println("\nDraw time is " + TimerEnd + " ms = " + TimerEnd / 1000 + " s");
        System.out.println("magnitude.size = " + magnitude.size() + " time.size = " + time.size());
        System.out.println("magmax = " + (magmax - 2));
        System.out.println("ymax = " + series.getHighestValueY() + " ymin = " + series.getLowestValueY());

        if (TimerEnd > 200) {
            System.gc();
            System.runFinalization();
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.coordinator);
        hub = new Hub(this);

        graph = (GraphView) findViewById(R.id.graph);
        test_view = (TextView) findViewById(R.id.test_view);
        check = (Button) findViewById(R.id.btn_test);
        check.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                sNames = hub.getSensorsNames();
                sList = hub.getSensorsList();
                sAvailable = hub.getSensorAvaialble();
                String Info = "";

                for (int i = 0; i < sNames.size(); i++) {
                    Info += "Name of sensor: " + sNames.get(i) + " status: " + (sAvailable.get(i) ? "<font color = #7C07>true</font>" : "<font color = #D10C0C>false</font>") + "<br>";
                }

                Info += "<br><br>More details:<br>";

                for (int i = 0; i < sList.size(); i++) {
                    Info += (i + 1) + ")" + sList.get(i) + ";<br>";
                }

                test_view.setText(Html.fromHtml(Info));

            }
        });

        //form series


        int tmin = 0;//time[0]
        int tmax = 10;
        graph.getViewport().setMinX(tmin);
        graph.getViewport().setMaxX(tmax);


        int magmin = 0;
        graph.getViewport().setMinY(magmin);


        graph.getViewport().setYAxisBoundsManual(true);
        graph.getViewport().setXAxisBoundsManual(false);


    }

    @Override
    public void onSensorChanged(SensorEvent event) {
//        test_view.setText(event.values[0]+" " + event.values[1]+" "+event.values[2]);
    }

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

    }
}

Profiler says:

Initial:

I/System.out: Draw time is 2 ms = 0 s
I/System.out: magnitude.size = 500 time.size = 500
I/System.out: magmax = 9.596914380508396
I/System.out: ymax = 9.596914380508396 ymin = 9.563461644595941
I/System.out: TimeEnd: 10 s
I/System.out: MaxSamples: 500

After 6 minutes of work:

I/System.out: Draw time is 207 ms = 0 s
I/System.out: magnitude.size = 500 time.size = 500
I/System.out: magmax = 9.596914380508396
I/System.out: ymax = 9.596914380508396 ymin = 9.563461644595941
I/art: Explicit concurrent mark sweep GC freed 27109(1809KB) AllocSpace objects, 0(0B) LOS objects, 24% free, 7MB/9MB, paused 579us total 43.945ms
I/System.out: TimeEnd: 10 s
I/System.out: MaxSamples: 500
Artemast
  • 7
  • 3

1 Answers1

1

After working for about 3 days, and finding the issue solution, I decided to make a profiler for the method. The first thing that you have to know is don't use resetData() method of the series!!! Because, there is a huge memory leak. The method resetData is not only reset data, but also it makes a new one, and redraws the chart inefficiently. Let's look at the method setDataGraph(). The weakest part of the method is the for loop.

    public static void setDataGraph(ArrayList<Double> time, ArrayList<Double> magnitude) {
            //somecode
            for (int i = 0; i < magnitude.size(); i++) {
                try {
                    series.appendData(new DataPoint(time.get(i), magnitude.get(i)), true, magnitude.size());
                } catch (Exception e) {
                    System.out.println("EXCEPTION IS : " + e.getMessage());
                }
            } //the weakest part

As I found that resetData eats a lot of the resources, I decided to clean ALL SERIES FOR EVERY FUNCTION CALL.

    private static LineGraphSeries<DataPoint> series;
    public static void setDataGraph(ArrayList<Double> time, ArrayList<Double> magnitude){
    series = new LineGraphSeries<DataPoint>();
    //some code
    //end of the method
    series = null
    }

And for reliability I did the if statement where I call trashcleaner if something fot wrong and it ate a lot of memory.

    if(TimerEnd>30){
        System.gc();
        System.runFinalization();
    }
    else if (TimerEnd > 5) {
        graph.removeAllSeries();
        series = null;
        series.setAnimated(true);
    }

The method removeAllSeries must be called to clean all the trash series in the graph, otherwise there will be the old dots in the graph.

So, the method looks such way now:

      private static LineGraphSeries<DataPoint> series ;//= new LineGraphSeries<>();
    
        private static long TimerStart;
        private static  long TimerEnd;
        
        public void setDataGraph(ArrayList<Double> time, ArrayList<Double> magnitude) {
            double magmax = Collections.max(magnitude) + 2; // Math.ceil(GetMax(magnitude))+2;//extra space
            graph.getViewport().setMaxY(magmax);
            graph.removeAllSeries();
            if(TimerEnd>30){
                System.gc();
                System.runFinalization();
            }
            else if (TimerEnd > 5) {
                graph.removeAllSeries();
                series = null;
                series.setAnimated(true);
            }
            series = new LineGraphSeries<>();
            TimerStart = System.currentTimeMillis();
            long EachStep = System.currentTimeMillis();
            for (int i = 0; i < magnitude.size(); i++) {
                try {
                    series.appendData(new DataPoint(time.get(i), magnitude.get(i)), true, magnitude.size());
                } catch (Exception e) {
                }
            }
            System.out.println("for loop dataPoints.add(DataPoint) takes " + (System.currentTimeMillis()-EachStep)  + " ms = " + (System.currentTimeMillis()-EachStep)/1000);
            EachStep = System.currentTimeMillis();
            graph.addSeries(series);
            graph.refreshDrawableState();
            System.out.println("graph.addSeries() takes " + (System.currentTimeMillis()-EachStep)  + " ms = " + (System.currentTimeMillis()-EachStep)/1000);
            graph.onDataChanged(false, false);
            TimerEnd = System.currentTimeMillis() - TimerStart;
            System.out.println("Draw time is " + TimerEnd + " ms = " + TimerEnd / 1000 + " s");
            System.out.println("magnitude.size = " + magnitude.size() + " time.size = " + time.size());
            System.out.println("magmax = " + (magmax - 2));
            System.out.println("ymax = " + series.getHighestValueY() + " ymin = " + series.getLowestValueY());
            series  = null;
    }

Now drawing takes for about 3ms, where the for loop takes 2 ms.

Again:

  1. Don't use resetData() in series, because it resets and updates the graph inefficiently
  2. Clear the series at the end of the method
  3. Use a condition for calling garbage collection
  4. Call removeAllSeries to clear old data from the line graph
  5. If the drawing took for the last iteration more than 5ms, it seems that leak occured. Reset series, and give new address.

I hope it will help somebody one day.

Artemast
  • 7
  • 3