Search code examples
androidserviceandroid-handlerthread

How to stop a ServiceHandler in Android, which uses HandlerThread?


I am trying to read sensor data from my phone. I have started a service, which created a HandlerThread to collect sensor data. The thread registers the sensor for a short while and unregisters it. When I stop the service, however, the thread keeps running, and I see print statements working. How do I stop this? This is my service code.

package com.example.myapplication;

import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.media.MediaPlayer;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.provider.Settings;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.time.LocalDateTime;

@RequiresApi(api = Build.VERSION_CODES.O)
public class SenseService extends Service {

    private Looper serviceLooper;
    private ServiceHandler serviceHandler;
    private Context context;
    private HandlerThread thread;


    private final class ServiceHandler extends Handler implements SensorEventListener {
        private SensorManager sensorManager;
        private Sensor mLight;
        private LocalDateTime currTime;
        private String senseTime;
        private String FILENAME="sensordata";
        private FileOutputStream fos;

        public ServiceHandler(Looper looper){
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            try {
                currTime = java.time.LocalDateTime.now();
                String newFileName  = currTime.toString()+FILENAME+".csv";
                fos = openFileOutput(newFileName, Context.MODE_APPEND);
            } catch (FileNotFoundException e) {
                System.out.println("did not open file");
                e.printStackTrace();
            }
            sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
            mLight = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);

            try {
                do {
                    sensorManager.registerListener(this, mLight, 10000000);
                    Thread.sleep(2000);
                    System.out.println("thread sleeping");
                    sensorManager.unregisterListener(this);
                    Thread.sleep(5000);
                } while (true);
            }catch (InterruptedException e) {
                e.printStackTrace();
            } {

            }
            stopSelf(msg.arg1);
        }

        @Override
        public void onSensorChanged(SensorEvent event) {
            float lux = event.values[0];
            String val = Float.toString(lux);
            senseTime = java.time.LocalDateTime.now().toString();
            System.out.println(Float.toString(lux));
            val = senseTime+","+val+"\n";

            try {
                fos.write(val.getBytes());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

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

        }
    }

    @Override
    public void onCreate(){
        thread = new HandlerThread("ServiceStartArguments");
        thread.start();

        serviceLooper = thread.getLooper();
        serviceHandler = new ServiceHandler(serviceLooper);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
        Message msg = serviceHandler.obtainMessage();
        msg.arg1 = startId;
        serviceHandler.sendMessage(msg);
        return START_STICKY;
    }


    @Override
    public void onDestroy() {
        super.onDestroy();
        Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show();
        System.out.println("Stopping service");
        thread.quitSafely();
        stopSelf();
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}

Solution

  • You use large amount of delays to put the threads into the sleep state. Also you are using an infinite loop without any condition to break the loop here:

    do {
        sensorManager.registerListener(this, mLight, 10000000);
        Thread.sleep(2000);
        System.out.println("thread sleeping");
        sensorManager.unregisterListener(this);
        Thread.sleep(5000);
    } while (true); // Define a condition or interrupt the thread
    

    In this condition the handlerthread's looper will not quit until the all messages in the queue are handled. But with your implementation it will always be occupied with the first message due to that infinite loop.

    You must either somehow interrupt the threads or put a condition to that infinite loop if you want that all messages be handled when you quit safely from handler thread. Or if no further processing needed when the service is stopped, you can try the quit() method instead of quitSafely().