Search code examples
javaandroidprocessterminateandroid-things

Android (Things): How to do clean-up before program ends?


I have a Raspberry Pi 3 running Android Things. For simplicity, assume it only rotates a stepper motor.

To again simplify things, the stepper motor is rotated by telling coil by coil which ones to charge and which ones not to charge. In a Raspberry Pi you connect four output pins to four input pins of the stepper motor. Then you fire up the pins one by one in a continuous sequence with some milliseconds between each run.

If I stop the program in Android Studio by pressing Stop 'MainActivity' the program code gets killed but the output pins in the Raspberry Pi still stay charged. In my case, if I stop the program in Android Studio, one of my stepper motor coils remains charged (and gets overheated).

QUESTION: What is the best way to do clean-up in Android before the program shuts down?

I have tried onDestroy() and onPause() but neither of those are guaranteed to be called when the program closes. (They also have never worked in my case).

I have also tried to add a shutdown hook but even that doesn't turn off the output pin. The shutdownhook, which is located in MainActivity's onCreate() method, looks like this below:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    Runtime.getRuntime().addShutdownHook(new Thread(){
        @Override
        public void run(){
            try{
                mRobotics.stopMotor();
            } catch (Exception e){
                // Ignore error
            }
        }
    });
// onCreate continues...

The method stopMotor() looks like this:

public void stopMotor(){
    this.motorHandler.removeCallbacksAndMessages(null);
    try {
        mStepper1.setValue(false);
        mStepper2.setValue(false);
        mStepper3.setValue(false);
        mStepper4.setValue(false);

    } catch (Exception e){
        // Nothing here
    }
}

There are a lot of related questions on, for example, stopping threads when the program closes but I haven't found anything from StackOverflow that works in my case.


Solution

  • You are correct that clicking the Stop button in Android Studio simply terminates your application process without calling any of the life cycle methods. The same is true if your application crashes or Android needs to terminate your app due to memory pressure, so it's something that can happen at runtime as well. There are no hooks you can add to anticipate every time this may happen.

    One option is to move your motor driver logic into a separate app module and control it through a bound service. This way, when the main app terminates (either during development or due to a crash), the driver app can manage that appropriately. It's also generally a good separation of concerns to separate your driver code from your main app this way.

    Here's an example of what that might look like:

    driver.apk

    class MotorDriverService : Service() { 
    
        override fun onCreate() {
            super.onCreate()
            startMotor()
        }
    
        override fun onDestroy() {
            super.onDestroy()
            stopMotor()
        }
    }
    

    main.apk

    class MainActivity : Activity() { 
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            val intent = ...
            bindService(intent, connection, Context.BIND_AUTO_CREATE)
        }
    
        override fun onDestroy() {
            super.onDestroy()
            unbindService(connection)
        }
    
        private val connection = object : ServiceConnection {
            override fun onServiceConnected(className: ComponentName, service: IBinder) { }
    
            override fun onServiceDisconnected(name: ComponentName) { }
        }
    }
    

    The reason this works better is because bound service connections are automatically managed. Android creates the service when the activity wants to bind to it, and destroys it when there are no more clients bound (which in this case would also happen in the main.apk terminates or crashes).

    The driver and main have to be two separate apps (not an activity/service in the same APK) because this only works if the two run in completely separate processes.