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!!!)
سؤال
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!!!)
نصائح أخرى
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)