Jetpack笔记

Posted fakerXuan

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Jetpack笔记相关的知识,希望对你有一定的参考价值。

什么是Jetpack

Jetpack是众多优秀组件的集合。是谷歌推出的一套引领android开发者逐渐统一开发规范的架构

Jetpack的优势

  • 基于生命周期感知的能力,可以减少NPE崩溃、内存泄露、模板代码。为我们开发出健壮且高质量的程序提供强力保障
  • 组件可以单独使用,也可以搭配使用,搭配Kotlin语言特性可以进一步加速开发。

Jetpack组件库介绍

在这里插入图片描述

  • Navigation
    在这里插入图片描述
    在这里插入图片描述
  • Lifecycle
    它持有组件(如Activity Fragment)生命周期状态的信息,并且允许其他对象观察此状态
    • 添加依赖
      在这里插入图片描述
      在这里插入图片描述
  • ViewModel:具备生命周期感知能力的数据存储组件
    页面配置更改数据不丢失、生命周期感应、数据共享
    在这里插入图片描述
  • ViewModel的数据存储、生命周期感知
    在这里插入图片描述
  • ViewModel的数据共享
    在这里插入图片描述
  • LiveData:具备生命周期感知能力的数据订阅、分发组件
    在这里插入图片描述
  • Room:轻量级ORM数据库,本质上是一个SQLite抽象层
    在这里插入图片描述
  • Room数据库读写
    在这里插入图片描述
    在这里插入图片描述
  • DataBinding:databinding只是一种工具,它解决的是View和数据之间的双向绑定。MVVM是一种结构模式,两者是有本质区别的。
    在这里插入图片描述
  • 布局中绑定数据
    在这里插入图片描述
  • WorkManager:新一代后台管理组件,功能十分强悍。Service能做的事情它都能做
    在这里插入图片描述
  • 执行任务
    在这里插入图片描述

使用Jetpack架构开发模式

在这里插入图片描述

在这里插入图片描述

LIfecycle架构组件原理解析

Lifecycle是什么?

持有组件(如Activity Fragment)生命周期状态的信息的组件,并且允许其他对象观察此状态

Lifecycle如何使用

在这里插入图片描述
在这里插入图片描述

  • FullLIfeCycle人Observer
    在这里插入图片描述
  • LifecycleEventObserver
    在这里插入图片描述
  • Fragment实现LIfecycle
    在这里插入图片描述
  • LIfecycleOwner、LIfecycle、LIfecycleRegistry的关系
    在这里插入图片描述
  • Activity实现LIfecycle
    在这里插入图片描述
  • ReportFragment核心源码
    在这里插入图片描述
    在这里插入图片描述
  • LIfecycleRegistry
    在这里插入图片描述
  • 宿主生命周期与宿主状态模型图
    在这里插入图片描述
  • 添加observer时,完整的生命周期事件分发
    在这里插入图片描述

LIveData架构组件原理解析

什么是LiveData

在这里插入图片描述

  • Handler与LiveData对比
    在这里插入图片描述
    在这里插入图片描述

LiveData的几种用法

  • MutableLiveData
    在这里插入图片描述
  • MediatorLivedata
    在这里插入图片描述
  • Transformations.map操作符
    在这里插入图片描述

LiveData核心方法

在这里插入图片描述

LiveData实现原理

  • 黏性消息分发流程
    在这里插入图片描述
  • 普通消息分发流程
    在这里插入图片描述

LiveData实战与应用

  • 需求分析:基于LiveData打造一款不会内存泄露,不用反注册的消息总线。且支持黏性事件
    在这里插入图片描述
  • 疑难解惑
    在这里插入图片描述
  • 控制黏性事件的突破口在于观察者的version字段
    在这里插入图片描述
    在这里插入图片描述
  • 支持黏性事件订阅、分发的StickyLiveData
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  • 代码实现
import androidx.lifecycle.*
import java.util.concurrent.ConcurrentHashMap

object HiDataBus {

    private val eventMap = ConcurrentHashMap<String, StickyLiveData<*>>()
    fun <T> with(eventName: String): StickyLiveData<T> {
        //基于事件名称 订阅、分发消息,
        //由于 一个 livedata 只能发送 一种数据类型
        //所以 不同的event事件,需要使用不同的livedata实例 去分发
        var liveData = eventMap[eventName]
        if (liveData == null) {
            liveData = StickyLiveData<T>(eventName)
            eventMap[eventName] = liveData
        }
        return liveData as StickyLiveData<T>
    }

