Jetpack:LiveData使用指南,实现原理详细解析!

11 篇文章 0 订阅


简介

LiveData是一个可被观察的数据容器类。可以将LiveData理解为一个数据的容器,它将数据包装起来,使数据成为被观察者,当该数据发生变化时,观察者能够获得通知。

LiveData和ViewModel的关系

本篇文章内容结合使用了ViewModel,如果不了解ViewModel可移步查看Jetpack:ViewModel使用指南,实现原理详细解析!
ViewModel用于存放页面所需要的各种数据,不仅如此,我们还可以在其中放一些与数据相关的业务逻辑。我们可以在ViewModel中进行数据的加工、获取等操作。因此,ViewModel的数据可能会随着业务的变化而变化。

但是对于页面来说,它并不需要关心ViewModel中的业务变化,只关心数据在变化之后可以获取到通知并进行更新。LiveData就可以做到,在ViewModel中的数据发生变化时通知页面。所以,LiveData通常被放在ViewModel中用来包装那些需要被界面观察的数据。

依赖

buildscript {
    //android lifecycle version
    ext.lifecycle_version = "2.3.0"
}
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
// LiveData
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"

LiveData的基本使用

写一个计时器,每隔一秒钟是同LiveData通知主界面刷新。

1.LiveData是一个抽象类,不能直接使用。通常我们使用的是它的直接子类MutableLiveData。使用MutableLiveData将要发送的num包装起来,异步线程的话使用postValue设置要发送的值。ViewModel的代码如下所示:

class TimingWithLiveDataViewModel : ViewModel() {

    /**
     * 当前的计数 初始值为0
     */
    val currentNum: MutableLiveData<Int> = MutableLiveData(0)

    /**
     * job
     */
    var job: Job? = null

    /**
     * 开始计数
     */
    fun startTiming() {
        if (job == null) {
            job = viewModelScope.launch(Dispatchers.Default) {
                while (true) {
                    delay(1000)
                    var value = currentNum.value ?: 0
                    currentNum.postValue(++value)
                }
            }
        }
    }

    override fun onCleared() {
        super.onCleared()
        job?.cancel()
        job = null
    }

}

2.在activity中,使用observe,设置监听回调。完成页面与ViewModel之间的通信。

class TimingWithLiveDataActivity : AppCompatActivity() {

    lateinit var binding: ActivityCommonBinding

    lateinit var timingWithLiveDataViewModel: TimingWithLiveDataViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        initView()
        initViewModel()
        initData()
    }

    @SuppressLint("SetTextI18n")
    private fun initView() {
        binding = ActivityCommonBinding.inflate(layoutInflater)
        setContentView(binding.root)
        binding.btDoAny.visibility = View.VISIBLE
        binding.btDoAny.text = "reset"
    }

    private fun initViewModel() {
        timingWithLiveDataViewModel =
            ViewModelProvider(this)[TimingWithLiveDataViewModel::class.java]
    }

    @SuppressLint("SetTextI18n")
    private fun initData() {
        //开始倒计时
        timingWithLiveDataViewModel.startTiming()

        timingWithLiveDataViewModel.currentNum.observe(this, { currentNum ->
            //观察到数据变化 更新UI
            binding.tvContent.text = "currentNum:$currentNum"
        })

        //通过LiveData重置数据
        binding.btDoAny.setOnClickListener {
            timingWithLiveDataViewModel.currentNum.value = 0
        }
    }

}

通过LiveData.observe()方法对LiveData所包装的数据进行观察。当我们希望修改LiveData所包装的数据时,则可以通过LiveData.postValue()/LiveData.setValue()方法来完成。postValue()方法用在非UI线程中,若在UI线程中,则使用setValue()方法。注意:setValue()和observe()不可以使用在非UI线程之中,否则会抛异常,稍后的源码分析会讲到,先贴上抛异常的代码。

static void assertMainThread(String methodName) {
    if (!ArchTaskExecutor.getInstance().isMainThread()) {
        throw new IllegalStateException("Cannot invoke " + methodName + " on a background thread");
    }
}

LiveData.observeForever()与observe()的区别

LiveData还有一个名为observeForever()的方法,使用起来与observe()没有太大差别。它们的区别主要生命周期的监听。

observe

会在lifecycle大于等于Started的时候才为激活状态(可以观察到数据变化给回调),在lifecycle为Destroy的时候会自动调用removeObserver()移除观察者。可以有效的防止内存泄漏,程序异常。

observeForever

无论页面处于什么状态,observeForever()都能收到通知。因此,在用完之后,一定要记得调用removeObserver()方法来移除观察者,否则会造成了内存泄漏。

