Android Architecture Components

前言:

作为一名移动互联网App研发人员,在实际项目的研发过程中,保质保量高效率,方便快捷,同时方便开发者之间的互相协作地完成开发任务,尤为重要。为了达成这个目的,我们一般会进行一些可复用,可扩展性,方便其他开发者调用的模块化和组件化的框架的搭建。当然其中不乏一些优秀的官方或第三方的框架,比如图片库:Fresco, UImageLoader Glide;网络库,OkHttp,Retrofit,Volley;消息分发 : EventBus等等。这些优秀的库的出现,极大的方便了无数的App 开发者,并在数以万计的App中使用。 而今天要介绍的是Google I / O 大会上发布的架构化组件,即Android Architecture Components。

什么是Android Architecture Components

A collection of libraries that help you design robust, testable, and maintainable apps. Start with classes for managing your UI component lifecycle and handling data persistence.
Android架构化组件是通过提供管理开发者UI 组件的生命周期和数据持久化方案的一系列库,来帮助开发者设计出,稳健性,可测试性和可维护的App应用。

在此之前,Google官方并没有推荐指定任何的应用程序开发架构,这意味着,我们可以自由的选择设计模式,Mvp Mvc MvvM等。但是同时,这也意味着我们需要编写大量的基础框架代码来实现这些设计模型。不仅如此,Google官方对组件(View ,Fragment, Activity等)的生命周期管理,并没有提供一套完整的可复用的解决方案,这对开发者来说会产生极大的困扰。比如我们在组件中进行异步操作,如查询数据库,网络请求,异步解析数据,设置定时器时,一旦组件销毁,异步操作完成回调UI线程时,就极易出现如空指针,内存泄漏等各种问题。而今天要介绍的Architecture Components就可以很轻松的解决这些问题。

Architecture Components主要包括两部分:

1)生命周期管理组件: ViewModel,LiveData,LifecycleObserver,LifecycleOwner

New lifecycle-aware components help you manage your activity and fragment lifecycles. Survive configuration changes, avoid memory leaks and easily load data into your UI using Above.
能感知并管理activity fragment的生命周期的组件,可以帮助开发者在配置变化中保持数据不丢失,避免内存泄漏,并且方便地将数据交给UI层展现。

2)本地数据持久化: Room

Avoid boilerplate code and easily convert SQLite table data to Java objects using Room
. Room provides compile time checks of SQLite statements and can return RxJava, Flowable and LiveData observables.
Room可以帮助开发者避免写大量样板代码,并且可以地将数据库表数据,转换成Java 实体数据。Room提供SQLite语句的编译时检查,并且可以返回 Rxjava Flowable 和LiveData类型的可被观察数据。

1、ViewModel:

ViewModel是用来以一种能感知生命周期的方式来存储和管理与UI有关的数据。它允许数据在配置发生变化时,能够存活下来而不丢失。比如最常见的,Activity 横竖屏切换时,我们先想想正常流程下,横竖屏切换时,Activity的生命周期是怎样的。

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

    Chronometer chronometer = findViewById(R.id.chronometer);
    chronometer.start();
}

布局文件如下:

   <RelativeLayout
      xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:tools="http://schemas.android.com/tools"
      android:id="@+id/activity_main"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:paddingBottom="@dimen/activity_vertical_margin"
      android:paddingLeft="@dimen/activity_horizontal_margin"
      android:paddingRight="@dimen/activity_horizontal_margin"
      android:paddingTop="@dimen/activity_vertical_margin"
      tools:context="com.example.android.lifecycles.step1.ChronoActivity1">

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Hello World!"
    android:layout_centerVertical="true"
    android:layout_centerHorizontal="true"
    android:id="@+id/hello_textview"/>

<Chronometer
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_above="@+id/hello_textview"
    android:layout_centerHorizontal="true"
    android:id="@+id/chronometer"/>
 </RelativeLayout>

我们先来看下图:(现在用的录屏转gif软件真的很难用,很难把握时机点,动图有点怪,见谅~)

切换时重置.gif

