SensorHandler-Thread lässt UI einfrieren

  • Antworten:2
Fabian
  • Forum-Beiträge: 2

16.05.2014, 22:32:41 via Website

Moin Leute!

Ich beiße mir die letzten Tage die Zähne an einer Anwendung aus, die Sensoren auslesen und diese in eine txt.-Datei schreiben soll. Das Problem liegt darin, das auslesen der SensorEvents in einen Thread auszulagern. Diesen möchte ich nach jedem auslesen eines SensorEvents für eine zuvor definierte Zeit schlafen legen (sleep()). Allerdings schläft dann nicht nur der jeweilige SensorHandler-Thread ein, sondern auch der UI-Thread. Es wirkt also so, als ob der gesamte Code gar nicht nebenläufig, sondern sequentiell abläuft, obwohl es meiner Ansicht nach nicht so sein dürfte. Ich habe neben etlichen Codevarianten auch einen Versuch mit Handlern gestartet, allerdings bisher erfolglos. Findet einer von euch eventuell den Fehler?

Der Code

Main(Activity)-class

package de.example.fsensor;


import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import de.example.fsensor.R;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.Activity;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import static com.google.common.base.Preconditions.*;

@SuppressLint("UseSparseArrays") @TargetApi(Build.VERSION_CODES.KITKAT) public class FSensor extends Activity{

static SensorManager mSensorManager;
private static HashMap<Integer, ArrayList<TextView>> textfields = new HashMap<Integer, ArrayList<TextView>>();
private EditText mEdit;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_fsensor);

    checkArgument(this.isExternalStorageReadable() == true | this.isExternalStorageWritable() == true, "Externer Speicher kann nicht gelesen bzw. beschrieben werden!");

    mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);

    mEdit = (EditText)findViewById(R.id.editText1);

    initTextfields(textfields);
    SharedData.sensorList = initSensorsAndLogs(SharedData.initListLogAndSensor);

    startSensorHandlers(SharedData.sensorList);
 }

protected void onPause(){
    super.onPause();
    for (SensorHandler s : SharedData.threadList){
        mSensorManager.unregisterListener(s);
        s.interrupt();
    }
    SharedData.threadList.removeAll(SharedData.threadList);

}

protected void onResume(){
    super.onResume();
    SharedData.sensorList = initSensorsAndLogs(SharedData.initListLogAndSensor);
    startSensorHandlers(SharedData.sensorList);
}

public static void inComingSensorchange(SensorEvent event, SensorHandler sensor) {
    int type = event.sensor.getType();
    textfields.get(type).get(0).setText(String.valueOf(event.values[0]));
    textfields.get(type).get(1).setText(String.valueOf(event.values[1]));
    textfields.get(type).get(2).setText(String.valueOf(event.values[2]));
}


public void startTracking(View view) {
     SharedData.logging = true;
 }

public void stopTracking(View view) {
     SharedData.logging = false;  
 }

public void submitDelay(View view) {
    SharedData.delay = Integer.parseInt(mEdit.getText().toString());
    SharedData.real_delay = SharedData.delay - SharedData.sensorOverhead;
}

// Check if external storage is available for read and write
private boolean isExternalStorageWritable() {
    String state = Environment.getExternalStorageState();
    return Environment.MEDIA_MOUNTED.equals(state);
}

// Checks if external storage is available or at least read
private boolean isExternalStorageReadable() {
    String state = Environment.getExternalStorageState();
    return Environment.MEDIA_MOUNTED.equals(state);
}

private void initTextfields(HashMap<Integer,  ArrayList<TextView>> textfields){ 

    ArrayList<TextView> temp1 = new ArrayList<TextView>();
    ArrayList<TextView> temp2 = new ArrayList<TextView>();
    ArrayList<TextView> temp3 = new ArrayList<TextView>();

    temp1.add((TextView) findViewById(R.id.xboxA));
    temp1.add((TextView) findViewById(R.id.yboxA));
    temp1.add((TextView) findViewById(R.id.zboxA));

    temp2.add((TextView) findViewById(R.id.xboxB));
    temp2.add((TextView) findViewById(R.id.yboxB));
    temp2.add((TextView) findViewById(R.id.zboxB));

    temp3.add((TextView) findViewById(R.id.xboxC));
    temp3.add((TextView) findViewById(R.id.yboxC));
    temp3.add((TextView) findViewById(R.id.zboxC));


    textfields.put(Sensor.TYPE_ACCELEROMETER, temp1);
    textfields.put(Sensor.TYPE_MAGNETIC_FIELD, temp2);
    textfields.put(Sensor.TYPE_ROTATION_VECTOR, temp3);
}

private HashMap<Sensor, Log> initSensorsAndLogs(Map<Integer, String> init){
    HashMap<Sensor, Log> result = new HashMap<Sensor, Log>();
    Sensor temp1;
    Log temp2;
    for(int i : init.keySet()){
        temp1 = mSensorManager.getDefaultSensor(i);
        temp2 = (new Log(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS) + init.get(i)));
        result.put(temp1, temp2);
    }
    return result;
}

private void startSensorHandlers(HashMap<Sensor, Log> sensors){
     for(Sensor s : SharedData.sensorList.keySet()){
            SensorHandler newSensorHandler = new SensorHandler(SharedData.sensorList.get(s));
            newSensorHandler.setPriority(Thread.NORM_PRIORITY);
            SharedData.threadList.add(newSensorHandler);
            mSensorManager.registerListener(newSensorHandler, s, SensorManager.SENSOR_DELAY_FASTEST);
        }       

}

}

SensorHandler-Thread

package de.example.fsensor;

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

