AndroidX全解析

Posted open-Xu

tags:

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

目录

一.android Jetpack

Android Jetpack

Jetpack是一套库、工具和指南,可帮助开发者更轻松地编写优质应用。这些组件可帮助您遵循最佳做法、让您摆脱编写样板代码的工作并简化复杂任务,以便您将精力集中放在所需的代码(业务代码)上。

Android Jetpack组件包含一系列Android库,这些库是为协同工作而构建的,它们都在Android应用中提供向后兼容性。google在不但完善这套库,而且将它放在官网首页单独的一栏,足以证明google对它的重视和支持。它包含四个部分内容:

二.AndroidX

AndroidX不是一个Android版本,它是一套API集合,命名空间为androidx,其中的所有软件包都使用一致的命名空间,以字符串androidx开头。


AndroidX包含了Android Jetpack组件库的所有内容,也就是说Android Jetpack组件相关代码是放在AndroidX包中的。

不仅如此,AndroidX还包含Android支持库的内容,在之前我们使用Android支持库相关API(android.support.v4.app.Fragmentandroid.support.v7.widget.RecyclerView等)都需要依赖很多支持库:

support库可以看作是三方库,只是它是由google官方发布的,这部分内容是在发布android sdk时没有考虑到(未包含),之后又想在低版本android设备上使用的内容(基于低版本sdk实现),所以google在提供了一个新的api时会以support的形式提供,我们就可以像使用三方库一样依赖使用

implementation 'com.android.support:appcompat-v7:27.1.0'
implementation 'com.android.support:recyclerview-v7:27.1.0'
implementation 'com.android.support:design:27.1.0'

这样会导致很多问题,比如所有com.android.support库必须使用完全相同的版本规范,混合版本可能导致运行时崩溃,这为项目管理带来很多挑战。

现在这些支持库都被集合在AndroidX中了,今后可能会发布新的支持库也都将在AndroidX库中进行,原来的support支持库不再维护(最后版本Support Library 28),所以不应该继续使用。

目前很多官方组件库都迁移到了AndroidX,一些三方开源库也在积极响应,所以Android Jetpack是今后Android开发的一个趋势,如果公司现有项目还没有迁移到AndroidX,应该尽快迁移,而新开发的项目更应该基于AndroidX。

有关androidx命名空间中的所有软件包和类,请参阅 AndroidX参考文档

2.1 在项目中使用androidx库

如果要在新项目中使用命名空间为 androidx 的库,按照如下步骤:

  • 将编译SDK compileSdkVersion设置为Android9.0(API级别28)或更高版本
  • gradle.properties文件中将以下两个Android Gradle插件标记设置为true
### Android 插件会使用对应的 AndroidX 库,而非支持库。默认为 false。
android.useAndroidX=true
### Android 插件会通过重写其二进制文件来自动迁移现有的第三方库,以使用 AndroidX 依赖项。默认为 false。
android.enableJetifier=true
#### root build.gradle
allprojects 
	repositories 
		google()   //添加 google() 代码库
		jcenter()
	


#### app build.gradle
implementation 'androidx.appcompat:appcompat:1.0.2'

Jetpack库和Support库的内容都被集合在AndroidX中了,但是每个库还是单独维护的,我们可以选择性使用其中某些库

2.2 老项目迁移到AndroidX

谷歌开发者微信公众号前不久2020/02/26发表了一篇中文版文章是时候迁移至 AndroidX了!,介绍了为什么要迁移至AndroidX以及怎样迁移,讲解非常详细,大家也可以看一下。


开发模式

在讲解MVVM模式之前,我们要搞清楚这种模式为什么会出现?它有什么优点能解决什么问题?这两个问题其实想表达的是一个意思,任何事物的出现必定是为了解决一些问题,那解决了这些问题就是这个事物的优点。

MVVM模式是两年才被提出推广,它不是凭空出现的,开发模式的优化是一个进化的过程(MVC—>MVP—>MVVM)。早期开发最火的模式是MVC,后来由于种种弊端,出现了MVP,然后才是MVVM,这三种模式就是后者对前者的优化,所以为了搞清楚MVVM,不妨从头开始说起,这样才能知道MVVM到底有什么好处。

模式只是一种思想,在不同的语言、平台都可以使用,并非局限于Android。每个人对每种开发模式的理解不一样,模式中的字母对应什么内容也没有强行规定,只要自己能够理解就行。下面我们简单的分析一下这些模式

