【Android】程序开发组件—探究Jetpack

news2025/1/10 2:42:47

引言

Jetpack是一个开发组件工具集,它的主要目的是帮助我们编写出更加简洁的代码,并简化我们的开发过程,在这么多的组件当中,最需要我们关注的其实还是架构组件,接下来就对Jetpack的主要架构组件进行学习!

ViewModel

ViewModel的诞生

  1. 瞬时数据的丢失:在之前应该已经写过一些程序了,当我们开启屏幕旋转的时候会发现之间的数据丢失,这是因为在屏幕旋转的时候相当于新创建了一个当前活动
  2. 异步调用时的内存泄漏:UI 控制器(Activity 或 Fragment)有自己的生命周期,它们可能会在不需要时被销毁(例如,用户按下 Home 键,或者屏幕旋转)。然而,异步任务一旦开始,通常会在后台线程中运行,直到任务完成,不管 UI 控制器的状态如何,如果在异步任务中持有 UI 控制器的强引用(如直接引用 Activity),那么即使 UI 控制器已经被销毁,这个引用仍然存在。这意味着垃圾回收器(GC)不能回收 UI 控制器的实例,因为它仍然被异步任务引用,从而导致内存泄漏
  3. 类膨胀提高维护难度和测试难度:类似于活动当中的代码量过多

ViewModel的作用

它是介于View(视图)和Model(数据模型)之间的桥梁,使数据和视图能够分离,也能保持通信
在这里插入图片描述

实践:

新建一个活动放置一个TextView,和一个按钮,当每次按下这个按钮就会使TextView部分的数字加一,这个逻辑大家想必都能很快的写出来,找到按钮与文本控件,为按钮注册点击事件,每次使用getText()setText()方法修改文本部分的内容,但当我们的屏幕进行旋转的时候,数据就会丢失变为0,因此使用ViewModel来解决这个问题,我们为按钮注册点击事件,创建一个MyViewModel继承于ViewModel,将文本内容的数字设置在这里

public class MyViewModel extends ViewModel {
    public int number;
}

主活动的代码:

public class MainActivity extends AppCompatActivity {
    private TextView textView;
    private MyViewModel viewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ......
        textView = findViewById(R.id.Textview);
        viewModel = new ViewModelProvider(this, new ViewModelProvider.AndroidViewModelFactory(getApplication())).get(MyViewModel.class);
        textView.setText(String.valueOf(viewModel.number));
    }
	//为按钮注册的点击事件
    public void plus(View view) {
        textView.setText(String.valueOf(++viewModel.number));
    }
}

大部分的代码之间经常看到,分析一下中间的代码:

  • ViewModelProvider是Android架构组件库中的一个类,用于获取ViewModel实例。
  • this参数传递的是当前的Activity实例。
  • new ViewModelProvider.AndroidViewModelFactory(getApplication())创建了一个AndroidViewModelFactory实例,它是一个工厂类,用于创建AndroidViewModel类型的ViewModel实例。getApplication()方法用于获取当前应用程序的上下文。
  • .get(MyViewModel.class)调用ViewModelProviderget方法,传入MyViewModel.class作为参数,以获取MyViewModel的实例。

此时无论我们如何旋转屏幕数据都不会丢失!

ViewModel的生命周期

ViewModel 的生命周期比 ActivityFragment 的生命周期更长,它不会因为配置更改(如屏幕旋转)而重建。

ViewModel 的生命周期

  1. 创建 (onCreate): 当 ViewModel 首次被请求时,它会被创建。这通常发生在 ActivityFragmentonCreateonCreateView 方法中,通过 ViewModelProvider 获取 ViewModel 实例时。
  2. 准备 (onCleared): ViewModel 提供了一个 onCleared() 方法,这是一个生命周期回调,当 ViewModel 被清除并且即将销毁时会被调用。你可以在这个回调中执行清理工作,比如取消网络请求、注销观察者等。
  3. 活跃: 在 ViewModel 被创建后,它会保持活跃状态,直到与它关联的 ActivityFragment 被销毁。即使 Activity 因为配置更改(如屏幕旋转)而重建,ViewModel 也不会被销毁,它会保持原有的状态。
  4. 销毁 (onCleared): 当 ActivityFragment 被销毁,并且没有任何其他组件与 ViewModel 关联时,ViewModel 会被销毁。onCleared() 方法会在销毁时被调用。

