Jetpack core components under MVVM

foreword

JetpackThe establishment of the architecture components and "standardized development model" means that Androidthe development of the software has entered a mature stage. Only with MVVMa deep understanding of the code can the standardized and normalized code be written naturally.

This time, the author will introduce the following content in a simple way. Since it is a summary record of my study, it is more suitable for MVVMreaders who are not very familiar with it but want to know the whole picture:

  • Jetpack MVVM
  • Jetpack Lifecycle
  • Jetpack LiveData
  • Jetpack ViewModel
  • Jetpack DataBinding

Jetpack MVVM

Before starting the text, let's review MVP:

MVP, Model-View-Presenter, the responsibilities are classified as follows:

  • Model, the data model layer, is used to acquire and store data.
  • View, view layer, ieActivity/Fragment
  • Presenter, the control layer, is responsible for business logic.

We know that MVPit is the right MVCimprovement that solves MVCtwo problems:

  • ViewResponsibility is clear, logic is no longer written in Activity, put in Presenter;
  • Modelno longer holdView

MVPThe most common implementation is this:

ViewThe layer receives user operation events, notifies them Presenter, Presenterperforms logical processing, then notifies Modelthe update data, Modelsends the updated data Presenter, Presenterand then notifies Viewthe update interface.

MVPThe essence is interface-oriented programming, which also has some pain points:

  • A large number of interfaces will be introduced IView, IPresenterincreasing the complexity of implementation.
  • Viewand Presenterhold each other to form a coupling.

With the development, Jetpack MVVM was born in response to the situation. It is a specific implementation of MVVMthe pattern in the development, and it is the official and recommended implementation method. It's layered:AndroidGoogleMVVM

  • Model layer: used to obtain and store data
  • View layer: namelyActivity/Fragment
  • ViewModel layer: responsible for business logic

MVVMThe core is data-driven , and the decoupling is done more thoroughly ( ViewModelwithout holding view).

ViewEvents are generated, and ViewModelafter logical processing, Modelthe update data is notified, and Modelthe updated data is sent to ViewModelautomatically ViewModelnotify the View to update the interface

Jetpack Lifecycle

origin

Before Lifecyclethat, life cycle management was maintained manually. For example, we often Activityinitialize onStartsome members (for example MVP, Presenter) MediaPlayer, etc., and then onStoprelease the internal resources of these members in .

class MyActivity extends AppCompatActivity {
    private MyPresenter presenter;
    public void onStart(...) {
        presenter= new MyPresenter ();
        presenter.start();
    }
    public void onStop() {
        super.onStop();
        presenter.stop();
    }
}
class MyPresenter{
    public MyPresenter() {
    }
  
    void start(){
       // 耗时操作
      checkUserStatus{
        if (result) {
          myLocationListener.start();
        }
      }
    }
    void stop() {
      // 释放资源
      myLocationListener.stop();
    }
}

The above code itself is not a big problem. Its disadvantage is that in the actual production environment, there will be many pages and components that need to respond to the state changes of the life cycle, so a lot of code must be placed in the life cycle method, which will cause the code (such as and ) to onStart()become onStop()bloated , difficult to maintain.

In addition, there is another question:

MyPresenteronStartThe operation in the class checkUserStatusis a time-consuming operation. If it takes too long, Activityit will be completed before it is executed when it is destroyed, and then it will be stopexecuted after a while, but there will be no more . In this way, the component Resources cannot be released normally. If it still holds references inside, it will also cause memory leaks.myLocationListenerstartmyLocationListenerstopActivity

Lifecycle

So, Lifecycleit came out, which encapsulates the complex operations of life cycle management LifecycleOwner(such as Activity, Fragment and other "view controller" base classes) through the "template method mode" and "observer mode".

For developers, there is only one sentence in the "view controller" class getLifecycle().addObserver(new MyObserver()), and Lifecyclewhen the life cycle of the view controller changes, MyObserverthey can perceive it internally.

protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   setContentView(R.layout.activity_lifecycle);
   // 使MyObserver感知生命周期
   getLifecycle().addObserver(new MyObserver());
}

See how it's done:

# ComponentActivity
private final LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this);   
public Lifecycle getLifecycle() {
    return mLifecycleRegistry;
}
# LifecycleRegistry
public LifecycleRegistry(@NonNull LifecycleOwner provider) {
    this(provider, true);
}
private FastSafeIterableMap<LifecycleObserver, ObserverWithState> mObserverMap =
            new FastSafeIterableMap<>();