三. MVC

在Android初期,google在设计时就是基于MVC模式(三层模型Model-View-Controller)的,将同一种类型的代码放在一块实现代码分层,这样方便管理。所以我们在开发项目时都是写布局layout,创建bean,在Activity中写逻辑获取数据,刷新页面这一套固定流程。

Model

public class User 
    private String userName;
    private String userPwd;
    public User(String userName, String userPwd) 
        this.userName = userName;
        this.userPwd = userPwd;
    
    public String getUserName() 
        return userName;
    
    public void setUserName(String userName) 
        this.userName = userName;
    
    public String getUserPwd() 
        return userPwd;
    
    public void setUserPwd(String userPwd) 
        this.userPwd = userPwd;
    

View

<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        <com.openxu.core.view.TitleLayout
            android:id="@id/titleLayout"
            style="@style/TitleDefStyle"
            app:textcenter="登录"
            app:iconBack="@null"/>
        <EditText
            android:id="@+id/et_name"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:layout_margin="@dimen/activity_sides_margin"
            android:hint="请输入用户名"/>
        <EditText
            android:id="@+id/et_pwd"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:layout_margin="@dimen/activity_sides_margin"
            android:hint="请输入密码"/>
        <Button
            android:id="@+id/btn_login"
            style="@style/btn_red"
            android:layout_marginTop="20dp"
            android:text="登录"/>
    </LinearLayout>
    <ProgressBar
        android:id="@+id/progressBar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:visibility="gone"
        android:layout_centerInParent="true"/>
</RelativeLayout>

Controller

public class LoginActivity extends BaseActivity 
    private Button btn_login;
    private EditText et_name, et_pwd;
    private ProgressBar progressBar;
    @Override
    protected int getContentView() 
        return R.layout.activity_login;
    
    @Override
    protected void initView() 
        btn_login = findViewById(R.id.btn_login);
        et_name = findViewById(R.id.et_name);
        et_pwd = findViewById(R.id.et_pwd);
        progressBar = findViewById(R.id.progressBar);

        btn_login.setOnClickListener(v -> 
            User user = new User(et_name.getText().toString().trim(), et_pwd.getText().toString().trim());
            //延迟2s模拟请求登录接口
            progressBar.setVisibility(View.VISIBLE);
            new Handler().postDelayed(() -> 
                progressBar.setVisibility(View.GONE);
                if (TextUtils.isEmpty(user.getUserName()) || TextUtils.isEmpty(user.getUserPwd()))
                    XToast.error("登录失败,请检查用户名密码").show();
                    return;
                
                XToast.success("登录成功").show();
            , 2000);
        );
    

数据模型JavaBean对应模型层(Model),xml布局对应视图层(View),Activity对应控制层(Controller),这种设计的初衷是好的,也在一定程度上实现了分层,但是随着Android的发展,应用的复杂性,导致问题逐渐暴露出来。

视图层对应的xml布局是一个静态页面,绑定数据等操作需要在Activity中绑定。模型层的数据请求、数据库处理和一些I/O操作都被放到了Activity中,模型层仅仅是JavaBean。而充当控制层的Activity中不仅仅要承担业务逻辑,还需要担任绑定数据等视图操作。这样会导致Activity中代码量非常大,代码臃肿繁杂,可读性差且不利于维护

四. MVP

鉴于MVC模式的弊端,在Android领域就有人尝试使用MVP模式(Model-View-Presenter)来解决这些问题,各大开源平台上也有很多MVP的开源框架。下面看一下使用MVP实现登录功能的项目结构(代码量较多,寄不贴了):

相对于MVC,MVP模式在Android中应用的最大改进就是,将业务逻辑的控制和数据请求的内容从Activity中剥离出来,让Activity变为View层了,解决了Activity内容臃肿的问题,而且层次更加分明。业务逻辑在Presenter层处理,数据请求等跟数据相关的都划分为Model层。三层之间通过接口Interface实现通信,Presenter作为中间层,View不与Model直接接触,业务逻辑和数据层是可以复用的,实现了解耦。

MVP最大的问题就是需要定义大量的接口,代码量变多了,虽然有很多开源框架实现了一键生成模板,但是还是有很多人接受不了,我本人就是其中一个,所以当初公司项目重构时搭建框架没有选择MVP模式

五. MVVM

