0

I have a Timer in my App that infinitely runs an Animation. like this:

Timer t = new Timer();
t.scheduleAtFixedRate(new TimerTask() {

    @Override
    public void run() {
        runOnUiThread(new Runnable() {

            @Override
            public void run() {
                //Running Animation Code
            }
        });
    }
}, 1000, 1000);

Now I realized that this code runs even if user click Back Button of android. if fact it runs in the background and it seems uses a lot of memory.

I need this code run ONLY if user in the app. In fact when user click on Back Button, this Timer goes to end and if user clicks on Home Button, after a while that user doesn't use the App, terminates this Timer.

What I need is to prevent using memory. Because i realized if this codes runs a while, App freezes! I need a normal behavior.

Fcoder
  • 9,066
  • 17
  • 63
  • 100

3 Answers3

0

You can do it like this, in onBackPressed() or onDestroy(), whatever suits you.

if (t != null) {
   t.cancel();
}

If you need, you can start timer in onResume() and cancel it in onStop(), it entirely depend on you requirement.

If a caller wants to terminate a timer's task execution thread rapidly, the caller should invoke the timer's cancel method. - Android Timer documentation

You should also see purge and How to stop the Timer in android?

0

Disclaimer: This might not be the 100% best way to do this and it might be considered bad practice by some.

I have used the below code in a production app and it works. I have however edited it (removed app specific references and code) into a basic sample that should give you a very good start.

The static mIsAppVisible variable can be called anywhere (via your App class) in your app to check if code should run based on the condition that the app needs to be in focus/visible.

You can also check mIsAppInBackground in your activities that extend ParentActivity to see if the app is actually interactive, etc.


public class App extends Application {
    public static boolean mIsAppVisible = false;


    ...
}

Create a "Parent" activity class, that all your other activities extend.

public class ParentActivity extends Activity {
    public static boolean mIsBackPressed = false;
    public static boolean mIsAppInBackground = false;
    private static boolean mIsWindowFocused = false;
    public boolean mFailed = false;
    private boolean mWasScreenOn = true;

    @Override
    protected void onStart() {
        applicationWillEnterForeground();

        super.onStart();
    }

    @Override
    protected void onStop() {
        super.onStop();

        applicationDidEnterBackground();
    }

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

        // If something calls "finish()" it needs to behave similarly to
        // pressing the back button to "close" an activity. 
        mIsBackPressed = true;
    }

    @Override
    public void onWindowFocusChanged(boolean hasFocus) {
        mIsWindowFocused = hasFocus;

        if (mIsBackPressed && !hasFocus) {
            mIsBackPressed = false;
            mIsWindowFocused = true;
        }

        if (!mIsWindowFocused && mFailed)
            applicationDidEnterBackground();

        if (isScreenOn() && App.mIsAppVisible && hasFocus) {
            // App is back in focus. Do something here...

            // this can occur when the notification shade is
            // pulled down and hidden again, for example.
        }

        super.onWindowFocusChanged(hasFocus);
    }

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

        if (!mWasScreenOn && mIsWindowFocused)
            onWindowFocusChanged(true);
    }

    @Override
    public void onBackPressed() {
        // this is for any "sub" activities that you might have
        if (!(this instanceof MainActivity))
            mIsBackPressed = true;

        if (isTaskRoot()) {
            // If we are "closing" the app
            App.mIsAppVisible = false;
            super.onBackPressed();
        } else
            super.onBackPressed();
    }

    private void applicationWillEnterForeground() {
        if (mIsAppInBackground) {
            mIsAppInBackground = false;
            App.mIsAppVisible = true;

            // App is back in foreground. Do something here...

            // this happens when the app was backgrounded and is
            // now returning
        } else
            mFailed = false;
    }

    private void applicationDidEnterBackground() {
        if (!mIsWindowFocused || !isScreenOn()) {
            mIsAppInBackground = true;
            App.mIsAppVisible = false;

            mFailed = false;

            // App is not in focus. Do something here...
        } else if (!mFailed)
            mFailed = true;
    }

    private boolean isScreenOn() {
        boolean screenState = false;
        try {
            PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);

            screenState = powerManager.isInteractive();
        } catch (Exception e) {
            Log.e(TAG, "isScreenOn", e);
        }

        mWasScreenOn = screenState;

        return screenState;
    }
}

For your use you might want to create a method in your activity (code snippet assumes MainActivity) that handles the animation to call the t.cancel(); method that penguin suggested. You could then in the ParentActivity.applicationDidEnterBackground() method add the following:

if (this instanceof MainActivity) {
    ((MainActivity) this).cancelTimer();
}

Or you could add the timer to the ParentActivity class and then not need the instanceof check or the extra method.

Aidan Host
  • 146
  • 2
  • 11
0

If your Activity is the last element in the BackStack, then it will be put in the background as if you pressed the Home button.

As such, the onPause() method is triggered.

You can thus cancel your animation there.

@Override protected void onPause() {
    this.timer.cancel();
}

You should as well start your animation in the onResume() method. Note that onResume() is also called right after onCreate(); so it's even suitable to start the animation from a cold app start.

@Override protected void onResume() {
    this.timer.scheduleAtFixedRate(...);
}

onPause() will be also called if you start another Application from your app (e.g: a Ringtone Picker). In the same way, when you head back to your app, onResume() will be triggered.


There is no need to add the same line of code in onBackPressed().

Also, what's the point in stopping the animation in onStop() or onDestroy()?

Do it in onPause() already. When your are app goes into the background, the animation will already be canceled and won't be using as much memory.

Don't know why I see such complicated answers.

payloc91
  • 3,724
  • 1
  • 17
  • 45