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