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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章