SensorHandler-Thread lässt UI einfrieren

  • 6 Antworten
  • Neuester Beitrag
Diskutiere SensorHandler-Thread lässt UI einfrieren im Android App Entwicklung im Bereich Betriebssysteme & Apps.
D

Die_Maske

Neues Mitglied
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)-Thread

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);
// }
}
}
}
 
ui_3k1

ui_3k1

Gesperrt

Hello,

probiere es mal wie folgt zu implementieren:

Du gibst ein Intervall ein (bsp 1000 fuer 1000ms = 1s). Jetzt addierst du auf die Startzeit dein Intervall und vergleichst es mit der aktuellen Zeit, wenn wieder 1000ms vergangen sind schreibst du die Daten in deine Datei.

Keine Garantie darauf dass es so der eleganteste Weg ist (bzw. ob es ueberhaupt funktioniert, weiss ich ehrlich gesagt auch nicht) - aber ein Versuch ist es imho allemal wert :p
 
A

amfa

Experte
Dein Thread tut ja auch nichts anderes als eine Endlosschleife zu durchlaufen.
Du hast also 2 Threads, der eine macht gar nichts ausser eine Endloschleife zu durchlaufen, der andere macht UI und alles andere, es ist vollkommen egal in welcher Klasse du den SensorEventListener implementierst.
Das ganze wird klarer, wenn du dir den Listener in einer ganz eigenen Klasse implementierst.
Dann siehst du, dass der Aufruf eigentlich de exakt gleiche ist. Es kommt nämlich nicht darauf an WO der Code steht, sondern VON WO er aufgerufen wird.

Das hier:
mSensorManager.registerListener(newSensorHandler, s, SensorManager.SENSOR_DELAY_FASTEST);
hat also gleichen Effekt als wenn du statt newSensorHandler beispielsweise this reinschreibst und den Listener direkt in der MainActivity implementierst.

Von daher macht deine App genau das was du ihr sagst, den Listener im UI Thread ausführen.

Ich meine auch, dass
mSensorManager.registerListener()
Den Sensor immer im UI Thread registiert, da hast du keinen Einfluß drauf.
Selbst wenn du ihn erst in deinem Thread registrierst.
Für dein Vorhaben wäre also ui_3k1 Vorschlag zu gebrauchen, dass du dir die Zeit merkst und erst wenn diese abgelaufen ist das nächste SensorEvent verarbeitest.

Denn selbst WENN dein SensorListener in einem eigenen Thread laufen würde, würden die Messages bzw die Aufrufe an den quasi in eine Warteschlange gestellt.
Heißt du bekommst das erste Event, dein Thread wartet 1000ms während der Zeit kommt das 2. Event..
Wenn die Zeit nun abgelaufen ist wird aber das 2. Event verarbeitet und nicht das aktuelle so würden sich mit der Zeit ganz viele Events ansammeln die dann abgearbeitet würden.
 
D

Die_Maske

Neues Mitglied
Zunächst einmal bedanke ich mich für die schnellen und aufschlussreichen Antworten! Ich hatte wohl ein falsches Verständnis von der Thread-Klasse, bzw. Funktionen, die ich in diese Klasse definiere.

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 (wo mir erstmal auch nichts anderes einfällt als den Vergleich mit der Systemzeit durchuführen, aber wenn dieser nebenläufig geschieht, dürfte der Overhead ja geringer sein) 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?
 
A

amfa

Experte
Guck dir doch die Handler Klasse an
Handler | Android Developers

Dein Thread könnte dann so aussehen:
Code:
public class myThread extends Thread implements Handler.Callback, SensorEventListener{

//EDIT: Nachtrag
Handler handler = new Handler(this);
//Ich bin mir nicht sicher ob new Handler() reichen sollte
//mSensor musste dir halt noch holen wie du es oben ja schon machst
mSensorManager.registerListener(this, s, SensorManager.SENSOR_DELAY_FASTEST, handler);

...... normales thread zeugs ....
// Die brauchst du glaub ich gar nicht, da mit dem Handler nur definiert wird, in welchem Thread die Aufrufe des SensoreventListeners landen
public boolean handleMessage (Message msg) {
//das hier wird dann vom SensorManager aufgerufen
//in der msg müssten dann die SensorEvents stecken
}
}
Edit:
Siehe oben.
 
