1

I have a Splashscreen where I am running an animation. Following is my logic regarding moving from Splash screen to MainActivity.

Minimum visible time for Splash screen = minTime

Maximum visible time for Splash screen = maxTime

API is called which gets response in some time - apiTime

1. Show Splash screen for at least minTime.

2. Call an API. If the API's response is received in less than maxtime, move to next screen immediately otherwise, move to next screen in maxtime

Following is my code:

public class SplashActivity extends AppCompatActivity {

private ImageView container;
private AnimationDrawable animationDrawable;
int apiTime = 2000, minTime = 1000, maxTime = 5000;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_splash);

    container = findViewById(R.id.iv_icons);
    container.setBackgroundResource(R.drawable.splash_animation);

    animationDrawable = (AnimationDrawable) container.getBackground();
}

@Override
protected void onResume() {
    super.onResume();
    animationDrawable.start();
    final long start = System.currentTimeMillis();

    //calling api in thread simultaneously. As soon as response is received, move to next screen.
    //Thread.sleep is just dummy for api response time
    Thread t1 = new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                Thread.sleep(apiTime);
                //if apiTime is less than minTime, then we wait till minTime
                long time = minTime - (System.currentTimeMillis() - start);
                if (time > 0) {
                    Thread.sleep(time);
                }
                moveToNextScreen();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    });
    t1.start();

    hardMoveToNextScreen();
}

private void moveToNextScreen() {
    Intent i = new Intent(SplashActivity.this, MainActivity.class);
    startActivity(i);
    finish();
}

private void hardMoveToNextScreen () {
    Handler handler = new Handler();
    handler.postDelayed(new Runnable() {
        @Override
        public void run() {
            moveToNextScreen();
        }
    }, maxTime);
}

}

Now, according to the time values I have used, the thread t1 calls method moveToNextScreen() before method hardMoveToNextScreen() calls the same method. So, once the activity is finished, I should move to MainActivity.

The problem that I am facing is that MainActivity is opening twice. Once from the thread and then from hardMoveToNextScreen() method. But, this should not happen as I am already calling finish() which means that once I move to MainActivity, any method from SplashActivity should not be called anymore.

What am I doing wrong?

Vivek Singh
  • 1,142
  • 14
  • 32
Yesha
  • 648
  • 1
  • 7
  • 29
  • Can you condition your methods with : if(!isFinishing). This might ensure that either one of the methods from not executing. – ACR Jun 02 '20 at 14:51

2 Answers2

3

So first I will tell you the reason why this is happening and after that, I will move onto the solution to this problem.

Reason:

The activity instance remains in the memory even after the onDestroy() method is called. Now that doesn't mean that the activity instance will remain there forever. It is destroyed by the Android OS based on the memory requirements of the system. This is very well described in this answer- What is Activity.finish() method doing exactly? and in this one too - Activity instance remains in the memory after onDestroy() (Also, there are many answers on StackOverflow describing the exact same thing. Just search it).

Solution:

Use isDestroyed() method which returns a boolean to check whether the activity's onDestroy() method is called or not. You can find its documentation here - https://developer.android.com/reference/android/app/Activity.html#isDestroyed%28%29

So the moveToNextScreen() method should look something like this now -

private void moveToNextScreen() {
    if (!isDestroyed()) {
        Intent i = new Intent(SplashActivity.this, MainActivity.class);
        startActivity(i);
        finish();
    }
}
  • 1
    I have personally executed this code on my machine and it works fine and creates only one instance of MainActivity as was required by you – Dhiraj Chhabra Jun 03 '20 at 09:30
1

You are calling moveToNextScreen() twice. First time in thread and second time in hardMoveToNextScreen().

If you remove hardMoveToNextScreen(); after t1.start(); it should work fine for you.

Or even better option is to remove Thread and have hardMoveToNextScreen() method with handler.

UPDATED ANSWER:

If you want to keep double logic declare global variable:

private boolean activityFinished = false;

and then change moveToNextScreen() method to this:

private void moveToNextScreen() {
    if( !activityFinished ) {
        Intent i = new Intent( SplashActivity.this, MainActivity.class );
        startActivity( i );
        finish();
        activityFinished = true;
    }
}
Apollo
  • 420
  • 3
  • 6
  • Yes I know that I am calling moveToNextScreen() twice. That is exactly what I need to do. I want only one of the methods either the one from Thread or hardMoveToNextScreen() to get called. This should be the case as I am calling finish eventually from both places, so whatever is executed first, should just finish the SplashActivity – Yesha Jun 02 '20 at 18:24
  • I have copied your code in my project and do some double checks. Put breakpoints on moveToNextScreen() in thread and handler, and put another breakpoint on startActivity(i) method. Run debug and you will see that startActivity is called twice. – Apollo Jun 02 '20 at 18:58
  • I know it's called twice. That's what the problem is! – Yesha Jun 03 '20 at 05:06
  • 1
    Hmm ok, I didn't clearly understand that you want to keep both logic. Please check updated answer. This should work for you. – Apollo Jun 03 '20 at 07:24