public void addObserver(@NonNull LifecycleObserver observer) {
  mObserverMap.putIfAbsent(observer, statefulObserver);
  ...
}
public void removeObserver(@NonNull LifecycleObserver observer) {
   mObserverMap.remove(observer);
}
void dispatchEvent(LifecycleOwner owner, Event event) {
  State newState = event.getTargetState();
  mState = min(mState, newState);
  mLifecycleObserver.onStateChanged(owner, event);
  mState = newState;
}

Because Activityit is implemented LifecycleOwner, it can be used directlygetLifecycle()

# ComponentActivity
protected void onCreate(@Nullable Bundle savedInstanceState) {
    // 关键代码:通过ReportFragment完成生命周期事件分发
    ReportFragment.injectIfNeededIn(this); 
    if (mContentLayoutId != 0) {
        setContentView(mContentLayoutId);
    }
}
# ReportFragment
static void dispatch(@NonNull Activity activity, @NonNull Lifecycle.Event event) {
    if (activity instanceof LifecycleOwner) {
        Lifecycle lifecycle = ((LifecycleOwner) activity).getLifecycle();
        if (lifecycle instanceof LifecycleRegistry) {
          // 处理生命周期事件,更新当前都状态并通知所有的注册的LifecycleObserver
          ((LifecycleRegistry) lifecycle).handleLifecycleEvent(event);
        }
    }
}
# LifecycleRegistry
public void handleLifecycleEvent(@NonNull Lifecycle.Event event) {
    enforceMainThreadIfNeeded("handleLifecycleEvent");
    moveToState(event.getTargetState());
}

summary

So Lifecyclethe existence of is to solve the problem of "lifecycle management" consistency.

Jetpack LiveData

origin

When there is none LiveData, we distribute messages in scenarios such as network request callbacks and cross-page communication, mostly through EventBusinterfaces callback.

For example, the frequently used EventBusmethod of waiting for the message bus will have problems:

It lacks a constraint. When we use it, it is easy to use it everywhere, and it will be very difficult to trace the source of the data in the end.

In addition, EventBusit is also very troublesome to deal with the life cycle. Due to the need for manual control, it is easy to have inconsistent life cycle management.

LiveData

First look at the official introduction:

LiveDatais an observable data storage class. Unlike regular observable classes, LiveDatait is lifecycle aware, meaning it follows the lifecycle of other application components (like Activity/Fragment). This awareness ensures that LiveDataonly application component observers that are in an active lifecycle state are updated.

If the life cycle of the observer is in STARTEDor RESUMEDstate, LiveDatathe observer will be considered to be in the active state, and the active observer will be notified of the update, and the inactive observer will not receive the change notification.

LiveDataIt is the embodiment of the observer mode , starting LiveDatawith observethe method:

public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
  // LifecycleOwner是DESTROYED状态,直接忽略
  if (owner.getLifecycle().getCurrentState() == DESTROYED) {
      return;
  }
  // 绑定生命周期的Observer
  LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
  ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
  // 让该Observer可以感知生命周期
  owner.getLifecycle().addObserver(wrapper);
}

observeForeverSimilar to observe(), except that it will think that the observer is always active and will not automatically remove the observer.

LiveDataA very important part is the data update: ·

LiveDataThe native API provides two ways for developers to update data, namely setValue()and postValue(), calling them will trigger the observer and update the UI .

setValue()The method must be called in the main threadpostValue() , and the method is more suitable for calling in the child thread . postValue()It will eventually be called setValue, just look at setValuethe method:

protected void setValue(T value) {
  assertMainThread("setValue");
  mVersion++;
  mData = value;
  dispatchingValue(null);
}
void dispatchingValue(@Nullable ObserverWrapper initiator) {
  ...
  for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =
          mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
      considerNotify(iterator.next().getValue());
  }
}
private void considerNotify(ObserverWrapper observer) {
    if (!observer.mActive) {
        return;
    }
    ...
    observer.mObserver.onChanged((T) mData);
}

Small question: We have an advantage in using it LiveDatathat no memory leaks will occur. How do we do it?

observeThis requires finding the answer from the methods mentioned above

