bluelinelabs / Conductor

A small, yet full-featured framework that allows building View-based Android applications
Apache License 2.0
3.9k stars 344 forks source link

Conductor lifecycle #321

Open JuzTosS opened 7 years ago

JuzTosS commented 7 years ago

Hi! I made a diagram of the lifecycle, tried putting all the methods in it. Does it seem correct?

As you can see the diagram is quite large. Sometimes methods don't have a strict order. Probably it should be fixed?

untitled diagram_eng

Would appreciate any comments.

leonardo2204 commented 7 years ago

This is really cool !

sockeqwe commented 7 years ago

Not sure what you mean with "Activity is going to be killed" though...

Leonardo Ferrari notifications@github.com schrieb am Mi., 28. Juni 2017, 21:20:

This is really cool !

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/bluelinelabs/Conductor/issues/321#issuecomment-311760764, or mute the thread https://github.com/notifications/unsubscribe-auth/AAjnrvKMyj_oIitjoont-uKt5tGA5FDYks5sIqdfgaJpZM4OIJOu .

JuzTosS commented 7 years ago

@sockeqwe This happens when the system kills an activity. I simulated it by enabled "Don't keep activities" in the developer menu in a phone.

sockeqwe commented 7 years ago

If the activity gets killed, the whole process gets killed and you don't get any callbacks at all. Dont keep Activities is not simulating a process death.

Kirill Akhmetov notifications@github.com schrieb am Do., 29. Juni 2017, 09:51:

@sockeqwe https://github.com/sockeqwe This happens when the system kills an activity. I simulated it by enabled "Don't keep activities" in the developer menu in a phone.

— You are receiving this because you were mentioned.

Reply to this email directly, view it on GitHub https://github.com/bluelinelabs/Conductor/issues/321#issuecomment-311890257, or mute the thread https://github.com/notifications/unsubscribe-auth/AAjnriRl5jgWS2Hlvl7tHOlF4Su4nXblks5sI1dsgaJpZM4OIJOu .

JuzTosS commented 7 years ago

@sockeqwe Are you sure? If the whole process gets killed, activity gets killed too. But there is another case when an activity is killed by the system to save resources, but the process is still alive.

https://developer.android.com/reference/android/app/Activity.html

  • If an activity has lost focus but is still visible (that is, a new non-full-sized or transparent activity has focus on top of your activity), it is paused. A paused activity is completely alive (it maintains all state and member information and remains attached to the window manager), but can be killed by the system in extreme low memory situations.
    • If an activity is completely obscured by another activity, it is stopped. It still retains all state and member information, however, it is no longer visible to the user so its window is hidden and it will often be killed by the system when memory is needed elsewhere.
    • If an activity is paused or stopped, the system can drop the activity from memory by either asking it to finish, or simply killing its process. When it is displayed again to the user, it must be completely restarted and restored to its previous state.
sockeqwe commented 7 years ago

My bad, i thought I have seen the word process death somewhere in your diagram.

Zhuinden commented 7 years ago

Process death is onPause() -> onSaveInstanceState() -> onStop(), and then boom everything is dead, but the activity stack restores the app from where it was, with savedInstanceState != null.

EricKuck commented 7 years ago

@JuzTosS what issues do you see here that you believe should be fixed?

One thing I'd like to clarify here is that onChangeStarted and onChangeEnded should probably not be included here, or at least not in a way that shows the ordering of these calls in comparison to onAttach and onDetach. There is no guaranteed ordering of these calls for either flow you have shown here. When views are attached and detached is dependent on many things, including whether or not the host Activity is visible and what kind of change handler is being used.

Given that caveat, the only things that are potentially in different call orders here would be onSaveViewState() coming before or after onDetach()

JuzTosS commented 7 years ago

@EricKuck Yea I'm talking exactly about the moments you mentioned.

So I need to investigate more about onChangeStarted and onChangeEnded as you said they can't be compared in order with onAttach and onDetach.

The only one thing left then. It is changing the order of onSaveViewState and onDetach

bejibx commented 6 years ago

@JuzTosS

But there is another case when an activity is killed by the system to save resources, but the process is still alive.

Activity documentation lies about this, see https://commonsware.com/blog/2011/10/03/activities-not-destroyed-to-free-heap-space.html

