Android Jetpack 组件LiveData源码解析

目录
  • 前言
  • 基本使用
    • 疑问
  • 源码分析
    • Observer
      • ObserverWrapper
      • LifecycleBoundObserver
    • MutableLiveData
      • postValue
      • setValue
  • 问题答疑
  • LiveData 特性引出的问题
    • 问题解决
  • 最后

前言

本文来分析下 LiveData 的源码,以及其在实际开发中的一些问题。

基本使用

一般来说 LiveData 都会配合 ViewModel 使用,篇幅原因关于 ViewModel 的内容将在后续博客中分析,目前可以将 ViewModel 理解为一个生命周期比 Activity 更长的对象,且不会造成内存泄漏。

示例代码:

MainViewModel.kt

class MainViewModel: ViewModel() {
    // 定义 LiveData 注意这里给了 0 作为初始值
    val number = MutableLiveData<Int>(0)
    fun add(){
        // 相当于 number.setValue(number.getValue() + 1)
        number.value = number.value?.plus(1)
    }
    fun sub(){
        // 相当于 number.setValue(number.getValue() - 1)
        number.value = number.value?.minus(1)
    }
}

MainACtivity.kt

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // 获取 ViewModel 实例
        val vm = ViewModelProvider(this).get(MainViewModel::class.java)
        // 调用 ViewModel 方法 进行加法操作
        bnAdd.setOnClickListener {
             vm.add()
        }
        // 调用 ViewModel 方法 进行减法操作
        bnSub.setOnClickListener {
            vm.sub()
        }
        // 观察 LiveData 变化, Observer 是接口,kotlin 写法简化
        vm.number.observe(this, Observer {
            tvNumber.text = it.toString()
        })
    }
}

XML 非常简单就不贴了,看下效果图:

疑问

很简单的功能,但是有两个问题需要注意:

  • 在 XML 中并没有给中间的 TextView 设置 text 属性,仅仅给 LiveData 赋值了初始值 0,就可以直接显示到 TextView 上;
  • 数值发生变化后,进行横竖屏切换后 TextView 依然保持着最新值(如果 number 作为普通 Int 放在 Activity 中,当 Activity 由于横竖屏切换导致重建会重新变为 0);

本文将以这两个问题作为切入点,对 LiveData 源码进行分析。

源码分析

Observer

从实例代码中很容易看出这是典型的观察者模式,当 LiveData 发生变化时会对其订阅者发送通知,将最新值传递过去,Observer 就相当于其观察者,先来看一下 Observer 接口:

public interface Observer<T> {
    void onChanged(T t);
}

当 LiveData 发生变化时,就会触发其观察者的 onChanged 方法,并传递最新值;

再看一下其添加订阅时的源码:

public abstract class LiveData<T> {
    //...
    public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
        // 检查是否在主线程
        assertMainThread("observe");
        // 如果观察者所在组件的生命周期为 DESTROYED 则直接 return
        if (owner.getLifecycle().getCurrentState() == DESTROYED) {
            return;
        }
        // LifecycleBoundObserver 实现了 ObserverWrapper
        // 理解为这是对 观察者 Observer 的一层包装类即可
        LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
        // mObservers 是一个 Map 容器,原始的 Observer 为 key,包装后的 wrapper 为 value
        ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
        // 同一个 observer 不能在不同的生命周期组件中进行订阅
        if (existing != null && !existing.isAttachedTo(owner)) {
            throw new IllegalArgumentException("Cannot add the same observer"
                    + " with different lifecycles");
        }
        // 重复订阅直接return
        if (existing != null) {
            return;
        }
        // LifecycleBoundObserver 利用 Lifecycle 实现自动解绑
        // Lifecycle 原理详见我之前的博客
        owner.getLifecycle().addObserver(wrapper);
    }
    // ...
}

从源码中得知订阅必须在主线程(这一点也非常适用于 Android 的 UI 更新), 订阅后会放入一个 Map 容器中存储;

ObserverWrapper

接着来看一下 LiveData 是如何对 Observer 进行包装的,LifecycleBoundObserver 实现了 ObserverWrapper,那么就先来看看 ObserverWrapper 的源码:

