作者:heiyulong
原文:https://mp.weixin.qq.com/s/V2haCRugRYCGDZrA9iw7bQ
本次主要讲解的内容:
1、Jetpack介绍
2、Jetpack架构组件库的相关用法
google 官方解释:
Jetpack 是一个由多个库组成的套件,可帮助开发者遵循最佳做法,减少样板代码并编写可在各种 Android 版本和设备中一致运行的代码,让开发者精力集中编写重要的代码。
Jetpack 是 Google 为解决 Android 开发碎片化,打造成熟健康生态圈提出的战略规划,是 Google 对 Android 未来提出的发展方向,同时它也是众多优秀 Android 组件的集合。
Google 官方解释:
Jetpack 的优势:
如上图:Jetpack 主要包括 4 个部分,分别是【Architecture:架构】、【UI:界面】、【behavior:行为】和【foundation:基础】。
目的:帮助开发者设计稳健、可测试且易维护的应用;
目的:提供横向功能,例如向后兼容性、测试、安全、Kotlin 语言支持,并包括多个平台开发的组件;
目的:帮助开发者的应用与标准 Android 服务(如通知、权限、分享)相集成;
注意:
上面的图之前在官网是存在的,现在官网已经找不到此图,只有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<List<User>> users;
public LiveData<List<User>> getUsers() {
if (users == null) {
users = new MutableLiveData<List<User>>();
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<Item> selected = new MutableLiveData<Item>();
public void select(Item item) {
selected.setValue(item);
}
public LiveData<Item> 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.
});
}
}
该方法的优势:
使用 LiveData 对象
官网使用参考
1、创建 LiveData 对象
LiveData 是一种可用于任何数据的封装容器,其中包括可实现 Collections 的对象,如 List。LiveData 对象通常存储在 ViewModel 对象中,并可通过 getter 方法进行访问,如以下示例中所示:
public class NameViewModel extends ViewModel {
// Create a LiveData with a String
private MutableLiveData<String> currentName;
public MutableLiveData<String> getCurrentName() {
if (currentName == null) {
currentName = new MutableLiveData<String>();
}
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<String> nameObserver = new Observer<String>() {
@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 代码。请注意赋值表达式中 @{} 语法的使用:
<TextView
android:text="@{viewmodel.userName}" />
注意:
如果您使用Data Biding的主要目的是取代 findViewById() 调用,请考虑改用ViewBinding。
使用过ButterKnife的都知道,目前ButterKnife作者建议切换至ViewBindng使用;在许多情况下,ViewBinding可简化实现,提高性能,提供与DataBinding相同的好处。
布局和绑定表达式
数据绑定的布局以根标记 layout 开头,后跟 data 元素和 view 根元素。如下:
注意:布局表达式应保持精简,因为它们无法进行单元测试,并且拥有的 IDE 支持也有限。为了简化布局表达式,可以使用自定义绑定适配器。
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<!--data 中的 user 变量描述了可在此布局中使用的属性。 -->
<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>
系统会为每个布局文件生成一个绑定类。
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里支持使用以下表达式:
不支持以下表达式:
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}"
<EditText
android:id="@+id/example_text"
android:layout_height="wrap_content"
android:layout_width="match_parent"/>
<TextView
android:id="@+id/example_output"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{exampleText.text}"/>
1、首先在 xml 的 data 节点中引用View
2、然后设置visibility
<data>
<import type="android.view.View"/>
</data>
android:visibility="@{student.boy ? View.VISIBLE : View.INVISIBLE}"
方法引用:
android:onClick="@{handlers::onClickFriend}"
绑定表达式可将视图的点击监听器分配给MyHandlers 类的 onClickFriend() 方法,如下所示:
public class MyHandlers {
public void onClickFriend(View view) { ... }
}
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="handlers" type="com.example.MyHandlers"/>
<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}"
android:onClick="@{handlers::onClickFriend}"/>
</LinearLayout>
</layout>
注意:
1、在表达式中,您可以引用符合监听器方法签名的方法。
2、当表达式求值结果为方法引用时,数据绑定会将方法引用和所有者对象封装到监听器中,并在目标视图上设置该监听器。
3、如果表达式的求值结果为 null,则数据绑定不会创建监听器,而是设置 null 监听器。
4、表达式中的方法签名必须与监听器对象中的方法签名完全一致。
监听器绑定:
android:onClick="@{() -> presenter.onSaveClick(task)}"
绑定表达式可将视图的点击事件绑定打给Presenter 类的 onSaveClick(Task task) 方法,如下所示:
public class Presenter {
public void onSaveClick(Task task){}
}
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="task" type="com.android.example.Task" />
<variable name="presenter" type="com.android.example.Presenter" />
</data>
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent">
<Button android:layout_width="wrap_content" android:layout_height="wrap_content"
android:onClick="@{() -> presenter.onSaveClick(task)}" />
</LinearLayout>
</layout>
以上,我们尚未定义传递给 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 表达式进行求值。
后台私信回复 1024 免费领取 SpringCloud、SpringBoot,微信小程序、Java面试、数据结构、算法等全套视频资料。