Android业务架构 · 基础篇 · Jetpack四件套

14,434 阅读42分钟

【小木箱成长营】Android业务架构系列文章:

Android业务架构 · 提高篇 · MVC、MVP、MVVM和MVI四剑客

Android业务架构 · 实践篇 · MVI+Jetpack+Kotlin手把手搭建直播应用App

Tips: 关注小木箱成长营公众号, 回复"业务架构"可免费获取Android业务架构思维导图。

一、序言

Hello,我是小木箱,欢迎来到小木箱成长营业务架构系列教程,今天分享的内容是业务架构 · 基础篇 · Jetpack四件套。

2017年,Google发布了Android Architecture Components,包括Room、LiveData、ViewModel和Paging等组件,旨在帮助开发者更轻松地实现MVVM架构。

2018年,Google在I/O大会上推出的一套Android开发组件库,旨在帮助开发者更轻松、更高效地构建Android应用。

随着时间的推移,Android Jetpack不断地更新和增加新的组件,使得Android应用的开发更加高效、稳定和可维护。

今天的主题主要分为三个维度。第一个维度是4W2H分析Jetpack,第二个维度是Jetpack四件套。第三个维度是总结与展望。

其中,4W2H分析Jetpack主要针对Jetpack提出了6个高价值问题。

其中,Jetpack四件套列举了LifeCycle、LiveData、ViewModel和DataBing四种常见的Jetpack工具包。

如果学完业务架构系列教程,那么任何人都能完整构建一套适合企业业务背景的架构设计。

二、4W2H分析Jetpack

  2.1 What: Jetpack是什么?

 Android Jetpack是一组Android软件组件、工具和指南,它们可以帮助开发者构建高质量、稳定的Android应用程序。Jetpack中包含多个库,它们旨在解决Android应用程序开发中的常见问题,并提供一致的API和开发体验。

Jetpack中包含的库包括:

  1. ViewModel:帮助管理UI组件的生命周期并存储和管理UI相关的数据。
  2. LiveData:提供了响应式编程的功能,可以让数据在数据源发生变化时自动更新UI。
  3. Room:提供了一个抽象层,可以让开发者方便地访问和管理SQLite数据库。
  4. Navigation:提供了一种简单、一致的方式来处理应用程序的导航。
  5. WorkManager:提供了一种简单、可靠的方式来管理后台任务。 除此之外,Jetpack还包括了诸如Paging、Data Binding、Preferences、Security等库,这些库都旨在简化开发过程并提高应用程序的性能和可靠性。

  2.2 Where: 什么场景下使用Jetpack?

  Jetpack适用于开发各种类型的Android应用程序,包括单页面应用程序、多页面应用程序、后台任务应用程序等。下面是一些适合使用Jetpack的场景:

  1. 构建大型应用程序:Jetpack提供了一些库,如ViewModel、LiveData和Navigation,可以帮助开发者更好地管理应用程序的生命周期、状态和导航,使得构建大型应用程序更加容易。

  2. 处理后台任务:Jetpack中的WorkManager库提供了一种简单、可靠的方式来管理后台任务,如数据同步、推送通知、文件上传等。

  3. 数据库访问:Jetpack中的Room库提供了一个抽象层,可以让开发者方便地访问和管理SQLite数据库,使得数据存储和访问更加容易。

  4. 响应式编程:Jetpack中的LiveData和Data Binding库提供了响应式编程的功能,可以让数据在数据源发生变化时自动更新UI,提高应用程序的性能和可靠性。

  5. 代码重用:Jetpack中的各种库都旨在解决Android应用程序开发中的常见问题,并提供一致的API和开发体验,使得代码重用更加容易。

  2.3 Why: 为什么使用Jetpack?

以下是使用Jetpack的一些好处:

  1. 更好的代码组织和可维护性:Jetpack提供了一组库,这些库旨在解决Android应用程序开发中的常见问题,如生命周期管理、数据存储、后台任务处理等。使用Jetpack可以使代码更加模块化,易于维护。
  2. 更好的代码可读性:Jetpack提供了一致的API和开发体验,可以使代码更加易于理解和阅读。
  3. 更好的开发效率:Jetpack提供了一些库和工具,如Navigation和Data Binding,可以帮助开发者更快地开发Android应用程序,提高开发效率。
  4. 更好的性能和可靠性:Jetpack中的一些库,如LiveData和WorkManager,提供了响应式编程和后台任务处理的功能,可以提高应用程序的性能和可靠性。
  5. 更好的向后兼容性:Jetpack中的一些库,如ViewModel和LiveData,提供了对Android不同版本的向后兼容性支持,可以使开发者更容易地编写适用于不同版本的Android系统的应用程序。 综上所述,Jetpack可以帮助开发者更好地组织代码、提高开发效率、提高应用程序的性能和可靠性,并提供了对不同版本的Android系统的向后兼容性支持。

  2.4 Who: 什么样的团队该使用Jetpack?

Jetpack适用于各种规模的Android开发团队,特别是那些希望提高应用程序质量、开发效率和可维护性的团队。以下是一些团队适合使用Jetpack的场景:

  1. 大型团队:Jetpack提供了一致的API和开发体验,可以使大型团队更容易协作开发,提高团队的开发效率和代码质量。
  2. 跨职能团队:Jetpack提供了一些库和工具,如Navigation和Data Binding,可以帮助开发者更快地开发Android应用程序,使得跨职能团队之间更容易协作。
  3. 需要提高应用程序性能和可靠性的团队:Jetpack中的一些库,如LiveData和WorkManager,提供了响应式编程和后台任务处理的功能,可以提高应用程序的性能和可靠性。
  4. 需要提高代码可维护性的团队:Jetpack提供了一些库,如ViewModel和Room,可以帮助开发者更好地管理应用程序的状态和数据,使得代码更易于维护。
  5. 需要保持向后兼容性的团队:Jetpack中的一些库,如ViewModel和LiveData,提供了对Android不同版本的向后兼容性支持,可以使开发者更容易地编写适用于不同版本的Android系统的应用程序。   综上所述,Jetpack适合各种规模和类型的Android开发团队,特别是那些希望提高应用程序质量、开发效率和可维护性的团队。

  2.5 How: 怎样使用Jetpack?

  以下是使用Jetpack的一般步骤:

  1. 添加Jetpack库:Jetpack库可以通过在build.gradle文件中添加依赖项的方式进行添加。例如,添加Lifecycle库的依赖项:

  dependencies {
      def lifecycle_version = "2.3.1"// ViewModel
      implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version"// LiveData
      implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle_version"// Lifecycle
      implementation "androidx.lifecycle:lifecycle-runtime:$lifecycle_version"
  }

使用Jetpack库:在添加Jetpack库后,就可以在应用程序中使用Jetpack库提供的功能了。例如,使用ViewModel库创建一个ViewModel类:


   import androidx.lifecycle.ViewModel

   class MyViewModel : ViewModel() {
       // Add ViewModel logic here
   }