ViewModel 与 Activity/Fragment 生命周期的关系

  • Activity/Fragment 创建: 当 ActivityFragment 创建时,你可以通过 ViewModelProvider 获取 ViewModel 实例。如果 ViewModel 已经存在(比如在屏幕旋转后重建 Activity 时),则直接使用现有的实例。
  • Activity/Fragment 重建: 如果 Activity 因为配置更改而重建,ViewModel 会保持不变,这意味着 ViewModel 中的数据不会丢失。
  • Activity/Fragment 销毁: 当 ActivityFragment 被销毁时,ViewModel 不会被销毁,除非没有任何 ActivityFragment 与它关联。
  • ViewModel 销毁: 当最后一个与 ViewModel 相关联的 ActivityFragment 被销毁,并且 ViewModel 没有被其他组件引用时,它会被销毁。

LiveData

和ViewModel之间的关系

当ViewModel中的数据发生变化的时候,LiveData告诉View

在这里插入图片描述

使用

简单应用

在活动当中设置一个TextView来显示数据,同样的我们创建一个MyViewModel来防止数据的丢失

public class MyViewModel extends ViewModel {
    private MutableLiveData<Integer> current;
    public MutableLiveData<Integer> getCurrent() {
        if (current == null) {
            current = new MutableLiveData<>();
            current.setValue(0);
        }
        return current;
    }
}
public class MainActivity extends AppCompatActivity {
    TextView textView;
    MyViewModel viewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ......
        textView = findViewById(R.id.text);
        viewModel = new ViewModelProvider(this, new ViewModelProvider.AndroidViewModelFactory(getApplication())).get(MyViewModel.class);
        textView.setText(String.valueOf(viewModel.getCurrent().getValue()));
        viewModel.getCurrent().observe(this, new Observer<Integer>() {
            @Override
            public void onChanged(Integer i) {
                textView.setText(String.valueOf(i));
            }
        });
        startTimer();
    }

    private void startTimer() {
        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
                //非UI线程postValue()
                //UI线程setValue()
                viewModel.getCurrent().postValue(viewModel.getCurrent().getValue() + 1);
            }
        }, 1000, 1000);
    }
}

此时运行程序:

ViewModel与LiveData实现Fragment之间的数据传递

我们创建一个活动,在这个活动当中放置两个碎片,碎片当中放置各自放置一个滑动条,此时活动的布局如下:

当我们拖动碎片当中的一条滑动条,另一个是不动的,因为我们没有将两个碎片联系起来,若我们想将两个滑动条进行关联,使滑动一条滑动条另一个也跟着动就会想到通过Fragment之间的数据传递部分进行联系,就会使主活动的代码量非常大并且 Fragment 之间高度耦合,这使得代码难以维护和扩展。此时我们就可以使用 ViewModel + LiveData去实现。

  1. 新建一个MyViewModel继承于ViewModel
