相信大家最近应该看过一篇比较火的文章:Google 官方应用架构的最佳实践指南, 本文就是按照官方应用架构写的一个实例Drible, 也是基于我之前写的Dribbble的demo,打算重新写一个Dribbble App。
下面就再总(粘)结(贴)一下新的架构的亮点,以及我个人在使用过程中的体验。
新推出的框架主要是围绕以下两条准则,开发体验更棒的App:
第一条准则:「不要在应用程序组件中保存任何应用数据或状态,并且组件间也不应该相互依赖」。
第二条准则:「通过 model 驱动应用 UI,并尽可能的持久化」。
为了更好的遵循这两条准则, 官方推出的两个组件: Lifecycle 和 Room
Lifecycle
这里官方给我们提供了几种新的结构,一起来看一下:
- LiveData : 是一个可被观察的数据持有者,应用了观察者设计模式, 与RxJava很类似, 但LiveData的优势是可以和生命周期进行绑定,他有两个观察函数,如果把生命周期对象LifecycleOwner作为参数,当生命周期结束的时候会自动removeObserve, 防止内存泄漏。下面是两段项目中的代码:
resultData.observe(lifecycleOwner, new Observer>() {
@Override
public void onChanged(@Nullable Resource result) {
if (result.status == Resource.SUCCESS) {
//手动移除Observer
resultData.removeObserver(this);
requestSuccess(result.data);
} else if (result.status == Resource.ERROR) {
resultData.removeObserver(this);
requestFailed(result.message);
}
}
});
dbLiveData.observeForever(new Observer() {
@Override
public void onChanged(@Nullable Shot[] shots) {
if (shots != null && shots.length > 0) {
Log.d("shotRepo", "db shots changed not null");
dbLiveData.removeObserver(this);
shotsLiveData.setValue(Resource.success(shots));
} else {
Log.d("shotRepo", "db shots changed null");
}
}
});
- ViewModel: 这个对象说起来比较复杂,但使用起来非常简单, 以下内容主要来自官网:
Google设计ViewModel主要是用来存储和管理UI相关的数据, 那他是如何管理数据的呢? 简单的说,就是数据不该被回收的时候,不会被回收; 应该被回收的时候, 数据就会自动被清理掉。
什么是不该被回收的情况呢? 官方做了一些说明, 例如屏幕旋转, activity被回收重新创建的情况, 虽然系统提供了onSaveInstanceState()可以保存数据, 但是我们肯定碰到过在saveInstanceState()保存过多数据, 结果抛出异常TransactionTooLargeException
, 因为官方建议在saveInstanceState中只保存一些状态值,数据不能超过系统约定的值(API25是1Mb)。那这样的情况下,再回到这个页面的时候就只能再次请求网络获取数据了, 用户就会看到Loading界面,体验不佳。
当然ViewModel自然也不会一直持有数据对象, 当绑定的界面activity被销毁的时候, ViewModel就会调用onCleared清除数据。
总结一下:ViewModel是一种简单而高效的方法,实现了数据层和界面层之间的分离,保证数据层不受界面层生命周期的影响。
ps: ViewModel还可以实现不同的Fragment共享同一个ViewModel数据对象(这方面还没实践,不做深入展开)
附一张官方的ViewModel生命周期图:
Room
官方描述是“a SQLite object mapping library”, 是一个数据库和java中的对象映射的组件,我们来看下面这段代码就明白了:
@Entity(tableName = "shots")
public class Shot implements Parcelable {
@PrimaryKey
private int id;
private String title;
private String description;
private int width;
private int height;
private int page;
}
以上只是在原来bean对象的基础上加了几个注解, 我们就可以生成对应的数据库的一张表,里面的字段就对应表中的字段,同时也通过注解的方式定义主键,外键等。
第二步, 我们需要定义一些数据库操作的方法, 直接定义一个抽象类注解方法的sql语句即可:
@Dao
public interface ShotDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
void insertAll(Shot[] shots);
@Query("select * from shots where shots.page = :pageId order by shots.ind asc")
LiveData loadShots(int pageId);
@Query("select * from shots where shots.page = :pageId order by shots.ind asc")
Shot[] loadShotSync(int pageId);
@Query("delete from shots where shots.page = :pageId")
void deleteShots(int pageId);
}
最后,声明数据库的名称,以及数据库包含的表, 并且我们在程序启动的时候,需要调用方法创建一个数据库:
@Database(entities = {Shot.class}, version = 1)
@TypeConverters(DribleConverter.class)
public abstract class DribleDatabase extends RoomDatabase {
static final String DATABASE_NAME = "drible_db";
public abstract ShotDao shotDao();
}
//
DribleDatabase db = Room.databaseBuilder(context.getApplicationContext(),
DribleDatabase.class, DATABASE_NAME).build();
我们项目中肯定也会用到缓存,但是我之前接触的缓存总存在一些问题, 例如网络缓存,只能针对一些数据不怎么变化的内容,而那些用户操作后的数据,再使用网络缓存,则会造成错误,例如收藏了一个商品,那么再展现商品的时候,收藏图标应该被点亮。
而Room组件这样把所有的数据字段都映射到数据库中,如果用户收藏了商品,那么直接更新数据库的这条内容的这个字段值, 修改数据库即可。
另外Room数据库返回的数据还支持LiveData
最后,说说我的实践项目, 地址如下:
https://github.com/binqiangsun/Drible
Dribbble 是一个面向创作家、艺术工作者、设计师等创意类作品的人群,提供作品在线服务,供网友在线查看已经完成的作品或者正在创作的作品的交流网站。
这个项目就是根据官方提供的Api接口实现的APP
- 目前已经按照应用架构实现了首页流的展示,但是由于首页流是分页的, 所以还是存在一点问题, 我在github中都有记录;
- 项目中有一个service module, 这个是我专门分层出来的封装一些基础功能的module, 例如Retrofit的封装, recycler adapter的基类等;
-
我会持续按照架构的思想完成整个App