Android开发

Android Architecture Component

2021-01-04  本文已影响0人  青叶小小

一、前言

AAC出现之前,已经有了 DataBinding 帮助我们构建 MVVM 的应用程序,但仍有许多缺陷,比如:

Activity / Fragment 销毁时,VM 并不知情,在一段时间内收到数据变更后还会试图去刷新UI导致Crash(虽然,在 ViewDataBinding 类中最后执行 executePendingBindings 之前会检查 mRoot(即根视图)是否 attach,仍有小概率Crash);同样也可能有内存泄露。

AAC的出现,弥补上这点(代码写的太烂导致的问题,这不能怪 Google哈),AAC的设计目标就是:健壮、易测、可维护!
另一个重要的原则是应该\color{red}{通过模型驱动界面(最好是持久性模型)}

模型是负责处理应用数据的组件。它们独立于应用中的 View 对象和应用组件,因此不受应用的生命周期以及相关的关注点的影响。

持久性是理想之选,原因如下:

应用所基于的模型类应明确定义数据管理职责,这样将使应用更可测试且更一致。


AAC的组件库:

基于AAC的MVVM应用架构如下:


final-architecture.png

二、组件介绍

2.1、LifeCycle-Aware Component

它是由Lifecycle,LifecycleOwner组成!

2.1.1、LifeCycle

Lifecycle 是一个用来保存组件生命周期的状态信息的类,它允许其他对象观察这些状态信息。
主要使用了Event 和 State 这两个枚举类来跟踪绑定的组件的生命周期状态信息。

image.png

2.1.2、LifecycleOwner

LifecycleOwner 是只有一个方法的接口,表示这个类有一个 Lifecycle。只有一个 getLifecycle() 方法:

2.1.3、示例

没有 LifeCycle 时,我们是这么实现定位功能的:

class MyLocationListener {
    public MyLocationListener(Context context, Callback callback) {
        // ...
    }

    void start() {
        // connect to system location service
    }

    void stop() {
        // disconnect from system location service
    }
}

class MyActivity extends AppCompatActivity {
    private MyLocationListener myLocationListener;

    @Override
    public void onCreate(...) {
        myLocationListener = new MyLocationListener(this, (location) -> {
            // update UI
        });
    }

    @Override
    public void onStart() {
        super.onStart();
        myLocationListener.start();
        // manage other components that need to respond to the activity lifecycle
    }

    @Override
    public void onStop() {
        super.onStop();
        myLocationListener.stop();
        // manage other components that need to respond to the activity lifecycle
    }
}

使用 LifeCycle 来实现定位功能:

class MyLocationListener implements LifecycleObserver {
    private boolean enabled = false;
    public MyLocationListener(Context context, Lifecycle lifecycle, Callback callback) {
       lifecycle.addObserver(this);
       ...
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_START)
    void start() {
        if (enabled) {
           // connect
        }
    }

    public void enable() {
        enabled = true;
        if (lifecycle.getCurrentState().isAtLeast(STARTED)) {
            // connect if not connected
        }
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
    void stop() {
        // disconnect if connected
    }
}

class MyActivity extends AppCompatActivity {
    private MyLocationListener myLocationListener;

    public void onCreate(...) {
        myLocationListener = new MyLocationListener(this, getLifecycle(), location -> {
            // update UI
        });
        Util.checkUserStatus(result -> {
            if (result) {
                myLocationListener.enable();
            }
        });
  }
}

2.2、LiveData

\color{red}{LiveData 是一种持有可被观察数据的类}
和其他可被观察的类不同的是,LiveData 是有生命周期感知能力的,这意味着它可以在 activities, fragments 或者 services 生命周期是活跃状态时更新这些组件。

\color{red}{那么什么是活跃状态呢?}
lifecycle 中提到的 STARTED 和 RESUMED就是活跃状态,只有在这两个状态下LiveData 是会通知数据变化的。

\color{red}{使用 LiveData,必须配合实现了 LifecycleOwner 的对象。}
当对应的生命周期对象 DESTORY 时,才能移除观察者。
对于 activities 和 fragments 非常重要,因为他们可以在生命周期结束的时候立刻解除对数据的订阅,从而避免内存泄漏等问题。

\color{red}{优点}

示例:
LiveData 其实是一个包装类,适用于任何数据类型,通常与 ViewModel 共同使用。如下示例演示了如何创建一个 LiveData 对象。

class UserProfileViewModel : ViewModel() {
    var user = MutableLiveData<User>(); // LiveData 是抽象类,实际用 MutableLiveData
}

2.3、ViewModel

ViewModel 对象为特定的界面组件(如 Fragment 或 Activity)提供数据,并包含数据处理业务逻辑,以与模型进行通信。
例如,ViewModel 可以调用其他组件来加载数据,还可以转发用户请求来修改数据。
ViewModel 不了解界面组件,因此不受配置更改(如在旋转设备时重新创建 Activity)的影响。


image1.png

\color{red}{优点}

示例:

// User.kt
data class User(
    var name: String? = null,
    var password: String? = null
)

// UserViewModel.kt
class UserViewModel : ViewModel() {
    var user = MutableLiveData<User>()
    
    fun start() {
        Thread {
            while (true) {
                Thread.sleep(3000)
    
                user.postValue(User(
                    UUID.randomUUID().toString().substring(0, 8),
                    UUID.randomUUID().toString().substring(0, 4)
                ))
            }
        }.start()
    }
}

访问 ViewModel

// layout
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text=""
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

// Activity
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        // 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.
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // androidx 2.0.0 写法;
        // 1.x 写法 ViewModelProvider.of
        val userViewModel = ViewModelProvider(this, ViewModelProvider.NewInstanceFactory()).get(UserViewModel::class.java)
        userViewModel.user.observe(this, Observer { user ->
            text.text = "${user.name} - ${user.password}"
        })
    }
}

2.4、Repository

Repository 这层的思想很简单:ViewModel 层需要获取数据,数据来自:

因此,Repository 可以很好的理解为数据抽象层(数据中心 / 数据仓库)。
ViewModel 不需要关心数据从哪里来,只在需要时,向对应的 Repository 请求即可!

\color{red}{layout、Activity、User 不变}

// RepoCallback.kt
typealias onRepoCallback<T> = (data: T) -> Unit?

// UserRpository.kt
class UserRepository {
    fun start(callback: onRepoCallback<User>) {
        Thread {
            while (true) {
                Thread.sleep(3000)

                callback(User(
                    UUID.randomUUID().toString().substring(0, 8),
                    UUID.randomUUID().toString().substring(0, 4)
                ))
            }
        }.start()
    }
}

// UserViewModel.kt
class UserViewModel : ViewModel() {
    var user = MutableLiveData<User>()
    private val userRepository = UserRepository()

    fun getUserData() {
        userRepository.start { data -> user.postValue(data) }
    }
}

可以看到:

2.5、Room

Room在SQLite上提供了一个方便访问的抽象层。App把经常需要访问的数据存储在本地将会大大改善用户的体验。这样用户在网络不好时仍然可以浏览内容。当用户网络可用时,可以更新用户的数据。


\color{red}{官方解释:}
使用原始的SQLite可以提供这样的功能,但是有以下两个缺点:

针对以上的缺点,Google提供了Room来解决这些问题。Room包含以下三个重要组成部分:

ROOM.png

关于 Room,本文就不再展开了,有需要的直接看官方Demo!

上一篇下一篇

猜你喜欢

热点阅读