结合Jetpack组件使用:Jetpack库提供的组件可以结合使用,以提高应用程序的开发效率和可维护性。例如,使用ViewModel库和LiveData库实现一个响应式的用户界面:

  
    class MyActivity : AppCompatActivity() {

        private lateinit var viewModel: MyViewModel

        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_my)

            // Get a reference to the ViewModel
            viewModel = ViewModelProvider(this).get(MyViewModel::class.java)

            // Observe a LiveData object in the ViewModel
            viewModel.someData.observe(this, Observer {
                // Update the UI with the new data
            })
        }
    }

以上是使用Jetpack的一般步骤。需要根据具体的Jetpack库和应用程序需求进行相应的配置和代码实现。

  2.6 How Much: 使用Jetpack业务价值

使用Jetpack可以带来以下业务价值:

  1. 提高开发效率:Jetpack提供了一些开发工具和库,例如ViewModel、LiveData和Room,可以减少重复的编写代码,简化开发流程,并提高开发效率。
  2. 提高应用程序质量:Jetpack提供了一致的API和开发体验,可以减少由于人为因素引起的代码错误,提高应用程序的质量。
  3. 提高应用程序性能:Jetpack中的一些库,例如Lifecycle和WorkManager,提供了响应式编程和后台任务处理的功能,可以提高应用程序的性能和响应速度。
  4. 简化应用程序架构:Jetpack提供了一些组件和库,例如ViewModel和Data Binding,可以帮助开发者更好地管理应用程序的状态和数据,并简化应用程序的架构。
  5. 支持向后兼容性:Jetpack中的一些库,例如ViewModel和LiveData,提供了对Android不同版本的向后兼容性支持,可以使开发者更容易地编写适用于不同版本的Android系统的应用程序。

综上所述,使用Jetpack可以带来多种业务价值,可以提高应用程序的质量、性能和开发效率,同时简化应用程序架构和支持向后兼容性,可以使应用程序更易于维护和升级。

三、Jetpack四件套

3.1 LifeCycle

3.1.1 LifeCycle基础定义

Android Jetpack Lifecycle是Android Jetpack组件库中的一部分,Lifecycle是基于Android Framework中的Lifecycle概念而构建的。

Lifecycle提供了一种轻松管理组件(如Activity和Fragment)生命周期的方式,同时也支持自定义组件的生命周期。

Jetpack Lifecycle提供了一组类和接口,使得开发者可以在组件的生命周期各个阶段执行相应的操作。

这些类和接口包括:

  • LifecycleOwner: 拥有生命周期的对象,通常是Activity和Fragment。

  • LifecycleObserver: 监听组件的生命周期事件的观察者对象。

  • Lifecycle: 组件的生命周期,包括CREATED、STARTED、RESUMED、PAUSED、STOPPED、DESTROYED等状态。

  • LiveData: 一个可观察的数据容器,可以在组件生命周期的不同阶段更新数据。

使用Jetpack Lifecycle,可以更容易地避免内存泄漏和其他生命周期相关的问题。

例如,可以在组件被销毁时自动释放资源、取消网络请求等操作。

此外,Jetpack Lifecycle还提供了一种方式来创建自定义的生命周期状态,以更好地满足App的需求。

总之,Jetpack Lifecycle是Android Jetpack组件库中的一个重要组件,可以帮助开发者更轻松地管理组件的生命周期,从而提高App的质量和性能。

3.1.2 LifeCycle基础使用

在App的主Activity中实现一个简单的计时器,当Activity处于前台时,计时器会不断递增,当Activity被销毁时,计时器将停止。

具体实现步骤如下:

  1. 在gradle文件中添加Jetpack组件库的依赖。

dependencies {
    implementation "androidx.lifecycle:lifecycle-extensions:2.4.0"
}
  1. 创建一个名为Timer的Java类,并实现LifeCycleObserver接口。

public class Timer implements LifecycleObserver {
    private Handler handler;
    private int seconds = 0;

    @OnLifecycleEvent(Lifecycle.Event.ON_START)public void startTimer() {
        handler = new Handler();
        handler.post(new Runnable() {
            @Overridepublic void run() {
                Log.d("Timer", "Seconds: " + seconds);
                seconds++;
                handler.postDelayed(this, 1000);
            }
        });
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)public void stopTimer() {
        handler.removeCallbacksAndMessages(null);
        handler = null;
    }
}
  1. 在MainActivity中添加LifecycleOwner,并在onCreate方法中添加Observer。

public class MainActivity extends AppCompatActivity {

    @Overrideprotected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 获取LifecycleOwner对象LifecycleOwner lifecycleOwner = this;

        // 将Timer实例添加为Observer
        getLifecycle().addObserver(new Timer());

        // ...
    }

    // ...
}

这样,当Activity处于前台时,Timer实例中的startTimer方法会被调用,计时器会开始递增;

当Activity被销毁时,Timer实例中的stopTimer方法会被调用,计时器会停止。

这个例子展示了如何使用Jetpack LifeCycle组件来管理App组件的生命周期。

当App中存在需要在组件生命周期不同阶段执行的操作时,使用LifeCycle可以更方便地实现这些操作,同时避免了一些常见的生命周期问题。

3.1.3 LifeCycle优势劣势

优势

  1. 管理生命周期方便

使用LifeCycle组件可以更方便地管理App组件的生命周期,避免了一些常见的生命周期问题,如内存泄漏和空指针异常等。

  1. 模块化编程

使用LifeCycle组件可以将App的业务逻辑分解为模块化的组件,每个组件负责管理自己的生命周期,便于代码复用和维护。

  1. 规范化编程

使用LifeCycle组件可以规范化App的开发,使代码更易于阅读、理解和维护。

  1. 支持多个组件

LifeCycle组件支持多个组件进行生命周期管理,可以轻松地在多个组件之间共享状态和数据。

劣势

  1. 需要继承LifecycleObserver:在实现LifeCycle功能的时候,需要继承LifecycleObserver接口,这会导致代码的继承关系稍微有点复杂。
  2. 需要添加注释:在使用LifeCycle组件的时候,需要添加一些注释来指示方法是在什么时候被调用,否则可能会出现一些难以诊断的问题。

3.1.4 LifeCycle应用场景

Jetpack LifeCycle组件的实际开发应用场景包括:

  1. Activity和Fragment生命周期管理:使用LifeCycle组件可以更方便地管理Activity和Fragment的生命周期,避免了一些常见的生命周期问题,如内存泄漏和空指针异常等。
  2. 后台服务管理:使用LifeCycle组件可以更方便地管理后台服务的生命周期,可以在App退出后自动停止后台服务,避免了一些不必要的资源浪费。
  3. 数据库连接管理:使用LifeCycle组件可以更方便地管理数据库连接的生命周期,可以在App退出时自动关闭数据库连接,避免了一些不必要的资源浪费。
  4. 网络请求管理:使用LifeCycle组件可以更方便地管理网络请求的生命周期,可以在Activity或Fragment销毁时自动取消网络请求,避免了一些不必要的网络请求。
  5. 视图控制器管理:使用LifeCycle组件可以更方便地管理视图控制器的生命周期,可以在Activity或Fragment销毁时自动清除视图控制器的状态,避免了一些不必要的状态保存和恢复操作。

3.1.5 LifeCycle原理分析

类图

LifecycleOwner表示拥有生命周期的组件,比如Activity和Fragment。