private abstract class ObserverWrapper {
    final Observer<? super T> mObserver; // Observer 原始对象
    boolean mActive; // 是否激活
    int mLastVersion = START_VERSION; // 版本号 默认 -1
    ObserverWrapper(Observer<? super T> observer) {
        mObserver = observer; // 赋值
    }
    abstract boolean shouldBeActive(); // 抽象方法
    boolean isAttachedTo(LifecycleOwner owner) {
        return false;
    }
    void detachObserver() {
    }
    void activeStateChanged(boolean newActive) {
        if (newActive == mActive) { // 如果值一样则返回
            return;
        }
        mActive = newActive; // 不一样则更新 mActive
        changeActiveCounter(mActive ? 1 : -1); // 记录有多少个激活状态的observer
        // 注意这里,如果mActive是从false变更为true 则调用一次 dispatchingValue
        // dispatchingValue 的源码下面再分析
        if (mActive) {
            dispatchingValue(this);
        }
    }
}

LifecycleBoundObserver

接着看一下 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);
    }
    // 如果再 activity 中进行 observer
    // 当 activity 生命周期发生变化时 会回调到这里
    @Override
    public void onStateChanged(@NonNull LifecycleOwner source,
            @NonNull Lifecycle.Event event) {
        Lifecycle.State currentState = mOwner.getLifecycle().getCurrentState();
        // 自动解绑
        if (currentState == DESTROYED) {
            // removeObserver 内部会将 observer 从 map 容器中移除
            // 并且调用其 detachObserver 方法
            removeObserver(mObserver);
            return;
        }
        Lifecycle.State prevState = null;
        while (prevState != currentState) {
            prevState = currentState;
            // activeStateChanged 上面已经说过了
            // 如果 mActive 由 fasle 变更为 true 会执行一次 dispatchingValue
            activeStateChanged(shouldBeActive());
            currentState = mOwner.getLifecycle().getCurrentState();
        }
    }
    // ...
}

MutableLiveData

上述的观察者相关的重要源码已经分析完,接着来看一下示例代码中定义的 MutableLiveData 源码:

public class MutableLiveData<T> extends LiveData<T> {
    public MutableLiveData(T value) {
        super(value);
    }
    public MutableLiveData() {
        super();
    }
    @Override
    public void postValue(T value) {
        super.postValue(value);
    }
    @Override
    public void setValue(T value) {
        super.setValue(value);
    }
}

继承自 LiveData,作用很明显暴露出其 postValue、setValue 方法,那么就先来看一下这两个方法调用逻辑

postValue

先来看看 postValue:

public abstract class LiveData<T> {
    protected void postValue(T value) {
        boolean postTask;
        synchronized (mDataLock) {
            // mPendingData 默认值为 NOT_SET
            postTask = mPendingData == NOT_SET;
            // 调用 postValue 后,会赋值成传进来的 value
            mPendingData = value;
        }
        if (!postTask) { // 第一次调用 肯定为 true
            return;
        }
        // 核心在于这一行,postToMainThread
        // 看名字也知道是切换到主线程去执行 mPostValueRunnable
        ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
    }
}

ArchTaskExecutor.getInstance() 会初始化其内部的 mDelegate 变量,其最终实现是 DefaultTaskExecutor;DefaultTaskExecutor 内部包含一个主线程 Handler,其 postToMainThread 方法就是利用 Handler 将 runnable 发送至主线程执行。这里面的源码比较简单,就不贴出来细节了,看一下 mPostValueRunnable 具体执行了什么:

private final Runnable mPostValueRunnable = new Runnable() {
    @Override
    public void run() {
        Object newValue;
        synchronized (mDataLock) { // 加锁同步
            newValue = mPendingData; // 获取最新传递过来的值
            mPendingData = NOT_SET; // 将 mPendingData 恢复为默认值
        }
        // 最终还是调用了 setValue
        setValue((T) newValue);
    }
};

可以看出 postValue 可以在任意线程调用,最终都会被切换到主线程调用 setValue,但是需要注意,频繁调用 postValue 可能会只保留最后一次的值,因为每次 postValue 会导致 mPendingData 设置为新的值,但如果多次 postValue 在子线程执行,但是主线程还没有来得及执行 mPostValueRunnable,会导致 mPendingData 没有被恢复为 NOT_SET,那么 postTask 即为 false,但 mPendingData 会设置为最新值,当 mPostValueRunnable 执行时从 mPendingData 中获取的也是最新值。

setValue

postValue 内部最终调用了 setValue,那么就来看看 setValue 的源码:

