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的基本使用了