5.1 Android架构组件

Android架构组件是Android Jetpack的一部分,它是一组库,可帮助您设计稳健、可测试且易维护的应用。这个架构组件可以说是google在Android平台推广MVVM模式而编写的一套类库,使用这套类库中的API很优雅的实现MVVM模式。

下面我们一个一个的学习这些支持类库,相关类库版本信息和依赖方法参考:AndroidX版本说明


5.2 appcompat

如果要使用ViewModel,不能再继承普通android.app.Activity,而是继承androidx.appcompat.app.AppCompatActivity,Fragment需要继承androidx.fragment.app.Fragment

androidx库中的AppCompatActivityFragment实现了LifecycleOwnerViewModelStoreOwner等必要接口,而ViewModelStoreOwner是创建ViewModel时必须要实现的接口。androidx.appcompat:appcompat就是之前的com.android.support:appcompat-v7,这个库的作用是对低版本做兼容,而且里面有很多资源文件我们在开发中需要用到,比如style.xml中的主题。

//api 'com.android.support:appcompat-v7:28.0.0'
//添加依赖
api 'androidx.appcompat:appcompat:1.1.0'

5.3 ViewModel

诸如 Activity和Fragment之类的界面控制器主要用于显示界面数据、对用户操作做出响应或处理操作系统通信(如权限请求)。如果要求界面控制器也负责从数据库或网络加载数据,那么会使类越发膨胀。为界面控制器分配过多的责任可能会导致单个类尝试自己处理应用的所有工作,而不是将工作委托给其他类。以这种方式为界面控制器分配过多的责任也会大大增加测试的难度。从界面控制器逻辑中分离出视图数据所有权的做法更易行且更高效

上面这段话摘自官网,意思就是ViewModel的作用就是替Activity分担加载数据的工作,让Activity只负责显示界面、响应用户交互等工作。

5.3.1 ViewModle的使用

  • 添加依赖
//添加依赖
def lifecycle_version = "2.2.0"
def arch_version = "2.1.0"
// ViewModel
api "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
// LiveData(示例中需要用到)
api "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
  • 创建VM继承ViewModel,编写业务逻辑代码(数据请求)
/**
 * Author: openXu
 * Time: 2020/5/13 16:56
 * class: LoginViewModel
 * Description:
 *
 * 需要继承ViewModel类,ViewModel类源码注释有示例怎样使用。
 *
 * 1. ViewModel负责为Activity/Fragment准备和管理数据,它还可以处理一部分业务逻辑。
 * 2. ViewModel总是与Activity/Fragment关联创建,并且只要Activity/Fragment还活着就会保留,直到被销毁。
 * 换句话说,这意味着如果ViewModel的所有者因配置更改(例如旋转)而被销毁,则ViewModel不会被销毁。所有者的新实例将重新连接到现有的ViewModel。
 * 3. ViewModel通常通过LiveData或者DataBinding实现与Activity/Fragment的数据通信
 * 4. ViewModel的唯一职责是管理数据,他不应该持有Activity/Fragment的引用
 *
 */
public class LoginViewModel extends ViewModel 

    //对请求接口的结果数据使用LiveData封装,LiveData可以通知Activity/Fragment数据变化
    public final MutableLiveData<Boolean> loginResult = new MutableLiveData<>();
    //显示进度对话框
    public final MutableLiveData<Boolean> showProgress = new MutableLiveData<>();
    public void login(User user)
        //显示进度对话框
        showProgress.setValue(true);
        //延迟2s模拟请求登录接口
        new Handler().postDelayed(() -> 
            //隐藏进度对话框
            showProgress.setValue(false);
            if (TextUtils.isEmpty(user.getUserName()) || TextUtils.isEmpty(user.getUserPwd()))
                //登录失败,设置结果数据为false
                loginResult.setValue(false);
                return;
            
            //登录成功,设置结果数据为true
            loginResult.setValue(true);
        , 2000);
    

    /**
     * ★Activity调用onDestroy()之前会调用此方法
     * 表示ViewModel不再使用并将销毁,可以在此进行清除数据的操作,避免内存泄漏
     */
    @Override
    protected void onCleared() 
        super.onCleared();
        XLog.w("===========Activity销毁了,ViewModel清除数据");
    


  • 创建Activity/Fragment,继承androidx.appcompat.app.AppCompatActivity,Fragment需要继承androidx.fragment.app.Fragment