public class MyViewModel extends ViewModel {
    private MutableLiveData<Integer> progress;
    public MutableLiveData<Integer> getProgress() {
        if (progress == null) {
            progress.setValue(0);
        }
        return progress;
    }
}
  1. 对Fragment部分代码进行修改,另一个碎片的代码也是一样的
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {

    View root = inflater.inflate(R.layout.fragment_blank, container, false);
    SeekBar seekBar = root.findViewById(R.id.progress_bar1);
    MyViewModel myViewModel = new ViewModelProvider(getActivity(), new ViewModelProvider.AndroidViewModelFactory(getActivity().getApplication())).get(MyViewModel.class);
    //第一处
    myViewModel.getProgress().observe(getActivity(), new Observer<Integer>() {
        @Override
        public void onChanged(Integer i) {
            seekBar.setProgress(i);
        }
    });
    //第二处
    seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
        //第三处
        @Override
        public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
            myViewModel.getProgress().setValue(progress);
        }

        @Override
        public void onStartTrackingTouch(SeekBar seekBar) {
			//当用户开始触摸 SeekBar 时,这个方法会被调用
        }

        @Override
        public void onStopTrackingTouch(SeekBar seekBar) {
			//当用户停止触摸 SeekBar 时,这个方法会被调用
        }
    });
    return root;
}
  • 第一处:这行代码注册了一个观察者(Observer)来监听 myViewModel.getProgress() LiveData 中的数据变化。当 LiveData 中的值发生变化时,onChanged(Integer i) 方法会被调用,并且新的进度值 i 会被传递到这个方法中。
  • 第二处:为 SeekBar 设置了一个监听器,用于监听用户与 SeekBar 的交互(如拖动进度条),当进度条发生变化的时候就会执行里面的方法
  • 第三处:当用户拖动 SeekBar 或 SeekBar 的进度发生变化时,这个方法会被调用,progress 参数表示 SeekBar 当前的进度值。fromUser 参数是一个布尔值,表示进度变化是否由用户操作引起(如果是程序调用 setProgress 则为 false)。

我们并没有对主活动进行任何的代码添加与修改,接下来运行程序:

LiveData的优势

  1. 生命周期感知LiveData 遵循观察者的生命周期。它确保在观察者(如 ActivityFragment)处于活跃状态时才发送数据更新,从而避免在组件已经销毁后更新 UI 导致的内存泄漏。
  2. 避免内存泄漏: 由于 LiveData 与观察者的生命周期绑定,它不会向已经销毁的观察者发送更新,这减少了因 UI 控制器生命周期管理不当而导致的内存泄漏风险。
  3. 数据变化的响应式更新: 当数据发生变化时,LiveData 会自动通知活跃的观察者。这种响应式编程模式使得 UI 能够自动响应数据的变化,而无需手动轮询数据。
  4. 可观察的数据存储LiveData 可以存储数据的最新状态,当观察者开始观察 LiveData 时,它会立即接收到当前的数据状态,确保 UI 显示的数据是最新的。
  5. 支持转换操作LiveData 可以与 TransformationsMediatorLiveData 等工具结合使用,支持复杂的数据转换和组合操作,使得数据的处理更加灵活和强大。
  6. 线程安全LiveData 的更新操作是线程安全的,你可以在后台线程中更新 LiveData 的数据,而无需担心线程同步问题。
  7. 简化数据共享LiveData 可以简化不同组件间的数据共享,特别是当多个组件需要观察同一份数据时,LiveData 提供了一种简洁的观察和更新机制。
  8. 易于测试: 由于 LiveData 的数据更新是可观察的,你可以更容易地编写测试用例来验证数据更新是否按预期触发了 UI 的变化。
  9. 减少 boilerplate 代码LiveData 减少了在 Activity 或 Fragment 中处理数据更新和 UI 响应的样板代码,使得代码更加简洁和易于维护。
  10. 支持数据恢复: 在配置更改(如屏幕旋转)后,LiveData 可以帮助恢复数据状态,因为它保持了数据的最新值,直到观察者重新观察它。

DataBinding

意义:让布局文件承担了部分原本属于页面的工作,使页面与布局耦合度进一步降低

应用

基本应用

根据一个例子来理解吧!

  1. 新建一个活动,活动当中放置一个图片和两个TextView控件,我们之前为TextView设置内容都是在主活动当中先获取到控件,在使用setText()方法设置,现在我们试试不在活动当中,此时的布局文件大家都会写,在这里就不展示了
  2. 打开build.gradle文件,添加以下内容,注意不要忘记sync now

在这里插入图片描述

  1. 鼠标位于布局文件的最前端点击alt+回车,就会出现以下内容,我们需要添加红色边框部分内容