    //通过一堆的反射,获取livedata当中的mversion字段,来控制黏性数据的分发与否,但是我们认为这种反射不够优雅。
    class StickyLiveData<T>(private val eventName: String) : LiveData<T>() {
        internal var mStickyData: T? = null
        internal var mVersion = 0

        fun setStickyData(stickyData: T) {
            mStickyData = stickyData
            setValue(stickyData)
            //就是在主线程去发送数据
        }

        fun postStickyData(stickyData: T) {
            mStickyData = stickyData
            postValue(stickyData)
            //不受线程的限制
        }

        override fun setValue(value: T) {
            mVersion++
            super.setValue(value)
        }

        override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
            observerSticky(owner, false, observer)
        }

        fun observerSticky(owner: LifecycleOwner, sticky: Boolean, observer: Observer<in T>) {
            //允许指定注册的观察则 是否需要关心黏性事件
            //sticky =true, 如果之前存在已经发送的数据,那么这个observer会受到之前的黏性事件消息
            owner.lifecycle.addObserver(LifecycleEventObserver { source, event ->
                //监听 宿主 发生销毁事件,主动把livedata 移除掉。
                if (event == Lifecycle.Event.ON_DESTROY) {
                    eventMap.remove(eventName)
                }
            })
            super.observe(owner, StickyObserver(this, sticky, observer))
        }
    }

    class StickyObserver<T>(
        val stickyLiveData: StickyLiveData<T>,
        val sticky: Boolean,
        val observer: Observer<in T>
    ) : Observer<T> {
        //lastVersion 和livedata的version 对齐的原因,就是为控制黏性事件的分发。
        //sticky 不等于true , 只能接收到注册之后发送的消息,如果要接收黏性事件,则sticky需要传递为true
        private var lastVersion = stickyLiveData.mVersion
        override fun onChanged(t: T) {

            if (lastVersion >= stickyLiveData.mVersion) {
                //就说明stickyLiveData  没有更新的数据需要发送。
                if (sticky && stickyLiveData.mStickyData != null) {
                    observer.onChanged(stickyLiveData.mStickyData)
                }
                return
            }

            lastVersion = stickyLiveData.mVersion
            observer.onChanged(t)
        }

    }
}

ViewModel架构组件原理解析

什么是ViewModel

  • 具有宿主生命周期感知能力的数据存储组件
  • ViewModel保存的数据,在页面配置变更导致页面销毁重建之后依然存在
  • 配置变更:横竖撇切换、分辨率调整、权限变更、系统字体样式变更

ViewModel的用法

  • 常规用法:存储的数据,仅仅只能当页面因为配置变更导致的销毁再重建时可复用。复用的是ViewModel的实例对象整体
    在这里插入图片描述
    在这里插入图片描述
  • 进阶用法
    在这里插入图片描述
    这段依赖是我手写的可能会有拼写错误!
api 'androidx.lifecycle:lifecycle-viewmodel-savedstate:2.2.0'

在这里插入图片描述

  • 跨页面的数据共享
    在这里插入图片描述

配置变更ViewModel复用实现原理

在这里插入图片描述

ViewModel数据复用进阶SavedState

在这里插入图片描述
在这里插入图片描述

  • SavedStateRegistry模型。一个总Bundle,key-value存储这每个ViewModel对应的子Bundle
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

总结

在这里插入图片描述

Room架构组件原理解析

什么是Room

在这里插入图片描述

Room高频用法

  • Room数据库创建
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

数据库创建实现原理

  • Room数据抽象设计
    在这里插入图片描述
  • 数据库创建流程
    在这里插入图片描述

Room与LIveData巧妙结合,数据变更监听

  • Room与LiveData巧妙结合
    在这里插入图片描述
  • 数据变更监听
    在这里插入图片描述

实战:基于Room封装APP离线缓存框架HiStorage

需求分析

在这里插入图片描述

疑难解惑

在这里插入图片描述

  • 持久化时,需要把object数据转换成二进制。body以及内嵌对象需要实现Serializeable
    在这里插入图片描述
    在这里插入图片描述
  • 定义数据库操作对象
    在这里插入图片描述
  • 定义缓存数据库CacheDataBase
    在这里插入图片描述