public abstract class BaseActivity extends AppCompatActivity 
	...

  • onCreate()方法使用new ViewModelProvider(this).get(XXXViewModel.class)获取ViewModel实例对象,并调用其方法请求数据
public class LoginActivity extends BaseActivity 

    private Button btn_login;
    private EditText et_name, et_pwd;
    private ProgressBar progressBar;
    LoginViewModel viewModel;
    @Override
    protected int getContentView() 
        return R.layout.activity_login;
    
    @Override
    protected void initView() 
		btn_login = findViewById(R.id.btn_login);
        et_name = findViewById(R.id.et_name);
        et_pwd = findViewById(R.id.et_pwd);
        progressBar = findViewById(R.id.progressBar);

        /**
         * ★使用new ViewModelProvider(ViewModelStoreOwner owner).get(XXXViewModel.class)获取对应的ViewModel对象
         * ViewModelProvider构造方法接受ViewModelStoreOwner实例,androidx.appcompat.app.AppCompatActivity和androidx.fragment.app.Fragment都实现了ViewModelStoreOwner
         */
        viewModel = new ViewModelProvider(this).get(LoginViewModel.class);
        //★在Activity中监听viewModel的LiveData数据变化,实现ViewModel和Activity的通信
        viewModel.showProgress.observe(this, aBoolean -> 
            progressBar.setVisibility(aBoolean?View.VISIBLE:View.GONE);
        );
        viewModel.loginResult.observe(this, aBoolean -> 
            if(aBoolean)
                XToast.success("登录成功").show();
            else
                XToast.error("登录失败,请检查用户名密码").show();
        );
        btn_login.setOnClickListener(v -> 
            //★调用viewModel的login()方法实现登录接口请求
            User user = new User(et_name.getText().toString().trim(), et_pwd.getText().toString().trim());
            viewModel.login(user);
        );
    

5.3.2 ViewModel的优势

  • ViewModel的作用与Presenter一样,可以替Activity分担加载数据等业务逻辑工作,让Activity只负责显示界面、响应用户交互等工作
  • ViewModel能适配组件的生命周期,当组件被真正销毁时能实现自动清理。我们不用再手动在Activity中处理ViewModel的生命周期管理工作
  • ViewModel配合架构组件中其他支持库(LiveData)可以很优雅的实现与组件的解耦。Presenter完全通过接口实现解耦

5.3.3 ViewModel的生命周期

下图左边是官网上对ViewModel生命周期的描述,右边是我对该声明周期做的测试打印的log和解释


5.4 LiveDate

LiveData是一种可观察的数据存储器类(可用于任何类型数据包括复杂数据的封装),就是在我们数据外面包裹一层变为被观察者。与常规的被观察类不同,LiveData具有生命周期感知能力,它遵循应用组件(如 Activity、Fragment 或 Service)的生命周期。这种感知能力可确保LiveData仅更新处于活跃生命周期状态的应用组件观察者,并能够自动清理以防止对象泄漏和过多的内存消耗。。

5.4.1 LiveData的优势

  • 不会因Activity停止而导致崩溃。如果Activity不处于活跃状态,它不会接收任何LiveData事件
  • 数据始终保持最新状态,确保界面符合数据状态。如果非活跃状态的Activity观察的LiveData数据发生变化,它会在再次变为活跃状态时接收最新的数据
  • 不再需要手动处理生命周期。观察LiveData数据的观察者绑定到了Activity/Fragment的LifecycleOwner对象,当调用onDestroy()方法时,LiveData会自动移除观察者,所以不会发生内存泄露。

5.4.2 LiveData的使用

  1. ViewModel类中创建LiveData实例以存储某种类型的数据
/**
 * LiveData是一个抽象类,而且它没有公开可用的方法来更新存储的数据,应该使用其子类MutableLiveData
 * MutableLiveData类公开了setValue(T)和postValue(T)方法用于更新存储的数据
 * setValue(T)应该在主线程中调用,postValue(T)应该在子线程中调用
 */
public final MutableLiveData<Boolean> loginResult = new MutableLiveData<>();
public final MutableLiveData<Boolean> showProgress = new MutableLiveData<>();
  1. ActivityFragmentonCreate()方法中创建Observer观察者对象观察LiveData数据变化
