Preface:

Forgive me for the clickbait 😑😑

Lifecycle series:

Lifecycle — (1) The basics

Lifecycle – (2) Source code analysis of Event & State

Lifecycle – (3) Source code analysis registration & send

Lifecycle – (4) Response to source code analysis

Pending:

Lifecycle – (5) Integrate Lifecycle in several ways with the source code differences

It’s been a long time since I wrote this article, but I recently decided to write about another basic piece of Android: the official Android architecture component series. Lifecycle is going to be written down, so this series will be about Lifecycle.


The body of the

In Lifecyele series – (1) Basic explanation, we have talked about three ways to integrate Lifecycle, we have talked about three ways to integrate, in fact, one way to integrate the source code, the other are similar, we will explain the first most common way to integrate (other later free to add).

1. Add register listener:

Landlord class concrete implementation: LifecycleRegistry class

LifecycleRegistry is a class that extends Lifecycle, so we can look directly at Lifecycle (PS: There will be a long, long code introduction to this class, because this class is the core, you can skip it) :

public class LifecycleRegistry extends Lifecycle {
    
    //'Single introduction to core attributes:'
    
    //'LifecycleObserver that joins will be added to the Map queue (Ps: this data structure is the core, also explained in detail)'
    private FastSafeIterableMap<LifecycleObserver, ObserverWithState> mObserverMap =
            new FastSafeIterableMap<>();
            
    //'Current State'        
    private State mState;
    
    //'Number of observers currently being added'
    private int mAddingObserverCounter = 0;
    //State (Sync() method) State check value
    private boolean mHandlingEvent = false;
    //'Is there a new Event coming in?'
    private boolean mNewEventOccurred = false;
    //'This is an instance of an Activity if we're an Activity.'private final WeakReference<LifecycleOwner> mLifecycleOwner; . . . @Override public void addObserver(@NonNull LifecycleObserver observer) { //LifecycleObserver will be INITIALIZED by default, but will be DESTROYED if LifecycleRegistry is in a DESTROYED state. We will directly change the LifecycleObserver into DESTROYED, and many logic will fail in the following process (just as the Activity has changed to onDestory, it cannot change to other onCraete, onResume state. There's no going back.
        State initialState = mState == DESTROYED ? DESTROYED : INITIALIZED;
        
        
        //'We know that LifecycleObserver is an observer, and it's ok to send notifications to the observer of the queue. The List
      
        is managed by the queue. But if we now send the ON_RESUME status directly, but our LifecycleObserver is INITIALIZED, we might want the observer to receive ON_CREATE,ON_START, and ON_RESUME, So we have to know the state of the current LifecycleObserver, and then depending on its state and the final state value that needs to be changed, then step by step change over, and then step by step send events. So we need to know the State of the LifecycleObserver, so we have to wrap the State field and LifecycleObserver' with an ObserverWithState class.
      
        ObserverWithState statefulObserver = new ObserverWithState(observer, initialState);
        
        //'And what we see here is that we're using putIfAbsent to queue, and putIfAbsent, which we'll talk about when we talk about this data structure, is that unlike a Map structure, which overwrites the old value with the new value, it just returns the old value that's already there. So if the LifecycleObserver passed in is found in the queue, we have added it before, and now we are adding it again, and return it. '
        ObserverWithState previous = mObserverMap.putIfAbsent(observer, statefulObserver);
        if(previous ! = null) {return; } / /'Get the current LifecycleOwner not null because weak references are being used, otherwise the rest will be broken'
        LifecycleOwner lifecycleOwner = mLifecycleOwner.get();
        if (lifecycleOwner == null) {
            // it is null we should be destroyed. Fallback quickly
            return; } / /'First look at the literal mAddingObserverCounnter: Adding-observer-counter number of lifecycleobservers being added, we can see mAddingObserverCounnter++ below, and then finally after the whole addObserver() method is done, It's going to run mAddingObserverCounter-- again, so the normal process is to come in and add a LifecycleObserver, and we're going to add one with this mAddingObserverCounter, When the LifecycleObserver is completely added, we subtract one. This is fine, but if multiple observers are added concurrently, one of them may be added before the entire addObserver() method is completed, and another one may be added. And then that's when our mAddingObserverCounter goes! = 0. '
        
