本次主要讲解的内容:
1、Jetpack介绍
2、Jetpack架构组件库的相关用法
Google 官方解释:
Jetpack 是一个由多个库组成的套件,可帮助开发者遵循最佳做法,减少样板代码并编写可在各种 Android 版本和设备中一致运行的代码,让开发者精力集中编写重要的代码。
Jetpack 是 Google 为解决 Android 开发碎片化,打造成熟健康生态圈提出的战略规划,是 Google 对 Android 未来提出的发展方向,同时它也是众多优秀 Android 组件的集合。
Google 官方解释:
遵循最佳做法:Android Jetpack 组件采用最新的设计方法构建,具有向后兼容性,可以减少崩溃和内存泄露。
消除样板代码:Android Jetpack 可以管理各种繁琐的 Activity(如后台任务、导航和生命周期管理),以便您可以专注于打造出色的应用。
减少不一致:这些库可在各种 Android 版本和设备中以一致的方式运作,助您降低复杂性。
Jetpack 的优势:
Jetpack 拥有基于生命周期感知的能力,可以减少 NPE(空指针异常) 崩溃、内存泄漏,为开发出健壮且流畅的程序提供强力保障;
Jetpack 可以消除大量重复样板式的代码,可以加速 Android 的开发进程,组件可搭配工作,也可单独使用,同时配合 Kotlin 语言特性能够显著提高工作效率;
统一开发模式,抛弃传统的 MVC, MVP;
如上图:Jetpack 主要包括 4 个部分,分别是【Architecture:架构】、【UI:界面】、【behavior:行为】和【foundation:基础】。
目的:帮助开发者设计稳健、可测试且易维护的应用;
Lifecycle:具备宿主生命周期感知能力的组件 特性:持有组件(如 Activity 或 Fragment)生命周期状态的信息,并且允许其他对象观察此状态;
LiveData:新一代具备生命周期感知能力的数据订阅、分发组件 特性:支持共享资源、支持黏性事件的分发、不再需要手动处理生命周期、确保界面符合数据状态;
ViewModel:具备生命周期感知能力的数据存储组件 特性:页面因配置变更导致的重启,此时数据不丢失;可以实现跨页面(跨 Activity)的数据共享;
SavedState 架构组件原理解析 特性:因内存不足,电量不足导致页面被回收时可以搭配 ViewModel 实现数据存储与恢复;
Room:轻量级 orm 数据库,本质上是一个 SQLite 抽象层 特性:使用简单(类似于 Retrofit 库),通过注解的方式实现相关功能,编译时自动生成相关实现类
DataBinding:只是一种工具,解决的是 View 和数据之间的双向绑定 特性:支持数据与视图双向绑定、数据绑定空安全、减少模板代码、释放 Activity/Fragment 压力;
Paging: 列表分页组件,可以轻松完成分页预加载以达到无限滑动的效果 特性:巧妙融合 LiveData、提供多种数据源加载方式;不足之处:不支持列表数据增删改,列表添加 HeaderView,FooterView 定位不准确;
Navigation 组件原理分析:端内统一路由组件 特性:能够为 Activity,Fragment,Dialog,FloatWindow 提供统一的路由导航服务,可以传递参数,指定导航动画,还支持深度链接等主要能力;不足:十分依赖 xml 配置文件不利于组件化,模块化
WorkManager:新一代后台任务管理组件,service 能做的事情它都能做 特性:支持周期性任务调度、链式任务调度、丰富的任务约束条件、程序即便退出,依旧能保证任务的执行;
目的:提供横向功能,例如向后兼容性、测试、安全、Kotlin 语言支持,并包括多个平台开发的组件;
Android KTX:优化了供 Kotlin 使用的 Jetpack 和 Android 平台 API,帮助开发者以更简洁、更愉悦、更惯用的方式使用 Kotlin 进行 Android 开发;
AppCompat:帮助较低版本的 Android 系统进行兼容;
Auto:开发 Android Auto 应用的组件,提供了适用于所有车辆的标准化界面和用户交互;
检测:从 AndroidStudio 中快速检测基于 Kotlin 或 Java 的代码;
多 Dex 处理:为具有多个 Dex 文件应用提供支持;
安全:安全的读写加密文件和共享偏好设置;
测试:用于单元和运行时界面测试的 Android 测试框架;
TV:构建可让用户在大屏幕上体验沉浸式内容的应用;
Wear OS:开发 Wear 应用的组件;
目的:帮助开发者的应用与标准 Android 服务(如通知、权限、分享)相集成;
CameraX:帮助开发简化相机应用的开发工作,提供一致且易于使用的界面,适用于大多数 Android 设备,并可向后兼容至 Android 5.0(API 21);
DownloadManager:处理长时间运行的 HTTP 下载的系统服务;
媒体和播放:用于媒体播放和路由(包括 Google Cast)的向后兼容 API;
通知:提供向后兼容的通知 API,支持 Wear 和 Auto;
权限:用于检查和请求应用权限的兼容性 API;
设置:创建交互式设置,建议使用 AndroidX Preference Library 库将用户可配置设置集成到应用中;
分享操作:可以更轻松地实现友好的用户分享操作;
切片:切片是一种 UI 模板,创建可在应用外部显示应用数据的灵活界面元素;
Animation and Transition:该框架包含用于常见效果的内置动画,并允许开发者创建自定义动画和生命周期回调;
Emoji Compatibility:即便用户没有更新 Android 系统也可以获取最新的表情符号;
Fragment:组件化界面的基本单位;
布局:用 XML 中声明 UI 元素或者在代码中实例化 UI 元素;
调色板:从调色板中提取出有用的信息;
注意:
上面的图之前在官网是存在的,现在官网已经找不到此图,只有jetpack架构组件的文档入口;
猜想google官方也是旨在强化Architecture架构组件,因为UI、Behavior、Foundation 这三部分大多是对已有内容的收集整理。
所以接下来的关注点主要是在Architecture 架构部分
参考官网Jetpack使用
1、Jetpack 库可以单独使用,也可以组合使用,以满足应用的不同需求。
2、Jetpack 库在 androidx 命名空间中发布。
androidx介绍:
androidx 命名空间中的工件包含 Android Jetpack 库。与支持库一样,androidx 命名空间中的库与 Android 平台分开提供,并向后兼容各个 Android 版本。
AndroidX 对原始 Android 支持库进行了重大改进,后者现在已不再维护。androidx 软件包完全取代了支持库,不仅提供同等的功能,而且提供了新的库。
AndroidX 还包括以下功能:
AndroidX 中的所有软件包都使用一致的命名空间,以字符串 androidx 开头。支持库软件包已映射到对应的 androidx.* 软件包。有关所有旧类到新类以及旧构建工件到新构建工件的完整映射,请参阅软件包重构页面。
与支持库不同,androidx 软件包会单独维护和更新。从版本 1.0.0 开始,androidx 软件包使用严格的语义版本控制。您可以单独更新项目中的各个 AndroidX 库。
版本 28.0.0 是支持库的最后一个版本。我们将不再发布 android.support 库版本。所有新功能都将在 androidx 命名空间中开发。
所以,目前我们的项目【朴新网校】、【朴新助教】、【数学宝典】都已经迁移使用了androidx库。
androidx迁移参照官网
注意:Jetpack 是各种组件库的统称,AndroidX 是这些组件的统一包名。
官网推荐应用架构:
在项目中运用到的框架的搭建基本基于改图,运用到的jetpack组件库有ViewModel、LiveData、DataBinding;接下来逐一介绍他们的特性和用法。
ViewModel 类旨在以注重生命周期的方式存储和管理界面相关的数据。ViewModel 类让数据可在发生屏幕旋转等配置更改后继续留存。
LiveData 是一种可观察的数据存储器类。与常规的可观察类不同,LiveData 具有生命周期感知能力,意指它遵循其他应用组件(如 Activity、Fragment 或 Service)的生命周期。这种感知能力可确保 LiveData 仅更新处于活跃生命周期状态的应用组件观察者。
LiveData , ViewModel 组件都是基于Lifecycle来实现的,LiveData&ViewModel也是结合使用的,接下来具体看下如何使用
声明依赖项
dependencies {
def lifecycle_version = "2.2.0"
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version"
// LiveData
implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle_version"
}
实现 ViewModel
架构组件为界面控制器提供了 ViewModel 辅助程序类,该类负责为界面准备数据。在配置更改期间会自动保留 ViewModel 对象,以便它们存储的数据立即可供下一个 Activity 或 Fragment 实例使用。例如,如果您需要在应用中显示用户列表,请确保将获取和保留该用户列表的责任分配给 ViewModel,而不是 Activity 或 Fragment,如以下示例代码所示:
public class MyViewModel extends ViewModel {
private MutableLiveData> users;
public LiveData> getUsers() {
if (users == null) {
users = new MutableLiveData>();
loadUsers();
}
return users;
}
private void loadUsers() {
// Do an asynchronous operation to fetch users.
}
}
然后,您可以从 Activity 访问该列表,如下所示:
public class MyActivity extends AppCompatActivity {
public void onCreate(Bundle savedInstanceState) {
// 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.
MyViewModel model = new ViewModelProvider(this).get(MyViewModel.class);
model.getUsers().observe(this, users -> {
// update UI
});
}
}
ViewModel在 Fragment 之间共享数据
使用 ViewModel 对象解决这一常见的难点。这两个 Fragment 可以使用其 Activity 范围共享 ViewModel 来处理此类通信
public class SharedViewModel extends ViewModel {
private final MutableLiveData- selected = new MutableLiveData
- ();
public void select(Item item) {
selected.setValue(item);
}
public LiveData
- getSelected() {
return selected;
}
}
//获取 ViewModelProvider 时,会收到相同的 SharedViewModel 实例(其范围限定为该 Activity)
public class MasterFragment extends Fragment {
private SharedViewModel model;
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
//通过requireActivity() Fragment 会检索包含它们的 Activity
model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
itemSelector.setOnClickListener(item -> {
model.select(item);
});
}
}
//获取 ViewModelProvider 时,会收到相同的 SharedViewModel 实例(其范围限定为该 Activity)
public class DetailFragment extends Fragment {
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
//通过requireActivity() Fragment 会检索包含它们的 Activity
SharedViewModel model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
model.getSelected().observe(getViewLifecycleOwner(), { item ->
// Update the UI.
});
}
}
该方法的优势:
Activity 不需要执行任何操作,也不需要对此通信有任何了解。
除了 SharedViewModel 约定之外,Fragment 不需要相互了解。如果其中一个 Fragment 消失,另一个 Fragment 将继续照常工作。
每个 Fragment 都有自己的生命周期,而不受另一个 Fragment 的生命周期的影响。如果一个 Fragment 替换另一个 Fragment,界面将继续工作而没有任何问题。
使用 LiveData 对象
官网使用参考
1、创建 LiveData 对象
LiveData 是一种可用于任何数据的封装容器,其中包括可实现 Collections 的对象,如 List。LiveData 对象通常存储在 ViewModel 对象中,并可通过 getter 方法进行访问,如以下示例中所示:
public class NameViewModel extends ViewModel {
// Create a LiveData with a String
private MutableLiveData currentName;
public MutableLiveData getCurrentName() {
if (currentName == null) {
currentName = new MutableLiveData();
}
return currentName;
}
// Rest of the ViewModel...
}
最初,LiveData 对象中的数据并未经过设置。
注意:请确保用于更新界面的 LiveData 对象存储在 ViewModel 对象中,而不是将其存储在 Activity 或 Fragment 中,原因如下:
1、避免 Activity 和 Fragment 过于庞大。现在,这些界面控制器负责显示数据,但不负责存储数据状态。
2、将 LiveData 实例与特定的 Activity 或 Fragment 实例分离开,并使 LiveData 对象在配置更改后继续存在。
2、观察 LiveData 对象
在大多数情况下,在应用组件的 onCreate() 方法是开始观察 LiveData 对象原因如下:
1、确保系统不会从 Activity 或 Fragment 的 onResume() 方法进行冗余调用。
2、确保 Activity 或 Fragment 变为活跃状态后具有可以立即显示的数据。一旦应用组件处于 STARTED 状态,就会从它正在观察的 LiveData 对象接收最新值。只有在设置了要观察的 LiveData 对象时,才会发生这种情况。
LiveData 仅在数据发生更改时才发送更新,并且仅发送给活跃观察者。此行为的一种例外情况是,观察者从非活跃状态更改为活跃状态时也会收到更新。此外,如果观察者第二次从非活跃状态更改为活跃状态,则只有在自上次变为活跃状态以来值发生了更改时,它才会收到更新。
以下示例代码说明了如何开始观察 LiveData 对象:
public class NameActivity extends AppCompatActivity {
private NameViewModel model;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Other code to setup the activity...
// Get the ViewModel.
model = new ViewModelProvider(this).get(NameViewModel.class);
// Create the observer which updates the UI.
final Observer nameObserver = new Observer() {
@Override
public void onChanged(@Nullable final String newName) {
// Update the UI, in this case, a TextView.
nameTextView.setText(newName);
}
};
// Observe the LiveData, passing in this activity as the LifecycleOwner and the observer.
model.getCurrentName().observe(this, nameObserver);
}
}
3、更新 LiveData 对象
LiveData 没有公开可用的方法来更新存储的数据。MutableLiveData 类将公开 setValue(T) 和 postValue(T) 方法,如果您需要修改存储在 LiveData 对象中的值,则必须使用这些方法。通常情况下会在 ViewModel 中使用 MutableLiveData,然后 ViewModel 只会向观察者公开不可变的 LiveData 对象。
设置观察者关系后,您可以更新 LiveData 对象的值(如以下示例中所示),这样当用户点按某个按钮时会触发所有观察者:
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
String anotherName = "John Doe";
model.getCurrentName().setValue(anotherName);
}
});
注意:
您必须调用 setValue(T) 方法以从主线程更新 LiveData 对象。
如果在 worker 线程中执行代码,则您可以改用 postValue(T) 方法来更新 LiveData 对象。
Data Binding是一种支持库,借助该库,您可以使用声明性格式(而非程序化地)将布局中的界面组件绑定到应用中的数据源。
布局通常是使用调用界面框架方法的代码在 Activity 中定义的。例如,以下代码调用 findViewById() 来查找 TextView 控件并将其绑定到 viewModel 变量的 userName 属性:
TextView textView = findViewById(R.id.sample_text);
textView.setText(viewModel.getUserName());
以下示例展示了如何在布局文件中使用Data Binding 将文本直接分配到TextView。这样就无需调用上述任何 Java 代码。请注意赋值表达式中 @{} 语法的使用:
注意:
如果您使用Data Biding的主要目的是取代 findViewById() 调用,请考虑改用ViewBinding。
使用过ButterKnife的都知道,目前ButterKnife作者建议切换至ViewBindng使用;在许多情况下,ViewBinding可简化实现,提高性能,提供与DataBinding相同的好处。
布局和绑定表达式
数据绑定的布局以根标记 layout 开头,后跟 data 元素和 view 根元素。如下:
注意:布局表达式应保持精简,因为它们无法进行单元测试,并且拥有的 IDE 支持也有限。为了简化布局表达式,可以使用自定义绑定适配器。
系统会为每个布局文件生成一个绑定类。
1、默认情况下,类名称基于布局文件的名称,它会转换为驼峰形式并在末尾添加 Binding 后缀。
2、以上布局文件名为 activity_main.xml,因此生成的对应类为 ActivityMainBinding,且都是ViewDataBinding的子类,所有布局对应的生成的绑定类都可以是ViewDataBinding类
3、此类包含从布局属性(例如,user 变量)到布局视图的所有绑定,并且知道如何为绑定表达式指定值。
4、建议的绑定创建方法是在扩充布局时创建,如以下示例所示:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
User user = new User("Test", "User");
binding.setUser(user);
}
a、Activity 数据绑定 ( DataBinding ) :
1、DataBindingUtil类方法:
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
2、生成的布局绑定类的inflate()方法:
ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//DataBindingUtil类方法
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
//生成的布局绑定类的inflate()方法
//ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());
User user = new User("Test", "User");
binding.setUser(user);
}
b、 Fragment、ListView 或 RecyclerView 适配器中使用数据绑定 ( DataBinding )
DataBindingUtil 或 生成的布局绑定类deinflate() 方法,如以下代码示例所示:
ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);
// or
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);
xml里支持使用以下表达式:
算术运算符 + - / * %
字符串连接运算符 +
逻辑运算符 && ||
二元运算符 & | ^
一元运算符 + - ! ~
移位运算符 >> >>> <<
比较运算符 == > < >= <=(请注意,< 需要转义为 <)
instanceof
分组运算符 ()
字面量运算符 - 字符、字符串、数字、null
类型转换
方法调用
字段访问
数组访问 []
三元运算符 ?:
不支持以下表达式:
this
super
new
显式泛型调用
1、生成的数据绑定代码会自动检查有没有 null 值并避免出现 Null 指针异常。
2、例如,在表达式 @{user.name} 中,如果 user 为 Null,则为 user.name 分配默认值 null。
3、如果您引用 user.age,其中 age 的类型为 int,则数据绑定使用默认值 0。
android:text="@{user.name}"
android:text="@={user.name}"
如果左边运算数不是 null,则 Null 合并运算符 (??) 选择左边运算数,如果左边运算数为 null,则选择右边运算数。
android:text="@{user.displayName ?? user.lastName}"
//等效于如下三目表达式
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
1、表达式可以通过以下语法按 ID 引用布局中的其他视图:
2、绑定类将 ID 转换为驼峰式大小写。
3、在以下示例中,TextView 视图引用同一布局中的 EditText 视图:android:text="@{exampleText.text}"
1、首先在 xml 的 data 节点中引用View
2、然后设置visibility
android:visibility="@{student.boy ? View.VISIBLE : View.INVISIBLE}"
方法引用:
android:onClick="@{handlers::onClickFriend}"
绑定表达式可将视图的点击监听器分配给MyHandlers 类的 onClickFriend() 方法,如下所示:
public class MyHandlers {
public void onClickFriend(View view) { ... }
}
注意:
1、在表达式中,您可以引用符合监听器方法签名的方法。
2、当表达式求值结果为方法引用时,数据绑定会将方法引用和所有者对象封装到监听器中,并在目标视图上设置该监听器。
3、如果表达式的求值结果为 null,则数据绑定不会创建监听器,而是设置 null 监听器。
4、表达式中的方法签名必须与监听器对象中的方法签名完全一致。
监听器绑定:
android:onClick="@{() -> presenter.onSaveClick(task)}"
绑定表达式可将视图的点击事件绑定打给Presenter 类的 onSaveClick(Task task) 方法,如下所示:
public class Presenter {
public void onSaveClick(Task task){}
}
以上,我们尚未定义传递给 onClick(View) 的 view 参数。
监听器绑定提供两个监听器参数选项:您可以忽略方法的所有参数,也可以命名所有参数。
如果您想命名参数,则可以在表达式中使用这些参数。
例如,上面的表达式可以写成如下形式:
android:onClick="@{(view) -> presenter.onSaveClick(task)}"
或者,如果您想在表达式中使用参数,则采用如下形式:
public class Presenter {
public void onSaveClick(View view, Task task){}
}
android:onClick="@{(theView) -> presenter.onSaveClick(theView, task)}"
监听长按事件,表达式应返回一个布尔值。
public class Presenter {
public boolean onLongClick(View view, Task task) { }
}
android:onLongClick="@{(theView) -> presenter.onLongClick(theView, task)}"
注意:
1、监听器绑定这些是在事件发生时进行求值的 lambda 表达式。
2、数据绑定始终会创建一个要在视图上设置的监听器。
3、事件被分派后,监听器会对 lambda 表达式进行求值。