Lifecycle表示组件的生命周期,LifecycleObserver表示一个组件的生命周期观察者。

LifecycleRegistry是Lifecycle接口的一个实现类,它维护了一个生命周期状态机,用于记录组件的生命周期状态和生命周期事件。

LifecycleRegistry提供了一系列方法,用于管理组件的生命周期状态和生命周期事件。当组件的生命周期事件发生变化时,LifecycleRegistry会自动更新状态机,并通知所有的LifecycleObserver观察者对象,以便它们可以相应地更新自己的状态。

LifecycleOwner可以通过getLifecycle()方法获取到一个Lifecycle对象,然后将自己的生命周期观察者对象添加到Lifecycle对象中,从而实现对组件生命周期的监听。

当组件的生命周期事件发生变化时,Lifecycle会自动通知所有的生命周期观察者对象,以便它们可以相应地更新自己的状态。

image.png

源码

Lifecycle库的核心是Lifecycle接口和LifecycleObserver接口。

Lifecycle接口定义了一组方法,用于将LifecycleOwner与LifecycleObserver进行关联。

public abstract class Lifecycle {
    //添加观察者
    @MainThread
    public abstract void addObserver(@NonNull LifecycleObserver observer);
    //移除观察者
    @MainThread
    public abstract void removeObserver(@NonNull LifecycleObserver observer);
    //获取当前状态
    public abstract State getCurrentState();

//生命周期事件,对应Activity生命周期方法
    public enum Event {
        ON_CREATE,
        ON_START,
        ON_RESUME,
        ON_PAUSE,
        ON_STOP,
        ON_DESTROY,
        ON_ANY  //可以响应任意一个事件
    }
    
    //生命周期状态. (Event是进入这种状态的事件)
    public enum State {
        DESTROYED,
        INITIALIZED,
        CREATED,
        STARTED,
        RESUMED;

        //判断至少是某一状态
        public boolean isAtLeast(@NonNull State state) {
            return compareTo(state) >= 0;
        }
    }

LifecycleObserver接口定义了一组回调方法,用于接收LifecycleOwner的生命周期事件。

在Lifecycle库的实现中,Lifecycle接口有两个重要的实现类,分别是LifecycleRegistry和LifecycleOwner。

LifecycleRegistry实现了Lifecycle接口,并提供了一组方法,用于管理LifecycleOwner的生命周期状态。

LifecycleOwner是一个接口,用于标识拥有生命周期状态的对象,通常是Activity或Fragment。

//androidx.activity.ComponentActivity,这里忽略了一些其他代码,我们只看Lifecycle相关
public class ComponentActivity extends androidx.core.app.ComponentActivity implements LifecycleOwner{
    ...
   
    private final LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this);
    ...
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mSavedStateRegistryController.performRestore(savedInstanceState);
        ReportFragment.injectIfNeededIn(this); //使用ReportFragment分发生命周期事件
        if (mContentLayoutId != 0) {
            setContentView(mContentLayoutId);
        }
    }
    @CallSuper
    @Override
    protected void onSaveInstanceState(@NonNull Bundle outState) {
        Lifecycle lifecycle = getLifecycle();
        if (lifecycle instanceof LifecycleRegistry) {
            ((LifecycleRegistry) lifecycle).setCurrentState(Lifecycle.State.CREATED);
        }
        super.onSaveInstanceState(outState);
        mSavedStateRegistryController.performSave(outState);
    }

    @NonNull
    @Override
    public Lifecycle getLifecycle() {
        return mLifecycleRegistry;
    }
}

在LifecycleRegistry中,有一个名为mObserverMap的成员变量,用于存储LifecycleObserver对象和其关联的EventObserver对象。

当LifecycleOwner的生命周期状态更改时,LifecycleRegistry会自动调用mObserverMap中与之相关联的EventObserver对象的相应方法,以便它们可以执行适当的操作。

//LifecycleRegistry.java
   //系统自定义的保存Observer的map,可在遍历中增删
    private FastSafeIterableMap<LifecycleObserver, ObserverWithState> mObserverMap = new FastSafeIterableMap<>();
            
    public void handleLifecycleEvent(@NonNull Lifecycle.Event event) {
        State next = getStateAfter(event);//获取event发生之后的将要处于的状态
        moveToState(next);//移动到这个状态
    }

    private void moveToState(State next) {
        if (mState == next) {
            return;//如果和当前状态一致,不处理
        }
        mState = next; //赋值新状态
        if (mHandlingEvent || mAddingObserverCounter != 0) {
            mNewEventOccurred = true;
            return;
        }
        mHandlingEvent = true;
        sync(); //把生命周期状态同步给所有观察者
        mHandlingEvent = false;
    }
    
        private void sync() {
        LifecycleOwner lifecycleOwner = mLifecycleOwner.get();
        if (lifecycleOwner == null) {
            throw new IllegalStateException("LifecycleOwner of this LifecycleRegistry is already"
                    + "garbage collected. It is too late to change lifecycle state.");
        }
        while (!isSynced()) {  //isSynced()意思是 所有观察者都同步完了
            mNewEventOccurred = false;
            //mObserverMap就是 在activity中添加observer后 用于存放observer的map
            if (mState.compareTo(mObserverMap.eldest().getValue().mState) < 0) {
                backwardPass(lifecycleOwner);
            }
            Entry<LifecycleObserver, ObserverWithState> newest = mObserverMap.newest();
            if (!mNewEventOccurred && newest != null
                    && mState.compareTo(newest.getValue().mState) > 0) {
                forwardPass(lifecycleOwner);
            }
        }
        mNewEventOccurred = false;
    }
    ...
    
     static State getStateAfter(Event event) {
        switch (event) {
            case ON_CREATE:
            case ON_STOP:
                return CREATED;
            case ON_START:
            case ON_PAUSE:
                return STARTED;
            case ON_RESUME:
                return RESUMED;
            case ON_DESTROY:
                return DESTROYED;
            case ON_ANY:
                break;
        }
        throw new IllegalArgumentException("Unexpected event value " + event);
    }

LifecycleRegistry还提供了一组方法,如handleLifecycleEvent()、getCurrentState()、addObserver()、removeObserver()等,用于管理组件的生命周期状态和LifecycleObserver对象。

在Lifecycle库的实现中,还有一些其他的类和接口,如GenericLifecycleObserver、FullLifecycleObserver、LifecycleEvent、EventObserver等,它们都是用于管理和处理组件生命周期事件的。

3.1.6 LifeCycle注意事项

3.1.6.1 不要在 onCreate() 方法中使用 Lifecycle 组件

Lifecycle 组件在 onCreate() 方法中尚未初始化完成,因此在该方法中使用它们可能会导致崩溃或不可预测的行为。建议在 onStart() 方法中使用 Lifecycle 组件。

3.1.6.2 不要手动调用 onDestroy() 方法

手动调用 onDestroy() 方法会破坏 Lifecycle 组件的生命周期,从而导致应用程序行为异常。Lifecycle 组件应该由系统自动管理,应该避免手动干预。

3.1.6.3 避免在 Fragment 中使用多个 LifecycleOwner