public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
  LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
  owner.getLifecycle().addObserver(wrapper);
}

The first one passed is LifecycleOwnerthat the second parameter Obserseris actually our observed callback. These two parameters are encapsulated into LifecycleBoundObserverobjects.

class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver {
    @Override
    public void onStateChanged(@NonNull LifecycleOwner source,
        @NonNull Lifecycle.Event event) {
    Lifecycle.State currentState = mOwner.getLifecycle().getCurrentState();
    if (currentState == DESTROYED) {
      // Destoryed状态下,自动移除mObserver,避免内存泄漏
      removeObserver(mObserver);
      return;
    }
    activeStateChanged(shouldBeActive());
    ...
 }

This explains why LiveDatait can automatically unsubscribe to avoid memory leaks , because it can sense Activityor Fragmentlife cycle internally.

PS: This design is very clever, give us an inspiration point:

When we first met the Lifecycle component and didn't understand it very well, we always subconsciously thought that it could manage the effective life cycle of large objects (for example), in fact, Presenterwe can completely apply this life cycle management to various Among the basic components of functions, such as large enough to consume memory MediaPlayer, complex drawing and design customization View, and small enough to be seen everywhere LiveData, they can LifecycleObserverrealize the ability to sense the life cycle through the implementation of the interface, and release heavy resources internally.

summary

LiveDataWith the ability to perceive the life cycle, let the observer update the interface when the application data changes, and there will be no memory leaks.

Jetpack ViewModel

origin

If not ViewModel, when we use MVPdevelopment, in order to realize the display of data on the UI, we often write many UIlayers Modelof code that call each other. These codes are cumbersome to write and have a certain degree of template. In addition, if some scenes (such as screen rotation) destroy and recreate the interface, then the interface-related data stored in it will be lost, and generally need to be stored and restored manually.

In order to solve these two pain points, ViewModelwe will come out and ViewModelreplace MVPthePresenter

ViewModelThe concept of is proposed in this way, it is like a state storage , storing various states in the UI.

Benefits of ViewModel

1. More standardized abstract interface

GoogleThe official recommendation ViewModelis to keep pure business code as much as possible , and not to hold any Viewlayer ( Activityor Fragment) or Lifecyclereferences, which ensures ViewModelthe testability of internal codes and avoids the Contextdifficulty of writing test codes due to related references (for example, MVPmiddle-levelPresenter codes) Testing requires additional costs, such as dependency injection or Mockto ensure unit testing).

It is also such a specification requirement that ViewModelthe UI layer reference cannot be held, which naturally avoids possible memory leaks.

2. Easier to save data

When the component is destroyed and rebuilt, the data related to the original component will also be lost. The simplest example is the rotation of the screen . If the data type is relatively simple and the amount of data is not large, you can onSaveInstanceState()store the data and pass it after the component is rebuilt to read and restore the data onCreate()from it Bundle. However, if there is a large amount of data that is inconvenient to serialize and deserialize, the above method will not be applicable.

ViewModelThe extended class will automatically retain its data in this case, and if it Activityis recreated, it will receive the same ViewModelinstance as before. When the ownership Activityis terminated, the method called by the framework ViewModelreleases onCleared()the corresponding resource.

3. More convenient communication between UI components

It is very common for Activitymultiple in one to communicate with each other. If the instantiation scope is the life cycle of , then two can hold the same instance, which means the sharing of data state .FragmentViewModelActivityFragmentViewModel

Next, analyze how its source code does this:

We can create a temporary, separate instance for the referenced page (such as Activity) through ViewModelProviderinjection . And through this you can getViewModelStoreOwnerViewModelViewModelProviderViewModelProviderViewModel

# this: ViewModelStoreOwner(interface)
ViewModelProvider(this).get(viewModelClass)

Divided into two steps of creation and acquisition, first look at ViewModelProviderwhat the creation does:

# ViewModelProvider 
public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
  // owner.getViewModelStore(),比如:owner是ComponentActivity
  this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
          ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
          : NewInstanceFactory.getInstance());
}
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
    mFactory = factory;
    mViewModelStore = store;
}
public interface ViewModelStoreOwner {
    ViewModelStore getViewModelStore();
}
# ComponentActivity implements ViewModelStoreOwner
public ViewModelStore getViewModelStore() {
    // 为空就创建
    ensureViewModelStore();
    return mViewModelStore;
}
void ensureViewModelStore() {
 if (mViewModelStore == null) {
     mViewModelStore = new ViewModelStore();
  }
}