这是一个计时Demo,在进行横竖屏切换时,我们发现,计数被重置了。这是因为Activity在 横竖屏切换时,Activity被销毁,并重新onCreate了。chronometer 生成了一个新的对象,所以计数重置了。但是众说周知,横竖屏切换时,数据被重置,在任何app应用中都是无法接受的。这时会有人说,可以通过android:configChanges="orientation|keyboardHidden" 等等,各种设置来实现Activity不被onCreate。这里且不说这种方式在高低版本兼容中可能出现的问题,横竖屏切换时的生命周期变化本身就可以专门写一篇文章来说明了。总之一句话,通过android:configChanges 来设置,以实现数据不被重置,太!麻!烦!那么还有其他方式来轻松的解决这个问题吗,是的,有,就是ViewModel。

先看图:

111.gif

从图中我们可以看到,在横竖屏切换后,时间的数据并没有被重置。

ChronometerViewModel :

public class ChronometerViewModel extends ViewModel {
@Nullable
private Long mStartTime;

@Nullable
public Long getStartTime() {
    return mStartTime;
}

public void setStartTime(final long startTime) {
    this.mStartTime = startTime;
}
}

Activity:

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

    Log.d("ansen", "onCreate------>2");
    // The ViewModelStore provides a new ViewModel or one previously created.
    ChronometerViewModel chronometerViewModel
            = ViewModelProviders.of(this).get(ChronometerViewModel.class);

    // Get the chronometer reference
    Chronometer chronometer = findViewById(R.id.chronometer);

    if (chronometerViewModel.getStartTime() == null) {
        // If the start date is not defined, it's a new ViewModel so set it.
        long startTime = SystemClock.elapsedRealtime();
        chronometerViewModel.setStartTime(startTime);
        chronometer.setBase(startTime);
    } else {
        // Otherwise the ViewModel has been retained, set the chronometer's base to the original
        // starting time.
        chronometer.setBase(chronometerViewModel.getStartTime());
    }

    chronometer.start();
}

ChronometerViewModel 在横竖屏切换后,是会继续存活的,ChronometerViewModel 里的mStartTime的数据自然也是存活的,这样就可以达到我们在前文预期的效果。
那么ViewModel是如何做到的呢?

ChronometerViewModel chronometerViewModel
            = ViewModelProviders.of(this).get(ChronometerViewModel.class);

这行代码中的this,为AppCompatActivity, 实现了LifecycleOwner接口。Framework保证,只要LifecycleOwner是存活着的,ViewModel就处于存活状态。在ViewModel的LifecycleOwner因为配置变化而销毁时(比如横竖屏切换),ViewModel本身不会销毁,新的LifecycleOwner 实例会和存活的ViewModel建立连接。说明图表如下:


ViewModel.png

注意:
1)Activity在配置变化如横竖屏切换时,Activity会被销毁,但是ViewModel不会因为配置变化而销毁。但是如果我们调用Activity 的finish()方法,activity 生命周期完全结束,处于finished状态时,ViewModel也会随之销毁。
2)如果ViewModel持有View或Context的引用,可能会造成内存泄漏,要避免这种情况。ViewModel的onCleared()方法,对于其持有的更长生命周期的对象来取消订阅,或者清除引用是有效的,但是不是用来取消View 或Context的引用,要避免这种行为。

2、LiveData:

在介绍LiveData之前,我们先思考上文中提出过的一个问题。
异步操作(网络请求,数据库查询,数据解析,定时器刷新)等结束回调给主线程更新UI前,如果此时Activity被销毁了,此时要怎么处理才能避免内存泄漏和空指针等异常问题?
ok,ok,ok,我知道,作为一个老生长谈,历史悠久的问题,聪明的你肯定知道答案。
比如 ondestroy()中做如下处理
a) 异步操作: AsyncTask.cancel() (如果已经执行了,异步操作也是取消不了的)
b) 定时器: TimeTask.cancel (),Timer.cancel()
c) Handler: Handler.removeAllMessagesAndCallBacks(null)
d)View : 对activity view作非空判断避免空指针
e) Retrofit+ RxJava 取消网络请求,通过取消订阅来不再回调UI线程。
......
哇,看到上面列的这些,觉得大家真的是厉害,内存泄漏,NullPointer处理都考虑到了,有经验!可是转念一想,不免又觉得很失落。且不说上面所述是否列完了所有情况(当然当然没有,肯定还有很多其他类似需要处理的生命周期管理的问题),而且还因为,我发现每个开发者,都需要关注这个问题,在做每个功能时,都需要考虑是否会出现上述的问题以及如何处理,而Android 截至上个Google IO大会之前,竟然没有一套面向所有开发者的可信赖的解决方案。这就很难受了。。。

