Android ABC 取其精华去其糟粕、JetPack好用的组件推荐

JetPack主流组件对比

jetpack组件名推荐指数槽点指数解析
LiveData★★★★★配合ViewModel和数据可以实现界面的动态更新,内部使用version版本控制和观察者模式,但是粘性事件不可选择,需要手动实现version进行事件控制
DataBinding★★★★★★★只需要加上标签即可自动生成ViewBinding(类似于ViewHolder),也可以在xml文件里直接进行赋值等操作,@BindingAdapter()可以动态绑定,调用方法,非常方便。但是xml文件中进行赋值操作逻辑不纯粹
WorkManager★★★★★实现Worker接口即可异步进行任务的执行,WorkContinuation可以定义任务的执行顺序,Constraints可以定义任务的执行环境,可以定义任务的执行时间等。但是任务之间不可以传递参数,是一个槽点 ,可以传递数据
Room★★★★Google对Sqlite的封装,因为其支持返回LiveData的缘故,所以推荐指数上到了4颗星,但是Room的性能确实不好,如果对数据库的性能有要求就无法选择Room了
ViewModel★★★★★可以根据页面的生命周期来管理页面的View状态,因为ViewModel和View载体(Activity、Fragment)分离的原因,ViewModel可以保存View的状态,例如Activity旋转导致的页面销毁等
DataStore★★本意是用于替换SharePreference,不过到现在都没有正式版本发出,等稳定后再用不迟
Startup★★★★用于启动时加载三方框架使用,如果框架之间有依赖还可以定制加载顺序
Paging★★★用于页面对多页数据进行管理,将数据提供者,数据和视图分开,但是现在还是限制太多,需要开发者进行修改

好用的组件使用说明

(参考Android官方文档)

LiveData

使用 LiveData 的优势

使用 LiveData 具有以下优势:

  • 确保界面符合数据状态

    LiveData 遵循观察者模式。当生命周期状态发生变化时,LiveData 会通知 Observer对象。您可以整合代码以在这些 Observer 对象中更新界面。观察者可以在每次发生更改时更新界面,而不是在每次应用数据发生更改时更新界面。

  • 不会发生内存泄漏

    观察者会绑定到 Lifecycle对象,并在其关联的生命周期遭到销毁后进行自我清理。

  • 不会因 Activity 停止而导致崩溃

    如果观察者的生命周期处于非活跃状态(如返回栈中的 Activity),则它不会接收任何 LiveData 事件。

  • 不再需要手动处理生命周期

    界面组件只是观察相关数据,不会停止或恢复观察。LiveData 将自动管理所有这些操作,因为它在观察时可以感知相关的生命周期状态变化。

  • 数据始终保持最新状态

    如果生命周期变为非活跃状态,它会在再次变为活跃状态时接收最新的数据。例如,曾经在后台的 Activity 会在返回前台后立即接收最新的数据。

  • 适当的配置更改

    如果由于配置更改(如设备旋转)而重新创建了 Activity 或 Fragment,它会立即接收最新的可用数据。

  • 共享资源

    您可以使用单一实例模式扩展LiveData对象以封装系统服务,以便在应用中共享它们。LiveData 对象连接到系统服务一次,然后需要相应资源的任何观察者只需观察 LiveData 对象。

Demo

class NameViewModel : ViewModel() {

        // Create a LiveData with a String
        val currentName: MutableLiveData<String> by lazy {
            MutableLiveData<String>()
        }

        // Rest of the ViewModel...
    }
    
class NameActivity : AppCompatActivity() {

        // Use the 'by viewModels()' Kotlin property delegate
        // from the activity-ktx artifact
        private val model: NameViewModel by viewModels()

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

            // Other code to setup the activity...

            // Create the observer which updates the UI.
            val nameObserver = Observer<String> { newName ->
                // Update the UI, in this case, a TextView.
                nameTextView.text = newName
            }

            // Observe the LiveData, passing in this activity as the LifecycleOwner and the observer.
            model.currentName.observe(this, nameObserver)
        }
    }

需要注意的是,所有的LiveData粘性事件不可选择,需要手动实现version进行事件控制,如果需要可选可以参照Android ABC LiveData的源码分析和去除粘性事件处理(附源码)

DataBinding

DataBinding包含ViewBinding

ViewBinding

为某个模块启用视图绑定功能后,系统会为该模块中包含的每个 XML 布局文件生成一个绑定类。每个绑定类均包含对根视图以及具有 ID 的所有视图的引用。系统会通过以下方式生成绑定类的名称:将 XML 文件的名称转换为驼峰式大小写,并在末尾添加“Binding”

