سؤال

I don't know if this behavior is normal because I suppose a call to finish() would stop the activity from being re-created.

Here is an excerpt of the activity class:

public class MyActivity extends FragmentActivity {

    private RetainFragment retainFragment;

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        ......

        retainFragment = getSupportFragmentManager().findFragmentByTag("RetainFragment");
        if(retainFragment == null) {
            retainFragment = new RetainFragment();
            getSupportFragmentManager().beginTransaction().add(retainFragment, "RetainFragment").commitAllowingStateLoss();
        }
        if(retainFragment.isFinish) {
            Log.v("MyActivity", "isFinish == true");
            this.finish();
        }
    }

    // a on-click event handler for a finish button
    public void onFinishClicked(View view) {
         retainFragment.isFinish = true;
         this.finish();
    }

    private class RetainFragment extends Fragment {
        private boolean isFinish = false;

        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            this.setRetainInstance(true);
        }
    }

}

The activity is persistent(I store the other persistent variables in the RetainFragment too but I didn't show them) and is closed only after the user has clicked the finish button, at which point onFinishClicked(View) would be called, and the activity should be finished by the system. I don't anticipate it to be recreated by the system after a coincident screen rotation. This happens rarely and I handle it by calling finish() again within the onCreate() method. However, it looks pretty ugly here because a finished activity is supposed to be dead forever and now I have to explicitly handle it. :( Is there any misunderstanding of the activity lifecycle or the retain fragment on my part?

هل كانت مفيدة؟

المحلول 2

This is a standard Android behavior. Take a look at this paragraph of Android documentation.

Unless you specify otherwise, a configuration change (such as a change in screen orientation, language, input devices, etc) will cause your current activity to be destroyed, going through the normal activity lifecycle process of onPause(), onStop(), and onDestroy() as appropriate. If the activity had been in the foreground or visible to the user, once onDestroy() is called in that instance then a new instance of the activity will be created, with whatever savedInstanceState the previous instance had generated from onSaveInstanceState(Bundle).

To avoid this, you can set android:configChanges attribute in its manifest. For any types of configuration changes you say that you handle there, you will receive a call to your current activity's onConfigurationChanged(Configuration) method instead of being restarted. If a configuration change involves any that you do not handle, however, the activity will still be restarted and onConfigurationChanged(Configuration) will not be called.

نصائح أخرى

If you work on Android, whenever you hear yourself saying "this Activity will be closed only after X". Don't. You should never rely on an Activity having a highly-controlled lifecycle. Your Activity should be designed in such a way that no matter when it is destroyed and recreated, it just works.

To retain information across rotations and so on, add code to onSaveInstanceBundle and then check for it on your onCreate and pull out the saved values.

I was encountering the same exact problem. That is, I had an Activity A that would start an Activity B using startActivityForResult. B's orientation is locked by the manifest. A's orientation is not locked.

When I rotated my device so the orientation was different, Activity B would not be recreated. This is expected and normal. However, once I performed a UI action that would finish() Activity B, instead of seeing Activity A being reconstructed and shown on the screen, I was shown Activity B again. Additionally, Activity's B's onActivityResult was being called instead of A's onActivityResult.

I figured out what was happening. I was using the same Intent object used to launch A to also launch B. After the orientation change and after B is finished, the Android system detects that Activity A needs to be rebuilt, since A was last built when the orientation was different. The system will reconstruct A using the same Intent object initially used to build A. However, the Intent object was modified to launch B instead, so the system will reconstruct Activity B instead of Activity A.

My code followed logic similar to below:

public class A extends Activity {
     @Override
     protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //...omitted code for clarity...
        Intent i = getIntent();
        i.setClass(this, B.class);
        startActivityForResult(i, 0);
     }

     @Override
     public void onActivityResult(int requestCode, int resultCode, Intent data) {
         //...omitted code for clarity...
     }
 }

And my Activity B looked like:

public class B extends Activity {
     //...omitted code for clarity....

     public void myFinishingClickHandler(View clickedView) {
         //...omitted code for clarity...
         finish();
     }
}

Quick walkthrough of the scenario:

  1. Device is in portrait mode. User launches Activity A. System will construct Activity A with an Intent I, whose target is set to A.
  2. Activity A modifies its Intent I by changing the target to B.
  3. Activity A starts an activity with Intent I. The system will construct Activity B since I is now pointing to B.
  4. B is now running. User changes the device orientation to landscape.
  5. User clicks button whose click listener calls myFinishingClickHandler. The method calls finish() on Activity B.
  6. Activity B finishes. The system begins the process of restoring Activity A.
  7. The system notes that Activity A was constructed when the device was in portrait orientation, but now the device is in landscape mode. The system will try recreating Activity A instead. The existing Activity A is sent through its destruction flow (calling onDestroy).
  8. The system will reconstruct Activity A using the saved Intent I. However, I's target is still Activity B. The system instead builds Activity B instead of Activity A.

Solution: Don't share Intents for launching activities. Use the Intent copy constructor.

Also, lock your activities' orientations to worry less about orientation changes (other configuration changes will cause your activities to be reconstructed, e.g. changing languages).

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top