我们知道,上述的问题,其实就是一个生命周期管理的问题。在UI不需要或者不能进行更新操作的时候,从其他线程回调数据过来强行更新,就会出现问题。而解决这个问题,现在就有了一个非常好的解决方案:
LiveData。

LiveData is an observable data holder class. Unlike a regular observable, LiveData is lifecycle-aware, meaning it respects the lifecycle of other app components, such as activities, fragments, or services. This awareness ensures LiveData only updates app component observers that are in an active lifecycle state.

LiveData 是一个可观察数据的容器。不像其他的可观察者,LiveData 是可以感知生命周期的,这意味着它能感知其他的app 组件如 activity fragment service等。这种生命周期感知能力使LiveData可以只在它的那些组件观察者active时通知更新(比如onResume等)。

LiveData同时也作为一个观察者,在它的观察对象(比如Activity Fragment等组件)的生命周期处于STARTED或者RESUMED状态时,LiveData处于活跃状态,LiveData只会在它的观察者们处于活跃状态时才会通知他们进行更新。LiveData的观察者如果本身生命周期处于不活跃状态,那么他们不会收到LiveData更新的通知。

我们先来看一个例子,还是一个时钟的例子,时间显示在自动更新。

LiveDataTimerViewModel:

public class LiveDataTimerViewModel extends ViewModel {

private static final int ONE_SECOND = 1000;

private MutableLiveData<Long> mElapsedTime = new MutableLiveData<>();

private long mInitialTime;

public LiveDataTimerViewModel() {
    mInitialTime = SystemClock.elapsedRealtime();
    Timer timer = new Timer();

    // Update the elapsed time every second.
    timer.scheduleAtFixedRate(new TimerTask() {
        @Override
        public void run() {
            final long newValue = (SystemClock.elapsedRealtime() - mInitialTime) / 1000;
            // setValue() cannot be called from a background thread so post to main thread.
            mElapsedTime.postValue(newValue);
        }
    }, ONE_SECOND, ONE_SECOND);

}

public LiveData<Long> getElapsedTime() {
    return mElapsedTime;
}
}

Activity:

public class ChronoActivity3 extends AppCompatActivity {

private LiveDataTimerViewModel mLiveDataTimerViewModel;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    setContentView(R.layout.chrono_activity_3);

    mLiveDataTimerViewModel = ViewModelProviders.of(this).get(LiveDataTimerViewModel.class);

    subscribe();
}

private void subscribe() {
    final Observer<Long> elapsedTimeObserver = new Observer<Long>() {
        @Override
        public void onChanged(@Nullable final Long aLong) {
            String newText = ChronoActivity3.this.getResources().getString(
                    R.string.seconds, aLong);
            ((TextView) findViewById(R.id.timer_textview)).setText(newText);
            Log.d("ChronoActivity3", "Updating timer");
        }
    };

    mLiveDataTimerViewModel.getElapsedTime().observe(this, elapsedTimeObserver);
}
}

我们可以看到LiveDataTimerViewModel 继承自ViewModel,ViewModel的作用上文已说过,这里不再赘述。
LiveDataTimerViewModel 拥有LiveData对象MutableLiveData,操作的数据类型是Long型的时间戳。
构造方法中,每一秒钟会更新一次数据,并将值postValue给LiveData对象mElapsedTime。注意:
因为定时器的线程是异步线程,而Activity是在UI线程中监听LiveData的时间变化的,所以此时需要用postValue给LiveData。而不是setValue(当然如果数据变化不是在异步线程而是在主线程中进行的,则需要用setValue)。

接下来看LiveData的observe方法。

public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<T> observer)

1)将所给的Observer加入到所给的Owner的生命周期范畴的Observer的列表中。事件分发会在主线程中进行,如果LiveData 已经set了数据,那么这个数据变化会被发送给Observer。
2)Observer只会在Owner处于活跃状态时(STARTED或者RESUMED状态)时才会接收到变化的通知。
3)如果Owner状态变为了DESTROYED状态,那么这个Observer会被移除。
4)在Owner处于不活跃状态时,数据发生变化不会通知给Observer。但是当Owner重新变活跃时,它会接收到最后一次可用的数据更新通知。
5)只要Owner不处于DESTROYED状态 , LiveData会一直持有Observer的强引用。一旦Owner变为DESTROYED状态,LiveData会移除对Observer的引用。
6)如果Owner在observe方法调用时处于DESTROYED状态,LiveData会忽略此次调用。
7)如果给定的Oberver和Owner已经在观察列表中了,LiveData会忽略此次调用。但是如果Oserver在观察列表中拥有多个Owner, LiveData会报IllegalArgumentException。