/**
 * observeForever(Observer)观察LiveData数据变化,这种方式没有关联LifecycleOwner对象,
 * 观察者会被视为始终处于活跃状态,因此它始终会收到关于修改的通知。
 * 可以通过调用removeObserver(Observer) 方法来移除这些观察者
 */
viewModel.loginResult.observeForever(new Observer<Boolean>() 
	@Override
	public void onChanged(Boolean aBoolean) 
		//当数据变化时回调
	
);
/**
 * observe(LifecycleOwner owner, Observer<? super T> observer)
 * 为LiveData添加观察者并绑定组件的生命周期,只有当组件活跃(started)时才会收到数据更新
 */
viewModel.loginResult.observe(this, new Observer<Boolean>() 
	@Override
	public void onChanged(Boolean aBoolean) 
		//当数据变化时回调
	
);

5.4.3 LiveData使用演示

参见工程com.openxu.mvvm.LoginActivity

演示过程:LoginActivity是一个模拟登录的页面,当点击登录按钮会调用LoginViewModel的登录方法,同时会打开两个新的遮罩页面,第一个遮罩页面是半透明的,第二个遮罩页面是不透明的。等待登录完成(2s),一层层返回,可以看到只有当Activity是可见状态(onStart()执行之后)才能接受LiveDate的数据更新


5.5 ViewBinding

虽然MVVM模式中将Activity和xml布局都作为View层,但是Activity和xml布局毕竟是两个独立的文件,这两个文件怎么实现交互?

5.5.1 findViewById()

最开始,我们为Activity设置布局id,然后通过findViewById()实例化控件,调用控件实例的方法(如setText())实现视图交互,如果页面很复杂,会造成Activity中大量的findViewById()的代码。

private Button btn_login;
private EditText et_name, et_pwd;
private ProgressBar progressBar;

btn_login = (Button)findViewById(R.id.btn_login);
et_name = (EditText)findViewById(R.id.et_name);
et_pwd = (EditText)findViewById(R.id.et_pwd);
progressBar = (ProgressBar)findViewById(R.id.progressBar);

5.5.2 ButterKnife

后来,ButterKnife出现了,这个开源库专注于Android系统View层的注入,可以省去findViewById()的调用,通过注解在编译阶段生成新的类帮我们完成View的初始化操作。

//初始化控件实例
@BindView(R.id.et_name) 
EditText username;
@BindView(R.id.et_pwd) 
EditText password;
//将控件数据绑定到对象上
@BindString(R.string.login_error) 
String loginErrorMessage;
//绑定事件
@OnClick(R.id.R.id.btn_login) 
void submit() 
// TODO call server...

但是我们还是需要在Activity中注册View的引用,而且ButterKnife的介绍中有一段话:

Attention: This tool is now deprecated. Please switch to view binding. Existing versions will continue to work, obviously, but only critical bug fixes for integration with AGP will be considered. Feature development and general bug fixes have stopped.

注意:现在不建议使用此工具。请切换到ViewBinding。很明显,现有版本将继续起作用,但是仅考虑与AGP集成的关键错误修复。功能开发和常规错误修复已停止。

5.5.3 ViewBinding

通过视图绑定功能,可以更轻松地编写可与视图交互的代码。在module中启用视图绑定之后,系统会为该module中的每个XML布局文件生成一个绑定类,绑定类的实例包含对在相应布局中具有ID的所有View的直接引用。避免了视图ID不存在导致的空指针异常,和视图类型转换异常

设置

ViewBinding可按模块启用,在需要启用的module的build.gradle脚本中添加如下配置:

android 
	...
	//注意:视图绑定在 Android Studio 3.6 Canary 11 及更高版本中可用
	viewBinding 
		enabled = true
	

开启绑定后,gradle配置阶段会自动为该module下所有xml布局生产绑定类,如果希望某些布局文件不要生成绑定类,请在布局中设置tools:viewBindingIgnore="true"

<LinearLayout
	...
	tools:viewBindingIgnore="true" >
    ...
</LinearLayout>

自动生成的Binding类

Android Gradle插件会将xml布局文件的名称转换为驼峰式大小写,并在末尾添加Binding一词作为自动生成的绑定类的类名,比如activity_login.xml会生成ActivityLoginBinding.java。这个类持有所有布局中设置了id的控件的引用,以及跟布局的引用。

使用

private ActivityLoginBinding binding;

