Question

This is driving me crazy. I have created a local service which monitors the accelerometer. Sometimes the service works and sometimes it does not. It seems that the service keeps just randomly unregistering itself.

The service reacts after a certain level of movement on the phone. The thing is that if I start the service, I can end it correctly right after. However, after I shake the phone (set off how the service should act) nothing happens and then when I try to turn off the service I get an error. What is really confusing me is that I have put break codes in my code which detects the movement, and the code which is called after a movement isn't even being executed.

Any help would be MUCH appreciated. Here is my code for the service and its manager.

public class AccelService extends Service {
    public static boolean listening = false;
    private static Context CONTEXT;
    private static Sensor sensor;
    private static SensorManager ASensorManager;

    private static SensorEventListener accelEventListener = new SensorEventListener() {

        private float x = 0;
        private float y = 0;
        private float z = 0;
        private double max = 0;
        private double force = 0;

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

        public void onSensorChanged(SensorEvent event) {

            x = event.values[0];
            y = event.values[1];
            z = event.values[2];
            force = Math.sqrt(x*x+y*y+z*z);
            if (force > max)
                max = force;

            if (force > AccelManager.dropValue)                
                AccelManager.onDrop(force);                
        } 
    };

private static void startListener() {
    ASensorManager = (SensorManager) CONTEXT.getSystemService(Context.SENSOR_SERVICE);
    List<Sensor> sensors = ASensorManager.getSensorList(Sensor.TYPE_ACCELEROMETER);
    if (sensors.size() > 0) {
        sensor = sensors.get(0);
        listening = ASensorManager.registerListener(accelEventListener, sensor, SensorManager.SENSOR_DELAY_GAME);
        AccelManager.setListening(listening);
    }
} 

private final IBinder mBinder = new AccelBinder();

public class AccelBinder extends Binder {
    AccelService getService() {
        return AccelService.this;
    }
}

public void onCreate() {
    CONTEXT = this;
    startListener();
}

public boolean isListening() {
    return listening;
}

@Override
public void onStart(Intent intent, int startId){
    Log.i("LocalService", "Received start id " + startId + ": " + intent);
}

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i("LocalService", "Received start id " + startId + ": " + intent);
        return AccelService.START_STICKY;
    }

    @Override
    public void onDestroy() {
        if (listening)
        stopListening();
        super.onDestroy();
    }

    public static void stopListening() {  
        AccelManager.setListening(false);
        try {
            if (ASensorManager != null && accelEventListener != null)
                ASensorManager.unregisterListener(accelEventListener);            
        } catch (Exception e) {}     
    }

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

Here is the Manager class.

public class AccelManager extends Activity {

    public static Boolean isListening = false;
    private static Boolean callMade = false;
    public static Context ctxt;
    public static int dropValue = 100;
    private static Intent i;

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    public void setContext(Context context) {
        ctxt = context;
        i = getIntent();
    }


    public void activateService(Boolean check) {
        if (check)
            doBindService();
        else {
            if (isListening) {
                if (AccelWatch != null) {
                    AccelWatch.stopSelf();
                    doUnbindService();
                }
            }
        }    
    }

    public static void onDrop(double force){
        if (!callMade) {
            Toast.makeText(ctxt, "Phone dropped: " + force, 5000).show();
            Intent i = new Intent(ctxt,DropDetected.class);
            AccelWatch.stopSelf();
            doUnbindService();
            callMade = true;
            ctxt.startActivity(i);
        }
    }

    private void doBindService() {
        if (!isListening) {
            i = new Intent(ctxt,AccelService.class);
            ctxt.bindService(i, AccelConnection, ctxt.BIND_AUTO_CREATE);
        }
    }

    private static void doUnbindService() {
        if (isListening)         
            ctxt.unbindService(AccelConnection);
    }    

    public void setDropValue(int drop) {
        dropValue = drop;
    }

    public boolean isRunning(){
        return AccelWatch.isListening();
    }

    public static void setListening(Boolean listen){
        isListening = listen;
    }

    private static AccelService AccelWatch;

    private static ServiceConnection AccelConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className, IBinder service)     {
            AccelWatch = ((AccelService.AccelBinder)service).getService();
            AccelWatch.startService(i);

            Toast.makeText(ctxt, "The service is binded.",Toast.LENGTH_SHORT).show();
        }

        public void onServiceDisconnected(ComponentName className) {
            AccelWatch = null;
            Toast.makeText(ctxt, "The service was disconnected.",Toast.LENGTH_SHORT).show();
        }
    };
}
Was it helpful?

Solution

Firstly, if you are thinking about attempting to make a Service that runs forever you should think about not doing so. Services that run all the time are really bad for battery life.

That aside, I would suggest getting rid of

    AccelWatch.startService(i); 

in onServiceConnected().

When you bind to a service, the service is started automatically, and onServiceConnected() is the asynchronous callback you get. So, you may effectively be attempting to restart your Service with that line.

Don't ever do this

    private static Context CONTEXT;

and then,

    CONTEXT = this;

You have just guaranteed that you will leak your Service (and your Activity as you did it there as well) as static references exist for the life of the application - not the life of the Service. Just use "this" if you need a context. You are already inside your Service/Activity.

Another source of leaking your service is here....

public class AccelBinder extends Binder {
     AccelService getService() {
         return AccelService.this;
     }
 }

This inner class is not static. In java this means that the inner class has a reference to the outer class. There is a bug (Issue 6426) in Android that means the system will always leak your binder. If your binder has a reference to the Service (as non-static inner classes do) then you will leak the service as well.

ServiceonStart() is deprecated since Version 5 (Android 1.6) as there are less than 7% of devices using this version I would suggest dropping it at least until you get your Service working - so that you don't have to think about it.

See how you get on with that. Persist with Services they are quite easy - it just takes a little bit of adjustment the first time (lifecycles etc)

As these problems are common - I decided to write a blog here to provide a little guidance

http://www.ozdroid.com/#!BLOG/2010/12/19/How_to_make_a_local_Service_and_bind_to_it_in_Android

The example uses the binding method of using a Service rather than onStart() - but I have tried to place things in context a little for beginners.

OTHER TIPS

You can add some logs to see what happened causing the service unregistering, and you can check whether there is exception of the service in the logcat view.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top