This step is the cornerstone: binding ViewModelStoreOwnerthe . To put it simply, the same one gets the same one .mViewModelStoreViewModelProviderViewModelStoreOwnermViewModelStore

How to get the corresponding ViewModel:

# ViewModelProvider
private final ViewModelStore mViewModelStore;
public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
    String canonicalName = modelClass.getCanonicalName();
    return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
    ViewModel viewModel = mViewModelStore.get(key);
    // 直接返回已存在的viewModel
    if (modelClass.isInstance(viewModel)) {
        return (T) viewModel;
    }
    if (mFactory instanceof KeyedFactory) {
        viewModel = ((KeyedFactory) mFactory).create(key, modelClass);
    } else {
        viewModel = mFactory.create(modelClass);
    }
    // 存储viewModel
    mViewModelStore.put(key, viewModel);
    return (T) viewModel;
}
# ViewModelStore
public class ViewModelStore {
  private final HashMap<String, ViewModel> mMap = new HashMap<>();
  final void put(String key, ViewModel viewModel) {
    ViewModel oldViewModel = mMap.put(key, viewModel);
    if (oldViewModel != null) {
        oldViewModel.onCleared();
    }
  }
}

That is, through such a design, the effect similar to a singleton can be achieved : each page can share the state across pages by ViewModelProviderinjecting Activitythis ;ViewModelStoreOwner

At the same time, it will not be completely reduced to a simple and crude singleton : each page can manage its private state through ViewModelProviderinjection .this

For example, the following specific example:

When a ViewModelcertain in the application has been ViewModelProviderpassed in Activityand passed in Fragment, thisin fact, two different ViewModelinstances are generated and belong to different ones ViewModelStoreOwner. When a page thisheld by is referenced , the page held by is not affected.ViewModeldestoryActivityViewModel

summary

ViewModelIt is to solve the "state management" and "page communication" problems. With it ViewModel, when we are developing, we can greatly reduce the code that UIlayers Modelcall each other, and put more focus on the writing of business code .

Jetpack DataBinding

origin

Before DataBindingappeared, to update the view, you need to reference the view, and then call setxxxthe method:

TextView textView = findViewById(R.id.sample_text);
if (textView != null && viewModel != null) {
    textView.setText(viewModel.getUserName());
}

This approach has several disadvantages:

  • It is easy to have a null pointer (there are differences between the horizontal and vertical layouts, such as the textView control exists in the horizontal screen, but not in the vertical screen), and the reference to the view generally needs to be judged as null
  • Need to write template codefindViewById
  • If the business is complex, a control will be called in multiple places

DataBinding

DataBindingIt is a relatively controversial component. Many people's DataBindingperception of is to xmlwrite logic in:

  • xmlWrite expression logic in , you ca debugn't go wrong
  • If the logic is written in xmlit, xmlit assumes Presenter/ViewModelthe responsibility, and the responsibility becomes confusing.

Of course, if you look at it from the perspective of writing logic in xml, it will indeed cause xmlinability to debug and confusion of responsibilities.

But that's not DataBindingthe essence of it. DataBinding, which means data binding , that is, controls in the layout are bound to observable data .

<TextView
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:text="@{user.name}"/>

When a new value user.nameis set set, the controls bound to the data can be notified and refreshed. DataBindingThat said, the only change after using is that you don't need to manually call the view to set the new state, you just set the data itself.

Therefore, DataBindingit is not that it is difficult to debug because the UI logic is moved to XML. It is only responsible for binding data and binding UI controls to the final state data they need.

two-way binding

In the example introduced above, the flow of data is one-way. You only need to monitor data changes and display them on the UI, which is a one-way binding.

But in some scenarios, UI changes need to affect ViewModelthe data state of the layer, such as the UI layer EditText, which needs to be edited and updated LiveDatadata. This is where two-way binding is required .

AndroidIn native controls, most of the two-way binding usage scenarios DataBindinghave been implemented for us, such asEditText

<EditText
  android:id="@+id/etPassword"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:text="@={fragment.viewModel.password }" />

Compared with one-way binding, only one more =symbol is needed to ensure that the stateView of the layer and ViewModelthe layer are synchronized.

