Android 架构组件 1.0

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层来处理。

架构原则

常见的错误是将所有的代码写入一个ActivityFragment。任何不处理 UI 或 与操作系统交互的代码都不应该出现在这些类中,你应该尽可能保持ActivityFragment精简,这样可以避免许多生命周期相关的问题。
请记住,你不拥有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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值