现在看这个例子就比较简单了。观察者是elapsedTimeObserver ,它监听LiveData里的long类型的时间变化。elapsedTimeObserver 的生命周期拥有者是this即ChronoActivity3 。在ChronoActivity3 处于活跃状态时,LiveData会将时间的变化通知给elapsedTimeObserver,并在onChange回调中更新UI。

此时我相信大家应该注意到一个点。就是我们不需要在每次数据变化后,再去refresh一遍UI了,我们只需要将需要观察的数据放入LiveData中,然后将数据变化通知给组件。这在大量不同数据变化需要更新不同UI的场景下,大大解放了我们的双手,我们再也不用在每个数据变化时,都去调用refreshUI方法了!

LiveData的优势:

1)LIveData确保你的UI使用的是最新的数据,而且还不需要你在数据变化时自己调用更新。
2)不会内存泄漏。(我们上文讨论过这个问题。因为Observer只会在自己的生命周期拥有者,LiveCycleOwner active的状态下,才会接收到数据更新的通知,所以不会再有内存泄漏的问题。)
3)不会在Activity销毁后还接收到更新UI的指令导致崩溃。
4)开发者不需要再手动管理生命周期。
5)即使在配置发生变化时,比如activty的横竖屏切换,观察者还是能收到最新的有效数据。
6)这一点感觉很好用。LiveData可以以单例模式在应用中存在。这时它就可以被应用中,任何需要它的观察者监听,以实现多页面多处监听更新等。

再介绍两个类:
MutableLiveData : 以public形式暴露了LiveData的setValue和postValue方法。
MediatorLiveData:可以观察其他的LiveData数据,并在其发生变化时通知给MediatorLiveData。

3、生命周期管理(LifeCycleObserver和LifeCycleOwner)

生命周期组件可以根据其他组件(如Activity Fragment等)的生命周期状态变化进行相应操作。这些组件可以帮助你写出更好组织,更轻量级,更容易管理的代码。

想象一下,如果我们要实现一个类似微信摇一摇震动的功能,而这个功能在用户锁屏时,即页面不可见时,是不能触发的,那么我们需要对onResume,onPause进行监听,操作如下:

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

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

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

class MyActivity extends AppCompatActivity {
private MyShakeListener myShakeListener;

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

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

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

这样操作是没有问题的,但是一个应用中大多数情况下是绝对不止一个功能需要监听类似的生命周期状态。一般我们用到的EventBus,ButterKnife等以及我们自定义的监听都需要用到类似功能。此时我们发现,我们需要在Activity或者Fragment的生命周期中,管理这么多的组件的相关生命周期方法,这样也太不优雅了。

我们可以通过LifecycleOwner 和LifecycleObserver来更好地处理生命周期状态变化的问题。

生命周期Events和状态变化如下图:

lifecycle-states.png
class MyShakeListener implements LifecycleObserver {
private boolean enabled = false;
public MyShakeListener(Context context, Lifecycle lifecycle, Callback callback) {
   ...
}

@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
}
}
myLifecycleOwner.getLifecycle().addObserver(new MyObserver());

通过LifeCycleObserver以及LifeCycleOwner, 一个类可以通过给其中的方法添加注解的方式来管理生命周期,然后通过myLifecycleOwner(比如Activity Fragment)的addObserver() 方法实现Observer对LifeOwner的生命周期的监听。

在进行生命周期的管理时,我们希望在某些不合适的生命周期阶段不触发某些操作。例如我们不能在activity的state已经保存的情况下,再去提交事务操作,这样会引发崩溃。而LifeCycleOwner允许我们查询当前的生命周期状态。LifeCycleOwner.getCurrentState(),可以根据当前状态来判断是否允许进行某些操作。