Fragment 自身就是一个 LifecycleOwner,因此不应该在 Fragment 中创建其他的 LifecycleOwner。这样会导致多个 LifecycleOwner 之间的状态不同步,从而导致应用程序出现问题。

3.2 LiveData

3.2.1 LiveData基础定义

Android Jetpack LiveData是一种用于管理应用程序界面和数据交互的组件。

LiveData是一种可观察的数据持有者,用于在应用程序组件(如Activity、Fragment和Service)之间共享数据,并在数据发生更改时通知观察者。

LiveData可以确保UI与数据的同步更新,避免了一些常见的错误,如内存泄漏和UI组件无法正确更新的问题。

LiveData具有生命周期感知功能,可以自动感知应用程序组件的生命周期,并在组件处于活动状态时更新UI,而在组件处于非活动状态时停止更新,从而有效地减少了资源消耗。

LiveData还提供了线程安全的访问数据的机制,避免了多线程并发访问的问题。

3.2.2 LiveData基础使用

如何 TextView 控件的显示内容呢?

首先,在 XML 布局文件中添加一个 TextView 控件:

<TextView 
    android:id="@+id/text_view" 
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />

然后,在 Activity 或 Fragment 中创建一个 LiveData 对象,用于更新 TextView 的显示内容:


public class MyActivity extends AppCompatActivity {

    private LiveData<String> mLiveData;
    private TextView mTextView;

    @Overrideprotected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mTextView = findViewById(R.id.text_view);

        mLiveData = new MutableLiveData<>();
        mLiveData.observe(this, new Observer<String>() {
            @Overridepublic void onChanged(String s) {
                mTextView.setText(s);
            }
        });
    }
}

在上述代码中,我们创建了一个 LiveData 对象,并将其与一个 TextView 控件关联。当 LiveData 对象中的值发生变化时,我们使用 Observer 来监听这个变化,然后更新 TextView 的显示内容。

最后,我们可以在代码中通过 setValue() 或 postValue() 方法来更新 LiveData 对象的值,从而更新 TextView 的显示内容。例如:


((MutableLiveData<String>) mLiveData)
.setValue("Hello, world!");

以上就是一个简单的 LiveData 案例,用于更新 TextView 控件的显示内容。当 LiveData 中的值发生变化时,TextView 控件会自动更新显示内容。

3.2.3 LiveData优势劣势

优势

  1. 生命周期感知:LiveData 可以感知组件(如 Activity 或 Fragment)的生命周期,从而避免了由于 UI 组件的生命周期变化而引发的空指针异常和内存泄漏等问题。

  2. 数据更新通知:LiveData 可以在数据发生变化时自动通知所有观察者更新数据,从而实现数据的实时更新和响应。

  3. 数据一致性:LiveData 可以确保在配置更改时保持数据的一致性,避免了数据丢失和重复加载等问题。

  4. 线程安全:LiveData 可以确保在主线程中更新 UI 界面,并支持在工作线程中进行异步操作,从而避免了多线程数据竞争问题。

  5. 与 ViewModel 结合使用:LiveData 可以与 ViewModel 结合使用,实现数据与 UI 界面的分离,从而提高了代码的可维护性和可测试性。

劣势

  1. 适用场景有限:LiveData 适用于数据变化频繁、需要实时更新的场景,对于数据变化较少的场景,使用 LiveData 可能会增加代码复杂性。
  2. API 限制:LiveData 是 Android Jetpack 组件,只能在支持 Jetpack 的 Android 版本上使用,对于一些较老的 Android 版本可能需要使用其他技术方案。

3.2.4 LiveData应用场景

LiveData的应用场景包括但不限于以下几个方面:

  1. 数据库操作:LiveData可以与Room持久化库结合使用,当数据库中的数据发生变化时,LiveData会自动通知UI组件进行更新。
  2. 网络请求:LiveData可以与Retrofit网络请求库结合使用,当网络请求的结果返回时,LiveData会自动通知UI组件进行更新。
  3. 数据共享:LiveData可以在不同的组件之间共享数据,例如,当一个Activity和一个Fragment需要共享数据时,可以将LiveData对象设置为一个公共的数据持有者。
  4. 资源释放:LiveData可以在UI组件不再处于活动状态时自动释放资源,避免出现内存泄漏等问题。
  5. 代码简洁:LiveData可以减少代码复杂度,通过数据观察者的方式,避免手动编写繁琐的数据更新代码。

3.2.5 LiveData原理分析

LiveData是一种可以感知生命周期的数据持有者,它可以让数据更新时通知UI界面进行更新,同时也能够避免因为生命周期问题带来的内存泄漏。

类图

LiveData的简化类图:

image.png

在上面的类图中,Observer是LiveData的观察者接口,LiveData是可观察数据的持有者类。LiveData具有生命周期感知能力,可以根据其生命周期状态自动管理数据的订阅和取消订阅。MutableLiveData是LiveData的可变子类,允许更新LiveData持有的数据。LiveData的observe()方法用于注册观察者,setValue()和postValue()方法用于更新LiveData数据。

源码

LiveData的核心代码在androidx.lifecycle.LiveData类中,下面对LiveData的源码进行简要分析:

  1. LiveData的基本结构

LiveData类是一个抽象类,它有一个泛型类型T,表示LiveData中存储的数据类型。LiveData类内部维护了一个数据源(mData)和一个观察者列表(mObservers),当LiveData中的数据发生改变时,会通知所有注册的观察者进行UI更新。


public abstract class LiveData<T> {
    private static final Object NOT_SET = new Object();
    private Object mData;
    private boolean mDispatchingValue;
    private int mActiveCount;
    private volatile Object mPendingData = NOT_SET;
    private volatile Object mVersion = new Object();
    private final MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor();
    private final SafeIterableMap<Observer<T>, ObserverWrapper> mObservers = new SafeIterableMap<>();

    // ...
}
  1. 观察者注册

LiveData中的观察者是通过observe()方法进行注册的,这个方法接受一个LifecycleOwner对象和一个Observer对象。LifecycleOwner是一个具有生命周期的对象,当LifecycleOwner的生命周期结束时,LiveData会自动解注册所有与该LifecycleOwner相关的观察者,避免内存泄漏。


public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
    if (owner.getLifecycle().getCurrentState() == DESTROYED) {
        // ignorereturn;
    }
    // ...LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
    // ...
}
  1. 观察者解注册

解注册则是通过removeObserver()方法进行的,该方法接受一个Observer对象,用于从观察者列表中删除相应的观察者。


public void removeObserver(@NonNull Observer<? super T> observer) {
    assertMainThread("removeObserver");
    // ...mObservers.remove(observerWrapper);
    // ...
}
  1. 数据更新和通知观察者

LiveData中的数据更新是通过setValue()和postValue()方法进行的。setValue()方法是在主线程中进行调用的,它会直接更新LiveData中的数据并通知所有的观察者进行UI更新;而postValue()方法是在异步线程中进行调用的,它会将要更新的数据封装成PendingPost对象,并提交给主线程的Handler进行处理。


protected void setValue(T value) {
    assertMainThread("setValue");
    mVersion++;
    mData = value;
    dispatchingValue(null);
}