例如,假设某个布局文件的名称为 result_profile.xml

<LinearLayout ... >
        <TextView android:id="@+id/name" />
        <ImageView android:cropToPadding="true" />
        <Button android:id="@+id/button"
            android:background="@drawable/rounded_button" />
    </LinearLayout>

所生成的绑定类的名称就为 ResultProfileBinding。此类具有两个字段:一个是名为 nameTextView,另一个是名为 buttonButton。该布局中的 ImageView 没有 ID,因此绑定类中不存在对它的引用。

每个绑定类还包含一个 getRoot() 方法,用于为相应布局文件的根视图提供直接引用。在此示例中,ResultProfileBinding 类中的 getRoot() 方法会返回 LinearLayout 根视图。

在 Activity 中使用视图绑定

如需设置绑定类的实例以供 Activity 使用,请在 Activity 的 onCreate() 方法中执行以下步骤:

  1. 调用生成的绑定类中包含的静态 inflate() 方法。此操作会创建该绑定类的实例以供 Activity 使用。
  2. 通过调用 getRoot() 方法或使用 Kotlin 属性语法获取对根视图的引用。
  3. 将根视图传递到 setContentView(),使其成为屏幕上的活动视图。
private lateinit var binding: ResultProfileBinding

    override fun onCreate(savedInstanceState: Bundle) {
        super.onCreate(savedInstanceState)
        binding = ResultProfileBinding.inflate(layoutInflater)
        val view = binding.root
        setContentView(view)
    }

使用

 binding.name.text = viewModel.name
 binding.button.setOnClickListener { viewModel.userClicked() }
在 Fragment 中使用视图绑定

如需设置绑定类的实例以供 Fragment 使用,请在 Fragment 的 onCreateView() 方法中执行以下步骤:

  1. 调用生成的绑定类中包含的静态 inflate() 方法。此操作会创建该绑定类的实例以供 Fragment 使用。
  2. 通过调用 getRoot() 方法或使用 Kotlin 属性语法获取对根视图的引用。
  3. 从 onCreateView() 方法返回根视图,使其成为屏幕上的活动视图。
    private var _binding: ResultProfileBinding? = null
    // This property is only valid between onCreateView and
    // onDestroyView.
    private val binding get() = _binding!!

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        _binding = ResultProfileBinding.inflate(inflater, container, false)
        val view = binding.root
        return view
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }

使用

binding.name.text = viewModel.name
binding.button.setOnClickListener { viewModel.userClicked() }   

注意:Fragment 的存在时间比其视图长。请务必在 Fragment 的 onDestroyView()方法中清除对绑定类实例的所有引用。

在模块中启用视图绑定之后,系统会为该模块中的每个 XML 布局文件生成一个绑定类。绑定类的实例包含对在相应布局中具有 ID 的所有视图的直接引用。

与 findViewById 的区别

与使用 findViewById 相比,视图绑定具有一些很显著的优点:

  • Null 安全:由于视图绑定会创建对视图的直接引用,因此不存在因视图 ID 无效而引发 Null 指针异常的风险。此外,如果视图仅出现在布局的某些配置中,则绑定类中包含其引用的字段会使用 @Nullable 标记。
  • 类型安全:每个绑定类中的字段均具有与它们在 XML 文件中引用的视图相匹配的类型。这意味着不存在发生类转换异常的风险。

这些差异意味着布局和代码之间的不兼容将会导致构建在编译时(而非运行时)失败。

与数据绑定的对比

视图绑定和数据绑定均会生成可用于直接引用视图的绑定类。但是,视图绑定旨在处理更简单的用例,与数据绑定相比,具有以下优势:

  • 更快的编译速度:视图绑定不需要处理注释,因此编译时间更短。
  • 易于使用:视图绑定不需要特别标记的 XML 布局文件,因此在应用中采用速度更快。在模块中启用视图绑定后,它会自动应用于该模块的所有布局。

反过来,与数据绑定相比,视图绑定也具有以下限制:

  • 视图绑定不支持布局变量或布局表达式,因此不能用于直接在 XML 布局文件中声明动态界面内容。
  • 视图绑定不支持双向数据绑定

考虑到这些因素,在某些情况下,最好在项目中同时使用视图绑定和数据绑定。您可以在需要高级功能的布局中使用数据绑定,而在不需要高级功能的布局中使用视图绑定。

DataBinding

数据绑定库可以使用声明性格式(而非程序化地)将布局中的界面组件绑定到应用中的数据源

使用前

findViewById<TextView>(R.id.sample_text).apply {
        text = viewModel.userName
    }

使用后

<TextView
        android:text="@{viewmodel.userName}" />

