Android期末复习5-3:使用多线程与LiveData

题目

相比5.1节的任务,本任务在功能逻辑上要复杂一些。点击START按钮后,启动后台线程,每隔0.01s更新1次计数值,通过LiveData的实现类MutableLiveData感知数据并在UI中更新。START按钮被点击后会变成PAUSE按钮,同时使能STOP按钮。PAUSE按钮有两种行为,具体如下所述。(1)在PAUSE按钮状态下,点击STOP按钮,定时器结束,PAUSE按钮变成START按钮,STOP按钮禁止。(2)点击PAUSE按钮,该按钮变成RESUME按钮,并且STOP按钮禁止,定时器暂停。点击RESUME按钮,定时器工作,并且继续在上一次计数值的基础上计数,RESUME按钮变成PAUSE按钮,STOP按钮使能。

实现效果

 LiveData:实现后台线程与前台UI的数据共享以及数据感知,一般不单独使用,而是和视图模型ViewModel结合在一起。

代码+注释

CounterViewModel.java

package com.example.zdl_task5_3;

import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;

//创建自定义视图模型继承标准ViewModel
public class CounterViewModel extends ViewModel {
    //一般用MutableLiveData<T>定义LiveData数据,T是泛型,只接受类,float对应类为Float
    private MutableLiveData<Float> counter;
    //构造方法:初始化LiveData的值
    public CounterViewModel(){
        counter=new MutableLiveData<>();//对counter实例化(即给counter创建实例)
        counter.setValue(0.0f);//对counter初始化
    }

    //生成get方法返回LiveData的值。Code->generate->Getter
    public MutableLiveData<Float> getCounter(){
        return counter;
    }
}

 CounterThread.java

package com.example.zdl_task5_3;

import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelStoreOwner;

import java.util.concurrent.atomic.AtomicBoolean;

public class CounterThread extends Thread{
    private ViewModelStoreOwner owner;//创建拥有者,通过拥有者创建视图模型,也可以定义为Run方法中的局部变量
    private AtomicBoolean isRunning;//原子变量,控制线程进行和结束
    private MutableLiveData<Float> counter;//自定义LiveData计数器
    private int mode;//控制后台线程进行的模式

    //mode的种类,设置为常量供外部引用
    public static final int MODE_RESTART=0;//restart模式,把计数器值清零
    public static final int MODE_RESUME=1;//resume模式,在上次计数器值的基础上技术

    //两种构造方法
    //构造方法一:除owner外传mode进行模式控制
    public CounterThread(ViewModelStoreOwner owner,int mode){
        //可以不传owner,而直接传ViewModel对象
        //使用owner传参,可以验证后台线程与前台UI使用同一个owner获得的视图模型是否相同
        //相同视图模型实例对应的LiveData是一个对象
        this.owner=owner;
        this.mode=mode;
    }
    //构造方法二:只传owner,使用默认restart模式
    public CounterThread(ViewModelStoreOwner owner){
        this.owner=owner;
        mode=MODE_RESTART;
    }

    @Override
    public void run() {
        //原子变量初始化为true
        isRunning=new AtomicBoolean(true);

        // 使用视图模型方法:
        // 创建并获得具体视图模型,new ViewModelProvider创建,参数是视图模型的拥有者
        // ViewModelProvider是一个辅助类,允许在Activity或Fragment中获取ViewModel实例,而无需自己直接实例化ViewModel
        // get()方法:通过传递ViewModel类的类型(Class)来获取ViewModel实例
        CounterViewModel counterViewModel=new ViewModelProvider(owner).get(CounterViewModel.class);
        counter=counterViewModel.getCounter();//获得自定义LiveData的值,counter不会随着线程消亡,而是在owner的生命周期一直存在

        //如果是restart模式,重置计数器的值
        if (mode==MODE_RESTART){
            counter.postValue(0.0f);//在线程中使用postValue()改值,在主UI中,setValue()和postValue()均可
        }
        //如果是resume模式,不用管,直接照下面流程走
        while (isRunning.get()){
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //线程每睡0.01s,更新一次counter的值
            counter.postValue(counter.getValue()+0.01f);
            //在主UI中通过counter的Observer接口和回调方法取出更新值,更新UI
        }
    }
    public void stopCounter(){
        isRunning.set(false);
    }
}

