Android Architecture Components Application Architecture Components Source Details (based on more than 1.0) (Part 2 ViewModel and LiveData)

Keywords: network JSON Android Google

Small partners familiar with m V P mode should be clear, m - > model, V - > View, p - > presenter, p layer calls the business processing of model layer, and returns the result to view layer through interface. The usage of view model introduced here is somewhat similar to that of P layer, but if you put business processing in this view model, it is not so like P layer, anyway, the design mode is how you use it, flexible operation. Use, can create many similar, but more than similar. If a network call is written in the ViewModel and the result is then called back to the view level Activity or frame through LiveData, the name LiveData means (active data), the name implies that if the return key is suddenly pressed in the network call, the ui updated interface will not be invoked, because it is checked out that the Activity or frame life cycle will come to an end. So if the data is inactive at this time, there is no need to update the ui interface. Having said so much, let's first look at the specific usage of these two categories:


public class LoginViewModel extends AndroidViewModel {

 
MutableLiveData loginString=new MutableLiveData();

First, create a LoginViewModel where you simulate a network login and declare a MediatorLiveData in this class, which is a direct subclass of LiveData.


Then write a goLogin method to simulate landing.

public void goLogin(){
        ArrayMap<String, String> params = new ArrayMap<String, String>();
        params.put("username", "18768156795");
        params.put("password","123456");
        OkhttpHelp.postUrl("https://chat.yggx.com/login/check").OnUIThread().params(params).post(new StringResultCallBack() {
            @Override
            public void onError(Request request, Exception e) {
            }

            @Override
            public void onResponse(String response) {
               // loginString.setValue(response);
                
loginString.setValue(response);
 } }); }

Finally, the method of calling ViewModle in activity

 private LoginViewModel model=new LoginViewModel(getApplication());
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(layout.activity_main);
        getLifecycle().addObserver(new ActivityDaiLi());
        model.loginString.observe(this, new Observer<String>() {
            @Override
            public void onChanged(@Nullable String s) {
                Log.i("huoying",s);
            }
        });
        model.goLogin();
    }

Note here that the setValue method of LiveData must be executed in the UI thread. Of course, the login return message here needs to be parsed by the client itself. If you update multiple UI controls with one interface, you can use Mediator LiveData, an indirect subclass of LiveData, as follows

 public void login1(LifecycleOwner owner) {
       /* LiveData data1=  Transformations.switchMap(loginString1, new Function<String, LiveData<Integer>>() {
            @Override
            public LiveData<Integer> apply(String input) {
             liveData=new MutableLiveData<Integer>() ;

                return liveData;
            }
        });
        loginString1.setValue("ww");
        liveData.setValue(6);*/


        data.observe(owner, new Observer<String>() {
            @Override
            public void onChanged(@Nullable String s) {
                String h = s.substring(1);
                String h1 = s.substring(1);
                Log.i("huoying", s + "");
                loginString1.setValue(h + "");
            }
        });
        ArrayMap<String, String> params = new ArrayMap<String, String>();
        params.put("username", "18768156795");
        params.put("password", "123456");
        OkhttpHelp.postUrl("https://chat.yggx.com/login/check").OnUIThread().params(params).post(new StringResultCallBack() {
            @Override
            public void onError(Request request, Exception e) {

            }

            @Override
            public void onResponse(String response) {
                data.setValue(response);
            }
        });
    }

Here the data is

MediatorLiveData data=new MediatorLiveData();

Then add multiple LiveData

data.addSource(loginString1, new Observer() {
            @Override
            public void onChanged(@Nullable Object o) {
                Log.i("huoying",o +"");
            }
        });

After successful login calls, actively call data.setValue(response); method, this method will lead to the following callback method execution, so here we can do something.

data.observe(owner, new Observer<String>() {
            @Override
            public void onChanged(@Nullable String s) {
                String h = s.substring(1);
                String h1 = s.substring(1);
                Log.i("huoying", s + "");
                loginString1.setValue(h + "");
            }
        });