JuzTosS commented 6 years ago

@bejibx Actually this link was in a @sockeqwe comment, for some reason he removed it from the comment.

SiebeSysmans commented 6 years ago

So where does onContextAvailable(...) come into this diagram? Right before the onCreateView(...)?

sockeqwe commented 6 years ago

Yes, onContextAvailable() is called before onCreateView() (and before onRestoreInstanceState() afaik)

Siebe Sysmans notifications@github.com schrieb am Do., 12. Okt. 2017 um 11:17 Uhr:

So where does onContextAvailable(...) come into this diagram? Right before the onCreateView(...)?

— You are receiving this because you were mentioned.

Reply to this email directly, view it on GitHub https://github.com/bluelinelabs/Conductor/issues/321#issuecomment-336070881, or mute the thread https://github.com/notifications/unsubscribe-auth/AAjnrjZNOFRNy-5yHyfnaiyJPUnj81peks5srdksgaJpZM4OIJOu .

Gaket commented 6 years ago

But there is another case when an activity is killed by the system to save resources, but the process is still alive.

Android never kills a particular activity, only a process as a whole. Here is an answer from Dianne Hackbor, Android core team member involved in the "out of memory killer" implementation: stackoverflow.com/a/7576275/1290264

mzgreen commented 6 years ago

Hey I've noticed a weird behavior related to lifecycle. I'm testing it on a sample app. I've modified NavigationControllerDemo by adding this:

static boolean alreadyReplaced = false;
    @Override
    protected void onChangeEnded(@NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) {
        super.onChangeEnded(changeHandler, changeType);
        if(changeType.isEnter) {
            Log.e("TAG","On Enter Animation Ended" + " isBeingDestroyed: " + isBeingDestroyed() + " index " + index);

            if(!alreadyReplaced) {
                getRouter().replaceTopController(RouterTransaction.with(new NavigationDemoController(index + 1, displayUpMode.getDisplayUpModeForChild()))
                        .pushChangeHandler(new HorizontalChangeHandler())
                        .popChangeHandler(new HorizontalChangeHandler()));

                getRouter().replaceTopController(RouterTransaction.with(new NavigationDemoController(index + 2, displayUpMode.getDisplayUpModeForChild()))
                        .pushChangeHandler(new HorizontalChangeHandler())
                        .popChangeHandler(new HorizontalChangeHandler()));

                alreadyReplaced = true;
            }
        }
    }

    @Override
    public void onDestroy() {
        Log.e("onDestroy", "index "+index);
        super.onDestroy();
    }

Important things to note: I'm using replaceTopController instead of push, and I'm intentionally pushing 2 controllers one after another to force a change before previous animation ended.

Now when I run the app and go to Navigation Demos section I see the following logs:

onDestroy: index 0
On Enter Animation Ended isBeingDestroyed: true index 1
onDestroy: index 1
On Enter Animation Ended isBeingDestroyed: false index 2

And this is a correct behavior. You can see that Conductor first calls onChangeEnded with isBeingDestroyed=true for controller with index=1 and then it calls onDestroy for this controller.

Now we have Controller with index=2 on top. Let's get the app to the background by pressing home button. And when app is in background we can use Terminate Application button in LogCat window or adb kill command to kill app's process. When it's done we go to recents menu and we go back to our app. Since the process was killed alreadyReplaced boolean will be reset to false and our 2 replace calls will be executed again. Here is what we get in the logs after going back to the app:

On Enter Animation Ended isBeingDestroyed: false index 2
onDestroy: index 2
onDestroy: index 3
On Enter Animation Ended isBeingDestroyed: true index 3
On Enter Animation Ended isBeingDestroyed: false index 4

Let's analyze what just happened. First enter animation of our Controller with index=2 was called, then it was destroyed because of 2 replace calls. Now weird thing happens. You can see that onDestroy of Controller with index=3 is called before it's onChangeEnded. I would expect it to behave the same as above when I first opened the screen. It seems that killing process affects somehow how Router changes the Controllers. Maybe some state is being lost? Afaik Conductor uses retained fragments which are not retained in case of process death.

Is it a bug or an expected behavior? I didn't try to find the root cause yet.