        //'Let's look at the mHandlingEvent argument, which literally means: Handle-event Handles Event events, which default to false, and is set to true in the sync() method (PS:sync(), which updates the State of the entire LifecycleObserver queue and sends the Event Event method). Sync () is then set to false {mHandlingEvent = true; sync(); mHandlingEvent = false; }} if the Event is currently being processed, the Event will be sent to the queue.
        
        //'isReentrance 'is a reentrant, so we consider adding multiple observers at the same time or sending multiple events at the same time as reentrant.boolean isReentrance = mAddingObserverCounter ! = 0 || mHandlingEvent; //'Get the target State (or the current State of LifecycleRegistry), which we'll explain later here.'
        State targetState = calculateTargetState(observer);
        
        //'Plus one before you start'
        mAddingObserverCounter++;
        
        //'The State value of the current added observer is compared to the target value because the added observer is DESTROYED or INITIALIZED. (PS: Remember the enumeration we mentioned earlier? For example, the observer State we add is INITIALIZED (value 1), and the target State is CREATED (value 2), so our observer State is less than the current target State.
        while ((statefulObserver.mState.compareTo(targetState) < 0
                && mObserverMap.contains(observer))) {
            
            //'Literally, store the observer's own state before changing its state.'
            pushParentState(statefulObserver.mState);
            
            //'dispatchEvent dispatches events, but the parameters in the dispatchEvent are based on the status of the current observer. '
            statefulObserver.dispatchEvent(lifecycleOwner, upEvent(statefulObserver.mState));
            
            //'After changing the observer's state, delete the existing state'
            popParentState();
            
            
            //'Recalculate the target State value'// mState / subling may have been changed recalculate targetState = calculateTargetState(observer); } / /'We said that if there is no reentrant, I update the entire queue. (If there is a reentrant, if I update the queue once and then update it again, updating a queue in between is a waste of resources.)
        if(! isReentrance) { // wedosync only on the top level. sync(); } / /'mAddingObserverCounter parameter minus one 'mAddingObserverCounter--; }}Copy the code

2. Send events to observer queue:

With registration out of the way, let’s look at the events we send:

For example, we send events in our Activity’s onResume:

 @Override
protected void onResume() {
    super.onResume();
    
    //'1. Send State to jump to State '
    registry.markState(Lifecycle.State.CREATED);
    
    //'2. Go straight to the Event and skip to the corresponding State of the Event '
    registry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);
 
}
Copy the code

And then our observer gets this notification.

Let’s take a look at the relevant source code sent:


//'By the first method'
public void markState(@NonNull State state) {
    //'Move directly to the specified State value'moveToState(state); } / /'By the second way'
public void handleLifecycleEvent(@NonNull Lifecycle.Event event) {
    //'Use the getStateAfter method to get the next State value (which is actually based on our State and Event diagram)'
    State next = getStateAfter(event);
    //'Then move directly to the specified State value'
    moveToState(next);
}
Copy the code

Moving on to moveToState:

private void moveToState(State next) {
    
    //'The current State is already consistent with the required changing State, so there is no need to run the following code (so if you send multiple values of the same State, the method will only execute once)'
    if (mState == next) {
        return; } / /'Change the current value to the value to change'
    mState = next;
    
    //'Remember what these two parameters mean?? MHandlingEvent true indicates that sync is being executed to traverse the observer queue for updates mAddingObserverCounter indicates that multiple observers have been added simultaneously.
    if(mHandlingEvent || mAddingObserverCounter ! = 0) {/ /'If this is done because mHandlingEvent is true, the queue is being updated, and a new State is being sent, then 1. There is no need to start a new queue update task, so return 2. Meanwhile, if the original queue update task is in the middle of the update, and the observers that have been updated are not the latest ones, the task will need to update the entire queue, so it needs a field in the sync() update queue to tell it that a new Event value is coming in. Yes, this field is mNewEventOccurred, so mNewEventOccurred = true; '
        
        //'If the entry was made because of mAddingObserverCounter, the reason is similar to the above (since we will also execute sync after adding an observer, there is no need to start a new one and tell the sync() method already executed with the mNewEventOccureed field)'
        
        mNewEventOccurred  = true;
        return; } // If sync is being performed, set mHandlingEvent totrue
    mHandlingEvent = true; sync(); // the sync operation is completefalse
    mHandlingEvent = false;
}
Copy the code

We can see that sync method is more important, because it plays a role in updating the queue, should have been directly look at the source of sync method, since it is said to update the queue, let’s first talk about the basic data structure of this queue, and then look back at our sync method source, easier to understand.

FastSafeIterableMap analysis of observer queue data structure:

PS: For readers who are really not interested in this paragraph, you can also skip…….

If you are not familiar with the basics of data structures, I strongly recommend that you look at my previous experience

The new LifecycleObserver will be added to the queue at registration time, so let’s talk about the data structure of this queue:

Put (key,value); put(key,value); put(key,value); get(key);

Let’s look at the basic code for HashMap:

public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
    ......
}
Copy the code

Can see a HashMap implementation the Map < K, V > interface, this interface defines the Map type definition of the related approaches, such as common put, get, either containsKey, keySet methods such as:

Comics: What is a HashMap? Comics: HashMap with high concurrency

Let’s look at our FastSafeIterableMap class again:

//'We know from the name that this class is just a (fast-SafeIterableMap), so the core is still the following safeIterableMap class.'public class FastSafeIterableMap<K, V> extends SafeIterableMap<K, V> { ...... } / /'SafeIterableMap implements the Iterable interface '
public class SafeIterableMap<K, V> implements Iterable<Map.Entry<K, V>> {

    ......
}
Copy the code

SafeIterableMap does not implement the Map interface, but implements the Iterable interface. Iterable interface should feel familiar immediately. Don’t know what it is??

public interface Collection<E> extends Iterable<E> {

    ......
}

Copy the code

Our Collection interface implements this interface, so Collection is more familiar:

PS: The introduction of specific Collection and related implementation classes, you can see the article: Collection overview, the above picture also references the article

So that’s instantly familiar, because this is a list structure, is our SafeIterableMap also a list structure, and the answer is correct.

Let’s look at the data structure step by step with the code:

'For example our addObserver method for Lifecycle:'
@Override
public void addObserver(@NonNull LifecycleObserver observer) {
    State initialState = mState == DESTROYED ? DESTROYED : INITIALIZED;
    ObserverWithState statefulObserver = new ObserverWithState(observer, initialState);
    
    //'putIfAbsent method used'ObserverWithState previous = mObserverMap.putIfAbsent(observer, statefulObserver); . }Copy the code

Let’s look at FastSafeIterableMap:

public class FastSafeIterableMap<K, V> extends SafeIterableMap<K, V> {
    
    //'Contains a HashMap inside, and so on! Isn't it a linked list? This HashMap is not used to store the last queue. We know that the disadvantage of a linked list is that finding elements requires traversing the list to find them, which is very time-consuming. And some of our methods, like checking if we have a value in the queue, or fetching the value of a particular Key, if it's too slow to walk through the list, we add the data to the queue, and then add it to the HashMap, and it's pretty quick to use the HashMap to check if we have a value or get a value. '
    private HashMap<K, Entry<K, V>> mHashMap = new HashMap<>();
    
    
    //'HashMap performs additional secondary storage to determine if the Key has been stored before '
    @Override
    protected Entry<K, V> get(K k) {
        returnmHashMap.get(k); } / /'This method is the key:'
    @Override
    public V putIfAbsent(@NonNull K key, @NonNull V v) {
    
        //'Whether the Key and Value are stored in the internal HashMap using the get method'Entry<K, V> current = get(key); // If it has a valueif(current ! = null) { //'I'm just going to return the value that I pulled out, and remember that put puts duplicate values in, it's not going to overwrite the original value, it's just going to return the old value.'
            returncurrent.mValue; } / /'If not, the internal HashMap stores the Key and Value, but the Value is not a Value passed in directly, it's put(), first into the list structure, then into the HashMap.'
        mHashMap.put(key, put(key, v));
        return null;
    }

    @Override
    public V remove(@NonNull K key) {
        //'Let's remove this Key from the list.'
        V removed = super.remove(key);
        //'Remove this value from the HashMap'
        mHashMap.remove(key);
        return removed;
    }
    
    
    public boolean contains(K key) {
        //'Determine if a value is present by HashMap'
        returnmHashMap.containsKey(key); } public map. Entry<K, V> ceil(K K) {// Check whether the corresponding value of the Key is includedif (contains(k)) {
            //'Get the corresponding value, and then take the last value of that value.'
            return mHashMap.get(k).mPrevious;
        }
        returnnull; }}Copy the code

As we can see, the difference between the so-called FastSafeIterableMap and SafeIterableMap is that there is an extra HashMap inside to help us quickly find elements without traversing the entire queue, saving time.

Just now we saw that when we added elements:

mHashMap.put(key, put(key, v));
Copy the code

It is added to the queue and then to the HashMap using the put method. Let’s look at the put() method:

Protected Entry<K, V> put(@nonnull K key, @nonnull V V) { Insert key and value Entry<K, V> newEntry = newEntry <>(key, V); //'Because it's new, it's going to increase the length of the list by one.'
    mSize++;
    
    //'List structure, as you know, is a long chain, so there's a head and a tail, and if the last one is empty, there's no element in the list, so this new element is both a head and a tail.'
    if (mEnd == null) {
        mStart = newEntry;
        mEnd = mStart;
        returnnewEntry; } / /'If there are already other elements in the list, then the last element is not the last element, so we make the last element point to our new element, and then our new element point to the last element, and then we assign the mEnd to the new element. '' mEnd.mNext = newEntry; newEntry.mPrevious = mEnd; mEnd = newEntry; return newEntry; }Copy the code

Yes, some of you may be confused, but that’s ok, I’ve prepared some pictures for you, in case you don’t.

First we know what it looks like inside an Entry object (in fact, we often see Entry objects in Map) :

static class Entry<K, V> implements Map.Entry<K, V> {
    
    //'the Key attribute'
    final K mKey;
    //'the Value attribute'
    final V mValue;
    //'Properties of next Entry'
    Entry<K, V> mNext;
    //'Properties of previous Entry'Entry<K, V> mPrevious; . . . }Copy the code

This is what the picture looks like:

And then how do these entries fit together? As shown below:

Is it so suddenly understood.

So the code we just added Entry to should make sense.

At the same time, recently learned to do dynamic animation, later used to write blog can be more and more convenient, such as the following linked list to delete elements (GIF reversed is to add elements) :

We talked about adding elements to the data structure, let’s talk about traversing the queue. Although our data structure does not directly inherit from Map, we see that it implements iterators, so how does HashMap iterate over data?

Iterator< map.entry <String, String>> it = map.entryset ().iterator(); // Determine if the iterator has the next element valuewhile(it.hasNext()){// Get the element value from the iterator map.entry <String, String> Entry = it.next(); System.out.println("key= "+entry.getKey()+" and value= "+entry.getValue());
}
Copy the code

So our observer queue will also have a method associated with getting iterators:

//'Positive-ordered iterator'
@NonNull
@Override
public Iterator<Map.Entry<K, V>> iterator() {
    ListIterator<K, V> iterator = new AscendingIterator<>(mStart, mEnd);
    mIterators.put(iterator, false);
    returniterator; } / /'Reverse iterator'
public Iterator<Map.Entry<K, V>> descendingIterator() {
    DescendingIterator<K, V> iterator = new DescendingIterator<>(mEnd, mStart);
    mIterators.put(iterator, false);
    returniterator; } / /'The biggest difference from the above two is that during the iteration process, new elements are added and can also be traversed to'.
public IteratorWithAdditions iteratorWithAdditions() {
    IteratorWithAdditions iterator = new IteratorWithAdditions();
    mIterators.put(iterator, false);
    return iterator;
}


Copy the code

I wanted to continue the analysis at length, but then I saw another blogger writing a great article about this data structure:

Understand SafeIterableMap from a source code perspective

So with the basis I said above, plus this article, the basic data structure will be able to master.

4. Specific process of Sync () :

Let’s look specifically at the queue update process:

private void sync() {
    //'Get if our observed is empty, if it is, it returns directly.'
    LifecycleOwner lifecycleOwner = mLifecycleOwner.get();
    if (lifecycleOwner == null) {
        Log.w(LOG_TAG, "LifecycleOwner is garbage collected, you shouldn't try dispatch "
                + "new events from it.");
        return; } / /'To see if synchronization is complete, see the instructions below.'
    while(! isSynced()) { mNewEventOccurred =false;
        
        //'The current actual mState value is smaller than the queue header state value, indicating that the queue state value is too large and should be reversed to become smaller.'
        if (mState.compareTo(mObserverMap.eldest().getValue().mState) < 0) {
            
            //'Back up'backwardPass(lifecycleOwner); } Entry<LifecycleObserver, ObserverWithState> newest = mObserverMap.newest(); / * *'mNewEventOccurred' is still false, does not change to true, (if it is true, a new Event is passed, and the update can end first) && The actual mState value is larger than the state at the end of the queue, indicating that the queue state value is too small. To get larger forward (why compare queue headers, explained later) '* * /if(! mNewEventOccurred && newest ! = null && mState.compareTo(newest.getValue().mState) > 0) { //'Move forward'    
            forwardPass(lifecycleOwner);
        }
    }
    mNewEventOccurred = false; } / /'Check whether the synchronization is complete:'
private boolean isSynced() {
    //'If the number of observers in the queue is empty, of course synchronization is complete, return true'
    if (mObserverMap.size() == 0) {
        return true; } / /'Current State of the observer of the queue header'
    State eldestObserverState = mObserverMap.eldest().getValue().mState;
    //'Current State of the observer at the end of the queue'State newestObserverState = mObserverMap.newest().getValue().mState; / * *'The head of the queue is the same as the tail of the queue, so the whole queue is the same.'
        &&         
    'Queue headers are the same as the latest State of LifecycleRegistry'
    ('Indicates that the status of all observers in the queue is updated to the latest State value, indicating that synchronization is complete.') * * /return eldestObserverState == newestObserverState && mState == newestObserverState;
}

Copy the code

If (back){XXXXXXX; if(back){XXXXXXX; Return}????

Why y do we compare elements in the queue when the backward and forward operations are in opposite directions?

Look at the forward operation and back operation of the specific source code (basically the same source code can be cited) :

private void backwardPass(LifecycleOwner lifecycleOwner) {
    Iterator<Entry<LifecycleObserver, ObserverWithState>> descendingIterator =
            mObserverMap.descendingIterator();
    
    //'Because our backward operation updates from the head of the queue to the end, the iterator is start-to-end (the reverse is true for forward)'
    while(descendingIterator.hasNext() && ! mNewEventOccurred) { Entry<LifecycleObserver, ObserverWithState> entry = descendingIterator.next(); ObserverWithState observer = entry.getValue();while((observer.mState.compareTo(mState) > 0 && ! mNewEventOccurred && mObserverMap.contains(entry.getKey()))) { Event event = downEvent(observer.mState); pushParentState(getStateAfter(event)); //'Call the dispatchEvent method with ObserverWithState for event distribution'observer.dispatchEvent(lifecycleOwner, event); popParentState(); }}}Copy the code
static class ObserverWithState {
    State mState;
    GenericLifecycleObserver mLifecycleObserver;

    ObserverWithState(LifecycleObserver observer, State initialState) {
        
        //'You can see that our Observer passed in actually returns a new Observer after processing it again through Lifecycling. GetCallback'
        mLifecycleObserver = Lifecycling.getCallback(observer);
        mState = initialState;
    }
    
    
    void dispatchEvent(LifecycleOwner owner, Event event) {
        //'Get the State of the Event'
        State newState = getStateAfter(event);
        //'Compare to the current latest mState and take the minimum'
        mState = min(mState, newState);
        //'Call onStateChanged method of the concrete real observer (or adapter observer ApdaterObserver) for callback notification'mLifecycleObserver.onStateChanged(owner, event); mState = newState; }}Copy the code

As we already know, we call onStateChanged method of mLifecycleObserver inside our wrapped ObserverWithState object, The mLifecycleObserver returns a new Observer by Lifecycling. GetCallback after processing our own Observer passed in when we addObserver. In the next article we will see how different the mLifecycleObserver we passed in is.

conclusion

This article introduces the source code for registering observers and sending events.