First of all, the general network data returns in json format. If we have two TextView1 and TextView1, which need to display one field of json data separately, then we can parse json in this method.
This is just a simulation of truncation instead of parsing. If you set the data that TextView wants back through loginString1.setValue(h +"), the following callback method will be invoked.

data.addSource(loginString1, new Observer() {
            @Override
            public void onChanged(@Nullable Object o) {
                Log.i("huoying",o +"");
            }
        });

Operate controls here, that is to say, what controls need and what data can be bound by callback method of LiveData. If there is an update of data including multiple controls, it can be managed and split by Mediator LiveData. So the idea is that Mediator LiveData is to split a data into the data we want if we can and bind it to different controls. Of course, if you don't like the trouble of using MediatorLiveData, you can use Transformations directly, which directly mentions enough map and switchMap methods to help you return MediatorLiveData objects.

Seeing if this framework feels comfortable, completely isolates UI and data operations, and guarantees that UI controls will be updated only if the form is considered to be alive. Otherwise, there is no need or some mistakes to avoid. If the above usage is combined with mvp and mvvm, it will appear that the code is of high-end atmospheric quality. Okay, now that I can use them, I will analyze how to write the source code of these two classes.
First, take a look at the Android View Model

public class AndroidViewModel extends ViewModel {
    @SuppressLint("StaticFieldLeak")
    private Application mApplication;

    public AndroidViewModel(@NonNull Application application) {
        mApplication = application;
    }

    /**
     * Return the application.
     */
    @NonNull
    public <T extends Application> T getApplication() {
        //noinspection unchecked
        return (T) mApplication;
    }
}

The method in the class is simple enough to hold a reference to an Application, and there's nothing to say, followed by its parent class, ViewModel.

public abstract class ViewModel {
    /**
     * This method will be called when this ViewModel is no longer used and will be destroyed.
     * <p>
     * It is useful when ViewModel observes some data and you need to clear this subscription to
     * prevent a leak of this ViewModel.
     */
    @SuppressWarnings("WeakerAccess")
    protected void onCleared() {
    }
}

Complete Abstract method, there is an onCleared method, users can rewrite the method for data release.

Next, take a look at how MutableLiveData is constructed

public class MutableLiveData<T> extends LiveData<T> {
    @Override
    public void postValue(T value) {
        super.postValue(value);
    }

    @Override
    public void setValue(T value) {
        super.setValue(value);
    }

The difference between postValue and setValue is that postValue guarantees that the onChange method is executed in the main thread, while setValue checks whether it is executed in the main thread and does not throw an exception. Continue with the MediatorLiveData class

public class MediatorLiveData<T> extends MutableLiveData<T> {
    private SafeIterableMap<LiveData<?>, Source<?>> mSources = new SafeIterableMap<>();

    /**
     * Starts to listen the given {@code source} LiveData, {@code onChanged} observer will be called
     * when {@code source} value was changed.
     * <p>
     * {@code onChanged} callback will be called only when this {@code MediatorLiveData} is active.
     * <p> If the given LiveData is already added as a source but with a different Observer,
     * {@link IllegalArgumentException} will be thrown.
     *
     * @param source    the {@code LiveData} to listen to
     * @param onChanged The observer that will receive the events
     * @param <S>       The type of data hold by {@code source} LiveData
     */
    @MainThread
    public <S> void addSource(@NonNull LiveData<S> source, @NonNull Observer<S> onChanged) {
        Source<S> e = new Source<>(source, onChanged);
        Source<?> existing = mSources.putIfAbsent(source, e);
        if (existing != null && existing.mObserver != onChanged) {
            throw new IllegalArgumentException(
                    "This source was already added with the different observer");
        }
        if (existing != null) {
            return;
        }
        if (hasActiveObservers()) {
            e.plug();
        }
    }

    /**
     * Stops to listen the given {@code LiveData}.
     *
     * @param toRemote {@code LiveData} to stop to listen
     * @param <S>      the type of data hold by {@code source} LiveData
     */
    @MainThread
    public <S> void removeSource(@NonNull LiveData<S> toRemote) {
        Source<?> source = mSources.remove(toRemote);
        if (source != null) {
            source.unplug();
        }
    }

    @CallSuper
    @Override
    protected void onActive() {
        for (Map.Entry<LiveData<?>, Source<?>> source : mSources) {
            source.getValue().plug();
        }
    }

    @CallSuper
    @Override
    protected void onInactive() {
        for (Map.Entry<LiveData<?>, Source<?>> source : mSources) {
            source.getValue().unplug();
        }
    }

    private static class Source<V> implements Observer<V> {
        final LiveData<V> mLiveData;
        final Observer<V> mObserver;
        int mVersion = START_VERSION;

        Source(LiveData<V> liveData, final Observer<V> observer) {
            mLiveData = liveData;
            mObserver = observer;
        }

        void plug() {
            mLiveData.observeForever(this);
        }

        void unplug() {
            mLiveData.removeObserver(this);
        }

        @Override
        public void onChanged(@Nullable V v) {
            if (mVersion != mLiveData.getVersion()) {
                mVersion = mLiveData.getVersion();
                mObserver.onChanged(v);
            }
        }
    }
}

The implementation of this class is also relatively few. AddiSource adds a single Mutable LiveData, and addSource wrapper class for Observer. Observer is registered and cancelled through onActive and onInactive. For later data disassembly, the removeSource method cancels the Source saved by the collection. Well, that's the basic composition of these classes.
All monitoring starts with registration, to see what LiveData's registration actually implements.

 public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<T> observer) {
        if (owner.getLifecycle().getCurrentState() == DESTROYED) {
            // ignore
            return;
        }
        LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
        LifecycleBoundObserver existing = mObservers.putIfAbsent(observer, wrapper);
        if (existing != null && existing.owner != wrapper.owner) {
            throw new IllegalArgumentException("Cannot add the same observer"
                    + " with different lifecycles");
        }
        if (existing != null) {
            return;
        }
        owner.getLifecycle().addObserver(wrapper);
    }

OK, if you are not familiar with it, please read a detailed source code of Lifecycle Observer and Lifecycle owner. Here the ordinary Observer Observer class is enclosed with a Lifecycle Bound Observer decoration class. Finally, the Observer is registered with Lifecycle Registry through Lifecycle. The last article links up and finally calls back Lifecycle Observer's onStateChanged method when the activity or framework lifecycle changes. It's just the Lifecycle Observer interface that we implement for monitoring the lifecycle, and here's the subclass Lifecycle BoundObserver that implements the Lifecycle Observer interface, as follows

public interface GenericLifecycleObserver extends LifecycleObserver {


 class LifecycleBoundObserver implements GenericLifecycleObserver 

Back here to the previous article on how to get packaging classes

static GenericLifecycleObserver getCallback(Object object) {
        if (object instanceof FullLifecycleObserver) {
            return new FullLifecycleObserverAdapter((FullLifecycleObserver) object);
        }

        if (object instanceof GenericLifecycleObserver) {
            return (GenericLifecycleObserver) object;
        }.....

What do you see if this Lifecycle Observer is Generic Lifecycle Observer directly using Generic Lifecycle Observer, that is to say, when the Activity life cycle changes, it calls back Lifecycle Bound Observer's onStateChanged method, which is implemented as follows:

public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
            if (owner.getLifecycle().getCurrentState() == DESTROYED) {
                removeObserver(observer);
                return;
            }
            // immediately set active state, so we'd never dispatch anything to inactive
            // owner
            activeStateChanged(isActiveState(owner.getLifecycle().getCurrentState()));
        }

Here we first determine whether the current activity has been destroyed. If it is destroyed, there is no need to notify the data changes. For example, if you use Frament to jump the activity in a successful callback method on the network, getActivity (). StartActivity sometimes happens when getActivity () is null. The reason for this phenomenon is to terminate the activity ahead of time. We can only add a judgment of whether getActivity is null or if (add ()). So this judgment is the first security lock, followed by the isActiveState method.

 static boolean isActiveState(State state) {
        return state.isAtLeast(STARTED);
    }

Comparing the current life cycle state with the STARTED state, the isActiveState method returns true only when the Activity's life cycle state is onResumed or onSarted. That is to say, only when the Activity is onResumed or onSarted, when LiveData is considered valid, will the callback of UI updates be executed. Otherwise, there is no need to execute, that is, data binding as we often call it. Life cycle.
 void activeStateChanged(boolean newActive) {
            if (newActive == active) {
                return;
            }
            active = newActive;
            boolean wasInactive = LiveData.this.mActiveCount == 0;
            LiveData.this.mActiveCount += active ? 1 : -1;
            if (wasInactive && active) {
                onActive();
            }
            if (LiveData.this.mActiveCount == 0 && !active) {
                onInactive();
            }
            if (active) {
                dispatchingValue(this);
            }
        }
    }

According to the above analysis, it is very simple to look at this method again. First, to judge whether the current state is consistent with the previous one, the method does not execute if it is consistent. Secondly, to calculate the current number of mActiveCount survivors. If the current one survives, then call the onActive method, otherwise call the onInactive method. After all, it is possible for multiple activities to share the LiveData of the same ViewModel. The mActiveCount count is added. Only when there is no LiveData alive, call back onInactive, and finally distribute event processing through dispatching Value.


 private void dispatchingValue(@Nullable LifecycleBoundObserver initiator) {
        if (mDispatchingValue) {
            mDispatchInvalidated = true;
            return;
        }
        mDispatchingValue = true;
        do {
            mDispatchInvalidated = false;
            if (initiator != null) {
                considerNotify(initiator);
                initiator = null;
            } else {
                for (Iterator<Map.Entry<Observer<T>, LifecycleBoundObserver>> iterator =
                        mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
                    considerNotify(iterator.next().getValue());
                    if (mDispatchInvalidated) {
                        break;
                    }
                }
            }
        } while (mDispatchInvalidated);
        mDispatchingValue = false;
    }

The general meaning of this method is that initiator is null if it actively notifies the callback method through setvalue, and observer who iterates through LiveData registry is null if it is called back through lifecycle, initiator is not null, and considerNotify is called directly. Either way, the considerify method will eventually be called. The mDispatching Value parameter ensures that multiple Activities can only have one Activity operation at a time when they share LiveData.

private void considerNotify(LifecycleBoundObserver observer) {
        if (!observer.active) {
            return;
        }
        // Check latest state b4 dispatch. Maybe it changed state but we didn't get the event yet.
        //
        // we still first check observer.active to keep it as the entrance for events. So even if
        // the observer moved to an active state, if we've not received that event, we better not
        // notify for a more predictable notification order.
        if (!isActiveState(observer.owner.getLifecycle().getCurrentState())) {
            observer.activeStateChanged(false);
            return;
        }
        if (observer.lastVersion >= mVersion) {
            return;
        }
        observer.lastVersion = mVersion;
        //noinspection unchecked
        observer.observer.onChanged((T) mData);
    }

The last method called checks the observer status again. Note that there is a version judgment bserver. lastVersion >= mVersion. Only when setValue is active ly invoked, version mVersion will be added 1. That is to say, callbacks to lifecycle changes will not ultimately be executed to observer.observer.onChanged((T) mData), and lifecycle changes will only record the status of Activity. Activeness, instead of telling the UI that it's time to update data, only when setValue, Activity updates the UI when onStart or onResumed. As the saying goes, your Activeness is dead, and I update your UI dry bird. That's the beauty of Google's design code. Do you remember the pits in Frament in those years? With this artifact, we will never be afraid of these pits again.


Posted by Blackwind on Fri, 14 Dec 2018 07:03:03 -0800