在这里插入图片描述

  • <data>:这个元素定义了布局文件中可以使用的数据源。
  • <variable:这个元素定义了一个变量,它可以在布局文件中被引用。
    • name="idol":这是变量的名称,在布局文件中可以通过这个名称来引用变量。
    • type="com.example.databinding.Idol":这是变量的类型,它指定了变量的数据类型。在这个例子中,Idol是一个Java类,它可能定义了一些属性,名字、等级,这些属性可以在布局文件中被绑定到UI组件上。
  1. 对主活动进行修改
ActivityMainBinding activityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
Idol idol = new Idol("熊大", "五星");
activityMainBinding.setIdol(idol);
  • activityMainBinding:这是一个变量,它存储了绑定类的实例,可以通过这个变量访问布局文件中定义的视图和变量。
  • DataBindingUtil.setContentView:这是一个静态方法,用于将布局文件绑定到当前的Activity。它返回一个绑定类(在这个例子中是ActivityMainBinding)的实例,这个绑定类是由Data Binding库根据布局文件自动生成的。
  • setIdol:这是在ActivityMainBinding类中定义的一个方法,它用于将Idol对象绑定到布局文件中定义的变量idol上。
  1. 对布局文件进行修改

在这里插入图片描述

接下来运行程序:

在这里插入图片描述

Important标签和事件绑定

  1. Important标签

若在我们创建的时候有一个值为整型怎么办,例如在Idol当中将star设置为整型,此时我们就需要将整形转化为字符串类型,创建一个类

public class StarUtils {
    public static String getStar(int star) {
        switch (star) {
            case 1:
                return "一星";
            case 2:
                return "二星";
            case 3:
                return "三星";
            case 4:
                return "四星";
        }
        return "";
    }
}

修改xml文件

<data>
    <variable
        name="idol"
        type="com.example.databinding.Idol" />
    <import type="com.example.databinding.StarUtils"/>
</data>
<TextView
    android:id="@+id/dengji"
    android:layout_gravity="center"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@{StarUtils.getStar(idol.star)}"
    android:textSize="25sp" />

此时在创建对象的时候:

Idol idol = new Idol("熊大", 1);

运行程序,也会得到相应的结果。

  • <import>:这是Data Binding布局文件中的一个XML标签,用于导入Java类。
  • type:这是<import>标签的一个属性,用于指定要导入的类或包的全限定名。
  1. 与事件绑定

设置一个按钮,为点击按钮注册事件

public class EventHander {
    private Context context;

    public EventHander(Context context) {
        this.context = context;
    }

    public void buttonOnclick(View view) {
        Toast.makeText(context, "喜欢", Toast.LENGTH_SHORT).show();
    }
}

将这个类添加到绑定资源当中

<data>
    <variable
        name="idol"
        type="com.example.databinding.Idol" />
    <variable
        name="eventbutton"
        type="com.example.databinding.EventHander" />
    <import type="com.example.databinding.StarUtils"/>
</data>

按钮控件进行添加绑定

android:onClick="@{eventbutton.buttonOnclick}"

此时运行程序,当按下按钮

在这里插入图片描述

二级页面的绑定

标签引用二级页面

在这里插入图片描述

我们将上面布局的按钮进行删除,将两个TextView控件放在一个xml文件之下,使主活动的xml文件引用这个xml文件,此时我们就不需要在一级页面将star进行转换,将Import标签进行删除,并对引用布局部分进行标签传递修改

<include layout="@layout/sub"
        app:idol="@{idol}"/>

在二级页面当中TextView的代码与之前的一级页面一样,注意不要忘记添加资源部分。此时运行程序的结果与之前一样。

文章到这里就结束了!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2111702.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据结构-----栈、队列

一、栈 1、栈(stack)是限定仅在表尾进行插入和删除操作的线性表。 把允许插入和删除的一端称为栈顶&#xff08;top)&#xff0c;另一端称为栈底&#xff08;bottom)&#xff0c;不含任何数据元素的栈称为空栈。栈又称为后进先出&#xff08;Last In First Out)的线性表,简称L…