//获取操作数据库数据的dao对象
           abstract val getCacheDao: CacheDao
}
  • HiStorage封装
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.ObjectInputStream
import java.io.ObjectOutputStream

object HiStorage {
    fun <T> saveCache(key: String, body: T) {
        val cache = Cache()
        cache.key = key
        cache.data = toByteArray(body)
        CacheDatabase.get().cacheDao.saveCache(cache)
    }

    fun <T> getCache(key: String): T? {
        val cache = CacheDatabase.get().cacheDao.getCache(key)
        return (if (cache?.data != null) {
            toObject(cache.data)
        } else null) as? T
    }

    fun deleteCache(key: String) {
        val cache = Cache()
        cache.key = key
        CacheDatabase.get().cacheDao.deleteCache(cache)
    }


    private fun <T> toByteArray(body: T): ByteArray? {
        var baos: ByteArrayOutputStream? = null
        var oos: ObjectOutputStream? = null
        try {
            baos = ByteArrayOutputStream()
            oos = ObjectOutputStream(baos)
            oos.writeObject(body)
            oos.flush()
            return baos.toByteArray()
        } catch (e: Exception) {
            e.printStackTrace()
        } finally {
            baos?.close()
            oos?.close()
        }

        return ByteArray(0)
    }

    private fun toObject(data: ByteArray?): Any? {
        var bais: ByteArrayInputStream? = null
        var ois: ObjectInputStream? = null
        try {
            bais = ByteArrayInputStream(data)
            ois = ObjectInputStream(bais)
            return ois.readObject()
        } catch (e: Exception) {
            e.printStackTrace()
        } finally {

            bais?.close()
            ois?.close()
        }

        return null
    }
}

实战:基于Historage实现APP接口缓存

需求分析

在这里插入图片描述

疑难解惑

  • 定义缓存策略
    在这里插入图片描述
  • 接口声明缓存能力
    在这里插入图片描述
  • HiRestful搭配HiStorage实现缓存能力
    在这里插入图片描述

实战:基于LiveData实现登录结果通知和账户信息管理

需求分析

  • 收拢登录页面的拉起动作,方便管理
  • 使用LIveData转发登录结果
  • 收拢账户信息的存储和获取

疑难解惑

  • 定义全局AccountManager暴露拉起登录与获取结果的方法
    在这里插入图片描述
    在这里插入图片描述
  • 把获取userProfile接口请求的动作转移到AccountManager

总结

  • Lifecycle组件,Activity是如何实现宿主能力的?
    ReportFragment注入,目的是为了兼容直接继承自Activity和自定义LifecycleOwner的场景

  • LiveData与传统消息订阅、分发组件的不同?
    观察者不活跃(宿主不可见)不分发数据,避免无用的后台资源抢占
    基于声明周期,可避免内存泄露、NPE等问题

  • ViewModel是如何实现复用的?
    本质是ViewModelStroe对象以NonConfigurationInstances的形式被存储到ActivityClientRecord中,在页面重建后,又被传递到新的Activity对象中

  • savedState组件是如何实现数据复用的?
    利用了onSaveInstanceState的被触发的时机,最终会存储到ActivityRecord对象中。页面重建后,又被传递到新的ViewModel对象中。

  • Room数据库如何设计一款通用的数据缓存组件?
    把缓存数据二进制序列化,相比于JSON序列化效率更高,读取缓存时能直接还原成原本类型

  • ViewPager刷新不起作用的解决方案以及背后原理
    getItemPosition(Object object)刷新前后,让我们自行决定object在Viewpager中的去留
    getItemId(Int position)最好返回一个与位置无关的独一无二的id。可避免复用问题

  • 首页模块还可以优化的地方
    在这里插入图片描述

以上是关于Jetpack笔记的主要内容,如果未能解决你的问题,请参考以下文章

Android Jetpack导航,另一个主机片段内的主机片段

使用 Jetpack 的 Android 导航组件销毁/重新创建的片段

Jetpack Navigation Drawer 总是重新创建片段

Jetpack导航。改变每个片段的工具栏按钮

Android Jetpack Navigation:如何在 OnNavigatedListener 中获取目的地的片段实例?

Android Jetpack 导航禁用滚动位置