protected void postValue(T value) {
    if (Looper.myLooper() != Looper.getMainLooper()) {
        // ...
        return;
    }
    mPendingData = value;
    if (mPendingData == NOT_SET) {
        // ignore
        return;
    }
    // ...
    mMainThreadExecutor.execute(mPostValueRunnable);
}

当LiveData中的数据发生更新时,LiveData会通知所有的观察者进行UI更新。LiveData使用了模板方法

设计模式中的观察者模式,它将数据源和观察者进行了解耦。LiveData类中的dispatchingValue()方法就是通知观察者进行UI更新的核心方法。


private void dispatchingValue(@Nullable LifecycleBoundObserver initiator) {
    if (mDispatchingValue) {
        mDispatchInvalidated = true;
        return;
    }
    mDispatchingValue = true;
    do {
        mDispatchInvalidated = false;
        if (initiator != null) {
            considerNotify(initiator);
            initiator = null;
        } else {
            Iterator<Map.Entry<Observer<T>, ObserverWrapper>> iterator = mObservers.iteratorWithAdditions();
            while (iterator.hasNext()) {
                considerNotify(iterator.next().getValue());
                if (mDispatchInvalidated) {
                    break;
                }
            }
        }
    } while (mDispatchInvalidated);
    mDispatchingValue = false;
}

dispatchingValue()方法中使用了一个迭代器遍历所有的观察者,然后调用considerNotify()方法进行UI更新。


private void considerNotify(ObserverWrapper observer) {
    if (!observer.mActive) {
        return;
    }
    if (!observer.shouldBeActive()) {
        observer.activeStateChanged(false);
        return;
    }
    if (observer.mLastVersion >= mVersion) {
        return;
    }
    observer.mLastVersion = mVersion;
    observer.mObserver.onChanged((T) mData);
}

considerNotify()方法首先判断观察者是否处于活跃状态,如果不是则直接返回;接着判断观察者是否应该处于活跃状态,如果不是则调用activeStateChanged()方法将观察者状态更新为非活跃状态;最后判断数据版本号是否发生变化,如果发生变化则调用观察者的onChanged()方法进行UI更新。

  1. 其他方法

除了以上核心方法之外,LiveData还提供了其他方法,例如getValue()方法用于获取LiveData中存储的数据;hasActiveObservers()方法用于判断是否存在处于活跃状态的观察者等等。


public T getValue() {
    Object data = mData;
    if (data != NOT_SET) {
        return (T) data;
    }
    return null;
}

public boolean hasActiveObservers() {
    return mActiveCount > 0;
}

以上是对LiveData源码的简要分析,LiveData使用了观察者模式,可以感知生命周期并进行UI更新,避免了因为生命周期问题带来的内存泄漏。

3.2.6 LiveData注意事项

数据倒灌

要解决LiveData数据倒灌问题,可以使用以下方法:

使用MediatorLiveData代替LiveData:MediatorLiveData可以作为一个中间层,将多个LiveData对象的数据源合并,从而避免数据倒灌问题。在UI组件的生命周期结束时,可以调用MediatorLiveData的removeSource()方法,将LiveData的数据源从MediatorLiveData中移除。

在ViewModel中使用LiveData:将LiveData对象作为ViewModel中的成员变量,并在ViewModel中进行数据更新和观察,可以避免LiveData数据倒灌问题。

在UI组件中使用自定义Observer:可以在自定义Observer中进行生命周期判断,当UI组件的生命周期已经结束时,不再更新UI界面。

下面是使用自定义Observer解决LiveData数据倒灌问题的示例代码:


    public class MyObserver<T> implements Observer<T> {

        private boolean mIsStarted = false;
        private Observer<T> mObserver;

        public MyObserver(Observer<T> observer) {
            mObserver = observer;
        }

        public void start() {
            mIsStarted = true;
        }

        public void stop() {
            mIsStarted = false;
        }

        @Overridepublic void onChanged(T t) {
            if (mIsStarted) {
                mObserver.onChanged(t);
            }
        }
    }

在UI组件中使用自定义Observer时,需要在UI组件的生命周期开始和结束时,调用MyObserver的start()和stop()方法,从而控制数据更新的时机,避免LiveData数据倒灌问题。


    public class MyActivity extends AppCompatActivity {

        private LiveData<Integer> mLiveData;
        private MyObserver<Integer> mObserver;

        @Overrideprotected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);

            mLiveData = new MutableLiveData<>();
            mObserver = new MyObserver<>(integer -> {
                // 更新UI界面
            });
            mLiveData.observe(this, mObserver);
        }

        @Overrideprotected void onStart() {
            super.onStart();
            mObserver.start();
        }

        @Overrideprotected void onStop() {
            super.onStop();
            mObserver.stop();
        }
    }

以上是解决LiveData数据倒灌问题的一些方法,也可以通过hook修改livedata源码observer.mLastVersion的值,使得if (observer.mLastVersion >= mVersion)成立,就不会导致没有注册观察者,还能接收到消息

setValue 不起效

调用LiveData的setValue()方法时,如果LiveData的观察者处于激活状态,那么LiveData会将最新的数据推送给观察者,并在观察者的回调方法中更新UI界面。但是,如果LiveData的观察者处于非激活状态,那么LiveData不会将数据推送给观察者,也不会更新UI界面。因此,如果调用setValue()方法后,UI界面没有发生更新,可能是因为LiveData的观察者处于非激活状态。

LiveData的观察者处于非激活状态的原因可能有以下几种:

  1. 观察者没有与LiveData建立连接:在调用LiveData的observe()方法时,需要将观察者与LiveData建立连接,只有建立连接后,LiveData才能将数据推送给观察者。如果没有建立连接,那么即使调用了setValue()方法,也无法更新UI界面。
  2. 观察者的生命周期已经结束:LiveData的观察者必须在其生命周期的活动状态中才能接收到数据更新。如果观察者的生命周期已经结束,那么即使调用了setValue()方法,也无法更新UI界面。
  3. 观察者处于非激活状态:LiveData的观察者在其生命周期的激活状态中才能接收到数据更新。如果观察者处于非激活状态,那么即使调用了setValue()方法,也无法更新UI界面。

因此,如果调用setValue()方法后,UI界面没有发生更新,可以检查观察者的连接状态和生命周期状态,确保LiveData的观察者处于激活状态,并且已经与LiveData建立连接。如果仍然无法解决问题,可以考虑使用postValue()方法,该方法可以在UI线程空闲时更新LiveData的数据,并在观察者处于激活状态时通知观察者进行UI更新。

内存泄漏

LiveData的观察者(Observer)默认是弱引用,但是如果观察者没有及时取消观察,可能会导致内存泄漏。

多次观察

如果一个LiveData对象被多个观察者同时观察,那么每个观察者都会收到相同的数据更新,可能会导致UI界面多次更新,造成性能问题。

生命周期不一致

LiveData是与生命周期相关联的,如果UI组件的生命周期结束了,但是LiveData仍在发送数据更新,那么就会引发异常。

线程安全

LiveData默认在主线程进行数据更新,如果需要在后台线程进行数据更新,就需要使用LiveData的postValue()方法,而不是setValue()方法。