public abstract class LiveData<T> {
    static final int START_VERSION = -1;
    private volatile Object mData;
    private int mVersion
    // 带初始值的构造器
    public LiveData(T value) {
        mData = value; // 直接给 mData 赋值
        mVersion = START_VERSION + 1; //版本号 +1,也就是 0
    }
    // 无参构造器
    public LiveData() {
        mData = NOT_SET;
        mVersion = START_VERSION; // 版本号默认 -1
    }
    protected void setValue(T value) {
        // 内部根据 Looper 判断是否在主线程,不在主线程则抛出异常
        assertMainThread("setValue");
        // 版本号 +1
        mVersion++;
        // LiveData 的数据,也就是被观察的数据,设置为最新值
        mData = value;
        // 这里是重点
        dispatchingValue(null);
    }
}

从源码中得知,setValue 只能从主线程调用,内部对版本号进行++操作,并且设置 mData 为最新值,最终调用 dispatchingValue:

// 用于保存其观察者 Observer,Observer 会包装成
private SafeIterableMap<Observer<? super T>, ObserverWrapper> mObservers =
        new SafeIterableMap<>();
void dispatchingValue(@Nullable ObserverWrapper initiator) {
    if (mDispatchingValue) { // 默认为 false
        mDispatchInvalidated = true;
        return;
    }
    mDispatchingValue = true; // 进入方法后设置为 true
    do {
        mDispatchInvalidated = false;
        // setValue 传进来的是 null 不会进入这个 if
        // initiator 实际上就是观察者,如果传递进来一个观察者对象
        // 则只进行一次 considerNotify 方法调用
        if (initiator != null) {
            considerNotify(initiator);
            initiator = null;
        } else { // 遍历自身的观察者
            for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =
                    mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
                // 调用 considerNotify 将观察者传入
                considerNotify(iterator.next().getValue());
                if (mDispatchInvalidated) {
                    break;
                }
            }
        }
    } while (mDispatchInvalidated);
    mDispatchingValue = false; // 方法执行结束前 设置为 false
}
private void considerNotify(ObserverWrapper observer) {
    if (!observer.mActive) { // 未激活状态直接返回
        return;
    }
    // 判断是否可以是激活状态
    // LifecycleBoundObserver 中则是判断所在组件的生命周期是否为激活状态
    if (!observer.shouldBeActive()) {
        observer.activeStateChanged(false); // 将 observer 的 mActive 设置为 fasle
        return;
    }
    // 如果 observer 的版本号 大于 LiveData 本身的版本号 则直接返回
    if (observer.mLastVersion >= mVersion) {
        return;
    }
    // 将 observer 的版本号和 LiveData 本身的版本号同步
    observer.mLastVersion = mVersion;
    // 触发其 onChanged 方法回调
    observer.mObserver.onChanged((T) mData);
}

setValue 的源码并不复杂,总结一下:

  • mVersion 版本号 ++ 操作,并且 mData 设置为最新数据;
  • dispatchingValue(null) 遍历观察者容器,对符合条件的观察者调用其 onChanged 方法回调。

问题答疑

从源码中我们可以了解到,当调用 LiveData.observer 时,我们传入的 observer 对象会被包装成为 LifecycleBoundObserver,会自动感知所在组件的生命周期;

又因为 Lifecycle 会在观察组件生命周期之后就会进行状态同步,所以我们再调用 LiveData.observer 之后会触发一次 activeStateChanged,导致 observer 的 mActive 由 fasle 变为 true,所以会进行一次 dispatchingValue;

在示例代码中我们给 MainViewModel 中的 number 赋值了初始值 0,那么初始化时会调用 LiveData 有参的构造函数,其中对 mVersion 进行了 +1 操作,此时的 LiveData 中的 mVersion 变为了 0,而 observer 中的 mLastVersion 为 -1,所以会进行一次分发,所以 TextView 的 text 被设置为了 0;

而第二个问题和上述的原因类似,不过特殊点在于 number 是被定义在在 ViewModel 中,开头也提到过 ViewModel 暂时可以理解为生命周期长于 Activity 的对象,那么当 Activity 由于横竖屏切换导致重建后, ViewModel 中的数据并没有清楚,LiveData 自然保持着他的 mData 最新值以及其 mVersion 版本号,当 Actvitiy 重新调用 LiveData.observer 进行订阅时,传入的 observer 的 mVersion 已经变为 -1,所以同样会触发一次 onChanged 回调得到最新值;