@Override
protected void onCreate(Bundle savedInstanceState) 
	super.onCreate(savedInstanceState);
	XLog.v("-----Activity.onCreate()");
	//使用静态inflate()方法获取生成的绑定类的实例
	binding = ActivityLoginBinding.inflate(R.layout.activity_login);
	//getRoot()方法获取布局文件的根视图对象
	setContentView(binding.getRoot);
	//通过binding对象中持有的view引用直接对view进行设置,而不需要findViewById()
	binding.name.setText("name"); 
	binding.button.setOnClickListener(v->);

注意

ViewBinding只有在Android Studio 3.6 Canary 11 及更高版本中才能使用。比如现在我的Android Studio版本才到3.2.1,肯定就没法用ViewBinding了,这么好的库不用岂不是浪费?其实这个库的功能非常简单,只是帮我们省去了findViewById()的烦恼,下面有一个更加强大的库,可以完全代替ViewBinding,还没有Studio版本限制。所以我这里就不升级了,直接上更加强大的库,如果大家Studio版本符合要求可以按照上面的步骤试一试。


5.6 DataBinding

5.6.1 使用入门

DataBinding

数据绑定库与Android Gradle插件捆绑在一起。您无需声明对此库的依赖项,但必须启用它。也就是说DataBinding相关的库我们不需要单独依赖,只需要在module中开启即可

  • 在需要使用DataBinding的module下build.gradle中开启dataBinding,会自动为包裹的xml布局生成用于访问布局的变量视图的Binding类。

★注意:即使某个module不需要使用DataBinding,但是它依赖的别的module使用了,也需要在该module中配置数据绑定

android 
	...
	dataBinding 
		enabled = true
	

  • xml布局用<layout>标签包裹:
//activity_login.xml

<layout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <data>
        <variable name="user" type="com.openxu.bean.User"/>
    </data>
	<RelativeLayout
		android:layout_width="match_parent"
		android:layout_height="match_parent">
		...
		
		<TextView
            android:id="@+id/tv_result"
            style="@style/text_style_def"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@user.userName"/>
	</RelativeLayout>
</layout>

  • 执行build任务重新构建,会在build目录中自动生成对应Binding类。类名称基于布局文件的名称转换为驼峰形式并在末尾添加Binding后缀。

  • 获取Binding实例对象(多种方式)
//Activity中通过DataBindingUtil.setContentView()设置布局获取绑定类实例对象(代替原来的setContentView(getContentView(id));)
ActivityLoginBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_login);

//也可以使用下面的方式绑定视图
ActivityLoginBinding binding = ActivityLoginBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());

//如果要在Fragment、ListView或RecyclerView适配器中使用数据绑定项,可以使用绑定类或DataBindingUtil类的inflate()方法
ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);

//如果布局是使用其他机制扩充的,可单独绑定
View viewRoot = LayoutInflater.from(this).inflate(layoutId, parent, attachToParent);
MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);
ViewDataBinding binding = DataBindingUtil.bind(viewRoot);

  • Binding使用
//通过DataBindingUtil设置布局获取绑定类实例对象
ActivityLoginBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_login);
//xml布局中带id的控件对象
String userName = binding.etName.getText().toString().trim();
String userPwd = binding.etPwd.getText().toString().trim();
//设置<data>变量
binding.setUser(new User(userName,userPwd));

5.6.2 自定义Binding类名称

默认情况下,绑定类是根据布局文件的名称生成的,以大写字母开头,移除下划线(_),将后一个字母大写,最后添加后缀Binding。该类位于模块包下的databinding包中。例如,布局文件activity_login.xml会生成ActivityLoginBinding.java。如果模块包是com.openxu.mvvm,则绑定类放在com.openxu.mvvm.databinding包中。通过调整data元素的class特性,绑定类可重命名或放置在不同的包中。示例:

//当前模块包+databinding   com.openxu.mvvm.databinding.ActivityLogin1Binding
<data class="ActivityLogin1Binding">
	<variable name="user" type="com.openxu.bean.User"/>
</data>

//当前模块包(相对路径)        com.openxu.mvvm.ActivityLogin1Binding
<data class=".ActivityLogin1Binding">
	<variable name="user" type="com.openxu.bean.User"/>
</data>

//完整软件包名称(绝对路径)     com.openxu.databinding.ActivityLogin1Binding
<data class="com.openxu.databinding.ActivityLogin1Binding">
	<variable name="user" type="com.openxu.bean.User"/>
</data>