ViewModel+LiveData实现Fragment间通信

原理

ViewModel时储存在ViewModelStore里面的,而ViewModelStore是通过getViewModelStore获取的,我们只需要传入activity的ViewModelStoreOwner接口,那么多个fragment之间就获取的是同一个ViewModelStore了,那这样就能获取到同一个ViewModel了,然后利用LiveData观察者的特性即可实现数据之间的共享了。

具体实现

1.定义ShareDataViewModel利用LiveData包装progress字段。

class ShareDataViewModel : ViewModel() {

    val progress: MutableLiveData<Int> by lazy(LazyThreadSafetyMode.NONE) {
        MutableLiveData(0)
    }
}

2.定义相关布局文件,将两个fragment平放在一个布局里面。

<?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">

    <FrameLayout
        android:id="@+id/fl_id_1"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"/>

    <View
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:background="@color/design_default_color_primary"/>

    <FrameLayout
        android:id="@+id/fl_id_2"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"/>

</LinearLayout>

定义fragment布局文件

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

    <TextView
        android:id="@+id/tv_content"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:layout_above="@id/seekbar"
        android:text="@string/app_name"/>

    <SeekBar
        android:id="@+id/seekbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:max="100"
        android:layout_centerInParent="true"/>

</RelativeLayout>

3.编写Fragment代码,实现具体的通信,注意ViewModelProvider传入activity

class SeekBarFragment
@JvmOverloads
constructor(private val text: String = "") : Fragment() {

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {

        return FragmentSeekbarBinding.inflate(layoutInflater).let {
            fragmentSeekBarBinding ->

            fragmentSeekBarBinding.tvContent.text = this.text

            //注意这里使用activity,因为如果想共享数据需要使用同一个ViewModelStore。所以都是用activity的。
            val liveDataProgress = ViewModelProvider(activity!!)[ShareDataViewModel::class.java].progress

            //通过observe观察ViewModel中字段数据的变化,并在变化时得到通知
            liveDataProgress.observe(this){
                progress->
                fragmentSeekBarBinding.seekbar.progress = progress
            }

            fragmentSeekBarBinding.seekbar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
                override fun onProgressChanged(
                    seekBar: SeekBar?,
                    progress: Int,
                    fromUser: Boolean
                ) {
                    //当用户操作SeekBar时,更新ViewModel中的数据
                    liveDataProgress.value = progress
                }
                override fun onStartTrackingTouch(seekBar: SeekBar?) {
                }
                override fun onStopTrackingTouch(seekBar: SeekBar?) {
                }
            })
            fragmentSeekBarBinding.root
        }
    }
}

5.编写activity代码,将fragment实例化,并放入activity中。

class ShareFragmentActivity : AppCompatActivity(){

    lateinit var binding: ActivityFragmentContentBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        initView()
    }

    private fun initView(){
        binding = ActivityFragmentContentBinding.inflate(layoutInflater)
        setContentView(binding.root)
        supportFragmentManager.beginTransaction().add(binding.flId1.id,SeekBarFragment("One-Fragment")).commit()
        supportFragmentManager.beginTransaction().add(binding.flId2.id,SeekBarFragment("Two-Fragment")).commit()
    }

}

结果

无论滑动上面的SeekBar还是下面的SeekBar,另一个Fragment的SeekBar一定会跟着滑动。在滑动SeekBar时,通过LiveData.setValue()方法,修改了ViewModel中LiveData包装的数据(progress字段)。由于Fragment通过LiveData.observe()方法监听了数据的变化,因此progress字段被修改后,Fragment能够第一时间收到通知并更新UI。

在这里插入图片描述

LiveData原理详解

LiveData.observe的源码:

public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
    assertMainThread("observe");
    if (owner.getLifecycle().getCurrentState() == DESTROYED) {
        // ignore
        return;
    }
    LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
    ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
    if (existing != null && !existing.isAttachedTo(owner)) {
        throw new IllegalArgumentException("Cannot add the same observer"
                                           + " with different lifecycles");
    }
    if (existing != null) {
        return;
    }
    owner.getLifecycle().addObserver(wrapper);
}

上面说到过observe只能在主线程使用,observe里面第一步就用assertMainThread进行了主线程判断,如果当前运行的不是主线程的话,直接就抛异常了(assertMainThread内部实现在上面贴过了)。同理LiveData的observeForever、removeObserver、removeObservers与setValue,也是一样的道理,在方法的第一步就用assertMainThread进行了判断,只能在主线程使用,否则就抛异常