注意:在许多情况下,视图绑定可简化实现,提高性能,提供与数据绑定相同的好处。如果使用数据绑定的主要目的是取代 findViewById() 调用,请考虑改用视图绑定

概览-看看长啥样

可以在表达式中使用的绑定变量在 data 元素(界面布局根元素的同级)内定义。这两个元素都封装在 layout 标记中

<layout xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:app="http://schemas.android.com/apk/res-auto">
        <data>
            <variable
                name="viewmodel"
                type="com.myapp.data.ViewModel" />
        </data>
        <ConstraintLayout... /> <!-- UI layout's root element -->
    </layout>

每一个布局表达式都有一个对应的绑定适配器,要求必须进行框架调用来设置相应的属性或监听器。例如,绑定适配器负责调用 setText() 方法来设置文本属性,或者调用 setOnClickListener() 方法向点击事件添加监听器。最常拥的绑定适配器(例如针对本页面的示例中使用的 android:text 属性)可供您在 android.databinding.adapters 软件包中使用

  @BindingAdapter("app:goneUnless")
    fun goneUnless(view: View, visible: Boolean) {
        view.visibility = if (visible) View.VISIBLE else View.GONE
    }

配置

android {
        ...
        dataBinding {
            enabled = true
        }
    }

注意:即使应用模块不直接使用数据绑定,也必须为依赖于使用数据绑定的库的应用模块配置数据绑定。

Layout Editor 中的 Preview 窗格显示数据绑定表达式的默认值(如果提供)。例如,Preview 窗格会在以下示例声明的 TextView 微件上显示 my_default

<TextView android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@{user.firstName, default=my_default}"/>

数据绑定布局文件略有不同,以根标记 layout 开头,后跟 data 元素和 view 根元素。此视图元素是非绑定布局文件中的根

<?xml version="1.0" encoding="utf-8"?>
    <layout xmlns:android="http://schemas.android.com/apk/res/android">
       <data>
           <variable name="user" type="com.example.User"/>
       </data>
       <LinearLayout
           android:orientation="vertical"
           android:layout_width="match_parent"
           android:layout_height="match_parent">
           <TextView android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               android:text="@{user.firstName}"/>
           <TextView android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               android:text="@{user.lastName}"/>
       </LinearLayout>
    </layout>

data 中的 user 变量描述了可在此布局中使用的属性。

<variable name="user" type="com.example.User" />

布局中的表达式使用“@{}”语法写入特性属性中。在这里,TextView 文本被设置为 user 变量的 firstName 属性:

<TextView android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:text="@{user.firstName}" />

总结:关于ViewBinding和DataBinding,前者通过编译时生成代码Impl的方式帮助开发者动态生成了View和xml直接的绑定关系,避免了空指针需要频繁写findviewById等问题,DataBinding则是Google基于ViewBinding提供给安卓开发者的一种mvvm组成部分,配合LiveData和ViewModel可以轻松实现MVVM框架的搭建和开发

WorkManager

WorkManager是一个 API,使您可以轻松调度那些即使在退出应用或重启设备时仍应运行的可延期异步任务。WorkManager API 是一个针对先前的 Android 后台调度 API(包括 FirebaseJobDispatcherGcmNetworkManagerJobScheduler)的合适的建议替换组件。
在这里插入图片描述

功能

除了具备更为简单且一致的 API 之外,WorkManager 还具备许多其他关键优势,其中包括:

工作约束

使用工作约束明确定义工作运行的最佳条件。(例如,仅在设备采用 Wi-Fi 网络连接时、当设备处于空闲状态或者有足够的存储空间时运行。)

其实就是可以限制每一个Worker的工作条件,基于API已给出的。

强大的调度

WorkManager 允许您使用灵活的调度窗口调度工作,以运行一次性OneTimeWorkRequest重复PeriodicWorkRequest工作。还可以对工作进行标记或命名,以便调度唯一的、可替换的工作以及监控或取消工作组。已调度的工作存储在内部托管的 SQLite 数据库中,由 WorkManager 负责确保该工作持续进行,并在设备重新启动后重新调度。此外,WorkManager 遵循低电耗模式等省电功能和最佳做法,因此您在这方面无需担心。

灵活的重试政策

有时工作会失败。WorkManager 提供了灵活的重试政策,包括可配置的退避政策

工作链接

对于复杂的相关工作,请使用流畅自然的界面将各个工作任务链接在一起,这样您便可以控制哪些部分依序运行,哪些部分并行运行。

WorkManager.getInstance(...)
    .beginWith(listOf(workA,workB))
    .then(workC)
    .enqueue()

