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的生命周期 -------------------------------@Overridevoid onCreateX(LifecycleOwner owner) {super.onCreateX(owner);}@Overridevoid onStartX(LifecycleOwner owner) {super.onStartX(owner);}@Overridevoid onStopX(LifecycleOwner owner) {super.onStopX(owner);}@Overridevoid onResumeX(LifecycleOwner owner) {super.onResumeX(owner);}@Overridevoid onPauseX(LifecycleOwner owner) {super.onPauseX(owner);}@Overridevoid onDestroyX(LifecycleOwner owner) {super.onDestroyX(owner);}@Overridevoid onAny(LifecycleOwner owner) {super.onAny(owner);}
}
示例,mainActivity:
public class MainActivity extends BaseActivity<FruitPresent> {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);EdgeToEdge.enable(this);setContentView(R.layout.activity_main);}@Overrideprotected FruitPresent createPresenter() {return new FruitPresent();}}
baseActivity的代码如下:
public abstract class BaseActivity<T extends BasePresenter> extends AppCompatActivity {protected T presenter;@Overrideprotected 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;@Overrideprotected 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() {@Overridepublic void onClick(View v) {test();}});}/*** 订阅 viewModel中,text的变化*/private void observerChange() {Observer<String> observer = new Observer<String>() {@Overridepublic void onChanged(String string) {// 收到数据变化时,通过setText 更新UItextView.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;@Overrideprotected 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() {@Overridepublic void onClick(View v) {test();}});}private void observerChange() {LiveDataBus.getInstance().with("text", String.class).observe(this, new Observer<String>() {@Overridepublic void onChanged(String string) {// 收到数据变化时,通过setText 更新UItextView.setText(string);}});}/*** 测试文字变化的代码*/private void test() {new Thread() {@Overridepublic 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" /><variablename="viewModel"type="com.liosen.androidnote.mvvm.viewmodel.TicTacToeViewModel" /></data><LinearLayoutandroid: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"><GridLayoutandroid:id="@+id/gl_chessboard"android:layout_width="wrap_content"android:layout_height="wrap_content"android:columnCount="3"android:rowCount="3"><Buttonstyle="@style/chessboard_btn"android:onClick="@{()->viewModel.onClickedChessboard(0,0)}"android:text='@{viewModel.board["00"]}' /><Buttonstyle="@style/chessboard_btn"android:onClick="@{()->viewModel.onClickedChessboard(0,1)}"android:text='@{viewModel.board["01"]}' /><Buttonstyle="@style/chessboard_btn"android:onClick="@{()->viewModel.onClickedChessboard(0,2)}"android:text='@{viewModel.board["02"]}' /><Buttonstyle="@style/chessboard_btn"android:onClick="@{()->viewModel.onClickedChessboard(1,0)}"android:text='@{viewModel.board["10"]}' /><Buttonstyle="@style/chessboard_btn"android:onClick="@{()->viewModel.onClickedChessboard(1,1)}"android:text='@{viewModel.board["11"]}' /><Buttonstyle="@style/chessboard_btn"android:onClick="@{()->viewModel.onClickedChessboard(1,2)}"android:text='@{viewModel.board["12"]}' /><Buttonstyle="@style/chessboard_btn"android:onClick="@{()->viewModel.onClickedChessboard(2,0)}"android:text='@{viewModel.board["20"]}' /><Buttonstyle="@style/chessboard_btn"android:onClick="@{()->viewModel.onClickedChessboard(2,1)}"android:text='@{viewModel.board["21"]}' /><Buttonstyle="@style/chessboard_btn"android:onClick="@{()->viewModel.onClickedChessboard(2,2)}"android:text='@{viewModel.board["22"]}' /></GridLayout><LinearLayoutandroid: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}"><TextViewandroid: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}"/><TextViewandroid: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或者Oboard.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(); //@Overrideprotected 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();}@Overridepublic boolean onCreateOptionsMenu(Menu menu) {MenuInflater inflater = getMenuInflater();inflater.inflate(R.menu.menu_tictactoe, menu);return super.onCreateOptionsMenu(menu);}@Overridepublic 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" /><variablename="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"><TextViewandroid: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" /><Buttonandroid: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;@Overrideprotected 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>() {@Overridepublic void onChanged(String string) {// 收到数据变化时,通过setText 更新UIbinding.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的基本使用了