Two-way binding is very simple to use, but the definition is a little more troublesome than one-way binding. Even if the native control DataBindinghas helped us realize it, we need to implement it ourselves for three-party controls or custom controls .

give a chestnut

Here is an SwipeRefreshLayoutexample of pull-down refresh to see how two-way binding is implemented:

When we need: When we LiveDatamanually set the value, SwipeRefreshLayoutthe corresponding UI will also change; conversely, when the user manually pulls down to perform the refresh operation, LiveDatathe corresponding value will also become true(representing the status of the refresh):

// refreshing实际是一个LiveData:
val refreshing: MutableLiveData<Boolean> = MutableLiveData()
object SwipeRefreshLayoutBinding {
  // 1.@BindingAdapter 在数据发生更改时要执行的操作:
  // 每当LiveData的状态发生了变更,SwipeRefreshLayout的刷新状态也会发生对应的更新。
  @JvmStatic
  @BindingAdapter("app:bind_swipeRefreshLayout_refreshing")
  fun setSwipeRefreshLayoutRefreshing(
          swipeRefreshLayout: SwipeRefreshLayout,
          newValue: Boolean
  ) {
      // 判断值是否变化了,避免无限循环
      if (swipeRefreshLayout.isRefreshing != newValue)
          swipeRefreshLayout.isRefreshing = newValue
  }
  
  // 2.@InverseBindingAdapter: view视图发生更改时要调用的内容
  // 但是它不知道特性何时或如何更改,所以还需要设置视图监听器
  @JvmStatic
  @InverseBindingAdapter(
          attribute = "app:bind_swipeRefreshLayout_refreshing",  
          event = "app:bind_swipeRefreshLayout_refreshingAttrChanged"    // tag
  )
  fun isSwipeRefreshLayoutRefreshing(swipeRefreshLayout: SwipeRefreshLayout): Boolean =
          swipeRefreshLayout.isRefreshing
 }
  
  // 3. @BindingAdapter: 事件监听器与相应的 View 实例相关联
  // 观察view的状态变化,每当swipeRefreshLayout刷新状态被用户的操作改变
  @JvmStatic
  @BindingAdapter(
          "app:bind_swipeRefreshLayout_refreshingAttrChanged",     // tag
          requireAll = false
  )
  fun setOnRefreshListener(
          swipeRefreshLayout: SwipeRefreshLayout,
          bindingListener: InverseBindingListener?
  ) {
      if (bindingListener != null)
          // 监听下拉刷新
          swipeRefreshLayout.setOnRefreshListener {
              bindingListener.onChange()
          }
  }

Two-way binding SwipeRefreshLayoutabstracts the refresh state into one LiveData<Boolean>, we only need to xmldefine it in , and then we can ViewModelwrite code around this state in .

<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:bind_swipeRefreshLayout_refreshing="@={fragment.viewModel.refreshing}">
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

Note: avoid endless loops

Two-way binding has a fatal problem, that is, ANRthe exception caused by an infinite loop.

When Viewthe state of the layer UI is changed, ViewModelthe corresponding update occurs. At the same time, the update returns to the notification Viewlayer to refresh the UI, and the operation of refreshing the UI will notify ViewModelthe update...

AppTherefore, in order to ensure that there will be no exceptions caused by infinite loops ANR, we need to add a judgment in the initial code block to ensure that Viewthe UI will only be updated when the state changes.

summary

DataBindingBy binding the "control" to the "observable data", its essence is to bind the final state data to the View instead of writing logic in xml . When the data is set new content, the data is bound The control is then notified and refreshed.


In order to help everyone better understand the knowledge points of the Jetpack system, here is a more complete and detailed record of the "Jetpack Beginner to Master" (including Compose) study notes! ! ! Friends who are interested in Jetpose Compose can refer to the following...

Jetpack Family Bucket (Compose)

Jetpack section

  1. Jetpack之Lifecycle
  2. Jetpack之ViewModel
  3. Jetpack之DataBinding
  4. Navigation of Jetpack
  5. Jetpack之LiveData

Compose part
1. Detailed introduction to Jetpack Compose
2. Compose study notes
3. Detailed explanation of Compose animation usage

Guess you like

Origin blog.csdn.net/m0_71506521/article/details/132237646