OpenAI gym: Trouble installing Atari dependency (Mac OS X)

题意&#xff1a; 使用OpenAI Gym库时&#xff0c;安装Atari环境可能会遇到一些依赖问题&#xff0c;尤其是在Mac OS X系统上 问题背景&#xff1a; Im new to OpenAI gym. Ive successfully installed OpenAI gym on my Mac OS X (High Sierra 10.13.3) laptop and made a D…

C语言程序设计(算法的概念及其表示)

一、算法的概念 一个程序应包括两个方面的内容: 对数据的描述:数据结构 对操作的描述:算法 著名计算机科学家沃思提出一个公式: 数据结构 +算法 =程序 完整的程序设计应该是: 数据结构+算法+程序设计方法+语言工具 广义地说,为解决一个问题而采取的方法和步骤…

学不会虚拟列表?10分钟带你实现高度固定的Vue虚拟列表方案及原理

前言 本文主要介绍长列表的一种优化方案&#xff1a;虚拟列表。本文主要是对传统的虚拟列表方案进行更加详尽的刨析&#xff0c;以便我们能够更加深入理解虚拟列表的原理。 虚拟列表目录 1、为什么需要使用虚拟列表2、什么是虚拟列表与懒加载的区别(重要) 3、实现思路4、通过节…

企业选ETL还是ELT架构?

作为数据处理的重要工具&#xff0c;ETL工具被广泛使用&#xff0c;同时ETL也是数据仓库中的重要环节。本文将从解释ETL工具是怎么处理数据&#xff0c;同时介绍ELT和ETL工具在企业搭建数据仓库的重要优势。 一、什么是ETL? ETL是Extract-Transform-Load的缩写&#xff0c;将…

【深度学习】多层感知机的从零开始实现与简洁实现

可以说&#xff0c;到现在我们才真正接触到深度网络。最简单的深度网络称为多层感知机。 多层感知机由多层神经元组成&#xff0c;每一层与它的上一层相连&#xff0c;从中接收输入&#xff1b;同时每一层也与它的下一层相连&#xff0c;影响当前层的神经元。 和以前相同&…

【深入解析】AI工作流中的HTTP组件:客户端与服务端执行的区别

在当今快速发展的技术环境中&#xff0c;AI工作流的设计和实现变得愈发重要。尤其是在处理HTTP组件时&#xff0c;前端执行与后端执行之间的区别&#xff0c;往往会对系统的安全性和数据的准确性产生深远的影响。今天&#xff0c;我们就来深入探讨这一话题&#xff0c;揭示前端…

音频基础学习四——声音的能量与分贝

文章目录 前言一、能量与分贝1.音频能量2.分贝3.两者的区别4. 应用场景 二、分贝的计算方式1.具体数学公式2.具体算法示例3.对于算法的释义大小端为什么通过计算得到的是负值范围实际结果 总结 前言 很多博客中经常会把声音的能量和分贝说成是一个东西&#xff0c;这种说法是错…

原型模式prototype

此篇为学习笔记&#xff0c;原文链接 https://refactoringguru.cn/design-patterns/prototype 能够复制已有对象&#xff0c; 而又无需使代码依赖它们所属的类 所有的原型类都必须有一个通用的接口&#xff0c; 使得即使在对象所属的具体类未知的情况下也能复制对象。 原型对…

云计算之云原生(上)

目录 一、消息队列RocketMQ 1.1 功能介绍 1.1.1 业务消息首选&#xff1a;消息队列 RocketMQ 1.1.2 【收发流量隔离约束】读写分离控制提高集群稳定性 1.1.3 【Dashboard 仪表盘】实时观测实例状态 1.1.4 【消息轨迹追踪】消息生命周期状态一目了然 1.1.5 【实时扩缩容】…

单点登录OAuth2.0