5.6.3 布局数据绑定表达式@

在根标签<layout>中的<data>标签中使用<variable>标记数据变量,该变量就是可以在此布局中使用的属性。name属性是变量名称,type属性是变量类型全名,在布局中使用@表达式绑定数据到控件属性中。上面的示例中,android:text="@user.userName"会访问User类的userName属性、getUserName()或者userName()中的一个(如果存在)。如果属性是不可访问的(private修饰无get方法),构建时会报错,不能生成Binding类:

通过Binding类的setUser(user)方法,可以为变量赋值,而xml布局中使用@访问的数据就会自动刷新。需要注意的是只有设置新的user对象才会触发数据绑定,如果仅仅是user对象的某个属性变化或者设置的user对象与之前相同,则不会触发数据绑定。

绑定表达式详解

  • 属性引用
//@表达式中可以直接引用对象的属性(属性、getter方法、lastName())
android:text="@user.lastName"
  • 可以在表达式语言中使用以下运算符和关键字
算术运算符 + - / * %
字符串连接运算符 +
逻辑运算符 && ||
二元运算符 & | ^
一元运算符 + - ! ~
移位运算符 >> >>> <<
比较运算符 == > < >= <=(请注意,< 需要转义为 &lt;)
instanceof
分组运算符 ()
字面量运算符 - 字符、字符串、数字、null
类型转换
方法调用
字段访问
数组访问 []
三元运算符 ?:
  • Null 合并运算符
//相当于 user.displayName!=null ? user.displayName : user.lastName
android:text="@user.displayName ?? user.lastName"
  • 避免出现 Null 指针异常
//生成的数据绑定代码会自动检查有没有null值并避免出现空指针异常。如下,在表达式 @user.name 中,如果user为Null,则为user.name分配默认值 null。如果您引用user.age,其中age的类型为int,则数据绑定使用默认值 0。
android:text="@user.name"
  • 集合

注意:要使 XML 不含语法错误,您必须转义 < 字符。例如:不要写成 List 形式,而是必须写成 List<String>。

<data>
	<import type="android.util.SparseArray"/>
	<import type="java.util.Map"/>
	<import type="java.util.List"/>
	<variable name="list" type="List&lt;String>"/>
	<variable name="sparse" type="SparseArray&lt;String>"/>
	<variable name="map" type="Map&lt;String, String>"/>
	<variable name="index" type="int"/>
	<variable name="key" type="String"/>
</data>
	//使用[]运算符访问常见集合,例如数组、列表、稀疏列表和映射
    …
    android:text="@list[index]"
    …
    android:text="@sparse[index]"
    …
    android:text="@map[key]"
	//还可以使用 object.key 表示法在映射中引用值。例如上面 @map[key] 可替换为 @map.key
	android:text="@map[key]"
  • 字符串字面量
//可将属性值用单引号括住,这样就可以在表达式中使用双引号表示字符串了
android:text='@map["firstName"]'
//也可以使用反单引号 ` (英文状态下键盘Esc下面的按键)表示字符串字面量
android:text="@map[`firstName`]"
  • 资源引用
android:padding="@large? @dimen/largePadding : @dimen/smallPadding"
  • 事件处理
//方法引用,这种方式方法的参数必须是(View view),不能是其他的,相当于设置onClick事件
public class LoginViewModel extends ViewModel 
    public void login(View v) ... 


<?xml version="1.0" encoding="utf-8"?>
<layout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <data >
        <variable name="vm" type="com.openxu.mvvm.LoginViewModel"/>
    </data>
<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">
	/* 绑定表达式@可将视图的点击监听器分配给 LoginViewModel.login() 方法 */
	<Button
		android:id="@+id/btn_login"
		style="@style/btn_red"
		android:onClick="@vm.login"
		或者android:onClick="@vm::login"
		android:text="登录"/>
</RelativeLayout>
</layout>

//监听器绑定,相当于在布局中就设置了onClick监听,当点击时执行匿名的监听的onClick方法,而这个方法调用presenter.onSaveClick(task)
public class Presenter

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

Fragment全解析系列:那些年踩过的坑

关于androidX

package.json 字段全解析

package.json 字段全解析

在迁移到 viewpager2 androidx 时发现此错误“无法解析方法 'super(androidx.fragment.app.FragmentManager)'”

迁移到 AndroidX 时无法解析变量“$animal.sniffer.version”