0. 环境:
电脑:Windows10
Android Studio: 2024.3.2
编程语言: Java
Gradle version:8.11.1
Compile Sdk Version:35
Java 版本:Java11
1. 本篇文章涉及到的内容
lifecycle
livedata
databinding
viewModel
2. lifecycle简单使用
2.1注解的方法已过时
以MVP架构为例:
我们在basePresenter中实现lifecycleObserver的监听即可:
package com.liosen.androidnote.presenter;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.OnLifecycleEvent;
public class BasePresenter implements LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
void onCreateX(LifecycleOwner owner) {
}
@OnLifecycleEvent(Lifecycle.Event.ON_START)
void onStartX(LifecycleOwner owner) {
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
void onStopX(LifecycleOwner owner) {
}
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
void onResumeX(LifecycleOwner owner) {
}
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
void onPauseX(LifecycleOwner owner) {
}
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
void onDestroyX(LifecycleOwner owner) {
}
@OnLifecycleEvent(Lifecycle.Event.ON_ANY)
void onAny(LifecycleOwner owner) {
}
}
此时,我们要在实际presenter中监听生命周期,就可以继承基类来实现
package com.liosen.androidnote.presenter;
import androidx.lifecycle.LifecycleOwner;
public class FruitPresent extends BasePresenter{
//省略业务代码
·
·
·
// ------------------------------- 此时,可以监听activity的生命周期 -------------------------------
@Override
void onCreateX(LifecycleOwner owner) {
super.onCreateX(owner);
}
@Override
void onStartX(LifecycleOwner owner) {
super.onStartX(owner);
}
@Override
void onStopX(LifecycleOwner owner) {
super.onStopX(owner);
}
@Override
void onResumeX(LifecycleOwner owner) {
super.onResumeX(owner);
}
@Override
void onPauseX(LifecycleOwner owner) {
super.onPauseX(owner);
}
@Override
void onDestroyX(LifecycleOwner owner) {
super.onDestroyX(owner);
}
@Override
void onAny(LifecycleOwner owner) {
super.onAny(owner);
}
}
示例,mainActivity:
public class MainActivity extends BaseActivity<FruitPresent> {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
}
@Override
protected FruitPresent createPresenter() {
return new FruitPresent();
}
}
baseActivity的代码如下:
public abstract class BaseActivity<T extends BasePresenter> extends AppCompatActivity {
protected T presenter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
presenter = createPresenter();
init();
}
protected abstract T createPresenter();
protected void init() {
getLifecycle().addObserver(presenter);
}
}
细心的网友已经看出来了,目前版本@OnLifecycleEvent的注解已经过时了
我们通过源码可以看到最新的方法:
/**
* Annotation that can be used to mark methods on {@link LifecycleObserver} implementations that
* should be invoked to handle lifecycle events.
*
* @deprecated This annotation required the usage of code generation or reflection, which should
* be avoided. Use {@link DefaultLifecycleObserver} or
* {@link LifecycleEventObserver} instead.
*/
@SuppressWarnings("unused")
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Deprecated
public @interface OnLifecycleEvent {
Lifecycle.Event value();
}
2.2 使用DefaultLifecycleObserver
直接实现DefaultLifecycleObserver,就可以重写这些方法了