避免重复观察

如果在一个UI组件中多次观察同一个LiveData对象,可能会导致重复观察,造成性能问题和数据不一致问题。因此,可以使用ViewModel中的getLiveData()方法,保证同一个LiveData对象只被观察一次。

3.2.7 EventBus vs Otto vs RxJava vs LiveData

image.png

3.3 ViewModel

3.3.1 ViewModel基础定义

Android Jetpack ViewModel 是一种用于管理 UI 相关数据的组件,它可以在屏幕旋转等配置更改时保存数据并重新创建 Activity 或 Fragment。ViewModel 是一个以生命周期感知的方式来保存和管理 UI 相关数据的类。

ViewModel 是一个专门用于存储和管理与 UI 相关的数据的类,它们可以在 Activity 或 Fragment 重新创建时保持数据的完整性,并且可以避免因为 Activity 或 Fragment 生命周期的变化而导致的数据丢失。ViewModel 还提供了一种在 Activity 和 Fragment 之间共享数据的机制。

在 ViewModel 中存储的数据是不受 Activity 或 Fragment 生命周期的影响的,这意味着在旋转屏幕或者配置更改时,ViewModel 中的数据将不会被清除,可以保持完整性,从而可以在 Activity 或 Fragment 重新创建时继续使用。

ViewModel 通常与 LiveData 或 RxJava 等响应式编程库一起使用,以实现数据的实时更新和响应。

3.3.2 ViewModel基础使用

如何在 ViewModel 中保存和管理数据,并在 Activity 或 Fragment 重新创建时保持数据的完整性。

  1. 创建一个名为MyViewModel的ViewModel类


public class MyViewModel extends ViewModel {

    private MutableLiveData<Integer> count = new MutableLiveData<>();

    public void setCount(int count) {
        this.count.setValue(count);
    }

    public LiveData<Integer> getCount() {
        return count;
    }
}

在这个 ViewModel 类中,我们创建了一个 MutableLiveData 类型的 count 变量,并在 setCount() 方法中设置它的值,同时在 getCount() 方法中将其作为 LiveData 返回,以实现数据的实时更新和响应。

  1. 在 Activity或Fragment中使用ViewModel


public class MyActivity extends AppCompatActivity {

    private MyViewModel viewModel;

    @Overrideprotected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        viewModel = ViewModelProviders.of(this).get(MyViewModel.class);
        viewModel.getCount().observe(this, count -> {
            // 更新 UI
        });
    }

    public void onButtonClicked(View view) {
        int count = viewModel.getCount().getValue() + 1;
        viewModel.setCount(count);
    }
}

在这个 Activity 中,我们使用 ViewModelProviders.of() 方法获取 MyViewModel 的实例,并将其与该 Activity 绑定。

然后,我们可以使用 getCount() 方法获取 count 变量的值,并在 setCount() 方法中设置其值。

通过调用 getCount().observe() 方法,我们可以在 LiveData 数据变化时更新 UI。

这个案例演示了如何使用 Android Jetpack ViewModel 来管理 UI 相关的数据,并在 Activity 或 Fragment 重新创建时保持数据的完整性。

使用 ViewModel 可以帮助我们更好地管理和保持与 UI 相关的数据,并提高应用程序的稳定性和可靠性。

3.3.3 ViewModel优势劣势

优势

  1. 保持数据的完整性:ViewModel 可以在屏幕旋转等配置更改时保存数据并重新创建 Activity 或 Fragment,从而保持数据的完整性,避免因为生命周期变化而导致的数据丢失。
  2. 简化代码:使用 ViewModel 可以将 UI 相关的数据与 UI 控件分离,避免了在 Activity 或 Fragment 中处理数据逻辑的繁琐代码,使代码更加清晰、简洁。
  3. 支持数据共享:ViewModel不会随着Activity的屏幕旋转而销毁,减少了维护状态的代码成本(数据的存储和读取、序列化和反序列化);
  4. 支持响应式编程:ViewModel 可以与 LiveData 或 RxJava 等响应式编程库一起使用,实现数据的实时更新和响应。

劣势

  1. 不适合处理长时间运行的任务:ViewModel 主要用于管理 UI 相关的数据,如果需要处理长时间运行的任务,需要在 ViewModel 中使用异步任务或者单独使用 Service。
  2. 数据持久化需要额外处理:ViewModel 中保存的数据只是暂时性的,如果需要长期保存数据,需要将数据保存到数据库或者 SharedPreferences 中。
  3. 需要了解生命周期:ViewModel 是以生命周期感知的方式来保存和管理数据的,因此需要了解 Activity 或 Fragment 的生命周期以便正确使用 ViewModel。

3.3.4 ViewModel应用场景

下面是一些实际开发过程中可能使用 ViewModel 的使用场景:

  1. 数据持久化:如果需要在应用程序的不同页面之间共享数据,并且希望在 Activity 或 Fragment 重新创建时保持数据的完整性,可以使用 ViewModel 来保存和管理数据。

  2. 处理屏幕旋转等配置更改:当应用程序的屏幕旋转或其他配置更改时,Activity 或 Fragment 可能会被销毁并重新创建,此时可以使用 ViewModel 来保存和恢复数据,避免因为生命周期变化而导致的数据丢失。

  3. 分离 UI 与数据:将 UI 相关的数据与 UI 控件分离,避免在 Activity 或 Fragment 中处理数据逻辑的繁琐代码,使代码更加清晰、简洁。

  4. 实现响应式编程:ViewModel 可以与 LiveData 或 RxJava 等响应式编程库一起使用,实现数据的实时更新和响应,从而提高应用程序的性能和用户体验。

  5. 避免内存泄漏:将数据存储在 ViewModel 中可以避免由于对 Activity 或 Fragment 的引用而导致的内存泄漏,从而提高了应用程序的稳定性和可靠性。

3.3.5 ViewModel原理分析

类图

  • ViewModel类是一个抽象类,包含了一个onCleared()方法,该方法会在ViewModel不再被使用时被调用,用于释放资源和清除状态。

  • AndroidViewModel是ViewModel的一个子类,它包含一个Application对象,用于在ViewModel中访问应用程序的上下文。

  • ViewModelProvider是一个帮助类,用于获取ViewModel实例。

  • ViewModelProvider.Factory是一个接口,用于创建ViewModel实例。

ViewModel.png

原理

Jetpack ViewModel 源码位于 androidx.lifecycle.ViewModel 包中,它包含了两个类:ViewModel 和 ViewModelProvider。

每个Activity都会绑定一个ViewModelStore,ViewModelStore通过HashMap保存ViewModel和String名称。ViewModel的持久化依赖ViewModelStore的存储和获取。

ViewModel 类的主要作用是定义一个用于存储 UI 组件数据的容器,并在 UI 组件生命周期变化时进行管理。具体来说,ViewModel 类继承了 Android 的 ViewModel 类,并添加了一些额外的方法,用于实现以下功能:

  1. 缓存 UI 组件数据
  2. 在 UI 组件销毁后清理数据
  3. 在 UI 组件重建时恢复数据