MainActivity.java

package com.example.zdl_task5_3;

import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {
    CounterThread thread;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.my_main);

        //通过视图模型的辅助类ViewModelProvider来new一个视图模型,get方法获取ViewModel实例
        CounterViewModel counterViewModel=new ViewModelProvider(this).get(CounterViewModel.class);
        //从counterViewModel中取得视图模型中的自定义的LiveData数据,生命周期同MainActivity
        MutableLiveData<Float> counter = counterViewModel.getCounter();
        TextView tv = findViewById(R.id.tv_result);
        //LiveData具有侦听功能,在Observer接口中感知数据的变动
        counter.observe(this, new Observer<Float>() {
            @Override
            public void onChanged(Float aFloat) {//数据有变动则调用onChanged方法,把变动的值aFloat传过来
                String s = String.format("%.2f",aFloat);
                tv.setText(s);//把变动的数据更新到TextView上
            }
        });

        //按钮
        Button bt_start = findViewById(R.id.bt_start);
        Button bt_stop = findViewById(R.id.bt_stop);
        bt_start.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //状态:Start->Pause->Resume->Pause->Resume->……
                //或者Start->Stop->Start
                String start_tag = bt_start.getText().toString();//获取当前按钮状态
                if(start_tag.equalsIgnoreCase("Start")){//若为Start(equalsIgnoreCase忽略大小写判断是否相等)
                    thread = new CounterThread(MainActivity.this);//单参数构造方法,restart,计数器值清零
                    bt_stop.setEnabled(true);//恢复stop按钮
                    bt_start.setText("Pause");//Start按钮按下后设置为Pause
                    thread.start();//启动线程
                }else if(start_tag.equalsIgnoreCase("Pause")){
                    thread.stopCounter();//暂停计数,停止线程,线程变为空
                    bt_stop.setEnabled(false);//禁用stop按钮(线程终止,不禁用也没用了)
                    bt_start.setText("Resume");//Pause按钮按下后设置为Resume
                }else{//Resume:在上次计数基础上计数,需要用两参数构造方法
                    thread = new CounterThread(MainActivity.this,CounterThread.MODE_RESUME);
                    bt_start.setText("Pause");//Resume按钮按下后设置为Pause
                    bt_stop.setEnabled(true);//恢复stop按钮
                    thread.start();//启动线程
                }
            }
        });
        bt_stop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                bt_start.setText("Start");//bt_start恢复成start按钮
                bt_stop.setEnabled(false);//禁用bt_stop按钮
                thread.stopCounter();
            }
        });
    }
}

 my_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="21211870234周冬玲" />

    <TextView
        android:id="@+id/tv_result"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="0.00" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <Button
            android:id="@+id/bt_start"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="Start" />

        <Button
            android:id="@+id/bt_stop"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:enabled="false"
            android:text="Stop" />
    </LinearLayout>

</LinearLayout>

 重点总结

LiveData的值如何更新?数据变动的侦听如何实现?LiveData的生命周期(依赖于视图模型,视图模型生命周期通过其拥有者控制,拥有者可以是MainActivity,也可以是后台线程,随着视图模型的拥有者消亡而消亡,根据自己应用需求决定视图模型的应用有着是谁,进而控制其生命周期)

之后再总结,先放张PPT压压惊

虽然可以使用静态变量(全局)来解这道题,将计数器设置为静态变量,但是当遇到两个Activity使用的同一个线程的类的话,因为只有1个静态变量,那这两个Activity计数器的值就不能做到完全独立,因为他们都共享同一个静态变量。用LiveData来解决就是两个Activity有不同的视图模型,不会有干扰。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值