LiveData 特性引出的问题

上述问题答疑中其实可以看出 LiveData 订阅后可以获取最新值这在数据流中属于粘性 事件。在示例代码中,横竖屏切换后仍然可以获取最新的值,这比较符合用户使用习惯。但实际开发中往往有着更复杂的场景,比如:定义一个 LiveData<Boolean>(false) 表示是否需要展示加载中弹窗,假设需求是用户点击按钮后展示,此时用户点击按钮,将其设置为 true,那么此时 Activiy 发生重建导致生命周期重新走一遍,此时的 LiveData 的 value 仍然为 true,重建后用户并没有点击按钮但弹窗仍然会显示;

这是一个很常见的业务需求,发生这种问题的根本原因是生命周期重新走之后导致 observer 的 mLastVersion 变更为 -1,而 LiveData 的 mVersion 不变,导致重新触发 onChanged 方法回调;

遇到这种情况该怎么办呢?难道 LiveData 设计的有问题?我认为这并非 google 官方设计的不好,而是 LiveData 本身就应该作用于时时刻刻需要获取最新值的场景,而并非所有的数据都需要放到 ViewModel 中用 LiveData 包裹。上述的问题更多的我认为是 LiveData 滥用而导致的。 但 LiveData 的 onChanged 的数据变化后进行回调很多场景使用起来又很方便,该怎么办?

问题解决

既然已经知道原因,源码又了解的差不多,很容易就能找到问题的切入点;那就是 considerNotify 方法中会有层层判断,只要有一个不符合则不会触发 onChanged 方法回调,可以反射修改 observer 的 mLastVersion 使其重新订阅后仍然和 LiveData 保持一致。 不过利用到了反射,那么风险度也自然提高。

还有更好的办法,SingleLiveData!我最初看到这个类是在 github 中的一个 issue 中,后来网上流传了很多版本,其原理是对 LiveData 进行包装,内部定义一个 HashMap<Observer<in T>, AtomicBoolean> 容器,重写其 observer 订阅方法,每个 observer 对应一个 AtomicBoolean 对象,在 setValue 之前先遍历将所有的 AtomicBoolean 设置为 true,接着重写其 observer 包装一层,在分发时判断并修改 AtomicBoolean 为 false。

我觉得这也是比较好的规避问题的方法,这里就随便贴一个了:

class SingleLiveData<T> : MutableLiveData<T>() {
    private val mPendingMap = HashMap<Observer<in T>, AtomicBoolean>()
    @MainThread
    override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
        val lifecycle = owner.lifecycle
        if (lifecycle.currentState == Lifecycle.State.DESTROYED) {
            return
        }
        mPendingMap[observer] = AtomicBoolean(false)
        lifecycle.addObserver(LifecycleEventObserver { source: LifecycleOwner?, event: Lifecycle.Event ->
            if (event == Lifecycle.Event.ON_DESTROY) {
                mPendingMap.remove(observer)
            }
        })
        super.observe(owner) { t: T ->
            val pending = mPendingMap[observer]
            if (pending != null && pending.compareAndSet(true, false)) {
                observer.onChanged(t)
            }
        }
    }
    @MainThread
    override fun observeForever(observer: Observer<in T>) {
        mPendingMap[observer] = AtomicBoolean(false)
        super.observeForever(observer)
    }
    @MainThread
    override fun removeObserver(observer: Observer<in T>) {
        mPendingMap.remove(observer)
        super.removeObserver(observer)
    }
    @MainThread
    override fun removeObservers(owner: LifecycleOwner) {
        mPendingMap.clear()
        super.removeObservers(owner)
    }
    @MainThread
    override fun setValue(t: T?) {
        for (value in mPendingMap.values) {
            value.set(true)
        }
        super.setValue(t)
    }
}

最后

我对于 LiveData 和网络上认为需要用 Flow 替换 LiveData 的观点不同,我觉得 LiveData 和 Flow 其实应该共存,或者说是结合实际场景具体选择,在需要绑定生命周期的场景下 LiveData 就是最佳选择,没必要强行使用 Flow,虽然 Flow 也提供了关联生命周期的做法,但如果项目中已经大面积使用 LiveData 真的没必要强行去替换,尤其是 Java Kotlin 结合的项目,Java 不支持 Flow 的情况下使用 LiveData 是最佳的选择;

