Android 架构组件 1.0
//ViewModel和LiveData
implementation 'android.arch.lifecycle:extensions:1.1.1'
//Room
implementation "android.arch.persistence.room:runtime:1.0.0"
annotationProcessor "android.arch.persistence.room:compiler:1.0.0"
概述
使用背景
在Android中,应用程序跳转行为是很常见的,所以应用程序必须正确处理这些流程。请记住,移动设备是资源受限的,所以在任何时候,操作系统都可能需要杀死一些应用程序,以腾出空间给新的应用。
这就要求:app组件
可以单独和无序地启动,并且可以在任何时候由用户或系统销毁。由于app组件
是短暂的,并且它们的生命周期(创建和销毁时)不在您的控制之下,因此不应该在app组件中存储任何app数据或状态,并且你的app组件不应相互依赖。
所以,优秀的APP应该由model层驱动UI。任何不处理UI或与操作系统交互的代码不应该在Activity或Fragment中。此外,数据应该由与UI隔离和与生命周期隔离的model层来处理。
架构原则
常见的错误是将所有的代码写入一个Activity
或Fragment
。任何不处理 UI 或 与操作系统交互的代码都不应该出现在这些类中,你应该尽可能保持Activity
或Fragment
精简,这样可以避免许多生命周期相关的问题。
请记住,你不拥有Activity或Fragment这些类,它们只是建立操作系统和你的应用程序之间契约的胶水类。Android操作系统可能会随时根据用户交互或其他因素(如低内存)来销毁它们。最好尽可能地减少依赖他们,以提供可靠的用户体验。
第二个重要原则是 你应该从一个模型驱动你的UI,最好是一个*持久化的模型。
之所以说持久化是理想的模型,原因有两个:
1. 如果操作系统销毁你的应用程序以释放资源,那么你的用户就不会丢失数据,即使网络连接不稳定或连接不上,您的应用程序也会继续工作。
2. 模型是负责处理应用程序数据的组件。它们独立于应用程序的 Views 和 app组件,因此模型与这些 app组件的生命周期问题是相隔离的。保持简洁的UI代码,以及不受约束的应用程序逻辑,可以使app的管理更加容易,基于具有明确定义的管理数据责任的模型类的应用程序,会更加具有可测试性,并使您的应用程序状态保持前后一致。
架构图
架构组件
LiveData
liveData是数据持有者,包含了被观察的值。
当 LiveData 所持有的数据改变时,它会通知相应的界面代码进行更新。
另外,LiveData会根据观察者的Lifecycle做出变更,只有观察者状态为STARTED or RESUMED,LiveData才会收到更新。
LiveData<Product> liveData = new MutableLiveData<>();
liveData.observe(this, new Observer<Product>() {
@Override
public void onChanged(@Nullable Product product) {
//刷新UI
}
});
//更新数据 变更LiveData的Product数据,相应的UI就会发生变更
//1.只有MutableLiveData才能这样调用
//2.只有设置了observe, postValue()、setValue()、getValue()才会生效。
liveData.postValue(product);
ViewModel
ViewModel 将视图的数据和逻辑从具有生命周期特性的实体(如 Activity 和 Fragment)中剥离开来。直到关联的 Activity 或 Fragment 完全销毁时,ViewModel 才会随之消失,也就是说,即使在旋转屏幕导致 Fragment 被重新创建等事件中,视图数据依旧会被保留。ViewModels 不仅消除了常见的生命周期问题,而且可以帮助构建更为模块化、更方便测试的用户界面。
所以ViewModel常用来保存LiveData,以防止多次创建Repository来获取LiveData。
public class ProductViewModel extends ViewModel {
private LiveData<Product> liveData;
//Repository用于获取LiveData,具体如何获取暂时不需要关心
private ProductRepository repository;
public void init(int id) {
//屏幕旋转时,LiveData不为空
//因为ViewModel绑定了aty,直到其销毁
if (liveData != null) {
return;
}
if (repository == null) {
repository = new ProductRepository();
}
liveData = repository.getProductLiveData(id);
}
public LiveData<Product> getProduct() {
return liveData;
}
}
Activity:
//屏幕旋转时,拿到的依然是之前的ViewModel
product = ViewModelProviders.of(this).get(ProductViewModel.class);
//由于model层没必要反复初始化,所以利用ViewModel跳过初始化优化了代码!
product.init(0);
//旋转屏幕后,重新设置它的观察事件
product.getProduct().observe(this, new Observer<Product>() {
@Override
public void onChanged(@Nullable Product product) {
if (product != null) {
tvTitle.setText(product.getTitle());
tvCount.setText(product.getCount() + "");
tvPrice.setText(product.getPrice() + "");
}
}
});
Room
Android从一开始就支持SQLite数据库,但是缺点很多:
1. 为了让SQLite工作,开发者一般需要编写大量样板代码。
2. SQLite没有保存POJO(纯Java对象)
3. 并且在编译时没有检查查询。
Room组件就是用来解决这些问题的!它是一个SQLite映射库,能够持久化Java POJO,直接将查询转换为对象,在编译时检查错误,并从查询结果生成LiveData可观察者。即,变更数据库,会直接作用于UI。
Bean类
@Entity //生成表格
public class Product {
@PrimaryKey //设定主键
private int id;
private String title;
private int price;
private int count;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}
DataBase
@Database(entities = {Product.class}, version = 1)
public abstract class ProductDataBase extends RoomDatabase {
public abstract ProductDao productDao();
}
Dao 用于增删改查
@Dao
public interface ProductDao {
@Insert(onConflict = REPLACE)
void save(Product product);
//这些SQL语句在code时就会校验
@Query("SELECT * FROM product WHERE id = :productId")
LiveData<Product> query(int productId);
}
如何使用?
//获取dataBase
productDataBase = Room.databaseBuilder(context.getApplicationContext(), ProductDataBase.class, "product.db").build();
//增 示例
productDataBase.productDao().save(new Product());
//查 示例
productDataBase.productDao().query(0);
Repository
ViewModel 的一个简单实现是直接调用 Webservice 来获取数据并将其 赋值给 product 对象,虽然这样是可行的,但是你的应用程序以后将很难维护。它赋予了 ViewModel 类太多的职责,违背了关注点分离原则。此外,ViewModel 的作用域与一个 Activity 或一个 Fragment 生命周期相关联,当他们的生命周期完成时将丢失所有的数据,这是非常糟糕的用户体验。因此,我们将 ViewModel 的这个工作委托给了一个新的模块 Repository 。
Repository用于承接ViewModel与数据库和网络的关系。它会从网络获取product,之后缓存并返回LiveData对象。
public class ProductRepository {
private ProductDao productDao;
public ProductRepository() {
productDao = ProductDataBaseHelper._instance(MyApplication.getApplication()).productDao();
}
public LiveData<Product> getProductLiveData(final int productId) {
//room与LiveData绑定 room修改数据库数据 会直接作用在LiveData绑定的UI上
LiveData<Product> productLiveData = productDao.query(productId);
//模拟网络操作 获取、更新数据 (用户第一眼会有缓存的数据 且这里可以根据实际情况来确定是否更新 节省流量)
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Product product = new Product();
product.setId(productId);
product.setTitle("spark");
product.setPrice(4999);
product.setCount(1);
//直接更新数据库 liveData会根据数据库变化刷新UI
productDao.save(product);
}
}).start();
return productLiveData;
}
}
重览架构图
1.首先 aty只和ViewModel进行交互,不关心数据的获取与更新。当model层数据变更时,会根据生命周期安全的驱动aty进行UI刷新。
aty代码如下:
public class MainActivity extends AppCompatActivity {
private ProductViewModel product;
private TextView tvTitle, tvPrice, tvCount;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
private void initView() {
//根据Bean类设置其相关View
product = ViewModelProviders.of(this).get(ProductViewModel.class);
product.init(0);
//只有设置了observer getValue才能生效
product.getProduct().observe(this, new Observer<Product>() {
@Override
public void onChanged(@Nullable Product product) {
if (product != null) {
tvTitle.setText(product.getTitle());
tvCount.setText(product.getCount() + "");
tvPrice.setText(product.getPrice() + "");
}
}
});
}
@Override
public void onContentChanged() {
super.onContentChanged();
tvTitle = findViewById(R.id.tvTitle);
tvPrice = findViewById(R.id.tvPrice);
tvCount = findViewById(R.id.tvCount);
}
}
2.ViewModel与Repository层进行交互,用户获取对应的LiveData。因为单一职责原则,ViewModel保证了即使aty旋转,生命周期重新执行,LiveData也不会反复初始化,优化了结构,同时也避免了数据反复加载。
public class ProductViewModel extends ViewModel {
private LiveData<Product> liveData;
private ProductRepository repository;
public void init(int id) {
if (liveData != null) {
return;
}
if (repository == null) {
repository = new ProductRepository();
}
liveData = repository.getProductLiveData(id);
}
public LiveData<Product> getProduct() {
return liveData;
}
}
3.最后,repository用于返回LiveData。它会先检索数据库的bean,并以LiveData的形式返回。这样做的好处是,进入aty时会优先展示缓存的数据,不至于空白,体验较好。同时退出App或意外情况关闭App,数据也是安全保存的。之后的网络请求,我们只需要修改数据库,对应的UI就会刷新,真正实现了model驱动view。
以下是repository代码,不涉及Room相关
public class ProductRepository {
private ProductDao productDao;
public ProductRepository() {
productDao = ProductDataBaseHelper._instance(MyApplication.getApplication()).productDao();
}
public LiveData<Product> getProductLiveData(final int productId) {
//room与LiveData绑定 room修改数据库数据 会直接作用在LiveData绑定的UI上
LiveData<Product> productLiveData = productDao.query(productId);
//模拟网络操作 获取、更新数据 (用户第一眼会有缓存的数据 且这里可以根据实际情况来确定是否更新 节省流量)
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Product product = new Product();
product.setId(productId);
product.setTitle("spark");
product.setPrice(4999);
product.setCount(1);
productDao.save(product);
}
}).start();
return productLiveData;
}
}
扩展
刚才是查询,如果想扩展商品数量+1的操作:
对于Activity:
应该由ViewModel去处理addOne的逻辑,因为它的作用就是把数据
和逻辑
从aty割离出来。
public void onBtnClick(View view) {
//可以看到这里直接ViewModel+1 看似只是UI的操作 屏蔽掉了数据与逻辑
product.addOne();
}
对于ViewModel:
它持有数据,负责处理逻辑。
public void addOne() {
//处理逻辑
Product product = liveData.getValue();
if (product != null && product.getCount() < MAX) {
product.setCount(product.getCount() + 1);
//满足条件 通知服务器保存数据
repository.postSave(product);
}
}
对于Repository:
它负责与model(room和sqlite)和数据源(网络等)进行交互。
//通知服务器保存product数据
public void postSave(final Product product) {
if (product == null) {
return;
}
//模拟网络操作
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//if success 注意 保存数据只能在子线程!
//save success 页面刷新数据
productDao.save(product);
}
}).start();
}
最后
回顾一下Activity的代码,可以发现,Activity只处理View相关,以及它的click事件。而且对于click事件,Activity从表象看也只是UI层面的操作,完全屏蔽了数据和逻辑。当屏幕旋转或activity被杀重启时,我们的界面也不会受到任何影响。
public class MainActivity extends AppCompatActivity {
private ProductViewModel product;
private TextView tvTitle, tvPrice, tvCount;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
private void initView() {
//根据Bean类设置其相关View
product = ViewModelProviders.of(this).get(ProductViewModel.class);
product.init(0);
//只有设置了observer getValue才能生效
product.getProduct().observe(this, new Observer<Product>() {
@Override
public void onChanged(@Nullable Product product) {
if (product != null) {
tvTitle.setText(product.getTitle());
tvCount.setText(product.getCount() + "");
tvPrice.setText(product.getPrice() + "");
}
}
});
}
@Override
public void onContentChanged() {
super.onContentChanged();
tvTitle = findViewById(R.id.tvTitle);
tvPrice = findViewById(R.id.tvPrice);
tvCount = findViewById(R.id.tvCount);
}
public void onBtnClick(View view) {
product.addOne();
}
}
参考资料
http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2017/1107/8715.html
https://juejin.im/entry/5a09199ff265da430e4ea6ab
https://code.tutsplus.com/zh-hans/tutorials/introduction-to-android-architecture–cms-28749