3. livedata的使用
3.1 livedata的简单使用
先写一个最简单的viewModel:
public class TextViewModel extends ViewModel {
private MutableLiveData<String> text; // 用于记录文字
public int count; // 用于记录次数
public MutableLiveData<String> getText() {
if (text == null) {
text = new MutableLiveData<>();
}
return text;
}
}
接着看示例代码:
public class MainActivity extends AppCompatActivity implements DefaultLifecycleObserver {
private TextView textView;
private Button button;
private TextViewModel model;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
textView = findViewById(R.id.textView);
button = findViewById(R.id.button);
model = new ViewModelProvider(this).get(TextViewModel.class);
observerChange();
// 按钮点击一次,计数增加一次
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
test();
}
});
}
/**
* 订阅 viewModel中,text的变化
*/
private void observerChange() {
Observer<String> observer = new Observer<String>() {
@Override
public void onChanged(String string) {
// 收到数据变化时,通过setText 更新UI
textView.setText(string);
}
};
model.getText().observe(this, observer);
}
/**
* 测试文字变化的代码
*/
private void test() {
String text = "liosen" + model.count++;
model.getText().setValue(text);
}
}
此时,我们每次点击按钮,count都会增加1次。然后通过viewModel储存数据,触发了数据变化。监听者监听到数据变化后,调用了onChanged,实现了UI更新。
好处是,即使发生了重新绘制(例如屏幕旋转)也可以保证数据不会被回收。重新加载页面时,也能显示livedata中缓存的数据。
3.2 livedata的消息通知
3.1中,我们学会了一个对象的数据存储。但是一个项目中,需要存储的对象肯定不止一个。如果每个对象都建一个viewModel,肯定会让项目非常不优雅。所以需要批量管理。于是,我们可以创建一个LiveDataBus
public class LiveDataBus {
private Map<String, MutableLiveData<Object>> bus; // hashMap用于存放订阅者
private static LiveDataBus liveDataBus = new LiveDataBus();
public LiveDataBus() {
bus = new HashMap<>();
}
public static LiveDataBus getInstance() {
return liveDataBus;
}
/**
* 注册订阅者
*/
public synchronized <T> MutableLiveData<T> with(String key, Class<T> type) {
if (!bus.containsKey(key)) {
bus.put(key, new MutableLiveData<Object>());
}
return (MutableLiveData<T>) bus.get(key);
}
}
此时,activity修改为:
public class MainActivity extends AppCompatActivity implements DefaultLifecycleObserver {
private TextView textView;
private Button button;
private TextViewModel model;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
textView = findViewById(R.id.textView);
button = findViewById(R.id.button);
model = new ViewModelProvider(this).get(TextViewModel.class);
observerChange();
// 按钮点击一次,计数增加一次
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
test();
}
});
}
private void observerChange() {
LiveDataBus.getInstance().with("text", String.class).observe(this, new Observer<String>() {
@Override
public void onChanged(String string) {
// 收到数据变化时,通过setText 更新UI
textView.setText(string);
}
});
}
/**
* 测试文字变化的代码
*/
private void test() {
new Thread() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
// livedata 发消息通知观察者
LiveDataBus.getInstance().with(
"text", // 这个key,为自定义。最终会储存在LiveDataBus中hashMap中
String.class).postValue(
"livedata: liosen" + count++ // 这个是具体变化的内容
);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}.start();
}
}
这样,我们就把每个viewModel都抽离出来,通过LiveDataBus中的map给保存好。
只需要每次通过
LiveDataBus.getInstance().with(key, String.class)
既可以postValue(),也可以observe();
这样就解决了项目中viewModel过多的问题
4. databinding和viewModel的使用
4.1 databinding的简单使用
可以参考我这篇文章:【安卓笔记】用MVC、MVP、MVVM来实现井字棋案例-CSDN博客。
我贴出文章中的部分内容:
······
4. MVVM实现井字棋功能
文件结构如下:(忽略mvc文件夹和mvp文件夹)4.1 第一步增加dataBinding
在app级别(如果有使用其他module,那该module也需要增加)的build.gradle中,android下增加,如下所示:android { ··· dataBinding { enable true } }
这里我插一嘴:dataBinding和viewBinding的区别:viewBinding:省略findViewById 的功能 dataBinding:除了viewBinding的功能,还能绑定data部分4.2 修改activity.xml
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <data> <import type="android.view.View" /> <variable name="viewModel" type="com.liosen.androidnote.mvvm.viewmodel.TicTacToeViewModel" /> </data> <LinearLayout android:id="@+id/main" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginTop="60dp" android:fitsSystemWindows="true" android:gravity="center_horizontal" android:orientation="vertical" tools:context=".TicTacToeActivity"> <GridLayout android:id="@+id/gl_chessboard" android:layout_width="wrap_content" android:layout_height="wrap_content" android:columnCount="3" android:rowCount="3"> <Button style="@style/chessboard_btn" android:onClick="@{()->viewModel.onClickedChessboard(0,0)}" android:text='@{viewModel.board["00"]}' /> <Button style="@style/chessboard_btn" android:onClick="@{()->viewModel.onClickedChessboard(0,1)}" android:text='@{viewModel.board["01"]}' /> <Button style="@style/chessboard_btn" android:onClick="@{()->viewModel.onClickedChessboard(0,2)}" android:text='@{viewModel.board["02"]}' /> <Button style="@style/chessboard_btn" android:onClick="@{()->viewModel.onClickedChessboard(1,0)}" android:text='@{viewModel.board["10"]}' /> <Button style="@style/chessboard_btn" android:onClick="@{()->viewModel.onClickedChessboard(1,1)}" android:text='@{viewModel.board["11"]}' /> <Button style="@style/chessboard_btn" android:onClick="@{()->viewModel.onClickedChessboard(1,2)}" android:text='@{viewModel.board["12"]}' /> <Button style="@style/chessboard_btn" android:onClick="@{()->viewModel.onClickedChessboard(2,0)}" android:text='@{viewModel.board["20"]}' /> <Button style="@style/chessboard_btn" android:onClick="@{()->viewModel.onClickedChessboard(2,1)}" android:text='@{viewModel.board["21"]}' /> <Button style="@style/chessboard_btn" android:onClick="@{()->viewModel.onClickedChessboard(2,2)}" android:text='@{viewModel.board["22"]}' /> </GridLayout> <LinearLayout android:id="@+id/ll_winner" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" android:visibility="@{viewModel.winner == null ? View.GONE : View.VISIBLE}" > <TextView android:id="@+id/tv_winner" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="20dp" android:textSize="40sp" tools:text="X" android:text="@{viewModel.winner}" /> <TextView android:id="@+id/tv_tips" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{viewModel.result}" android:textSize="30sp" /> </LinearLayout> </LinearLayout> </layout>可以看到有一些新内容:
首先,必须要用<layout></layout>包裹原有的xml
然后,<variable>标签需要至少引入viewModel,其中name为自定义的名称,type为绑定的ViewModel。此处,我需要将该xml和TicTacToeViewModel.java 进行绑定。
最后,看到<Button>标签中onClick方法变成了
android:onClick="@{()->viewModel.onClickedChessboard(0,0)}"格式为 "@{}" ,此处中间为lamda,viewModel中的onClickedChessboard方法,在后面的viewModel文件中会有。
text文字变成了
android:text='@{viewModel.board["00"]}'数据来源为viewModel中的board,同样的 在viewModel文件中会有。
4.3 增加viewModel文件
package com.liosen.androidnote.mvvm.viewmodel; import androidx.databinding.ObservableArrayMap; import androidx.databinding.ObservableField; import com.liosen.androidnote.mvvm.model.GameState; import com.liosen.androidnote.mvvm.model.Board; import com.liosen.androidnote.mvvm.model.Player; public class TicTacToeViewModel { public Board model; public final ObservableArrayMap<String, String> board = new ObservableArrayMap<>(); // 此处为被观察者,被观察的数据为map,用于存 <棋盘格子, 棋手> public final ObservableField<String> winner = new ObservableField<>(); // 此处也为被观察者,被观察的数据为String类型的对象 public final ObservableField<String> result = new ObservableField<>(); public TicTacToeViewModel() { model = new Board(); } public void reset() { model.restartGame(); winner.set(null); board.clear(); } public void onClickedChessboard(int row, int col) { Player player = model.mark(row, col); if (player != null) { // 棋盘格子 显示玩家X或者O board.put("" + row + col, player == null ? null : player.toString()); if (model.getWinner() != null) { // 如果胜利的棋手 不为空,则显示胜利的信息 winner.set(model.getWinner() == null ? null : model.getWinner().toString()); result.set("你赢了"); } else if (model.getState() == GameState.FINISHED) { winner.set(""); result.set("本局平局"); } } } }4.4 最后activity文件
package com.liosen.androidnote.mvvm.view; import android.os.Bundle; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import androidx.activity.EdgeToEdge; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.core.graphics.Insets; import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; import androidx.databinding.DataBindingUtil; import com.liosen.androidnote.R; import com.liosen.androidnote.databinding.ActivityMainMvvmBinding; import com.liosen.androidnote.mvvm.viewmodel.TicTacToeViewModel; public class TicTacToeMVVMActivity extends AppCompatActivity { // --------------------- View --------------------- TicTacToeViewModel viewModel = new TicTacToeViewModel(); // @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); EdgeToEdge.enable(this); // setContentView(R.layout.activity_main); // 此时已经不需要该行代码了,通过下面两行实现xml和activity的绑定 ActivityMainMvvmBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main_mvvm); // binding.setViewModel(viewModel); // ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> { Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); return insets; }); // 重置游戏 viewModel.reset(); } @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.menu_tictactoe, menu); return super.onCreateOptionsMenu(menu); } @Override public boolean onOptionsItemSelected(@NonNull MenuItem item) { if (item.getItemId() == R.id.reset) { viewModel.reset(); return true; } else { return super.onOptionsItemSelected(item); } } }activity中绑定viewModel即可。
甚至连findViewById也省了。如果需要在activity中操作UI,可以直接通过binding获取,例如:
binding.tvWinner
至于tvWinner是哪里来的:是由于在xml文件中,设置的id。这样生成binding文件(编译时自动生成)时,就会自动生成该field,可以通过binding获取到。
······
其中4. MVVM实现井字棋功能,就可以学会简单的使用。
4.2 结合lifecycle的使用
layout文件:
<?xml version="1.0" encoding="utf-8"?>
<layout>
<data>
<import type="android.view.View" />
<variable
name="viewModel"
type="com.liosen.androidnote.viewmodel.TextViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{viewModel.text}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{viewModel::test}"
android:text="count++"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
修改TextViewModel文件:
public class TextViewModel extends ViewModel {
private MutableLiveData<String> text; // 用于记录文字
public int count; // 用于记录次数
public MutableLiveData<String> getText() {
if (text == null) {
text = new MutableLiveData<>();
text.setValue("Hello World!"); // 初始化文字
}
return text;
}
/**
* 测试文字变化的代码
*/
public void test(View view) {
String text = "binding: liosen" + count++;
getText().setValue(text);
}
}
activity文件:
public class MainActivity extends AppCompatActivity {
ActivityMainBinding binding;
private TextViewModel model;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
model = new ViewModelProvider(this).get(TextViewModel.class);
binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
binding.setViewModel(model);
observerChange();
}
/**
* 订阅 viewModel中,text的变化
*/
private void observerChange() {
Observer<String> observer = new Observer<String>() {
@Override
public void onChanged(String string) {
// 收到数据变化时,通过setText 更新UI
binding.textView.setText(string);
}
};
model.getText().observe(this, observer);
}
}
可以看到,每次按钮的点击事件,实际上更新的是livedata中的text。由于我们在mainActivity中观察者观察了text的值,每当text的值发生变化时,就可以更新到UI当中。
4.3 viewModel
实际上,我们在3.1中已经实现了viewModel。3.2中也实现了viewModel。
3.1中的监听者在于自身viewModel
3.2中的监听者在activity中实现绑定了
两种都可以。
5. 写在最后
至此,我们就学会最基本的lifecycle、livedata、viewModel的基本使用了
1883

被折叠的 条评论
为什么被折叠?