接下来看和生命周期相关的内容,如果当前生命周期是DESTROYED的话就直接return。LifecycleOwner传入到了LifecycleBoundObserver里面进行了包装,将包装后的对象和observer传入了mObservers(一个key value集合)进行保存,最后注册生命周期。所以看看LifecycleBoundObserver做了什么操作

class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver {
        @NonNull
        final LifecycleOwner mOwner;

        LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer<? super T> observer) {
            super(observer);
            mOwner = owner;
        }

        @Override
        boolean shouldBeActive() {
            return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
        }

        @Override
        public void onStateChanged(@NonNull LifecycleOwner source,
                @NonNull Lifecycle.Event event) {
            Lifecycle.State currentState = mOwner.getLifecycle().getCurrentState();
            if (currentState == DESTROYED) {
                removeObserver(mObserver);
                return;
            }
            Lifecycle.State prevState = null;
            while (prevState != currentState) {
                prevState = currentState;
                activeStateChanged(shouldBeActive());
                currentState = mOwner.getLifecycle().getCurrentState();
            }
        }

        @Override
        boolean isAttachedTo(LifecycleOwner owner) {
            return mOwner == owner;
        }

        @Override
        void detachObserver() {
            mOwner.getLifecycle().removeObserver(this);
        }
    }

先看onStateChanged方法(当生命周期变化的话会回调该方法),判断如果当前的生命周期是DESTROYED直接就移除了观察者接下来判断当前生命周期的状态是否应该激活当前的观察者(只有观察者被激活才能接收到数据的变化),调用了shouldBeActive,而shouldBeActive则是判断了当前的生命周期是否大于等于STARTED,简单推理那应该就是生命周期在大于等于STARTED时才是激活状态。允许接收到值得变化。所以使用observe可接受到值得生命周期得范围应该时大于等于STARTED小于DESTROY

接下来看activeStateChanged方法,我们假设此时生命周期大于STARTED,则传入得就是true。

void activeStateChanged(boolean newActive) {
    if (newActive == mActive) {
        return;
    }
    // immediately set active state, so we'd never dispatch anything to inactive
    // owner
    mActive = newActive;
    changeActiveCounter(mActive ? 1 : -1);
    if (mActive) {
        dispatchingValue(this);
    }
}
}

赋值状态为激活状态,changeActiveCounter改变当前激活状态得数量(这里不进入方法内分析了,该方法主要记录了处于激活状态得observe数量,然后判断如果激活状态得数量由从0到1得话调用了onActive方法,激活状态数量由1到0得话调用了onInactive方法。这两方法都是空方法,需要我们自己手动取实现,比如继承MutableLiveData或者LiveData取自己实现一个LiveData,具体要不要实现取决于自己得业务逻辑)。接下来就是判断如果在激活状态的话,就调用dispatchingValue(传入了this)开始分发事件

    void dispatchingValue(@Nullable ObserverWrapper initiator) {
        if (mDispatchingValue) {
            mDispatchInvalidated = true;
            return;
        }
        mDispatchingValue = true;
        do {
            mDispatchInvalidated = false;
            if (initiator != null) {
                considerNotify(initiator);
                initiator = null;
            } else {
                for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =
                        mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
                    considerNotify(iterator.next().getValue());
                    if (mDispatchInvalidated) {
                        break;
                    }
                }
            }
        } while (mDispatchInvalidated);
        mDispatchingValue = false;
    }

因为传入了initiator,所以就调用了considerNotify

    private void considerNotify(ObserverWrapper observer) {
        if (!observer.mActive) {
            return;
        }
        if (!observer.shouldBeActive()) {
            observer.activeStateChanged(false);
            return;
        }
        if (observer.mLastVersion >= mVersion) {
            return;
        }
        observer.mLastVersion = mVersion;
        //调用到了observer得onChanged方法
        observer.mObserver.onChanged((T) mData);
    }

这个就很简单明了,满足激活状态下,shouldBeActive就是之前分析得大于等于STARTED,最后就调用了mObserver.onChanged就调用到了我们传入得Observer,实现了值得分发

observeForever与observe得区别就是,observe使用得是LifecycleBoundObserver进行得包装,对生命周期进行了监控,而observeForever使用了AlwaysActiveObserver进行了包装

private class AlwaysActiveObserver extends ObserverWrapper {

    AlwaysActiveObserver(Observer<? super T> observer) {
    	super(observer);
    }

    @Override
    boolean shouldBeActive() {
    	return true;
    }
}

里面没有对生命周期进行监控合适进行观察者移除销毁,且shouldBeActive永远返回true,所以无论页面处于什么状态,observeForever()都能收到通知。

再看看setValue得源码