ViewModelProvider 类的主要作用是创建 ViewModel 实例,并为其提供一个唯一的 key,用于在 Activity 或 Fragment 重建时找回已经存在的 ViewModel 实例。ViewModelProvider 类的实现方式比较简单,主要是通过一个 HashMap 来存储 ViewModel 实例,并提供了一些方法,用于创建和查找 ViewModel 实例。

下面是 ViewModel 类的源码分析:


open class ViewModel : ViewModelStoreOwner {
    private val mViewModelStore = ViewModelStore()

    @CallSuperoverride fun onCleared() {
        mViewModelStore.clear()
    }

    fun getViewModelStore(): ViewModelStore {
        return mViewModelStore
    }
}

ViewModel 类实现了 ViewModelStoreOwner 接口,并在内部维护了一个 ViewModelStore 对象,用于存储 UI 组件数据。在 ViewModel 被销毁时,会调用 onCleared() 方法来清空 ViewModelStore 中的数据。getViewModelStore() 方法用于返回 ViewModelStore 对象。

ViewModelStoreOwner 接口的实现代码如下:


interface ViewModelStoreOwner {
    fun getViewModelStore(): ViewModelStore
}

ViewModelProvider 类的源码分析如下:


class ViewModelProvider private constructor(
        private val mFactory: Factory,
        private val mViewModelStore: ViewModelStore
) {
    // 缓存 ViewModel 实例的 HashMapprivate val mViewModels = HashMap<String, ViewModel>()

    companion object {
        private val sCache = HashMap<String, ViewModelProvider>()

        /**
         * 返回当前 Activity 或 Fragment 的 ViewModelProvider 实例
         */@MainThreadfun of(owner: ViewModelStoreOwner): ViewModelProvider {
            return of(owner, FactoryHolder.DEFAULT_FACTORY)
        }

        /**
         * 返回当前 Activity 或 Fragment 的 ViewModelProvider 实例,可指定 Factory
         */@MainThreadfun of(owner: ViewModelStoreOwner, factory: Factory): ViewModelProvider {
            val store = owner.getViewModelStore()

            // 先从缓存中查找 ViewModelProvider 实例var viewModelProvider = sCache[store.mKey]
            if (viewModelProvider == null) {
                viewModelProvider = ViewModelProvider(factory, store)
                sCache[store.mKey] = viewModelProvider
            }

            return viewModelProvider
        }
    }

    /**
     * 返回指定 key 的 ViewModel 实例,如果不存在则通过 Factory
     */
     
fun <T : ViewModel?> get(key: String, modelClass: Class<T>): T {
    var viewModel = mViewModels[key]
    if (modelClass.isInstance(viewModel)) {
        @Suppress("UNCHECKED_CAST")
        return viewModel as T
    }

    // 创建新的 ViewModel 实例,并将其添加到缓存中
    viewModel = mFactory.create(modelClass)
    mViewModels[key] = viewModel

    return viewModel
}

/**
 * Factory 接口,用于创建 ViewModel 实例
 */
interface Factory {
    fun <T : ViewModel?> create(modelClass: Class<T>): T
}

/**
 * FactoryHolder 类,用于提供默认的 Factory 实现
 */
private object FactoryHolder {
    val DEFAULT_FACTORY: Factory = object : Factory {
        override fun <T : ViewModel?> create(modelClass: Class<T>): T {
            try {
                return modelClass.newInstance()
            } catch (e: IllegalAccessException) {
                throw RuntimeException(e)
            } catch (e: InstantiationException) {
                throw RuntimeException(e)
            }
        }
    }
 }
}

ViewModelProvider 类包含了一个 HashMap,用于缓存 ViewModel 实例。

它的 of() 方法用于创建 ViewModelProvider 实例,并通过 ViewModelStore 对象来区分不同的 Activity 或 Fragment。get() 方法用于返回指定 key 的 ViewModel 实例,并在缓存中查找是否存在对应的实例。如果缓存中不存在该实例,则调用 Factory 接口来创建一个新的 ViewModel 实例,并将其添加到缓存中。

Factory 接口提供了一个 create() 方法,用于创建 ViewModel 实例。FactoryHolder 类用于提供默认的 Factory 实现,它使用 Java 的反射机制来创建 ViewModel 实例。 最后,需要注意的是,ViewModelStore 和 ViewModelProvider 都是线程安全的,可以在多个线程中同时使用。

这意味着在多线程环境下,可以使用 ViewModel 来缓存和共享数据,从而减少数据的重复加载和提升应用程序的性能。

3.3.6 ViewModel注意事项

使用了错误的 ViewModel:

在某些情况下,可能会需要多个 ViewModel 来管理不同的 UI 数据。如果使用了错误的 ViewModel 来管理数据,可能会导致数据丢失或逻辑错误。因此,在使用 ViewModel 时应该清楚每个 ViewModel 的作用,避免出现混淆。

ViewModel 与生命周期的关系

ViewModel 的生命周期不同于 Activity 和 Fragment,它是被系统缓存的,因此可能会出现数据被清除的情况。在使用 ViewModel 时应该注意它的生命周期,及时保存数据并恢复数据。

使用无效的上下文

ViewModel 需要一个有效的上下文来创建实例,如果使用无效的上下文可能会导致 ViewModel 创建失败。因此,在使用 ViewModel 时应该注意上下文的有效性,避免出现创建失败的情况。

使用了错误的作用域

ViewModel 的作用域应该与需要管理的 UI 组件的生命周期相同,如果使用了错误的作用域,可能会导致数据被清除或者生命周期不一致。因此,在使用 ViewModel 时应该选择正确的作用域,避免出现问题。

3.4 DataBinding

3.4.1 DataBinding基础定义

DataBinding提供了一种声明性的方式将布局文件中的UI组件和应用程序的数据模型绑定在一起。

通过DataBinding,开发者可以将UI组件的值绑定到数据模型中的属性,使得在更新UI时不需要手动更新每个组件的值。

DataBinding库通过生成一个绑定类来实现UI和数据模型之间的绑定。

这个绑定类是在编译时自动生成的,它使用了数据模型和UI组件之间的绑定表达式,以便在运行时执行数据绑定。

3.4.2 DataBinding基础使用

技术需求

使用DataBinding实现一个简单的计数器功能。在每次更新计数器的值时,DataBinding会自动更新TextView的值,使得开发者不需要手动更新UI组件的值,可以更加专注于业务逻辑的实现

XML

<?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="counter"
            type="Integer" />
    </data>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <TextView
            android:id="@+id/tvCounter"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{counter.toString()}" />

        <Button
            android:id="@+id/btnIncrease"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="+" />

        <Button
            android:id="@+id/btnDecrease"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="-" />
    </LinearLayout>
</layout>

Code

创建DataBinding实例,并将activity_main.xml布局文件与DataBinding实例绑定。然后,将计数器的值赋给DataBinding实例的counter变量,并为增加和减少按钮设置点击事件。在点击事件中,更新计数器的值,并将新的值赋给DataBinding实例的counter变量。最后,调用DataBinding实例的executePendingBindings()方法,将UI组件的值更新到最新的值

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    private var counter = 0

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

        // 创建DataBinding实例并与布局文件绑定
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)

        // 设置初始值
        binding.counter = counter

        // 为按钮设置点击事件
        binding.btnIncrease.setOnClickListener {
            counter++
            binding.counter = counter
            binding.executePendingBindings()
        }

        binding.btnDecrease.setOnClickListener {
            counter--
            binding.counter = counter
            binding.executePendingBindings()
        }
    }
}

