Android Architecture Guide 的翻译

懒得处理样式了, 将就着看吧.

官网地址: https://developer.android.com/topic/libraries/architecture/guide.html#common_problems_faced_by_app_developers

Guide相比于电脑端应用, 移动端有很多额外的需求, 比如应用会被后台杀死, 四大组件短暂的生命周期, 跳到相机获取照片之类的应用间跳转, 玩一半来个电话. 以上情况都很普遍, 这就需要码农们管理好数据的持久化和加载时机.总结一下就是, 各组件的启动顺序是不确定的, 死亡时间也是不确定的. 这些都是不可控的, 所以不要把数据存到组件里!!!, 组件间也不应该互相依赖.架构原则一: 模块分离Activity & Fragment 只应该保留 UI处理 和 与操作系统交互 的逻辑.要明白, 这俩货不归你管, 丫只是个glue classes, 用于把你的应用套进Android操作系统里.架构原则二: Model 核心实现写进Model模块, 因为Model 因为没有生命周期问题和配置问题(比如横屏转竖屏). 最好加上持久化, 这可以避免App被杀死时丢失当前数据, 和丢失网络连接时直接傻逼.官方推荐架构煮个栗子, 比如要实现用户信息模块, 数据用 REST API 从后台获取.UI部分是常规的 UserProfileFragment.java和 user_profile_layout.xml UI要初始化页面, 则需要将 User ID 传给Model 模块, 并接收返回的 User object (pojo) . id 推荐通过 fragment.arguments 传入, 这可以保证程序被杀死重启后, 数据仍可以保留. Model 模块, UserProfileViewModel : ViewModel .这货相当于一个中间层, 用于给视图组件提供数据, 与数据处理层交互(比如通知处理层加载数据或是提交用户做出的数据变更).public class UserProfileViewModel extends ViewModel {    private String userId;    private User user;    public void init(String userId) { this.userId = userId; }    public User getUser() { return user; }}public class UserProfileFragment extends Fragment {    private static final String UID_KEY = "uid";    private UserProfileViewModel viewModel;    @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) {        super.onActivityCreated(savedInstanceState);        String userId = getArguments().getString(UID_KEY);        viewModel = ViewModelProviders.of(this).get(UserProfileViewModel.class);        viewModel.init(userId);    }    @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {        return inflater.inflate(R.layout.user_profile, container, false);    }} 现在需要解决的是交互问题, 在Model模块的User被赋值时, 我们需要通知UI更新页面. 这就引入了下一个要讲的模块 LiveData LiveData 是个数据容器, 类似于 Observable. 丫使你的组件可以在不添加外部依赖的情况下观察到容器中数据的变动. 丫也会替你关注生命周期变化, 避免内存泄漏.引入库 android.arch.lifecycle:reactivestreams 可以使 LiveData 与 RxJava2 同用.以下是加入LiveData的代码:public class UserProfileViewModel extends ViewModel {    ...    private User user;    private LiveDatauser;    public LiveDatagetUser() { return user; }}@Override public void onActivityCreated(@Nullable Bundle savedInstanceState) {    super.onActivityCreated(savedInstanceState);    viewModel.getUser().observe(this, user -> { // update UI });}每次Pojo数据变更的时候, 都会触发这个回调, 更新UI界面.如果你熟悉其他类似的观察者回调, 你就该知道, 我们没必要重载onStop() 方法来停止观察. 对于LiveData来说, 这货是看生命周期的, 只在UI页面活跃时(onStart() 之后, onStop() 之前)触发回调, 并且会在onDestroy() 后自动移除观察者.开发者在诸如屏幕旋转之类的配置变更时也不需要做额外的处理. ViewModel会自觉的存储数据, 并在变更完成后触发回调.这也是ViewModel不该放进UI视图的原因: 使得ViewModel可以无视View的生命周期.数据获取我们已经把ViewModel和Fragment关联在一起了, 那么ViewModel是怎么读取数据的呢? 假定我们使用Retrofit 库来访问后台提供的REST API.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}")    CallgetUser(@Path("user") String userId);}ViewModel 的本地实现可以直接调用Webservice 获取数据, 并存入Pojo. 但这个活不应该由ViewModel来做, 秉承职能分离的原则, ViewModel 需要找一个代理模块: RepositoryRepository(数据仓库)模块用于处理数据操作. 并对外提供简洁的API调用. 这货负责获取数据, 并在有外部API调用时处理数据并输出. 你可以把丫当做不同数据源(本地, 网络, 缓存, 等等)的转接器.UserRepository 使用示例如下:public class UserRepository {    private Webservice webservice;    // ...    public LiveDatagetUser(int userId) {        // This is not an optimal implementation, we'll fix it below        final MutableLiveDatadata = new MutableLiveData<>();        webservice.getUser(userId).enqueue(new Callback() {            @Override public void onResponse(Callcall, Responseresponse) {                // error case is left out for brevity                data.setValue(response.body());            }        });        return data;    }}尽管这货看起来没什么卵用, 但丫的存在还是很有意义的, 丫负责抽离数据源, ViewModel不需要知道数据是怎么来的, 而我们可以在必要的时候切换数据源.* 为了简化代码, 这里没有处理网络异常, 处理逻辑可以看Addendum: exposing network status.组件依赖上面的代码中, UserRepository 需要一个Webservice 实例才能干活. 而如果直接New一个对象出来, 就需要管理Webservice 的依赖. 所以你懂的, 解决方案有两个, 依赖注入和服务定位.Dependency Injection: 依赖注入(DI)可以从外部构造实例, 在调用时直接获取, 在运行时, 有专门的类负责提供依赖. Android中我们推荐使用自家的Dagger 2 .这货可以遍历依赖树, 还有编译时检测.Service Locator: 服务定位可以提供一个注册类, 这货比上面那个简单点, 所以你要是不会用DI, 就拿丫顶上.这俩货还有一个好处是方便测试, 这个点够吹一年. 当前Demo中将使用Dagger2.关联ViewModel和Repository我们来改一下UserProfileViewModel 的代码, 加上数据仓库的逻辑.public class UserProfileViewModel extends ViewModel {    private LiveDatauser;    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 LiveDatagetUser() { return this.user; }}数据缓存上面的数据仓库已经完美抽离了网络请求, 但这还不够屌. 现在的问题是, 丫在获取数据后, 使用一次就废了. 比如用户离开了UserProfileFragment 页面又回来, 还需要重新请求数据.这既浪费了带宽, 还增加了等待时间. 所以我们来增加一个数据源, 把数据保留在内存中.@Singleton  // informs Dagger that this class should be constructed oncepublic class UserRepository {    private Webservice webservice;    // simple in memory cache, details omitted for brevity    private UserCache userCache;    public LiveDatagetUser(String userId) {        LiveDatacached = userCache.get(userId);        if (cached != null) { return cached; }        final MutableLiveDatadata = 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() {            @Override public void onResponse(Callcall, Responseresponse) { data.setValue(response.body()); }        });        return data;    }}数据持久化现在, 在旋转屏幕或是跳转后返回时, UI视图会实时刷新, 因为数据可以从内存中直接读取了. 但如果应用被杀了还是一样傻逼.用户还需要进行一次网络请求, 经历一次网络等待, 来获取相同的数据. 你可以简单的缓存网络请求, 但这又会产生新的问题.如果是同一个Pojo的其他请求, 比如获取当前用户的好友列表. 逻辑上就生无可恋了. 所以你应该把它们合并一下.我的意思是, 你应该加上持久化层. 我们推荐RoomRoom 是一个简化过的本地持久化的对象映射库. 在编译时, 丫会检查所有的请求语句, 所以如果有问题会直接崩给你看. 丫内部封装了SQL细节, 还能监控数据库中的数据变化, 并触发LiveData的回调. 这货还附带线程监测, 不让你在主线程干数据存储的活.要使用Room, 先按规范刷一遍代码, 加上注解. 第一步, Pojo类需要加上@Entity 来标识数据表.@Entityclass User {  @PrimaryKey  private int id;  private String name;  private String lastName;  // getters and setters for fields}第二步, 创建数据库类, 继承 RoomDatabase@Database(entities = {User.class}, version = 1)public abstract class MyDatabase extends RoomDatabase { }要注意, 这货是抽象的, Room会帮你提供实现类的.第三步, 创建DAO类, 提供数据操作函数@Daopublic interface UserDao {    @Insert(onConflict = REPLACE)    void save(User user);    @Query("SELECT * FROM user WHERE id = :userId")    LiveDataload(String userId);}要注意, load 方法返回的是LiveData. Room会在数据库变更时自觉触发观察者回调, 且只在至少还有一个观察者活跃时更新数据.最后, 在第二步里面引用第三步的对象@Database(entities = {User.class}, version = 1)public abstract class MyDatabase extends RoomDatabase {    public abstract UserDao userDao();}要注意, Room是根据数据表被修改触发通知的, 所以如果发错了通知, 这个锅我不背.我们现在可以给UserRepository 加上Room的逻辑了.@Singletonpublic 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 LiveDatagetUser(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());            }        });    }}要注意, 尽管我们改变了UserRepository 的数据源, 但UserProfileViewModel 和UserProfileFragment 并没有受到影响. 大家说模块分离吼木吼呀. 在测试UserProfileViewModel时, 我们也可以直接提供一个伪数据源来干活.现在, 这个Demo就已经完结了. 用户过几天再登录也能直接刷出页面, 同时, 我们的数据仓库也会依据更新逻辑更新数据(本例中使用FRESH_TIMEOUT 来判定, 现实项目里可以使用更复杂的逻辑). 因为LiveData是回调更新UI的, 所以数据更新之后会自动二次刷新页面, 或者你也可以选择在需要更新数据时, 不显示数据库中的过时数据.在诸如下拉刷新之类的使用场景中, 通常需要提供说明网络处理情况的进度组件. 我们建议将UI与数据分离, 一个很重要的理由是, 这个进度组件可能因为多种原因而被更新.比如获取好友列表时, 可能会因为分页触发多次User数据更新. 而对于UI来说, 网络进度也只是一个数据节点, 你给我数据我展示就完了.这里也有两种常规解决方案:让getUser 的返回值携带网络状况的数据.给数据仓库添加一个返回数据刷新状态的函数. 如果你只想在特定场景显示网络进度, 比如下拉刷新, 推荐这种解决方案.唯一标准数据源对于REST API来说, 不同的端点返回相同的数据, 这种情况相当普遍. 比如获取用户数据, 和获取好友列表, 返回的都是同一个User对象, 只是它们的粒度不同.所以如果我们直接显示, 而不是先存入数据库的话, 显示出来的数据可能就乱套了.所以我们的数据库就相当于官方唯一指定旗舰数据源, 其他数据必须经过数据库统筹之后才能被传入UI使用. 我们同样也建议开发者指定一个数据总源.测试我们之前提到过, 这个框架的一个显著优点就是便于测试, 下面就来看看每个模块的测试方式.User Interface & Interactions: 现在, 开发者只需要在这里使用 Android UI Instrumentation test. 而测试UI我们推荐使用Espresso. 依赖方面, 你只需要mock一个ViewModel, 就可以开始你的测试了.ViewModel: 这货可以使用JUnit test来测试, 只需要mock一个UserRepository.UserRepository: 同样使用JUnit test来测试, mock Webservice和Dao. 测试一下服务器调用, 数据存入数据库, 以及数据过期时的处理. 因为这俩货都是接口, 所以你可以直接提供伪实现做更深度的测试.UserDao: 这货更推荐使用Instrumentation test来测试, 而且在不涉及UI的情况下仍可以保持相当的测试速度. 我们推荐在每个测试用例中, 创建一个只保留在内存中的数据库, 以避免外部影响.Webservice: 我们建议开发者在测试时尽量隔离外部环境, 所以请尽量不要在测这个模块时连接你们的后端. 我们建议使用MockWebServer 来创建一个本地服务用于测试.Testing Artifacts 我们还提供了一个Maven artifact来管理后台线程. 导入android.arch.core:core-testing, 这里有两个JUnit Rules用于测试:InstantTaskExecutorRule: 在调用线程执行指定操作.CountingTaskExecutorRule: 等待后台操作, 或是作为idling resource扔进Espresso架构图设计思想不同Activity或Fragment间的数据共享, 数据远程查询与本地持久化.以下是我们的建议.Manifest中定义的程序入口, activities, services, broadcast receivers, etc, 并不是数据源, 它们的生命周期不可控, 它们只需要一部分数据子集用于交互. 定义模块的时候, 要坚定的遵循单一职能原则. 比如数据缓存和数据绑定, 请分成两个模块.模块入口越少越好. 不要有我就只提供这个实现调用的想法, 毕竟出来混早晚要还的.在定义模块间交互的时候, 先想想测试时能不能分别单独测试. 比如封装流弊的网络请求框架可以让你单独测试本地数据持久化模块. 如果满地图都是网络请求, 你就傻逼了.App的核心在于与众不同, 不要天天敲代码就为了搭个模板.是数据就要持久化, 不要指望用户的网络和你测试时一样好.指定一个标准数据源, 然后只跟丫交互.附录: 网络状态处理前面的例子中, 为了简化代码, 我们省略了网络异常处理. 在这里会展示如何用Resource 类来封装数据和网络状态.//a generic class that describes a data with a statuspublic class Resource{    @NonNull public final Status status;    @Nullable public final T data;    @Nullable public final String message;    private Resource(@NonNull Status status, @Nullable T data, @Nullable String message) {        this.status = status;        this.data = data;        this.message = message;    }    public staticResourcesuccess(@NonNull T data) { return new Resource<>(SUCCESS, data, null); }    public staticResourceerror(String msg, @Nullable T data) { return new Resource<>(ERROR, data, msg); }    public staticResourceloading(@Nullable T data) { return new Resource<>(LOADING, data, null); }}网络加载本地显示, 程序员基本上都要处理这种问题. 为了处理这个倒霉问题, 我们设计了NetworkBoundResource, 用于规范化处理. 下面是丫的决策树.丫先绑定资源(Pojo), 然后开始观察数据库. 数据请求到来时, 丫判定现有数据是直接分发还是先请求后台更新, 还是同时发生: 先展示本地数据, 在网络请求完成后进行二次刷新.如果网络请求成功, 更新数据库, 手动触发更新UI. 如果失败, 发一个失败通知出去.* 数据更新后, 手动触发UI更新, 这个多数情况下没什么卵用, 因为数据库自己会这活. 但数据库干的活有时候不太靠谱, 比如有时数据没变化丫就不会触发回调(这特么不是好事么, 不对, 网络状态似乎需要刷新). 网络更新的数据原则上也不应该直接拿来分发, 因为违反了唯一标准数据源的原则(有可能数据在存入数据库时, 触发什么事务被更新了). 或是请求成功SUCCESS但是没有新数据产生时, 可能会发送错误的通知.// ResultType: Type for the Resource data// RequestType: Type for the API responsepublic abstract class NetworkBoundResource{    // Called to save the result of the API response into the database    @WorkerThread    protected abstract void saveCallResult(@NonNull RequestType item);    // Called with the data in the database to decide whether it should be    // fetched from the network.    @MainThread    protected abstract boolean shouldFetch(@Nullable ResultType data);    // Called to get the cached data from the database    @NonNull @MainThread    protected abstract LiveDataloadFromDb();    // Called to create the API call.    @NonNull @MainThread    protected abstract LiveData> createCall();    // Called when the fetch fails. The child class may want to reset components    // like rate limiter.    @MainThread    protected void onFetchFailed() {    }    // returns a LiveData that represents the resource, implemented    // in the base class.    public final LiveData> getAsLiveData();}要注意, 上面这货定义了两个类型参数 (ResultType, RequestType), 原因是API返回的数据可能跟本地使用的数据有区别.还要注意, 上面网络请求用的是ApiResponse, 这玩意就是把Retrofit2.Call返回的响应数据转换成LiveData. 下面是NetworkBoundResource 的后续实现.public abstract class NetworkBoundResource{    private final MediatorLiveData> result = new MediatorLiveData<>();    @MainThread    NetworkBoundResource() {        result.setValue(Resource.loading(null));        LiveDatadbSource = loadFromDb();        result.addSource(dbSource, data -> {            result.removeSource(dbSource);            if (shouldFetch(data)) fetchFromNetwork(dbSource);            else result.addSource(dbSource, newData -> result.setValue(Resource.success(newData)));        });    }    private void fetchFromNetwork(final LiveDatadbSource) {        LiveData> apiResponse = createCall();        // we re-attach dbSource as a new source,        // it will dispatch its latest value quickly        result.addSource(dbSource, newData -> result.setValue(Resource.loading(newData)));        result.addSource(apiResponse, response -> {            result.removeSource(apiResponse);            result.removeSource(dbSource);            //noinspection ConstantConditions            if (response.isSuccessful()) saveResultAndReInit(response);            else {                onFetchFailed();                result.addSource(dbSource, newData -> result.setValue( Resource.error(response.errorMessage, newData)));            }        });    }    @MainThread    private void saveResultAndReInit(ApiResponseresponse) {        new AsyncTask() {            @Override protected Void doInBackground(Void... voids) {                saveCallResult(response.body);                return null;            }            @Override protected void onPostExecute(Void aVoid) {                // we specially request a new live data,                // otherwise we will get immediately last cached value,                // which may not be updated with latest results received from network.                result.addSource(loadFromDb(), newData -> result.setValue(Resource.success(newData)));            }        }.execute();    }    public final LiveData> getAsLiveData() { return result; }}现在我们可以把这货用在之前的Demo中了.class UserRepository {    Webservice webservice;    UserDao userDao;    public LiveData> loadUser(final String userId) {        return new NetworkBoundResource() {            @Override protected void saveCallResult(@NonNull User item) { userDao.insert(item); }            @Override protected boolean shouldFetch(@Nullable User data) { return rateLimiter.canFetch(userId) && (data == null || !isFresh(data)); }            @NonNull @Override protected LiveDataloadFromDb() { return userDao.load(userId); }            @NonNull @Override protected LiveData> createCall() { return webservice.getUser(userId); }        }.getAsLiveData();    }}引入项目添加Google Maven仓库project build.gradle 添加高亮代码allprojects {    repositories {        jcenter()        google()    }}app build.gradle 添加依赖, kotlin中, 需要引入 kapt & kotlin-apt添加主要依赖模块, 包括  Lifecycles, LiveData, ViewModel, Room, and Paging. 以及测试模块 testing Room migrations.dependencies {    // ViewModel and LiveData    implementation "android.arch.lifecycle:extensions:1.0.0"    annotationProcessor "android.arch.lifecycle:compiler:1.0.0" // 如果使用Java8, 把这一行替换成下面那个// Java8 support for Lifecycles    implementation "android.arch.lifecycle:common-java8:1.0.0"    // Room    implementation "android.arch.persistence.room:runtime:1.0.0"    annotationProcessor "android.arch.persistence.room:compiler:1.0.0"    // Paging    implementation "android.arch.paging:runtime:1.0.0-alpha4-1"    // Test helpers for LiveData    testImplementation "android.arch.core:core-testing:1.0.0"    // Test helpers for Room    testImplementation "android.arch.persistence.room:testing:1.0.0"// RxJava support for Room    implementation "android.arch.persistence.room:rxjava2:1.0.0" // 如果要使用响应式编程库, 可以引入这两个库    // ReactiveStreams support for LiveData    implementation "android.arch.lifecycle:reactivestreams:1.0.0"}生命周期生命周期处理组件用于专门处理Activity&Fragment的生命周期状态改变事件, 丫可以使你的代码具有更好的组织逻辑, 更轻量级, 也更容易维护.之前, 常规处理方式是实现生命周期方法, 而这通常会造成代码难以维护并产生不必要的异常. 现在, 你可以直接针对组件本身而非某个生命周期方法进行处理.android.arch.lifecycle包允许开发者构建无需处理生命周期问题的组件, 我们已经替你处理好了.绝大多数Android自带组件都有生命周期, 丫本身是由操作系统进行管理的. 如果开发者不按我们的套路出牌, 由此产生的各种内存泄漏和崩溃我们不背锅.假设有一个Activity用于显示当前设备的位置信息. 常规代码如下: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    }}尽管这个代码看起来很合理, 在实际项目中, 往往最后就会有一堆处理生命周期变化的代码, 比如在onStart() and onStop()里面. 并且, Android并不保证onStart跑完才会跑onStop, 参考上面的代码, 有可能你在listener.start() 方法中处理一些耗时操作, 比如检查配置信息, 然后onStop触发了, 操作就被中断了. 从逻辑上看, LocationListener可能需要活的比Activity更久一点, 好歹榨干了再滚. android.arch.lifecycle 能帮你处理这些问题.class MyActivity extends AppCompatActivity {    private MyLocationListener myLocationListener;    @Override public void onCreate(...) {        myLocationListener = new MyLocationListener(this, location -> { // update UI });    }    @Override public void onStart() {        super.onStart();        Util.checkUserStatus(result -> {            // what if this callback is invoked AFTER activity is stopped?            if (result) myLocationListener.start();         });    }    @Override public void onStop() {        super.onStop();        myLocationListener.stop();    }}LifecycleLifecycle, 这里指的是android.arch.lifecycle 库中的一个类, 丫有两个主要的枚举(Event&State)用于保存组件生命周期状态的信息, 开发者可以通过它观察对应组件的生命周期状态, Event 这货对应于我们常用的onCreate这些回调函数. State 这货对应于Event事件完成后的组件状态, 比如created.这货通过方法注解来监视生命周期状态. 你可以调用Lifecycle类的addObserver()方法来添加一个观察者, 如下:public class MyObserver implements LifecycleObserver {    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)    public void connectListener() { ... }    @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)    public void disconnectListener() { ... }}myLifecycleOwner.getLifecycle().addObserver(new MyObserver());在这里, myLifecycleOwner对象实现了LifecycleOwner接口.LifecycleOwnerLifecycleOwner是一个单方法接口, 代表这货有一个Lifecycle, 需要实现getLifecycle()方法. 如果你想管理整个App进程的生命周期, 可以使用ProcessLifecycleOwner.丫从Fragment和AppCompatActivity中抽离出了生命周期逻辑, 并允许其他控件与之交互. 这货可以由任意类来实现.实现了LifecycleObserver的组件与实现了LifecycleOwner的组件可以无缝对接: 后者提供生命周期, 前者注册并开始观察.在下面的例子中, MyLocationListener实现了LifecycleObserver, 并用Lifecycle的onCreate()方法做初始化. 这样, 响应生命周期变化的逻辑就完整了. 把这个逻辑封入Listener而不是Activity使得代码更方便管理.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(); } });  }}一个常规使用情境是, 在生命周期处于抑制状态时, 不触发监听. 比如, 如果回调中是一个FragmentTransaction, 那么如果在Activity状态保存之后运行, 会使项目崩溃.为了简化例子, Lifecycle类允许其他对象查询当前生命周期状态.class MyLocationListener implements LifecycleObserver {    private boolean enabled = false;    public MyLocationListener(Context context, Lifecycle lifecycle, Callback callback) { ... }    public void enable() {        enabled = true;        if (lifecycle.getCurrentState().isAtLeast(STARTED)) { // connect if not connected }    }    @OnLifecycleEvent(Lifecycle.Event.ON_START)    void start() { if (enabled) { // connect } }    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)    void stop() { // disconnect if connected }}如此如此, LocationListener类就完全不惧生命周期问题了. 我们在外部调用丫时, 只需要初始化一下就好了. 其他的操作都已经在内部封装好了.如果一个库需要处理生命周期问题, 我们建议你使用这套生命周期组件. 这可以避免库的使用者编写额外的生命周期管理代码.实现自定义LifecycleOwnerSupport Library 26.1.0 之后, Fragments和Activities默认实现了LifecycleOwner接口.如果你想把一个自定义的类做成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;    }}Best PracticesUI Controller干的活越少越好. 丫不需要存储数据, 只需要定义数据变更后的逻辑, 然后直接找ViewModel要就好.改用数据驱动的UI, Controller只负责在数据变化时更新UI, 以及通知ViewModel数据变动.把数据逻辑放入ViewModel. 把这货当成UI Controller与其他部分的中间层. 要注意, 这货不负责获取数据, 丫应该把这个活委托给其他组件.使用Data Binding来保持View和Controller的简洁. 推荐使用Butter Knife来简化代码.如果UI层比较复杂, 推荐加一个presenter层. 虽然有点麻烦, 但易于测试.避免在ViewModel中引用Context. 有引用就没法回收了.Use Cases不同粒度的实时更新. 比如定位, 应用在前台时, 使用细粒度的更新, 在后台时, 使用粗粒度的更新. LiveData能保证在数据更新时实时变更UI.开始和停止数据缓存. 尽量早的开始视频缓冲, 在App完全启动后才开始播放, 在App销毁时停止缓冲.开始和停止网络连接. App在前台时实时更新网络数据, 切换到后台时暂停网络更新.暂停和恢复动图. 切换到后台时暂停动图, 切换到前台时恢复动图.Stop 事件的处理AppCompatActivity or Fragment的onSaveInstanceState()被触发时, 会开始ON_STOP事件的分发, 这个时候, UI被视为处于不可变动状态. 这也是为什么这时候运行FragmentTransaction会触发异常的原因.LiveData会自觉避免这个问题的发生, 丫会先调用来isAtLeast()确认一波.这里有一个问题, 就是AppCompatActivity的状态保存时, 还没有调用onStop()方法. 这个时间段里, 生命周期状态还没有被变更, 但UI是不可变动的. (也就是不能用State判定UI是否可变)Lifecycle的解决方式是无视onStop方法, 直接改变状态. 这种蛋疼的历史遗留问题, 目前有两个大坑, 目测几年之内都没法解决, 不过基本上不影响使用. LiveData这货通常应该在onCreate方法中被绑定. 因为关联操作只应该进行一次, 并且确保视图在活跃后可以立即加载最新的数据.这货通常只会在数据改变时发送通知, 且给发送给处于活跃状态的观察者. 比较特殊的情况是, 观察者被激活时, 也会收到通知. 而如果观察者第二次被激活, 则会先判断当前值与丫保存的值是否一致.以下是如何绑定LiveData的示例:public class NameActivity extends AppCompatActivity {    private NameViewModel mModel;    @Override protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        // Other code to setup the activity...        // Get the ViewModel.        mModel = ViewModelProviders.of(this).get(NameViewModel.class);        // Create the observer which updates the UI.        final ObservernameObserver = new Observer() {            @Override public void onChanged(@Nullable final String newName) {                // Update the UI, in this case, a TextView.                mNameTextView.setText(newName);            }        };        // Observe the LiveData, passing in this activity as the LifecycleOwner and the observer.        mModel.getCurrentName().observe(this, nameObserver);    }}observe(nameObserver) 方法被调用后,  onChanged() 被立即触发, 返回最新的name(LiveData中的 mCurrentName). 如果LiveData中本身没有数据, 就不会触发onChanged.LiveData数据更新这货本身是只读的, 如果有更新数据的需求, 请用MutableLiveData , 这厮有 setValue(T) 和 postValue(T) 方法用于写入数据. 标准架构是ViewModel 持有MutableLiveData 的引用负责读写, 而向观察者提供的是 只读的LiveData .架子搭起来之后, 就可以实现点击一个按钮, 触发数据更新, 更新所有关联的UI.mButton.setOnClickListener(new OnClickListener() {    @Override public void onClick(View v) {        String anotherName = "John Doe";        mModel.getCurrentName().setValue(anotherName);    }});点击事件调用setValue(T) 方法, 所有观察者的onChanged(John Doe) 方法被触发. setValue()[用于主线程调用]和 postValue()[用于其他线程调用]其他的应用场景还包括: 网络请求的响应, 数据库读取完成, 等等.RoomRoom 是个持久化库, 可以在查询后返回LiveData包装的数据, 查询操作本身是用DAO实现的.这货实现了在数据库异步查询和更新时同步更新LiveData 对象, 相关文档  Room persistent library guide.LiveData扩展这货在STARTED 和 RESUMED 状态下被视为是活跃的. 以下是扩展代码:public class StockLiveData extends LiveData{    private StockManager mStockManager;    private SimplePriceListener mListener = new SimplePriceListener() {        @Override public void onPriceChanged(BigDecimal price) { setValue(price); }    };    public StockLiveData(String symbol) { mStockManager = new StockManager(symbol); }    @Override protected void onActive() { mStockManager.requestPriceUpdates(mListener); }    @Override protected void onInactive() { mStockManager.removeUpdates(mListener); }} onActive() 方法在LiveData 有一个以上的观察者时被调用, 表明开始观察股票价格. onInactive() 方法在LiveData 所有观察者都傻逼了之后被调用, 表明可以和StockManager 说拜拜了. setValue(T) 方法在LiveData 数据更新时被调用, 通知观察者该干活了.StockLiveData 的使用方法如下:public class MyFragment extends Fragment {    @Override public void onActivityCreated(Bundle savedInstanceState) {        super.onActivityCreated(savedInstanceState);        LiveDatamyPriceListener = ...;        myPriceListener.observe(this, price -> { // Update the UI. });    }} observe() 中的 this, 也就是当前Fragment, 是个LifecycleOwner 实例, 也就是说, 这个对象的生命周期已经被纳入考察范围了.这意味着, 如果该对象不活跃了, 就不能接收通知了. 如果该对象被销毁了, 这个观察者也就被移除了.这进一步意味着, LiveData 可以被设计成一个单例, 被不同的视图一起使用. 比如:public class StockLiveData extends LiveData{    private static StockLiveData sInstance;    private StockManager mStockManager;    private SimplePriceListener mListener = new SimplePriceListener() {        @Override public void onPriceChanged(BigDecimal price) {            setValue(price);        }    };    @MainThread public static StockLiveData get(String symbol) {        if (sInstance == null) { sInstance = new StockLiveData(symbol); }        return sInstance;    }    private StockLiveData(String symbol) { mStockManager = new StockManager(symbol); }    @Override protected void onActive() { mStockManager.requestPriceUpdates(mListener); }    @Override protected void onInactive() { mStockManager.removeUpdates(mListener); }}使用方式如下:public class MyFragment extends Fragment {    @Override public void onActivityCreated(Bundle savedInstanceState) {        StockLiveData.get(getActivity()).observe(this, price -> { // Update the UI. });    }}这样, MyPriceListener 就可以被多个视图观察到, 且只有在至少一个观察者处于活跃状态时才处理事务.LiveData 的变形开发者可能存在以下的需求: 改变LiveData 中的值, 但不希望被发送给观察者. 或是希望返回一个基于其他LiveData 对象而有所调整的值.这就引出了 Lifecycle 包下的 Transformations 类.Transformations.map(source, func) map方法, 将数据源转换后传递给观察者.LiveDatauserLiveData = ...;LiveDatauserName = Transformations.map(userLiveData, user -> { user.name + " " + user.lastName });Transformations.switchMap(trigger, func)与map方法类似, 不同的是, 这货的func 返回的是一个LiveData 对象.private LiveDatagetUser(String id) { ...; }LiveDatauserId = ...;LiveDatauser = Transformations.switchMap(userId, id -> getUser(id) );变形后的数据可以略过生命周期检测, 在有观察者关注变形后的数据之前, 整个变形都不会被触发. 正是由于这货的延迟计算的特性, 与生命周期相关的行为如果需要在 ViewModel 对象中存储一个Lifecycle 对象, 这货将是一个合适的解决方案. 比如, 一个UI组件需要接收地址输入, 返回该地址的邮政编码.class MyViewModel extends ViewModel {    private final PostalCodeRepository repository;    public MyViewModel(PostalCodeRepository repository) { this.repository = repository; }    private LiveDatagetPostalCode(String address) {       // DON'T DO THIS       return repository.getPostCode(address);    }}如果这么写的话, 每次调用getPostalCode() 方法时, 都会重新创建一个LiveData 对象, 丫还需要注销之前的然后关注新创建的LiveData.如果UI组件被销毁重建了, 也会触发getPostalCode() 方法, 使之前的LiveData 被废弃.所以开发者应该转而使用变形作为解决方案, 如下.class MyViewModel extends ViewModel {    private final PostalCodeRepository repository;    private final MutableLiveDataaddressInput = new MutableLiveData();    public final LiveDatapostalCode = Transformations.switchMap(addressInput, (address) -> { return repository.getPostCode(address); });  public MyViewModel(PostalCodeRepository repository) { this.repository = repository }  private void setInput(String address) { addressInput.setValue(address); }}这里的postalCode就是public final的, 意味着丫可以直接读取且不做变化. 丫被定义成是addressInput的变种, 会随着repository.getPostCode()被调用而变化.丫只在有活跃的观察者时被触发, 否则在引入新的观察者之前, 不会进行计算.这个机制在底层上实现了延迟计算. 使得ViewModel对象可以直接实现对LiveData对象的引用并且变形.创建新的变形各种乱七八糟的变形方法并不是默认提供的. 要实现自定义变形方法, 你需要使用MediatorLiveData类, 监听其他的LiveData类, 并处理它们发出的事件. MediatorLiveData会将它的状态传递给数据源对象, 详细信息可以戳这里Transformations.合并多个数据源MediatorLiveData是LiveData的子类, 用于合并多个数据源. 这货的观察者们会在其中任意一个数据源变动时收到通知.比如UI里有一个MediatorLiveData对象, 丫有两个数据源, 一个本地数据库LiveData, 一个网络LiveData.那么, 只要任意一个数据源有变动, 都会触发UI更新. 具体例子戳这里: Addendum: exposing network status. ViewModel这货被设计为存储并管理UI相关的数据, 以及处理生命周期问题. 丫保证数据可以不受诸如屏幕旋转之类的配置信息变更的影响.Activity & Fragment 这些 UI Controller, 在处理生命周期问题时, 并不受开发者的控制. 所以如果操作系统决定要销毁或重启一个页面时, 存入其中的数据都会被清除. 少量数据可以通过onSaveInstanceState()方法存储并在 onCreate()中使用Bundle恢复, 但数据量上去之后就傻逼了.另一个问题是, 项目里经常需要进行异步调用, UI Controller需要管理这些异步调用, 并且在销毁时清理这些调用, 以防止可能的内存泄漏. 这特么就恶心了.而且在诸如页面重启动的时候, 你可能需要终止并重新开始一个调用, 而这通常是不必要的.UI Controller的设计本意是用来展示数据, 响应用户操作, 与操作系统交互(比如权限请求). 加载数据本身就不该是丫干的活, 要不你大可以一个Activity写完所有逻辑代码.Implement a ViewModel架构组件提供了帮助类ViewModel来为UI提供数据. 这货会在配置信息变更时自动保存数据, 并在页面重新可用时直接加载. 比如假设你需要展示用户列表:public class MyViewModel extends ViewModel {    private MutableLiveData> users;    public LiveData> getUsers() {        if (users == null) {            users = new MutableLiveData>();            loadUsers();        }        return users;    }    private void loadUsers() {        // Do an asyncronous 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 = ViewModelProviders.of(this).get(MyViewModel.class);        model.getUsers().observe(this, users -> {            // update UI        });    }}那么Activity重启时, 会收到之前的MyViewModel实例. 彻底销毁时, 会调用MyViewModel的onCleared()方法清理资源.* 注意, ViewModel万万不要持有View, Lifecycle, 或加了中间层的Context引用. ViewModel被设计为凌驾于生命周期持有者之上的存在, 这使得丫更容易测试. 丫可以持有生命周期观察者的实例, 比如LiveData. 但丫本身不应该关注生命周期变化. 如果ViewModel中的业务逻辑需要Context, 比如调用系统服务, 你可以继承AndroidViewModel类, 在构造方法中传入Application.ViewModel 的生命周期这货在创建时, 需要指定Lifecycle对象并传入ViewModelProvider. 在这个玩意进坟之后, 丫就躺着等死了. 这个Lifecycle如果是个Activity, 就死在finish(), 如果是个Fragment, 就死在detach().下图讲的是一个Activity在手机旋转和自杀时的生命周期变化情况, 以及相关联的ViewModel的存活状态. 换成Fragment也是这个套路.ViewModel通常会在系统第一次调用onCreate()时被获取. 之后会一直存在, 直到finish()Fragments 之间的数据共享一个Activity钟的多个Fragment之间互相交互是个很常规的使用情境. 比如你有一个Fragment提供选择列表, 另一个Fragment展示选中项的详细内容. 你不仅要处理各自的业务逻辑, 还需要在Activity中将两者进行绑定, 还有另一个Fragment未初始化之类的容错处理.ViewModel可以拯救你, 只需要让他俩共享一个ViewModel, 就妥了.public class SharedViewModel extends ViewModel {    private final MutableLiveDataselected = new MutableLiveData();     public void select(Item item) { selected.setValue(item); }     public LiveDatagetSelected() { return selected; }}public class MasterFragment extends Fragment {    private SharedViewModel model;    public void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);        itemSelector.setOnClickListener(item -> {            model.select(item);        });    }}public class DetailFragment extends Fragment {    public void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);        model.getSelected().observe(this, { item ->           // Update the UI.        });    }}这俩货在调用ViewModelProvider时, 都是通过getActivity()来获取实例的, 这使得他们获取的是同一个, 作用于当前Activity的SharedViewModel实例. 这么做的好处如下:Activity中不需要处理任何事情.Fragment之间不需要直接交互. 不受另一个Fragment的生命周期影响, 互相取代也不会造成任何问题.ViewModel 取代 Loaders加载类, 比如CursorLoader, 常用于保持UI与数据库的数据一致性. 丫现在已经可以退休了. ViewModel可以将UI从数据加载操作中分离出来, 减少类之间的强引用.以前, 一个常见的使用情境是, 使用CursorLoader来观察数据库. 数据库内容变更时, Loader自动触发重新加载方法, 更新UI, 如下: ViewModel使用Room和LiveData来达成同样的效果. ViewModel保证数据不受页面生命周期影响, Room用于在数据库变动时通知LiveData, 而LiveData负责更新UI.这里有一篇博客, 讲述了如何用这套逻辑替换AsyncTaskLoader. 要看的话戳这里: Blog.重复一遍, ViewModel是用来保护数据免受生命周期影响的. Saving UI StatesUI状态的保存和恢复, 是一个相当影响用户体验的活儿. 旋转设备, 重启App, 后台被杀时, 你都应该将应用恢复到用户期望的页面状态.对于简单的, 轻量化的数据, 通常会使用onSaveInstanceState()来保存.对于复杂的数据, 你可以使用ViewModel, onSaveInstanceState(), 与本地持久化相结合的方式来保存.本篇的主题就是这三者.简单难度: onSaveInstanceState()onSaveInstanceState()方法的设计初衷就是保存少量的数据, 用于重新加载页面. 丫有两个处理场景:App处于后台时, 由于内存原因而被终结. 配置信息变更, 比如屏幕旋转.两种情形下, Activity都处于Stopped状态, 而非Finished. 比如, 用户几个小时没开你的应用, 后台已经杀死了你的进程, 系统会调用onSaveInstanceState()的默认实现, 前提是你的UI Controller拥有一个ID. 然后用户打开你的应用, 系统会恢复之前保存的状态.* 注意, 如果用户主动杀死你的应用, 或者Activity的finish()方法被调用, 丫就是废的.系统会自动保存很多UI数据, 比如EditText中已输入的文本, 或是ListView滚动条的位置. 你也可以重写该方法以手动保存数据. 请不要用这货来保存大容量的数据, 比如Bitmap, 或是能让系统原地读秒的序列化数据结构. 因为序列化会消耗大量的系统内存, 而这个操作是在主线程干的, 所以会卡到用户怀疑人生.管理复杂状态: divide and conquerDivide, 如果数据结构复杂, 你可以试着将保存工作拆分, 然后使用不同的保存机制实现. 用户通常会在两种情况下离开一个Activity, 并期待对应的结果:用户完全关闭这个Activity. 比如, 最近打开项目中滑掉, 项目中返回上一级. 这种情况下, 用户期望再次打开时, 该Activity处于初始状态.用户旋转了屏幕或是从后台进程中返回. 比如, 用户在用搜索功能, 然后按下了Home键或是接听了一个电话. 然后用户返回搜索页面, 并期望之前的搜索关键字和结果列表仍然健在.要实现上述情况下的数据保存, 请同时使用本地持久化, ViewModel类, 以及onSaveInstanceState()方法. 他们仨用于存储不同复杂度的数据.本地持久化: 在打开或关闭Activity时, 存储不应该被丢弃的数据. 比如, 歌曲对象的列表, 包括音频文件和元数据.ViewModel类: 在内存中存储用于展示UI的数据. 比如, 最近搜索的歌曲对象列表, 以及最近的搜索语句(Search Query, 关键字).onSaveInstanceState()方法: 存储少量数据, 用于在系统重新创建Activity时, 简单初始化UI.对于复杂的对象, 请存入本地数据库, 然后用本方法存储对应的数据ID. 比如, 存储最近的搜索语句.比如一个用于搜索歌曲的Activity. 处理方式大致如下:用户添加歌曲时, ViewModel通过代理实现本地数据持久化. 如果这首新增的歌曲需要在UI中展示出来, 你还需要更新ViewModel对象. 切记不要在主线程进行数据库操作.用户搜索歌曲时, 从数据库读取的歌曲的复杂数据需要直接存入ViewModel对象. 搜索语句也需要存入ViewModel对象.Activity被切入后台时, 系统调用onSaveInstanceState()方法. 你应该在此存入搜索语句. 这货够轻量, 也足够载入所有需要的数据.恢复复杂状态: 组装数据片段用户返回Activity时, 有两种重新创建的常见情境:Activity已被系统关闭. onSaveInstanceState()方法中保存了搜索语句, 将语句传入ViewModel对象. ViewModel发现木有查询结果的缓存, 然后呼唤代理开始干活.配置信息变更, ViewModel对象发现有缓存, 直接更新UI.* 注意, Activity第一次初始化时, 木有搜索语句, 在创建ViewModel的时候, 传入空语句来告诉丫保持无数据状态.基于你自己的Activity实现, 你可能根本不需要onSaveInstanceState()方法. 比如, 浏览器会在退出前完成跳转, 所以丫更应该直接进行本地持久化. 之前的例子中, 意味着存入Shared Preferences.如果是从Intent打开一个Activity, extras bundle会自觉实现本地持久化, 不受那两种情况影响. 如果查询语句是这么来的, 你也不需要onSaveInstanceState()方法了.但ViewModel这货还是必须的, 丫可以省去不必要的数据重加载. Room Persistence Library这货就是一个管理SQLite的抽象层, 丫会在你的App运行时, 创建相关数据的缓存. 这个缓存, 就是之前提到的标准数据源, 使UI层在获取数据时无需关注是否有网络连接. 查看更多: Room. Paging Library分页嘛, 谁用谁知道.Overview很多应用都需要分批次展示大量的数据, 如果玩不明白, 会影响程序的性能表现, 还可能浪费流量.现有的用于分页的Android API, 都是垃圾.CursorAdapter可以将映射数据库查询结果映射到ListView的items, Cursor本身就慢成狗, 这货还脑残的只知道跑UI线程. 全套槽点在此:  Large Database Queries on Android.AsyncListUtil可以在将基于位置(position-based)的数据分页后扔进RecyclerView, 但丫也只能基于位置, 并且在可计数(Countable)数据集中, 强制使用null作为占位符.我们这个产品就解决了这些问题. 我们用了几个专门的类来简化数据请求的过程. 并且与Room无缝衔接.Classes这货有下面这些类, 附加功能请参考支持库的Api.DataSource.用于定义要获取分页数据的数据源. 你可以基于业务逻辑选择丫的子类.PageKeyedDataSource用于获取下一页(Page). 比如你在浏览社交媒体, 你可能会在网络请求时, 基于当前页, 发送请求下一页的Token.ItemKeyedDataSource用于获取下一项(Item). 比如评论类的App, 大众点评之流, 你可能会基于当前评论的ID, 获取下一条评论.PositionalDataSource用于基于任意位置获取分页数据. 比如, 从ID-1200开始, 拿20条数据给我.如果你是配套使用Room的话, 丫会自动生成 DataSource.Factory来作为PositionalDataSources使用. 比如: @Query("select * from users WHERE age > :age order by name DESC, id ASC")DataSource.FactoryusersOlderThan(int age);PagedList.这货负责从上面那货(DataSource)中读取数据. 你可以设置一次读取多少数据, 预读多少数据, 以尽量减少用户的等待时间. 这个类会向其他类发送数据更新的信号, 比如RecyclerView.Adapter, 以更新其内容.PagedListAdapter.这货是RecyclerView.Adapter接口的一个实现, 为 PagedList提供数据. 比如, 一个新分页被加载时, 这货通知RecyclerView新数据来了, 然后RecyclerView更新items, 并触发相关动画.PagedListAdapter会启用一个后台线程来计算PagedList变更时的更新计数, 比如, 数据库更新时生成的新数据页, 并调用对应的notifyItem…()方法以更新列表内容, 然后RecyclerView做出相应的变动.比如, 一个item在 PagedList变更时改变了位置, RecyclerView会触发item移动的动画.LivePagedListBuilder.这货会用你提供的DataSource.Factory生成一个LiveData对象. 并且, 如果你是配套使用Room的话, DAO会自动生成DataSource.Factory来作为PositionalDataSource使用, 比如:@Query("SELECT * from users order WHERE age > :age order by name DESC, id ASC")public abstract LivePagedListProviderusersOlderThan(int age);这个整形的参数告诉用户使用PositionalDataSource来进行基于位置的数据加载. (目测丫说的是那个泛型…)这些类加在一起, 组成了整个数据流, 后台线程提供数据, 主线程更新UI. 比如, 向数据库插入一个新数据时, DataSource被冻结, LiveData会在后台线程中生产出一个新的PagedList.Figure: 大部分的工作都在后台线程完成, 不会阻塞UI线程.* 使用流程看这里新生成的PagedList会被发送给主线程的PagedListAdapter. PagedListAdapter会随后使用后台的DiffUtil来计算需要变更的item的计数. 比对完成时, PagedListAdapter会利用得到的信息调用 RecyclerView.Adapter.notifyItemInserted()方法, 标记当前操作是插入一个新的item.UI线程的RecyclerView就知道丫只需要绑定一个新的item就完了, 然后开始播放插入动画.Database Sample以下是完整示例代码. 展示了用户添加, 移除, 以及变更数据库数据时, RecyclerView内容会自动更新.@Daointerface UserDao {    @Query("SELECT * FROM user ORDER BY lastName ASC")    public abstract DataSource.FactoryusersByLastName();}class MyViewModel extends ViewModel {    public final LiveData> usersList;    public MyViewModel(UserDao userDao) {        usersList = new LivePagedListBuilder<>(                userDao.usersByLastName(), /* page size */ 20).build();    }}class MyActivity extends AppCompatActivity {    @Override    public void onCreate(Bundle savedState) {        super.onCreate(savedState);        MyViewModel viewModel = ViewModelProviders.of(this).get(MyViewModel.class);        RecyclerView recyclerView = findViewById(R.id.user_list);        UserAdapteradapter = new UserAdapter();        viewModel.usersList.observe(this, pagedList -> adapter.setList(pagedList));        recyclerView.setAdapter(adapter);    }}class UserAdapter extends PagedListAdapter{    public UserAdapter() {        super(DIFF_CALLBACK);    }    @Override    public void onBindViewHolder(UserViewHolder holder, int position) {        User user = getItem(position);        if (user != null) {            holder.bindTo(user);        } else {            // Null defines a placeholder item - PagedListAdapter will automatically invalidate            // this row when the actual object is loaded from the database            holder.clear();        }    }    public static final DiffCallbackDIFF_CALLBACK = new DiffCallback() {        @Override        public boolean areItemsTheSame(@NonNull User oldUser, @NonNull User newUser) {            // User properties may have changed if reloaded from the DB, but ID is fixed            return oldUser.getId() == newUser.getId();        }        @Override        public boolean areContentsTheSame(@NonNull User oldUser, @NonNull User newUser) {            // NOTE: if you use equals, your object must properly override Object#equals()            // Incorrectly returning false here will result in too many animations.            return oldUser.equals(newUser);        }    }}Loading Data数据来源通常有两种情况: 网络与数据库, 其一, 或是合在一起. 两种情况下的, 用Retrofit来实现处理下拉刷新, 网络异常, 网络重试的示例, 统一戳这里: PagingWithNetworkSample.如果是单一数据源, 网络 or 数据库, 可以使用LiveData给UI喂数据, 做法是: 指定数据源, 向LivePagedListBuilder传入DataSource.Factory.

Figure 2. Single source of data provides DataSource.Factory to load content.

数据库在被观察时, 若有数据变动发生, 会'push'一个新的PagedList出来.

假设是当前数据源是网络, 后台没有发出更新通知, 诸如下拉刷新之类的信号会冻结当前数据源, 并'pull'一个新的PagedList出来. 这会异步更新所有数据.

如果是多数据源, 网络 and 数据库, 你可以使用本地存储的分页数据, 然后本地存储默默的从网络获取新的分页数据.

这可以减轻网络负载, 并在渣网速时提供更好的用户体验, 毕竟在数据库后台和用户中间加了一层缓冲.

Figure 3. Database is cache of network data - UI loads data from Database, and sends signals when out of data to load from network to database.

看图说话, 数据边界的回调方法(BoundaryCallback), 用来触发网络请求, 并将响应的数据直接存入数据库. 然后UI会关注数据库, 在更新时通知所有的UI观察者.

Room Persistence Library

需要帮助的话, 请到Stack Overflow, 并使用对应的问题标签:

android-arch, 基础库相关的问题

android-room, 数据库相关的问题

android-arch-lifecycle, 生命周期库相关的问题

也欢迎到 G+ community来提问题.

要报告Bug的话, 先戳这里读一下: https://developer.android.com/topic/libraries/architecture/feedback.html#issues.

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

推荐阅读更多精彩内容