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组件ViewModel基本用法详解

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

  • Android Jetpack Compose开发实用小技巧

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

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

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

  • 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跑马灯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

  • 详解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

  • vue loadmore 组件滑动加载更多源码解析

    上一篇讲到在项目中使用上拉加载更多组件,但是由于实际项目开发中由于需求变更或者说在webview中上拉加载有些机型在上拉时候会把webview也一起上拉导致上拉加载不灵敏等问题,我们有时候也会换成滑动到底部自动加载的功能. 既然都是加载更多,很多代码思想势必相似,主要区别在于上拉和滑动到底部这个操作上,所以,我们需要注意: 上拉加载是point指针touch触摸事件,现在因为是滑动加载,需要添加scroll事件去监听然后执行相应回调 上拉加载主要计算触摸滚动距离,滑动加载主要计算containe

  • Android 中 SwipeLayout一个展示条目底层菜单的侧滑控件源码解析

    由于项目上的需要侧滑条目展示收藏按钮,记得之前代码家有写过一个厉害的开源控件 AndroidSwipeLayout 本来准备直接拿来使用,但是看过 issue 发现现在有不少使用者反应有不少的 bug ,而且代码家现在貌似也不进行维护了.故自己实现了一个所要效果的一个控件.因为只是实现我需要的效果,所以大家也能看到,代码里有不少地方我是写死的.希望对大家有些帮助.而且暂时也不需要 AndroidSwipeLayout 大而全的功能,算是变相给自己做的项目精简代码了. 完整示例代码请看:GitHu

  • Android源码解析之截屏事件流程

    今天这篇文章我们主要讲一下Android系统中的截屏事件处理流程.用过android系统手机的同学应该都知道,一般的android手机按下音量减少键和电源按键就会触发截屏事件(国内定制机做个修改的这里就不做考虑了).那么这里的截屏事件是如何触发的呢?触发之后android系统是如何实现截屏操作的呢?带着这两个问题,开始我们的源码阅读流程. 我们知道这里的截屏事件是通过我们的按键操作触发的,所以这里就需要我们从android系统的按键触发模块开始看起,由于我们在不同的App页面,操作音量减少键和电

  • Android okhttp的启动流程及源码解析

    前言 这篇文章主要讲解了okhttp的主要工作流程以及源码的解析. 什么是OKhttp 简单来说 OkHttp 就是一个客户端用来发送 HTTP 消息并对服务器的响应做出处理的应用层框架. 那么它有什么优点呢? 易使用.易扩展. 支持 HTTP/2 协议,允许对同一主机的所有请求共用同一个 socket 连接. 如果 HTTP/2 不可用, 使用连接池复用减少请求延迟. 支持 GZIP,减小了下载大小. 支持缓存处理,可以避免重复请求. 如果你的服务有多个 IP 地址,当第一次连接失败,OkHt

随机推荐

其他