现在的MyShakeListener就已经是一个能感知生命周期的组件了。如果其他的Fragment 或者Activity需要用到它,只需要实例化MyShakeListener即可,其他剩余的操作都只在MyShakeListener中进行管理。

当然,你也可以通过如下方式,使你的class变成一个LifecycleOwner。你可以使用LifecycleRegistry类来实现,但是同时你需要通过下面的方式来分发相应的生命周期状态。

public class MyActivity extends Activity implements LifecycleOwner {
private LifecycleRegistry mLifecycleRegistry;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    mLifecycleRegistry = new LifecycleRegistry(this);
    mLifecycleRegistry.markState(Lifecycle.State.CREATED);
}

@Override
public void onStart() {
    super.onStart();
    mLifecycleRegistry.markState(Lifecycle.State.STARTED);
}

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

4、Room:

Room是Android官方提供的新的数据持久化方案组件。
Room组件主要可以进行数据库操作,并且可以和LiveData配合来监听数据库变化,以更新UI。

Room中有几个重要的注解:

@Database: 注解继承自RoomDatabase的类,主要用于创建数据库和Daos(数据访问对象)。
@Entity :用来注释实体类,@Database类通过entities属性,引用被@Entity注解的类,并通过
这个类的所有属性作为表的列名来创建数据库的表。
@Dao: 注解接口或抽象方法,用来提供访问数据库数据的方法。在使用@Database注解的类中必须定义一个不带参数的方法,这个方法返回使用@Dao注解的类。

1) 用注解 @Entity 定义实体类

@Entity
public class User {

@PrimaryKey
@NonNull
public String id;

public String name;

public String lastName;

public int age;

}

2)用@Database创建RoomDatabase的子类作为数据库。

@Database(entities = {User.class, Book.class, Loan.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {

private static AppDatabase INSTANCE;

public abstract UserDao userModel();

public abstract BookDao bookModel();

public abstract LoanDao loanModel();

public static void destroyInstance() {
    INSTANCE = null;
  }

}

3)@Dao 创建数据访问接口Dao

@Dao
public interface UserDao {
@Query("select * from user")
List<User> loadAllUsers();

@Query("select * from user where id = :id")
User loadUserById(int id);

@Query("select * from user where name = :firstName and lastName = :lastName")
List<User> findUserByNameAndLastName(String firstName, String lastName);

@Insert(onConflict = IGNORE)
void insertUser(User user);

@Delete
void deleteUser(User user);

@Query("delete from user where name like :badName OR lastName like :badName")
int deleteUsersByName(String badName);

@Insert(onConflict = IGNORE)
void insertOrReplaceUsers(User... users);

@Delete
void deleteUsers(User user1, User user2);

@Query("SELECT * FROM User WHERE :age == :age") // TODO: Fix this!
List<User> findUsersYoungerThan(int age);

@Query("SELECT * FROM User WHERE age < :age")
List<User> findUsersYoungerThanSolution(int age);

@Query("DELETE FROM User")
void deleteAll();
}

4)在RoomDatabase中引用dao:

@Database(entities = {User.class, Book.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {

private static AppDatabase INSTANCE;

public abstract UserDao userModel();

public abstract BookDao bookModel();
}

5、 如何获取数据并通知UI Controller更新UI

现在我们已经了解了上述Android架构化组件的基本用法,下面我们需要考虑一个问题。我们要如何更好的管理数据源,然后通知给UI Controller去更新数据呢?是的,没错,ViewModel + LiveData +Retrofit +Room +...

1)数据模型UserProfileViewModel ,用来保存需要UI更新所需要的数据

public class UserProfileViewModel extends ViewModel {
private String userId;
private User user;

public void init(String userId) {
    this.userId = userId;
}
public User getUser() {
    return user;
  }
}

2)如何通知UI更新

@Override
    public void onCreated(@Nullable Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    String userId = getArguments().getString(UID_KEY);
    UserProfileViewModel   viewModel = ViewModelProviders.of(this).get(UserProfileViewModel.class);
    viewModel.init(userId);
    viewModel.getUser().observe(this, user -> {
      // update UI
    });
  }

3)如何获取数据:

a、常见的网络数据获取方式(比如Retrofit)
b、本地持久化数据(比如Room)

