foreword
Jetpack
The establishment of the architecture components and "standardized development model" means that Android
the development of the software has entered a mature stage. Only with MVVM
a 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 MVVM
readers 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, ie
Activity/Fragment
- Presenter, the control layer, is responsible for business logic.
We know that MVP
it is the right MVC
improvement that solves MVC
two problems:
View
Responsibility is clear, logic is no longer written inActivity
, put inPresenter
;Model
no longer holdView
MVP
The most common implementation is this:
View
The layer receives user operation events, notifies them Presenter
, Presenter
performs logical processing, then notifies Model
the update data, Model
sends the updated data Presenter
, Presenter
and then notifies View
the update interface.
MVP
The essence is interface-oriented programming, which also has some pain points:
- A large number of interfaces will be introduced
IView
,IPresenter
increasing the complexity of implementation. View
andPresenter
hold each other to form a coupling.
With the development, Jetpack MVVM was born in response to the situation. It is a specific implementation of MVVM
the pattern in the development, and it is the official and recommended implementation method. It's layered:Android
Google
MVVM
- Model layer: used to obtain and store data
- View layer: namely
Activity/Fragment
- ViewModel layer: responsible for business logic
MVVM
The core is data-driven , and the decoupling is done more thoroughly ( ViewModel
without holding view
).
View
Events are generated, and ViewModel
after logical processing, Model
the update data is notified, and Model
the updated data is sent to ViewModel
automatically ViewModel
notify the View to update the interface
Jetpack Lifecycle
origin
Before Lifecycle
that, life cycle management was maintained manually. For example, we often Activity
initialize onStart
some members (for example MVP
, Presenter
) MediaPlayer
, etc., and then onStop
release 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:
MyPresenter
onStart
The operation in the class checkUserStatus
is a time-consuming operation. If it takes too long, Activity
it will be completed before it is executed when it is destroyed, and then it will be stop
executed 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.myLocationListener
start
myLocationListener
stop
Activity
Lifecycle
So, Lifecycle
it 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 Lifecycle
when the life cycle of the view controller changes, MyObserver
they 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 Activity
it 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 Lifecycle
the 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 EventBus
interfaces callback
.
For example, the frequently used EventBus
method 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, EventBus
it 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:
LiveData
is an observable data storage class. Unlike regular observable classes,LiveData
it is lifecycle aware, meaning it follows the lifecycle of other application components (like Activity/Fragment). This awareness ensures thatLiveData
only application component observers that are in an active lifecycle state are updated.
If the life cycle of the observer is in STARTED
or RESUMED
state, LiveData
the 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.
LiveData
It is the embodiment of the observer mode , starting LiveData
with observe
the 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);
}
observeForever
Similar to observe()
, except that it will think that the observer is always active and will not automatically remove the observer.
LiveData
A very important part is the data update: ·
LiveData
The 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 setValue
the 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 LiveData
that no memory leaks will occur. How do we do it?
observe
This 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 LifecycleOwner
that the second parameter Obserser
is actually our observed callback. These two parameters are encapsulated into LifecycleBoundObserver
objects.
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 LiveData
it can automatically unsubscribe to avoid memory leaks , because it can sense Activity
or Fragment
life 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,
Presenter
we can completely apply this life cycle management to various Among the basic components of functions, such as large enough to consume memoryMediaPlayer
, complex drawing and design customizationView
, and small enough to be seen everywhereLiveData
, they canLifecycleObserver
realize the ability to sense the life cycle through the implementation of the interface, and release heavy resources internally.
summary
LiveData
With 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 MVP
development, in order to realize the display of data on the UI, we often write many UI
layers Model
of 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, ViewModel
we will come out and ViewModel
replace MVP
thePresenter
ViewModel
The 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
Google
The official recommendation ViewModel
is to keep pure business code as much as possible , and not to hold any View
layer ( Activity
or Fragment
) or Lifecycle
references, which ensures ViewModel
the testability of internal codes and avoids the Context
difficulty of writing test codes due to related references (for example, MVP
middle-levelPresenter
codes) Testing requires additional costs, such as dependency injection or Mock
to ensure unit testing).
It is also such a specification requirement that ViewModel
the 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.
ViewModel
The extended class will automatically retain its data in this case, and if it Activity
is recreated, it will receive the same ViewModel
instance as before. When the ownership Activity
is terminated, the method called by the framework ViewModel
releases onCleared()
the corresponding resource.
3. More convenient communication between UI components
It is very common for Activity
multiple 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 .Fragment
ViewModel
Activity
Fragment
ViewModel
Next, analyze how its source code does this:
We can create a temporary, separate instance for the referenced page (such as Activity) through ViewModelProvider
injection . And through this you can getViewModelStoreOwner
ViewModel
ViewModelProvider
ViewModelProvider
ViewModel
# this: ViewModelStoreOwner(interface)
ViewModelProvider(this).get(viewModelClass)
Divided into two steps of creation and acquisition, first look at ViewModelProvider
what 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 ViewModelStoreOwner
the . To put it simply, the same one gets the same one .mViewModelStore
ViewModelProvider
ViewModelStoreOwner
mViewModelStore
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 ViewModelProvider
injecting Activity
this ;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 ViewModelProvider
injection .this
For example, the following specific example:
When a ViewModel
certain in the application has been ViewModelProvider
passed in Activity
and passed in Fragment
, this
in fact, two different ViewModel
instances are generated and belong to different ones ViewModelStoreOwner
. When a page this
held by is referenced , the page held by is not affected.ViewModel
destory
Activity
ViewModel
summary
ViewModel
It is to solve the "state management" and "page communication" problems. With it ViewModel
, when we are developing, we can greatly reduce the code that UI
layers Model
call each other, and put more focus on the writing of business code .
Jetpack DataBinding
origin
Before DataBinding
appeared, to update the view, you need to reference the view, and then call setxxx
the 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 code
findViewById
- If the business is complex, a control will be called in multiple places
DataBinding
DataBinding
It is a relatively controversial component. Many people's DataBinding
perception of is to xml
write logic in:
xml
Write expression logic in , you cadebug
n't go wrong- If the logic is written in
xml
it,xml
it assumesPresenter/ViewModel
the 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 xml
inability to debug and confusion of responsibilities.
But that's not DataBinding
the 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.name
is set set
, the controls bound to the data can be notified and refreshed. DataBinding
That 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, DataBinding
it 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 ViewModel
the data state of the layer, such as the UI layer EditText
, which needs to be edited and updated LiveData
data. This is where two-way binding is required .
Android
In native controls, most of the two-way binding usage scenarios DataBinding
have 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 ViewModel
the 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 DataBinding
has helped us realize it, we need to implement it ourselves for three-party controls or custom controls .
give a chestnut
Here is an SwipeRefreshLayout
example of pull-down refresh to see how two-way binding is implemented:
When we need: When we LiveData
manually set the value, SwipeRefreshLayout
the corresponding UI will also change; conversely, when the user manually pulls down to perform the refresh operation, LiveData
the 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 SwipeRefreshLayout
abstracts the refresh state into one LiveData<Boolean>
, we only need to xml
define it in , and then we can ViewModel
write 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, ANR
the exception caused by an infinite loop.
When View
the state of the layer UI is changed, ViewModel
the corresponding update occurs. At the same time, the update returns to the notification View
layer to refresh the UI, and the operation of refreshing the UI will notify ViewModel
the update...
App
Therefore, 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 View
the UI will only be updated when the state changes.
summary
DataBinding
By 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
- Jetpack之Lifecycle
- Jetpack之ViewModel
- Jetpack之DataBinding
- Navigation of Jetpack
- Jetpack之LiveData
Compose part
1. Detailed introduction to Jetpack Compose
2. Compose study notes
3. Detailed explanation of Compose animation usage