以上就是Android Jetpack 组件LiveData源码解析的详细内容,更多关于Android Jetpack LiveData的资料请关注我们其它相关文章!

(0)

相关推荐

  • Android开发Jetpack Compose元素Modifier特性详解

    目录 正文 有序性 不可变性 正文 本文将会介绍Jetpack Compose中的Modifier.在谷歌官方文档中它的描述是这么一句话:Modifier元素是一个有序.不可变的集合,它可以往Jetpack Compose UI元素中添加修饰或者各种行为.例如,背景.填充和单击事件监听器装饰或添加行为到文本或按钮.本文将会从修饰符的两个特性有序和不可变入手来探究修饰符的应用,以下是本文目录: 有序性 不可变性 有序性 官方对修饰符定义的这个特性包含两个层面的意思,一是修饰符的使用是链式的它是有先

  • Android Jetpack Compose开发实用小技巧

    目录 前言 实用小技巧 如何移除View点击阴影 Text文本如何垂直居中 如何移除Button的点击阴影 Dialog宽度如何全屏 如何提升编码效率 前言 在Compose开发的过程中,我们会经常遇到一些看起来很简单却不知道如何处理的小问题,比如去除点击阴影.Dialog全屏等问题,本文记录了这些常见小问题的处理方式.如有更好方案欢迎大佬们交流探讨- 实用小技巧 如何移除View点击阴影 这里的View指的是除了Button系列的之外,如Button.TextButton等,也就是自身没有on

  • 融会贯通Android Jetpack Compose中的Snackbar

    目录 正文 主要的实现思路 Snackbar UI部分 正文 开始写Compose的时候,真的有点不习惯.思考方式和以前完全不同,有点类似ReactNative. 写习惯了之后,还真有点欲罢不能,行云流水~ Snackbar感觉就是Toast Plus版,可以自定义视图,还可以进行交互,可以用在很多地方实现意想不到的效果. 主要的实现思路 主要的实现思路有两部步: 1.Snackbar的控制逻辑 2.Snackbar的UI部分 Snackbar UI部分 class MainActivity :

  • Android Jetpack 狠活Lifecycles与LiveData使用详解

    目录 前言 正篇 结语 前言 今天在工作时,测试突然提了一个Bug给我,要求我将APP中某活动页面的UI界面要根据用户在由此页面跳转的下个页面操作,在返回时要实时更新. 在检查代码时,发现我已经对界面可变数据用LiveData去观测,但由于页面变化后并没有重新初始化UI,所以我放在初始化UI的请求根本没有起效,如上图所示例子,在进入下一个页面如若关闭开关,返回时无法及时更新,于是我便想到了安卓科技与狠活Lifecycles,去监听onResume,在resume时用livedata去post数据

  • Android Jetpack组件ViewModel基本用法详解

    目录 引言 一.概述与作用 二.基本用法 小结 引言 天道好轮回,终于星期五,但是还是忙碌了一天.在项目中,我遇到了一个问题,起因则是无法实时去获取信息来更新UI界面,因为我需要知道我是否获取到了实时信息,我想到的办法有三,利用Handler收发消息在子线程与主线程切换从而更新信息,其二则是利用在页面重绘的时候(一般是页面变动如跳转下个页面和将应用切至后台),其三就是利用Jetpack中最重要的组件之一ViewModel,最后我还是选择了ViewModel,因为感觉更方便. 其实想到的前面两个方

  • Android Jetpack导航组件Navigation创建使用详解

    目录 引言 依赖项 创建导航图 导航宿主 导航到目的地 传递参数 NavigationUI 多模块导航 引言 导航是指支持用户导航.进入和退出应用中不同内容片段的交互.Android Jetpack 的导航组件可实现导航,无论是简单的按钮点击,还是应用栏和抽屉式导航栏等更为复杂的模式,该组件均可应对. 依赖项 def nav_version = "2.5.2" implementation "androidx.navigation:navigation-fragment-kt

  • Android Jetpack 组件LiveData源码解析

    目录 前言 基本使用 疑问 源码分析 Observer ObserverWrapper LifecycleBoundObserver MutableLiveData postValue setValue 问题答疑 LiveData 特性引出的问题 问题解决 最后 前言 本文来分析下 LiveData 的源码,以及其在实际开发中的一些问题. 基本使用 一般来说 LiveData 都会配合 ViewModel 使用,篇幅原因关于 ViewModel 的内容将在后续博客中分析,目前可以将 ViewMo

  • Android文件存储SharedPreferences源码解析

    1.我们都知道SharedPreferences 是android可以用来存放key value的的文件. SharedPreferences sp = getSharedPreferences("fileName", Context.MODE_PRIVATE); SharedPreferences.Editor editor = sp.edit(); editor.putString("key","value"); editor.commit(

  • Vue如何实现组件的源码解析

    官网上关于组件继承分为两大类,全局组件和局部组件.无论哪种方式,最核心的是创建组件,然后根据场景不同注册组件. 有一点要牢记,"Vue.js 组件其实都是被扩展的 Vue 实例"! 1. 全局组件 // 方式一 var MyComponent = Vue.extend({ name: 'my-component', template: '<div>A custom component!</div>' }); Vue.component('my-component

  • Android AsyncTask使用以及源码解析

    综述 在Android中,我们需要进行一些耗时的操作,会将这个操作放在子线程中进行.在子线程操作完成以后我们可以通过Handler进行发送消息,通知UI进行一些更新操作(具体使用及其原理可以查看Android的消息机制--Handler的工作过程这篇文章).当然为了简化我们的操作,在Android1.5以后为我们提供了AsyncTask类,它能够将子线程处理完成后的结果返回到UI线程中,之后我们便可以根据这些结果进行一列的UI操作了. AsyncTask的使用方法 实际上AsyncTask内部也

  • Android 内核代码 wake_up源码解析

    目录 内核中通常用法: wake_up 的源码: func 赋值过程 wait_queue_head 和 wait_queue_entry 数据结构 两种等待任务 wait_queue_entry:线程 和 函数 default_wake_function 函数 综上: 内核中通常用法: 内核有个函数 wake_up 和 wake_up_interruptible 通常来说看到这俩函数调用就是唤醒等待队列上的线程. 直到看了epoll的源码,发现并非如此. bool wakeup_conditi

  • Android跑马灯MarqueeView源码解析

    跑马灯效果,大家可以去原作者浏览https://github.com/sfsheng0322/MarqueeView 下面看自定义控件的代码 public class MarqueeView extends ViewFlipper { private Context mContext; private List<String> notices; private boolean isSetAnimDuration = false; private OnItemClickListener onIt

  • Android开发Jetpack组件LiveData使用讲解

    目录 LiveData概述 LiveData优势 共享资源 LiveData使用 1 LiveData基本使用 2 Transformations.map() 3 Transformations.switchMap() 4 MediatorLiveData.addSource()合并数据 LiveData概述 LiveData 是一种可观察的数据存储器类: 与常规的可观察类不同,LiveData 具有生命周期感知能力,意指它遵循其他应用组件(如 Activity.Fragment 或 Servi

  • Android Jetpack组件库LiveData源码深入探究

    目录 前言 一.LiveData 二.使用案例 三.LiveData 实现原理 四.LiveData 相关源码 五.LiveData分发问题 Android Jetpack之ViewModel.LiveData Android Jetpack之LifeCycle Android Jetpack之DataBinding+ViewModel+LiveData+Room 前言 Jetpack是一个由多个技术库组成的套件,可帮助开发者遵循最佳做法,减少样板代码并编写可在各种Android版本和设备中一致

  • flutter图片组件核心类源码解析

    目录 导语 问题 Image的核心类图及其关系 网络图片的加载过程 网络图片数据的回调和展示过程 补上图片内存缓存的源码分析 如何支持图片的磁盘缓存 总结 导语 在使用flutter 自带图片组件的过程中,大家有没有考虑过flutter是如何加载一张网络图片的? 以及对自带的图片组件我们可以做些什么优化? 问题 flutter 网络图片是怎么请求的? 图片请求成功后是这么展示的? gif的每一帧是怎么支持展示的? 如何支持图片的磁盘缓存? 接下来,让我们带着问题一起探究flutter 图片组件的

  • 详解vue mint-ui源码解析之loadmore组件

    本文介绍了vue mint-ui源码解析之loadmore组件,分享给大家,具体如下: 接入 官方接入文档mint-ui loadmore文档 接入使用Example html <div id="app"> <mt-loadmore :top-method="loadTop" :bottom-method="loadBottom" :bottom-all-loaded="allLoaded" :max-dis

随机推荐