【安卓笔记】lifecycle与viewModel

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

liosen

好活当赏!感谢老板的打赏~!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值