对于每项工作任务,您可以定义工作的输入和输出数据。将工作链接在一起时,WorkManager 会自动将输出数据从一个工作任务传递给下一个工作任务。

内置线程互操作性

WorkManager 无缝集成 RxJava协程,并可灵活地插入您自己的异步 API

使用 WorkManager 实现可延期、可靠的工作

WorkManager 适用于可延期工作,即不需要立即运行但需要可靠运行的工作,即使用户退出或设备重启也不受影响。例如:

  • 向后端服务发送日志或分析数据
  • 定期将应用数据与服务器同步

WorkManager 不适用于应用进程结束时能够安全终止的运行中后台工作,也不适用于需要立即执行的工作。

ViewModel

ViewModel类旨在以注重生命周期的方式存储和管理界面相关的数据。ViewModel 类让数据可在发生屏幕旋转等配置更改后继续留存。

架构组件为界面控制器提供了 ViewModel 辅助程序类,该类负责为界面准备数据。在配置更改期间会自动保留 ViewModel 对象,以便它们存储的数据立即可供下一个 Activity 或 Fragment 实例使用。例如,如果您需要在应用中显示用户列表,请确保将获取和保留该用户列表的责任分配给 ViewModel,而不是 Activity 或 Fragment,如以下示例代码所示:

    class MyViewModel : ViewModel() {
        private val users: MutableLiveData<List<User>> by lazy {
            MutableLiveData().also {
                loadUsers()
            }
        }

        fun getUsers(): LiveData<List<User>> {
            return users
        }

        private fun loadUsers() {
            // Do an asynchronous operation to fetch users.
        }
    }
    

然后,您可以从 Activity 访问该列表,如下所示:

    class MyActivity : AppCompatActivity() {

        override fun onCreate(savedInstanceState: Bundle?) {
            // Create a ViewModel the first time the system calls an activity's onCreate() method.
            // Re-created activities receive the same MyViewModel instance created by the first activity.

            // Use the 'by viewModels()' Kotlin property delegate
            // from the activity-ktx artifact
            val model: MyViewModel by viewModels()
            model.getUsers().observe(this, Observer<List<User>>{ users ->
                // update UI
            })
        }
    }
    

如果重新创建了该 Activity,它接收的 MyViewModel 实例与第一个 Activity 创建的实例相同。当所有者 Activity 完成时,框架会调用 ViewModel对象的 onCleared()方法,以便它可以清理资源。

注意ViewModel绝不能引用视图、Lifecycle或可能存储对 Activity 上下文的引用的任何类。

ViewModel 对象存在的时间比视图或 LifecycleOwners 的特定实例存在的时间更长。ViewModel 对象可以包含 LifecycleObservers,如 LiveData 对象。但是,ViewModel 对象绝不能观察对生命周期感知型可观察对象(如 LiveData 对象)的更改。 如果 ViewModel 需要 Application 上下文(例如,为了查找系统服务),它可以扩展 AndroidViewModel 类并设置用于接收 Application 的构造函数,因为 Application 类会扩展 Context。

ViewModel 的生命周期

ViewModel对象存在的时间范围是获取 ViewModel时传递给 ViewModelProviderLifecycleViewModel将一直留在内存中,直到限定其存在时间范围的 Lifecycle永久消失:对于 Activity,是在 Activity 完成时;而对于 Fragment,是在 Fragment 分离时。

下图说明了 Activity 经历屏幕旋转而后结束的过程中所处的各种生命周期状态。该图还在关联的 Activity 生命周期的旁边显示了 ViewModel的生命周期。此图表说明了 Activity 的各种状态。这些基本状态同样适用于 Fragment 的生命周期。
在这里插入图片描述

您通常在系统首次调用 Activity 对象的 onCreate() 方法时请求 ViewModel。系统可能会在 Activity 的整个生命周期内多次调用 onCreate(),如在旋转设备屏幕时。ViewModel存在的时间范围是从您首次请求 ViewModel 直到 Activity 完成并销毁。

在 Fragment 之间共享数据

Activity 中的两个或更多 Fragment 需要相互通信是一种很常见的情况。想象一下主从 Fragment 的常见情况,假设您有一个 Fragment,在该 Fragment 中,用户从列表中选择一项,还有另一个 Fragment,用于显示选定项的内容。这种情况不太容易处理,因为这两个 Fragment 都需要定义某种接口描述,并且所有者 Activity 必须将两者绑定在一起。此外,这两个 Fragment 都必须处理另一个 Fragment 尚未创建或不可见的情况。