3.4.3 DataBinding优势劣势

优势

  1. 简化代码

    使用DataBinding可以减少findViewById()方法的调用,使得代码更加简洁和易读。开发者可以通过DataBinding直接访问布局文件中的UI组件,而不需要手动查找和设置UI组件的属性。

  2. 双向绑定

    DataBinding支持双向绑定,可以将UI组件的值绑定到数据模型中的属性,也可以将数据模型的属性绑定到UI组件的值。这种双向绑定可以帮助开发者更加方便地实现UI和数据模型之间的同步。

  3. 减少错误

    DataBinding可以减少代码中的错误,因为使用DataBinding可以消除某些手动编写的代码。例如,DataBinding可以自动生成一些代码,帮助开发者绑定UI组件和数据模型,减少手动编写代码的机会。

  4. 性能优化

    DataBinding可以带来一些性能优化,例如通过生成绑定类来减少代码的执行时间。DataBinding还支持懒加载,可以在需要时才加载数据模型和UI组件。

劣势

  1. 兼容性问题

    DataBinding库需要使用较新的Android Gradle插件和Android SDK版本,这可能导致一些兼容性问题。如果开发者使用较老的Android版本,可能需要进行一些兼容性处理。

  2. 构建时间增加

    使用DataBinding可能会增加应用程序的构建时间,因为DataBinding库需要生成绑定类。在编译时需要分离和处理xml文件,增加了编译时间。

  3. 难以调试

    由于DataBinding库使用了代码生成,因此在调试时可能会出现一些困难。例如,开发者可能会发现在使用DataBinding时,调试器可能会跳过某些代码行,或者某些变量的值可能无法正确显示。

  4. 增大包的体积

    占用大量内存,每个可观测数据对象都会对应一个监听器WeakListener对象。

3.4.4 DataBinding应用场景

  1. MVVM架构:DataBinding可以帮助开发者实现MVVM架构中的视图模型(ViewModel)和视图(View)之间的绑定。通过DataBinding,开发者可以将视图和数据模型分离,提高代码的可读性和可维护性。
  2. 布局优化:DataBinding可以帮助开发者实现布局优化,例如通过使用绑定表达式和条件语句来控制UI组件的可见性。通过布局优化,可以使得应用程序的UI更加灵活和可定制。
  3. 多语言支持:DataBinding可以帮助开发者实现多语言支持,例如通过使用绑定表达式来动态设置UI组件的文本。通过多语言支持,可以使得应用程序的用户界面更加友好和易用。
  4. 动态主题:DataBinding可以帮助开发者实现动态主题,例如通过使用绑定表达式来动态设置UI组件的颜色和样式。通过动态主题,可以使得应用程序的UI更加美观和精致。

3.4.5 DataBinding原理分析

类图

源码

DataBinding的原理主要包括以下几个方面:

  1. 布局文件的解析和生成:在DataBinding中,布局文件会被解析并生成对应的ViewDataBinding类。这个类包含了布局文件中定义的所有UI组件的引用,以及绑定到这些UI组件的数据对象。
  2. 数据对象的绑定:DataBinding会在编译时生成代码来完成UI组件和数据对象的绑定。生成的代码会被包含在BR类中。这个类包含了应用程序中所有用于绑定的变量的引用。当数据对象发生变化时,DataBinding会自动更新UI组件的值。
  3. 观察者模式的应用:DataBinding使用观察者模式来保持UI和数据之间的同步。当数据对象发生变化时,DataBinding会自动更新UI组件的值。在DataBinding中,数据对象是被观察的对象,而UI组件是观察者。
  4. 双向绑定的实现:DataBinding支持双向绑定,即当UI组件的值发生变化时,DataBinding会自动更新数据对象的值。这是通过使用双向绑定适配器来实现的。适配器会在UI组件的值发生变化时自动更新数据对象的值。
  5. 属性转换器的使用:DataBinding还支持属性转换器,可以用来将数据对象中的值转换为UI组件可以显示的值。例如,可以使用属性转换器将日期格式化为特定的格式。
  6. 数据绑定表达式的应用:DataBinding还支持数据绑定表达式,可以用来在布局文件中动态计算UI组件的值。例如,可以使用数据绑定表达式将两个文本框中的值相加并将结果显示在第三个文本框中。

总的来说,Android Jetpack DataBinding的原理是基于数据绑定和观察者模式的。它通过生成代码来完成UI组件和数据对象之间的绑定,并使用观察者模式来保持UI和数据之间的同步。同时,DataBinding还支持双向绑定、属性转换器和数据绑定表达式等特性,使得它可以满足更加复杂的UI和数据交互需求。

3.4.6 DataBinding注意事项

  1. 错误:Cannot find the setter for attribute ‘XXX’ with parameter type XXX。这个错误通常是由于绑定表达式中的变量类型不正确或布局文件中的属性名称不正确导致的。可以检查变量类型和属性名称是否正确。
  2. 错误:Could not find accessor XXX。这个错误通常是由于布局文件中使用了不存在的变量或方法导致的。可以检查布局文件中使用的变量和方法是否存在。
  3. 错误:Variable XXX has incompatible type XXX。这个错误通常是由于DataBinding的变量类型和布局文件中的变量类型不一致导致的。可以检查DataBinding代码中变量的类型和布局文件中变量的类型是否一致。
  4. 错误:DataBinding不支持lambda表达式。如果使用lambda表达式,编译时会出现错误。可以使用方法引用或匿名内部类来替代lambda表达式。
  5. 性能问题:在使用数据绑定表达式时,要注意表达式的复杂度。复杂的表达式可能会影响应用程序的性能。可以尝试将复杂的表达式分解成更简单的表达式,以提高应用程序的性能。
  6. 混淆问题:如果应用程序使用了代码混淆,那么需要将DataBinding的类排除在混淆范围之外,否则会导致编译错误或运行时异常。
  7. ViewStub问题:在使用DataBinding的布局文件中,不能包含ViewStub,否则会导致编译错误。

3.4.7 ButterKnife VS ViewBinding VS RoboBinding VS DataBinding

image.png

四、总结与展望

《Android业务架构 · 基础篇 · Jetpack四件套》一文首先通过4W2H全方位的讲解了Jepack对Android业务开发的价值,然后通过基础定义、基础使用、优劣势分析、应用场景、原理分析、注意事项等多维度分析了Jetpack四件套。

在我们工作中,可维护、可扩展、可测试和可重用的业务架构对于提高应用程序的质量和效率意义非凡,而JetPack是帮助开发者快速地组织和管理应用程序的代码的工具包。

这也是小木箱强烈建议大家学习Jetpack很重要的原因。希望通过这篇文章能够让你意识到Jetpack对业务开发的重要性。

提高篇将介绍MVC、MVP、MVVM和MVI四剑客,同样是Android业务架构核心内容。 今天就到这里啦,我是 小木箱,我们下一篇见~