public class SensorHandler extends Thread implements SensorEventListener{

    private Log sensorLog;

    public SensorHandler(Log sensorLog){
        this.sensorLog = sensorLog;
        this.start();
    }

    public void run(){
        while(!isInterrupted()){    
        }
        sensorLog.close();
    }


    @Override
    public void onAccuracyChanged(Sensor arg0, int arg1) {
    }

    @Override
    public void onSensorChanged( final SensorEvent event) {
        FSensor.inComingSensorchange(event, this);
        if(SharedData.logging){
            sensorLog.createNewBlankEntryInFile();
            sensorLog.newLogEntry("\n" + "X-Koordinate: " + String.valueOf(event.values[0]) + "\n" + "Y-Koordinate: " + String.valueOf(event.values[1]) + "\n" + "Z-Koordinate: " + String.valueOf(event.values[2]));
            sensorLog.createNewBlankEntryInFile();
            try {
                Thread.sleep(SharedData.delay);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

//          if(SharedData.real_delay > 0){
//              final SensorHandler context = this;
//              FSensor.mSensorManager.unregisterListener(this);
//              new Handler().postDelayed(new Runnable() {
//                    @Override
//                    public void run() {
//                        FSensor.mSensorManager.registerListener(context, event.sensor, SensorManager.SENSOR_DELAY_FASTEST);  
//                    }}, SharedData.real_delay);
//          }
        }
    }
}

— geändert am 16.05.2014, 23:55:44

Antworten
impjor
  • Forum-Beiträge: 1.793

17.05.2014, 00:09:50 via App

Ich glaube, du hast da etwas missverstanden. Nur weil eine Klasse von Thread erbt, läuft der Code in ihr auf einem neuen Thread.
Lediglich die run()-Methode (die du gar nicht implementierst) wird auf einem neuen Thread aufgerufen, wenn Thread#start() aufgerufen wird.

Das ganze ist aber sowieso sinnlos, da SensorChanged nie auf einen neuem Thread aufgerufen würde.

Ich denke mal, du willst einfach nur alle 10s einen Wert in die Datei schreiben.
Statt einem sleep() benutze lieber folgende Variante: In einem long speicherst du die aktuelle Zeit (System.currentTimeMillis()) in der SensorChanged fragst du, ob schon 10s vorbei sind (System.currentTimeMillis() - gespeicherterWert > 10 000)
Wenn ja, Wert speichern und die aktuelle Zeit merken.

Ich frage mich, warum du aber einen DELAY_FASTEST beanspruchst, wenn du nur alle 10s einen Wert benötigst?

— geändert am 17.05.2014, 00:11:12

Liebe Grüße impjor.

Für ein gutes Miteinander: Unsere Regeln
Apps für jeden Einsatzzweck
Stellt eure App vor!

Antworten
Fabian
  • Forum-Beiträge: 2

18.05.2014, 21:25:49 via Website

impjor

Ich glaube, du hast da etwas missverstanden. Nur weil eine Klasse von Thread erbt, läuft der Code in ihr auf einem neuen Thread.
Lediglich die run()-Methode (die du gar nicht implementierst) wird auf einem neuen Thread aufgerufen, wenn Thread#start() aufgerufen wird.

Das ganze ist aber sowieso sinnlos, da SensorChanged nie auf einen neuem Thread aufgerufen würde.

Ich denke mal, du willst einfach nur alle 10s einen Wert in die Datei schreiben.
Statt einem sleep() benutze lieber folgende Variante: In einem long speicherst du die aktuelle Zeit (System.currentTimeMillis()) in der SensorChanged fragst du, ob schon 10s vorbei sind (System.currentTimeMillis() - gespeicherterWert > 10 000)
Wenn ja, Wert speichern und die aktuelle Zeit merken.

Ich frage mich, warum du aber einen DELAY_FASTEST beanspruchst, wenn du nur alle 10s einen Wert benötigst?

Zunächst danke für die schnelle und aufschlussreiche Antwort! Ich hatte tatsächlich ein falsches Verständnis von der Thread-Klasse, bzw. Funktionen, die ich in diese Klasse definiere. Zu deiner Frage: Du konntest es nicht wissen, aber in der App kann der User einen Delay in Millisekunden per Eingabe angeben. Ich wollte den Overhead des Sensors einfach möglichst gering halten, damit der gewählte Delay-Wert tatsächlich erreicht wird.

Die Lösung mit dem Abgleich der Systemzeit funktioniert tatsächlich, allerdings fühlt sich das Ganze für mich wie ein Hack an und auch bei dieser Lösung gibt es einen Overhead von bis zu 10-20 Millisekunden der mir doch etwas groß erscheint. Daher arbeite ich weiter an einer Lösung mit Threads und habe auch einen Anhaltspunkt gefunden als ich mir die Implementierung der SensorManager-Klasse angeschaut habe. Dort gibt es folgende Funktion:

public boolean registerListener(SensorEventListener listener, Sensor sensor, int rateUs,
        Handler handler) {

Wobei das Handler-Argument wie folgt spezifiziert wird:

@param handler
     *        The {@link android.os.Handler Handler} the
     *        {@link android.hardware.SensorEvent sensor events} will be
     *        delivered to.

Laut dieser Spezifizierung wird nun also das SensorEvent per Message an den Handler gesendet, allerdings frage ich mich (der ich noch nie wirklich mit Handlern gearbeitet habe), wie ich in dem Handler Zugriff auf dieses Event bekomme, beziehungsweise welche Funktion vom Handler getriggert wird. Weiß jemand von euch vielleicht mehr dazu?

Antworten