protected void setValue(T value) {
    assertMainThread("setValue");
    mVersion++;
    mData = value;
    dispatchingValue(null);
}

其实也没干啥,判断主线程,调用版本记录,赋值,返回就调用到了dispatchingValue方法,dispatchingValue不就和我们前面分析得一样嘛,只不过传入了空,这时候,走了else得逻辑从集合里面去除所有得observe进行调度分发而已

看看postValue得源码

    protected void postValue(T value) {
        boolean postTask;
        synchronized (mDataLock) {
            postTask = mPendingData == NOT_SET;
            mPendingData = value;
        }
        if (!postTask) {
            return;
        }
        //调度得主线程执行
        ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
    }
    
    private final Runnable mPostValueRunnable = new Runnable() {
        @SuppressWarnings("unchecked")
        @Override
        public void run() {
            Object newValue;
            synchronized (mDataLock) {
                newValue = mPendingData;
                mPendingData = NOT_SET;
            }
            //调用了setValue
            setValue((T) newValue);
        }
    };

其实就是将异步线程调度到主线程执行,执行得mPostValueRunnable里面还是调用了setValue

其他杂谈

postValue使用相关注意点

1.如果同时在主线程依次执行如下代码:则值“b”将首先设置,然后主线程将用值“a”覆盖它

liveData.postValue("a");
liveData.setValue("b");

2.如果在主线程执行发布任务之前多次调用此方法,则只会调度最后一个值。因为先把值给了mData,然后才调用得Observe得onChange将mData传入。

简单总结

LiveData的就是一个观察者模式,且它可以感知到页面的生命周期,只在页面存活时才会进行通知,避免了内存泄漏。当然你也可以使用observeForever()让LiveData忽略页面的生命周期,但用完后,一定要记得使用removeObserver()移除,否则会造成内存泄漏。

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
要实现跑马灯效果,可以使用`Animatable`和`AnimatableVector`来创建一个自定义的跑马灯效果的`Text`控件。 首先,需要添加以下依赖到你的项目中: ```kotlin implementation "androidx.compose.animation:animation:1.0.0" implementation "androidx.compose.animation:animation-core:1.0.0" implementation "androidx.compose.animation:animation-graphics:1.0.0" ``` 然后,可以创建一个自定义的`MarqueeText`控件,继承自`Text`: ```kotlin import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.* import androidx.compose.animation.core.AnimationConstants.Infinite import androidx.compose.foundation.layout.Box import androidx.compose.material.Text import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.vector.PathNode import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp @Composable fun MarqueeText( text: String, modifier: Modifier = Modifier, speed: Dp = 100.dp, textColor: Color = Color.Black, backgroundColor: Color = Color.White, ) { var textWidth by remember { mutableStateOf(0f) } var offsetX by remember { mutableStateOf(0f) } var isRunning by remember { mutableStateOf(false) } val infiniteTransition = rememberInfiniteTransition() val animatedValue = animateFloatAsState( targetValue = if (isRunning) -textWidth else 0f, animationSpec = infiniteRepeatable( animation = tween(durationMillis = (textWidth / speed).toInt(), easing = LinearEasing), repeatMode = RepeatMode.Restart, ) ) LaunchedEffect(text) { isRunning = true } Box(modifier = modifier) { Text( text = text, modifier = Modifier.alpha(0f), onTextLayout = { layoutResult -> textWidth = layoutResult.size.width.toFloat() } ) Box( modifier = Modifier.offset(x = animatedValue.value) .onSizeChanged { size -> offsetX = size.width.toFloat() } ) { Text( text = text, modifier = Modifier.offset(x = offsetX), color = textColor, onTextLayout = { layoutResult -> textWidth = layoutResult.size.width.toFloat() } ) } Box( modifier = Modifier.offset(x = animatedValue.value + offsetX) .onSizeChanged { size -> offsetX += size.width.toFloat() } ) { Text( text = text, color = textColor, backgroundColor = animateColorAsState(targetValue = backgroundColor).value, onTextLayout = { layoutResult -> textWidth = layoutResult.size.width.toFloat() } ) } } } ``` 然后,你可以在你的Compose函数中使用`MarqueeText`控件来实现跑马灯效果: ```kotlin @Composable fun MyScreenContent() { MarqueeText( text = "Hello, Jetpack Compose!", modifier = Modifier.fillMaxWidth(), speed = 100.dp, textColor = Color.White, backgroundColor = Color.Blue, ) } ``` 这样,你就可以在你的界面上看到一个带有跑马灯效果的文本了。你可以根据需要调整`speed`来控制跑马灯的速度,`textColor`和`backgroundColor`来设置文本和背景颜色。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

pumpkin的玄学

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值