可以使用 ViewModel对象解决这一常见的难点。这两个 Fragment 可以使用其 Activity 范围共享 ViewModel 来处理此类通信

    class SharedViewModel : ViewModel() {
        val selected = MutableLiveData<Item>()

        fun select(item: Item) {
            selected.value = item
        }
    }

    class MasterFragment : Fragment() {

        private lateinit var itemSelector: Selector

        // Use the 'by activityViewModels()' Kotlin property delegate
        // from the fragment-ktx artifact
        private val model: SharedViewModel by activityViewModels()

        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
            itemSelector.setOnClickListener { item ->
                // Update the UI
            }
        }
    }

    class DetailFragment : Fragment() {

        // Use the 'by activityViewModels()' Kotlin property delegate
        // from the fragment-ktx artifact
        private val model: SharedViewModel by activityViewModels()

        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
            model.selected.observe(viewLifecycleOwner, Observer<Item> { item ->
                // Update the UI
            })
        }
    }
    

请注意,这两个 Fragment 都会检索包含它们的 Activity。这样,当这两个 Fragment 各自获取 ViewModelProvider 时,它们会收到相同的 SharedViewModel 实例(其范围限定为该 Activity)。

此方法具有以下优势:

  • Activity 不需要执行任何操作,也不需要对此通信有任何了解。
  • 除了 SharedViewModel 约定之外,Fragment 不需要相互了解。如果其中一个 Fragment 消失,另一个 Fragment 将继续照常工作。
  • 每个 Fragment 都有自己的生命周期,而不受另一个 Fragment 的生命周期的影响。如果一个 Fragment 替换另一个 Fragment,界面将继续工作而没有任何问题。

Startup

App Startup提供了一种更高效的方式来在App启动时初始化组件并显式地定义它们的依赖关系

方法说明
create()包含初始化组件所需的所有操作,并返回T的实例
dependencies()返回初始化器所依赖的其他初始化器对象的列表。您可以使用此方法来控制应用程序在启动时运行初始化项的顺序

引用的库没有其他依赖的写法:

// Initializes WorkManager.
class WorkManagerInitializer : Initializer<WorkManager> {
    override fun create(context: Context): WorkManager {
        val configuration = Configuration.Builder().build()
        WorkManager.initialize(context, configuration)
        return WorkManager.getInstance(context)
    }
    override fun dependencies(): List<Class<out Initializer<*>>> {
        // No dependencies on other libraries.
        return emptyList()
    }
}

引用的库有其他依赖的写法:

// Initializes ExampleLogger.
class ExampleLoggerInitializer : Initializer<ExampleLogger> {
    override fun create(context: Context): ExampleLogger {
        // WorkManager.getInstance() is non-null only after
        // WorkManager is initialized.
        return ExampleLogger(WorkManager.getInstance(context))
    }

    override fun dependencies(): List<Class<out Initializer<*>>> {
        // Defines a dependency on WorkManagerInitializer so it can be
        // initialized after WorkManager is initialized.
        return listOf(WorkManagerInitializer::class.java)
    }
}

InitializationProvider

<provider
    android:name="androidx.startup.InitializationProvider"
    android:authorities="${applicationId}.androidx-startup"
    android:exported="false"
    tools:node="merge">
    <!-- This entry makes ExampleLoggerInitializer discoverable. -->
    <meta-data  android:name="com.example.ExampleLoggerInitializer"
          android:value="androidx.startup" />
</provider>

在manifest里声明startup的provider

上面两个Initializer只用声明ExampleLoggerInitializer即可,因为它已经依赖了WorkManger

tools:node="merge" 可以解决下面的重复神秘的冲突

App Startup库包含一组lint规则,您可以使用这些规则检查您是否正确定义了组件初始化程序。 您可以通过命令运行./gradlew:app:lintDebug’来执行这些lint检查。

AppInitializer

手动初始化你的应用在启动时不需要的组件

可以在条目中使用工具:node="remove",而不是简单地删除条目,以确保合并工具也从所有其他合并清单文件中删除条目。

<provider
    android:name="androidx.startup.InitializationProvider"
    android:authorities="${applicationId}.androidx-startup"
    android:exported="false"
    tools:node="merge">
    <meta-data android:name="com.example.ExampleLoggerInitializer"
              tools:node="remove" />
</provider>

注意:禁用组件的自动初始化也会禁用该组件依赖项的自动初始化。

手动调用

首先需要移除InitializationProvider里的meta-data依赖

<provider
    android:name="androidx.startup.InitializationProvider"
    android:authorities="${applicationId}.androidx-startup"
    tools:node="remove" />

然后手动调用即可

AppInitializer.getInstance(context)
    .initializeComponent(ExampleLoggerInitializer::class.java)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值