سؤال

I am trying to create a service which should log accelerometer values and timestamp on disk as fast as possible.

It works fine as long as the Activity is present, but the problem is as soon as I quit the Activity which this service is coming with, the service stops. I put a toast in onCreate, onStartCommand and onDestroy, for first two, it works normally but it never shows anything on onDestroy so I am clue less what is the cause. I also put breakpoints in Android Studio on onDestroy but it does not fire too.

Here is the complete code, please let me know what you think can be the problem:

package com.embedonix.mobilehealth.services.accelerometerlog;

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.os.Environment;
import android.os.IBinder;
import android.widget.Toast;

import com.embedonix.mobilehealth.AppConstants;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class AccelerometerLogService extends Service {

    private boolean mIsServiceStarted = false;
    private Context mContext = null;
    private SensorManager mSensorManager = null;
    private Sensor mSensor;
    private File mLogFile = null;
    private FileOutputStream mFileStream = null;
    private AccelerometerLogService mReference = null;
    private Float[] mValues = null;
    private long mTimeStamp = 0;
    private ExecutorService mExecutor = null;

    /**
     * Default empty constructor needed by Android OS
     */
    public AccelerometerLogService() {
        super();
    }

    /**
     * Constructor which takes context as argument
     *
     * @param context
     */
    public AccelerometerLogService(Context context) {
        super();

        if (context != null)
            mContext = context;
        else
            mContext = getBaseContext();
    }

    @Override
    public void onCreate() {
        super.onCreate();

        Toast.makeText(getBaseContext(), "Service onCreate", Toast.LENGTH_SHORT).show();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        if (isServiceStarted() == false) {

            mContext = getBaseContext();
            mReference = this;
            mSensorManager = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE);
            mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
            mValues = new Float[]{0f, 0f, 0f};
            mTimeStamp = 0;
            mExecutor = Executors.newSingleThreadExecutor();

            setupFolderAndFile();
            startLogging();
        }

        //set started to true
        mIsServiceStarted = true;


        Toast.makeText(mContext, "Service onStartCommand", Toast.LENGTH_SHORT).show();
        return Service.START_STICKY;
    }

    private void setupFolderAndFile() {
        mLogFile = new File(Environment.getExternalStorageDirectory().toString()
                + "/" + AppConstants.APP_LOG_FOLDER_NAME + "/test.txt");

        try {
            mFileStream = new FileOutputStream(mLogFile, true);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }

    private void startLogging() {

        mExecutor.execute(new Runnable() {
            @Override
            public void run() {
                mSensorManager.registerListener(
                        new SensorEventListener() {
                            @Override
                            public void onSensorChanged(SensorEvent sensorEvent) {
                                mTimeStamp = System.currentTimeMillis();
                                mValues[0] = sensorEvent.values[0];
                                mValues[1] = sensorEvent.values[1];
                                mValues[2] = sensorEvent.values[2];

                                String formatted = String.valueOf(mTimeStamp)
                                        + "\t" + String.valueOf(mValues[0])
                                        + "\t" + String.valueOf(mValues[1])
                                        + "\t" + String.valueOf(mValues[2])
                                        + "\r\n";

                                try {
                                    mFileStream.write(formatted.getBytes());
                                } catch (IOException e) {
                                    e.printStackTrace();
                                }
                            }

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

                            }
                        }, mSensor, SensorManager.SENSOR_DELAY_FASTEST
                );
            }
        });
    }

    @Override
    public void onDestroy() {
        super.onDestroy();

        //Flush and close file stream
        if (mFileStream != null) {
            try {
                mFileStream.flush();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                mFileStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        Toast.makeText(mContext, "Service onDestroy", Toast.LENGTH_LONG).show();
        mIsServiceStarted = false;
    }

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

    /**
     * Indicates if service is already started or not
     *
     * @return
     */
    public boolean isServiceStarted() {
        return mIsServiceStarted;
    }
}

UPDATE 1

I found out that this happens on Nexus 7 tablet. The same code works fine on my phone, MotoG. Both devices have Android 4.4.2 installed....what can be the reason?

هل كانت مفيدة؟

المحلول 3

None of the above are the answer. Unfortunately this is a known problem with Nexus 7 and propably Nexus 5 products of Google.

Here is the link to the bugreport (although I think its an intentional feautur!!!)

https://code.google.com/p/android/issues/detail?id=63793

نصائح أخرى

I think the problem exists into the logic to log accelerometer values and timestamp using services. An android service run on the main UI thread. If your service needs to do work in the background, it needs to be launched in a separate thread (like AsyncTask does) explicitly.

Running on the main thread you run the risk to interrupt UI responsiveness and in my opinion this is the root of your problems. As a result it may run ok on some devices and on some others no.

My advice is inside your service to run an AsyncTask as a background worker for logging accelerometer values.

Inside you class AccelerometerLogService you implement an AsyncTask like that :

public class AccelerometerLogService extends Service {
     ..........................
/**
  * @author 
  * Private class which logs accelerometer values and timestamp.
  */
 private class AsyncTaskRunner extends AsyncTask<String, String, String> {

  private String resp;

  @Override
  protected String doInBackground(String... params) {
   publishProgress("running..."); // Calls onProgressUpdate()

   try {
        setupFolderAndFile();  // Here you are doing the job
        startLogging();

   } catch (Exception e) {
    e.printStackTrace();
    resp = e.getMessage();
   }
   return resp;
  }

  /*
   * @see android.os.AsyncTask#onPostExecute(java.lang.Object)
   */
  @Override
  protected void onPostExecute(String result) {
   // execution of result of Long time consuming operation
   finalResult.setText(result);
  }

  /*
   * @see android.os.AsyncTask#onPreExecute()
   */
  @Override
  protected void onPreExecute() {
   // Things to be done before execution of long running operation. For
   // example showing ProgessDialog
  }

  /*
   * @see android.os.AsyncTask#onProgressUpdate(Progress[])
   */
  @Override
  protected void onProgressUpdate(String... text) {
   // Things to be done while execution of long running operation is in
   // progress. For example updating ProgessDialog
  }
 }}

and you call the AsyncTaskRunner inside onStartCommand like :

@Override
public int onStartCommand(Intent intent, int flags, int startId) {

    if (isServiceStarted() == false) {

        mContext = getBaseContext();
        mReference = this;
        mSensorManager = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE);
        mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
        mValues = new Float[]{0f, 0f, 0f};
        mTimeStamp = 0;
        mExecutor = Executors.newSingleThreadExecutor();

        _runner = new AsyncTaskRunner();  // Start backgroud thread here
    _runner.execute();  
    }

    //set started to true
    mIsServiceStarted = true;


    Toast.makeText(mContext, "Service onStartCommand", Toast.LENGTH_SHORT).show();
    return Service.START_STICKY;
}

runner is a private variable initialized like that :

public class AccelerometerLogService extends Service {

private boolean mIsServiceStarted = false;
.............

AsyncTaskRunner _runner = null;
.............

This mainly describes my solution but it demands a little more work from you. Hope it's more clear now.

Try change START_STICKY -> START_REDELIVER_INTENT. Although the documentation states that the method onDestroy() is called before the destruction of the service, but in practice, this method is not always called. Personally faced with this and most likely this is due to the implementation of the service (in accordance with the recommendations of Google)

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top