Zuletzt bearbeitet:
D

Die_Maske

Neues Mitglied
Moin zusammen! Ich habe soeben noch ein wenig getestet und habe auch eine Variante mit Handler zum Laufen bekommen, jedoch muss ich die Message per Hand an den Handler weiterleiten, anstatt das diese von dem SensorEvent getriggert wird. Auf mich wirkt es fast so als ob das Handler-Argument in der registerListener-Funktion nichts bewirkt und finde leider auch keine weiterführenden Informationen dazu als die bereits angegebenen. Der Quellcode meiner SensorHandler-Klasse sieht derzeit folgendermaßen aus und läuft entsprechend immer noch sequentiell ab:

package de.example.fsensor;

import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Handler;
import android.os.Message;

public class SensorHandler implements Handler.Callback, SensorEventListener{

private Log sensorLog;
private Handler mHandler;
private Sensor mSensor;
private long timestamp = System.currentTimeMillis();
private SensorEvent event;

public SensorHandler(Sensor sensor, Log sensorLog){
this.mSensor = sensor;
this.sensorLog = sensorLog;
mHandler = new Handler(this);
FSensor.mSensorManager.registerListener(this, mSensor, SensorManager.SENSOR_DELAY_FASTEST, mHandler);
}

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

@Override
public void onSensorChanged(SensorEvent event) {
Message msg = Message.obtain();
msg.obj = event;
mHandler.sendMessage(msg);
}


public boolean handleMessage(Message msg) {
event = (SensorEvent) msg.obj;
FSensor.inComingSensorchange(event);
if(SharedData.logging && checkDelay()){
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();
timestamp = System.currentTimeMillis();
}
return true;
};

private boolean checkDelay(){
return ((System.currentTimeMillis() - timestamp) > SharedData.delay);
}

public void closeLog(){
this.sensorLog.close();
}
}
Falls jemand noch Ideen zu dem Thema hat, wäre ich ihm sehr dankbar :)
 
A

amfa

Experte
Ist ungetestet aber so stell ich mir das vor:
Erst mal der Handler der von Handler erbt und somit ein Handler ist.
Wenn ich das richtig in der Doku verstanden hab gibst du mit dem Handler eigentlich nur den Thread an in dem der SensorEventListener aufgerufen wird., dieser wird gleichzeitig in der Klasse hier implementiert.
Probier das so mal aus
Code:
public class SensorHandler extends Handler implements Runnable,
        SensorEventListener {

    private Handler mHandler;
    private Sensor mSensor;
    private long timestamp = System.currentTimeMillis();
    private Context context;

    public SensorHandler(Context context, Sensor sensor) {
        this.context = context;
        this.mSensor = sensor;
    }

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

    @Override
    public void onSensorChanged(SensorEvent event) {
        if (checkDelay()) {
            // 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();
            timestamp = System.currentTimeMillis();
        }
    }

    private boolean checkDelay() {
        return ((System.currentTimeMillis() - timestamp) > 1000);
    }

    @Override
    public void run() {
        mHandler = new Handler();
        SensorManager mSensorManager = (SensorManager) context
                .getSystemService(Context.SENSOR_SERVICE);
        mSensorManager.registerListener(this, mSensor,
                SensorManager.SENSOR_DELAY_FASTEST, mHandler);

    }

}
In deiner Activity musst du jetzt nur noch sowas in der Art machen
Musst du halt schauen welchen Sensor du brauchst
Code:
        SensorManager mSensorManager = (SensorManager) 
                getSystemService(Context.SENSOR_SERVICE);
        Sensor sensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
        Thread thread = new Thread(new SensorHandler(this, sensor));
        thread.start();