OAuth 2.0&#xff08;Open Authorization 2.0&#xff09;是OAuth协议的第二个版本&#xff0c;于2012年正式成为RFC 6749标准。在OAuth 2.0之前&#xff0c;OAuth 1.0版本已经为Web应用提供了一定程度的授权功能&#xff0c;但随着时间的推移&#xff0c;这些版本逐渐显露出一…

“Docker网络模式详解与应用“

目录 前言 Docker内置网络 bridge 基本概念 案例 工作原理 使用场景 host 基本概念 案例 工作原理 使用场景 none 基本概念 案例 &#xff01;&#xff01;&#xff01;大佬救命 container 基本概念 案例 自定义网络 自定义bridge 基本概念 案例 Docker…

界面控件KendoReact中文教程 - 如何创建动态进度条?

Kendo UI致力于新的开发&#xff0c;来满足不断变化的需求。现在我们非常自豪地宣布&#xff0c;通过React框架的Kendo UI JavaScript封装来支持React Javascript框架。Kendo UI for React能够为客户提供更好的用户体验&#xff0c;并且能够更快地构建更好的应用程序。 KendoR…

树莓派外设驱动WiringPi库

树莓派外设驱动WiringPi库 文章目录 树莓派外设驱动WiringPi库一、树莓派安装WiringPi库二、WiringPi库的使用方法 一、树莓派安装WiringPi库 wiringPi库其实已经很熟悉了&#xff0c;在香橙派中大量使用过&#xff0c;这个库中集成了很多使用的功能性函数&#xff0c;树莓派安…

设计模式-行为型模式-状态模式

1.状态模式的定义 允许一个对象在其内部状态改变时改变他的行为&#xff0c;用于解决系统中复杂对象的状态转换以及不同状态下行为的封装问题&#xff0c;状态模式将一个对象的状态从该对象中分离出来&#xff0c;封装到专门的状态类中&#xff0c;使得对象的状态可以灵活变化&…

【STM32】GPIO输入实现按键控制LED

1.stm32cubemx配置 和上篇博客配置一样 2.代码编写 实现一个按键按下LED1亮&#xff0c;另一个按下LED灭 KEY1实现LED1亮&#xff0c;KEY2实现LED2灭 1.配置GPIOA,GPIOB时钟使能 2.配置GPIOB模式初始化 3.配置GPIOA模式初始化 基本和2一样&#xff0c;不一样的是按键使用的…

(详)Vue3 + Typescript 项目配置 eslint + prettier + husky + lint-staged

目录 1&#xff0c;前言1.1&#xff0c;eslint 和 prettier 的关系1.2&#xff0c;Node.js 版本的问题 1&#xff0c;eslint1.1&#xff0c;安装1.2&#xff0c;配置文件1.3&#xff0c;集成对 vue 文件的配置1.4&#xff0c;在 package.json 中添加命令 2&#xff0c;prettier…

【代码随想录训练营第42期 Day52打卡 - 岛屿问题2

目录 一、做题心得 二、题目与题解 题目一&#xff1a;卡码网 101. 孤岛的总面积 题目链接 题解&#xff1a;DFS 题目二&#xff1a;卡码网 102. 沉没孤岛 题目链接 题解&#xff1a;DFS 三、小结 一、做题心得 今天做题时间比较晚了&#xff0c;只打卡完成了岛屿问题…

条件生成模型 (conditional generation)

我们之前讲的 GAN 中的生 成器&#xff0c;它没有输入任何的条件&#xff0c;它只是输入一个随机的分布&#xff0c;然后产生出来一张图片。我们现 在想要更进一步的是希望可以操控生成器的输出&#xff0c;我们给它一个条件x&#xff0c;让他根据条件x跟 输入z 来产生输出y。那…

硬件-经典开机电路

文章目录 一&#xff1a;网友公司祖传的开机电路二&#xff1a;电路符号名称三&#xff1a;电路原理分析道友&#xff1a;对于利益相关的人&#xff0c;要展示你的实力和智力。对于利益不相关的人&#xff0c;展示你的礼貌就好。 一&#xff1a;网友公司祖传的开机电路 业务逻辑…