Search code examples
javaandroidandroid-sensorsandroid-threadandroid-handlerthread

How to resolve ANR problem occured in SensorEventListener operating separete thread?


I'm noob at Java Android Programming(actually I'm noob at overall computer programming). I intended to make SensorEventListener of Accelometer sensor in a thread separate from main thread at Service. onSencorChanged method works well in separate thread, however, not long after operating Service, ANR occurs. Although ANR occurs, if I don't terminate at the point of ANR occures and press 'wait' to keep application running, the application goes well as I intended. I have been tried to find how to resolve the problem for a long time, but I can't finally make my application stop occuring ANR.

This is my code using HandleThread.

public class SwingArmSensorService extends Service {
    private SensorManager mSensorManager;
    private Sensor mSensor;
    private AccelerometerListenerInService listener;
    private HandlerThread mSensorThread;
    private Handler mSensorHandler;
    private static SuperActivityUsingServiceMain activity;
    private int mSteps;
    private final String SWING_SENSOR_INTENT_STEP_DATA_NAME = "steps";

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (Looper.myLooper() == null) {
            Looper.prepare();
        }
        mSensorThread = new HandlerThread("Sensor Thread", Thread.MAX_PRIORITY); //
        mSensorThread.start(); //
        mSensorHandler = new Handler(mSensorThread.getLooper());
        listener = new AccelerometerListenerInService();
        mSensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);
        mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
        mSensorManager.registerListener(listener, mSensor, SensorManager.SENSOR_DELAY_NORMAL, mSensorHandler);
        mSteps = intent.getIntExtra(SWING_SENSOR_INTENT_STEP_DATA_NAME, 0);
        Looper.loop();

        ...
    }
    private class AccelerometerListenerInService implements SensorEventListener {
        private Handler mainHandler;

        private float previousY, currentY;
        private float x, y, z;

        private final float THRESHOLD = 10f;

        public AccelerometerListenerInService() {
            mainHandler = new Handler(Looper.getMainLooper());
        }

        @Override
        public void onSensorChanged(SensorEvent event) {
            x = event.values[0];
            y = event.values[1];
            z = event.values[2];
            currentY = y;

            if (Math.abs(currentY - previousY) > THRESHOLD) {
                mSteps++;
                // Handle UI at main thread here
                mainHandler.post(new Runnable() {

                    @Override
                    public void run() {
                        activity.setTextView(mSteps);
                    }
                });
            }

            previousY = y;
        }

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

I intended to show you only a partion related to question of code, but it's first time to post a question Stack Overflow, so if my code is too long I'm sorry.


Solution

  • Root cause

    In Android, a Service is running on the main thread which already has a Looper assign with it. In your code, when the service is started, it calls Looper.prepare() and Looper.loop() on the main thread, it will cause unexpected behavior with your app.

    Solution

    Delete the following code from SwingArmSensorService class

    if (Looper.myLooper() == null) {
        Looper.prepare();
    }
    
    Looper.loop();
    

    As the result, your code will be:

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        mSensorThread = new HandlerThread("Sensor Thread", Thread.MAX_PRIORITY); //
        mSensorThread.start(); //
        mSensorHandler = new Handler(mSensorThread.getLooper());
        listener = new AccelerometerListenerInService();
        mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
        mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
        mSensorManager.registerListener(listener, mSensor, SensorManager.SENSOR_DELAY_NORMAL, mSensorHandler);
        mSteps = intent.getIntExtra(SWING_SENSOR_INTENT_STEP_DATA_NAME, 0);
        ...
    }