public interface Webservice {
/**
 * @GET declares an HTTP GET request
 * @Path("user") annotation on the userId parameter marks it as a
 * replacement for the {user} placeholder in the @GET path
 */
@GET("/users/{user}")
Call<User> getUser(@Path("user") String userId);
}

4)封装UserRepository :

public class UserRepository {
private Webservice webservice;
// ...
public LiveData<User> getUser(int userId) {
    // This is not an optimal implementation, we'll fix it below
    final MutableLiveData<User> data = new MutableLiveData<>();
    webservice.getUser(userId).enqueue(new Callback<User>() {
        @Override
        public void onResponse(Call<User> call, Response<User> response) {
            // error case is left out for brevity
            data.setValue(response.body());
        }
    });
    return data;
  }
}

Android官方推荐我们使用Repository模式来进行数据获取逻辑的封装。Repository可以给app其余模块提供一个干净的api接口,这样其他模块就会独立于数据获取的逻辑之外,能更专注于他们本身的业务范畴。Repository知道从哪里获取数据,也知道数据更新时该如何调用。Repository可以将其他模块,比如本地持久化数据(Room),网络数据(Web Service), 缓存数据(Cache)等串联起来。
此时我们发现,ViewModel只需要从UserRepository来更新数据,但是ViewModel并不知道,也不需要关心这些数据从哪里来以及怎么来。从而达到ViewMode是与数据获取逻辑独立开的目的。

5)将ViewModel和Repository 串联起来。

public class UserProfileViewModel extends ViewModel {
private LiveData<User> user;
private UserRepository userRepo;

@Inject // UserRepository parameter is provided by Dagger 2
public UserProfileViewModel(UserRepository userRepo) {
    this.userRepo = userRepo;
}

public void init(String userId) {
    if (this.user != null) {
        // ViewModel is created per Fragment so
        // we know the userId won't change
        return;
    }
    user = userRepo.getUser(userId);
}

public LiveData<User> getUser() {
    return this.user;
  }
}

6) UserRepository从缓存中读取数据,如果缓存中没有,则从网络读取。

public class UserRepository {
private Webservice webservice;
// simple in memory cache, details omitted for brevity
private UserCache userCache;
public LiveData<User> getUser(String userId) {
    LiveData<User> cached = userCache.get(userId);
    if (cached != null) {
        return cached;
    }

    final MutableLiveData<User> data = new MutableLiveData<>();
    userCache.put(userId, data);
    // this is still suboptimal but better than before.
    // a complete implementation must also handle the error cases.
    webservice.getUser(userId).enqueue(new Callback<User>() {
        @Override
        public void onResponse(Call<User> call, Response<User> response) {
            data.setValue(response.body());
        }
    });
    return data;
  }
}

7)UserRepository从数据库中获取数据(Room),如果本地数据中没有,则从网络获取。

@Singleton
public class UserRepository {
private final Webservice webservice;
private final UserDao userDao;
private final Executor executor;

@Inject
public UserRepository(Webservice webservice, UserDao userDao, Executor executor) {
    this.webservice = webservice;
    this.userDao = userDao;
    this.executor = executor;
}

public LiveData<User> getUser(String userId) {
    refreshUser(userId);
    // return a LiveData directly from the database.
    return userDao.load(userId);
}

private void refreshUser(final String userId) {
    executor.execute(() -> {
        // running in a background thread
        // check if user was fetched recently
        boolean userExists = userDao.hasUser(FRESH_TIMEOUT);
        if (!userExists) {
            // refresh the data
            Response response = webservice.getUser(userId).execute();
            // TODO check for error etc.
            // Update the database.The LiveData will automatically refresh so
            // we don't need to do anything else here besides updating the database
            userDao.save(response.body());
        }
    });
  }
}

总结:

final-architecture.png

UI Controller通过ViewModel来更新数据,而ViewModel通过LiveData监听数据变化并通知给UI层更新。ViewModel通过Repository模式来封装数据获取逻辑。这些数据可以来自缓存(Cache),本地持久化数据(Room Sqlite),以及网络请求(Retroft OKHttp)等。通过上述方式,整个数据获取的业务模块,UI Controller及App的其他模块,就能更加的独立和解耦,整个app的业务框架就更清晰,更容易扩展和维护了。

1、Android Architecture Components官方文档
2、android-lifecycles
3、android-persistence
4、App 组件化/模块化之路——Android 架构化组件
5、Android Room使用

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容