Android Architecture Components 整理
Android Architecture Components是谷歌在Google I/O 2017發布一套幫助開發者解決Android架構設計的方案。
里面包含了兩大塊內容:
A new collection of libraries that help you design robust, testable, and maintainable apps.
即這是一個幫助構建穩定,易于測試和易于維護的App架構的庫。
?
ViewModel
ViewModel是為特定的UI(例如Activity/Fragment)提供數據的類,同時它也承擔和數據相關的業務邏輯處理功能:比如根據uid請求網絡獲取完整的用戶信息,或者將用戶修改的頭像上傳到服務器。因為ViewModel是獨立于View(例如Activity/Fragment)這一層的,所以并不會被View層的事件影響,比如Activity被回收或者屏幕旋轉等并不會造成ViewModel的改變。
?
?
LiveData
LiveData是一個包含可以被觀察的數據載體。這么說又有點不好理解了,其實他就是基于觀察者模式去做的。當LiveData的數據發生變化時,所有對這個LiveData變化感興趣的類都會收到變化的更新。并且他們之間并沒有類似于接口回調這樣的明確的依賴關系。LiveData還能夠感知組件(例如activities, fragments, services)的生命周期,防止內存泄漏。其實這和RxJava非常相似,但是LiveData能夠在組件生命周期結束后自動阻斷數據流的傳播,防止產生空指針等意外。這個RxJava是不同的。
?
?
獲取數據
用Retrofit的WebService獲取數據
public interface Webservice {/*** @GET declares an HTTP GET request* @Path("user") annotation on the userId parameter marks it as a* replacement for the {user} placeholder in the @GET path*/@GET("/users/{user}")Call<User> getUser(@Path("user") String userId); }Repository
Repository類的作用就是獲取并提供各種來源的數據(數據庫中的數據,網絡數據,緩存數據等),并且在來源數據更新時通知數據的獲取方。
public class UserRepository {private Webservice webservice;// ...public LiveData<User> getUser(int userId) {// This is not an optimal implementation, we'll fix it below(這不是最好的實現,以后會修復的)final MutableLiveData<User> data = new MutableLiveData<>();webservice.getUser(userId).enqueue(new Callback<User>() {@Overridepublic void onResponse(Call<User> call, Response<User> response) {// error case is left out for brevity(為了簡單起見,簡化了錯誤處理的情況)data.setValue(response.body());}});return data;} }連接ViewModel和Repository
?
public class UserProfileViewModel extends ViewModel {private LiveData<User> user;private UserRepository userRepo;@Inject // UserRepository parameter is provided by Dagger 2public UserProfileViewModel(UserRepository userRepo) {this.userRepo = userRepo;}public void init(String userId) {if (this.user != null) {// ViewModel is created per Fragment so// we know the userId won't changereturn;}user = userRepo.getUser(userId);}public LiveData<User> getUser() {return this.user;} }?
緩存數據
@Singleton // informs Dagger that this class should be constructed once public class UserRepository {private Webservice webservice;// simple in memory cache, details omitted for brevity(簡單的內存緩存)private UserCache userCache;public LiveData<User> getUser(String userId) {LiveData<User> cached = userCache.get(userId);if (cached != null) {return cached;}final MutableLiveData<User> data = new MutableLiveData<>();userCache.put(userId, data);// this is still suboptimal but better than before.// a complete implementation must also handle the error cases.webservice.getUser(userId).enqueue(new Callback<User>() {@Overridepublic void onResponse(Call<User> call, Response<User> response) {data.setValue(response.body());}});return data;} }持久化數據
Room
Room是一個對象映射庫,(姑且按GreenDAO的功能來理解吧)可以在在編譯的時候就能檢測出SQL語句的異常。還能夠在數據庫內容發生改變時通過LiveData的形式發出通知
?
? 1. 定義User類,并且使用@Entity注解:? ?
??
@Entity class User {@PrimaryKeyprivate int id;private String name;private String lastName;// getters and setters for fields }? 2.創建RoomDatabase:
?
@Database(entities = {User.class}, version = 1) public abstract class MyDatabase extends RoomDatabase { }3.創建DAO
@Dao public interface UserDao {@Insert(onConflict = REPLACE)void save(User user);@Query("SELECT * FROM user WHERE id = :userId")LiveData<User> load(String userId); }4.在RoomDatabase中訪問DAO
@Database(entities = {User.class}, version = 1) public abstract class MyDatabase extends RoomDatabase {public abstract UserDao userDao(); }?
把Room和UserRepository結合起來:
@Singleton public class UserRepository {private final Webservice webservice;private final UserDao userDao;private final Executor executor;@Injectpublic UserRepository(Webservice webservice, UserDao userDao, Executor executor) {this.webservice = webservice;this.userDao = userDao;this.executor = executor;}public LiveData<User> getUser(String userId) {refreshUser(userId);// return a LiveData directly from the database.(從數據庫中直接返回LiveData)return userDao.load(userId);}private void refreshUser(final String userId) {executor.execute(() -> {// running in a background thread(在后臺線程運行)// check if user was fetched recently(檢查數據庫中是否有數據)boolean userExists = userDao.hasUser(FRESH_TIMEOUT);if (!userExists) {// refresh the dataResponse response = webservice.getUser(userId).execute();// TODO check for error etc.// Update the database.The LiveData will automatically refresh so// we don't need to do anything else here besides updating the database(不用其他代碼就能實現數據自動更新)userDao.save(response.body());}});} }數據來源的唯一性
在上面提供的代碼中,數據庫是App數據的唯一來源。Google推薦采用這種方式。
最終的架構形態
下面這張圖展示了使用Android Architecture Components來構建的App整體的架構:
?
?
一些App架構設計的推薦準則
- 不要把在Manifest中定義的組件作為提供數據的來源(包括Activity、Services、Broadcast Receivers等),因為他們的生命周期相對于App的生命周期是相對短暫的。
- 嚴格的限制每個模塊的功能。比如上面提到的不要再ViewModel中增加如何獲取數據的代碼。
- 每個模塊盡可能少的對外暴露方法。
- 模塊中對外暴露的方法要考慮單元測試的方便。
- 不要重復造輪子,把精力放在能夠讓App脫穎而出的業務上。
- 盡可能多的持久化數據,因為這樣即使是在網絡條件不好的情況下,用戶仍然能夠使用App
- 保證數據來源的唯一性(即:提供一個類似UserRepository的類)
Lifecycle
Lifecycle是一個包含組件(Activity或者Fragment)生命周期狀態的類,這個類還能夠為其他類提供當前的生命周期。
Lifecycle使用兩個主要的枚舉來跟蹤他所關聯組件的生命周期。
- Event 事件 從組件或者Lifecycle類分發出來的生命周期,它們和Activity/Fragment生命周期的事件一一對應。(ON_CREATE,ON_START,ON_RESUME,ON_PAUSE,ON_STOP,ON_DESTROY)
- State 狀態 當前組件的生命周期狀態(INITIALIZED,DESTROYED,CREATED,STARTED,RESUMED)
LifecycleOwner
實現LifecycleOwner就表示這是個有生命周期的類,他有一個getLifecycle ()方法是必須實現的。com.android.support:appcompat-v7:26.1.0中的AppCompatActivity已經實現了這個接口,詳細的實現可以自行查看代碼。
對于前面提到的監聽位置的例子。可以把MyLocationListener實現LifecycleObserver,然后在Lifecycle(Activity/Fragment)的onCreate方法中初始化。這樣MyLocationListener就能自行處理生命周期帶來的問題。
class MyActivity extends AppCompatActivity {private MyLocationListener myLocationListener;public void onCreate(...) {myLocationListener = new MyLocationListener(this, getLifecycle(), location -> {// update UI});Util.checkUserStatus(result -> {if (result) {myLocationListener.enable();}});} }class MyLocationListener implements LifecycleObserver {private boolean enabled = false;public MyLocationListener(Context context, Lifecycle lifecycle, Callback callback) {...}@OnLifecycleEvent(Lifecycle.Event.ON_START)void start() {if (enabled) {// connect}}public void enable() {enabled = true;if (lifecycle.getState().isAtLeast(STARTED)) {// connect if not connected}}@OnLifecycleEvent(Lifecycle.Event.ON_STOP)void stop() {// disconnect if connected} }Lifecycles的最佳建議
- 保持UI Controllers(Activity/Fragment)中代碼足夠簡潔。一定不能包含如何獲取數據的代碼,要通過ViewModel獲取LiveData形式的數據。
- 用數據驅動UI,UI的職責就是根據數據改變顯示的內容,并且把用戶操作UI的行為傳遞給ViewModel。
- 把業務邏輯相關的代碼放到ViewModel中,把ViewModel看成是鏈接UI和App其他部分的膠水。但ViewModel不能直接獲取數據,要通過調用其他類來獲取數據。
- 使用DataBinding來簡化View(布局文件)和UI Controllers(Activity/Fragment)之間的代碼
- 如果布局本身太過復雜,可以考慮創建一個Presenter類來處理UI相關的改變。雖然這么做會多寫很多代碼,但是對于保持UI的簡介和可測試性是有幫助的。
- 不要在ViewModel中持有任何View/Activity的context。否則會造成內存泄露。
LiveData
LiveData是一種持有可被觀察數據的類。和其他可被觀察的類不同的是,LiveData是有生命周期感知能力的,這意味著它可以在activities, fragments, 或者 services生命周期是活躍狀態時更新這些組件。
要想使用LiveData(或者這種有可被觀察數據能力的類)就必須配合實現了LifecycleOwner的對象使用。在這種情況下,當對應的生命周期對象DESTORY時,才能移除觀察者。這對Activity或者Fragment來說顯得尤為重要,因為他們可以在生命周期結束的時候立刻解除對數據的訂閱,從而避免內存泄漏等問題。
使用LiveData的優點
- UI和實時數據保持一致 因為LiveData采用的是觀察者模式,這樣一來就可以在數據發生改變時獲得通知,更新UI。
- 避免內存泄漏 觀察者被綁定到組件的生命周期上,當被綁定的組件銷毀(destory)時,觀察者會立刻自動清理自身的數據。
- 不會再產生由于Activity處于stop狀態而引起的崩潰 例如:當Activity處于后臺狀態時,是不會收到LiveData的任何事件的。
- 不需要再解決生命周期帶來的問題 LiveData可以感知被綁定的組件的生命周期,只有在活躍狀態才會通知數據變化。
- 實時數據刷新 當組件處于活躍狀態或者從不活躍狀態到活躍狀態時總是能收到最新的數據
- 解決Configuration Change問題 在屏幕發生旋轉或者被回收再次啟動,立刻就能收到最新的數據。
- 數據共享 如果對應的LiveData是單例的話,就能在app的組件間分享數據。這部分詳細的信息可以參考繼承LiveData
- ?
使用LiveData
?
創建LiveData對象
LiveData是一個數據的包裝。具體的包裝對象可以是任何數據,包括集合(比如List)。LiveData通常在ViewModel中創建,然后通過gatter方法獲取。具體可以看一下代碼:
public class NameViewModel extends ViewModel {// Create a LiveData with a String 暫時就把MutableLiveData看成是LiveData吧,下面的文章有詳細的解釋 private MutableLiveData<String> mCurrentName;public MutableLiveData<String> getCurrentName() {if (mCurrentName == null) {mCurrentName = new MutableLiveData<String>();}return mCurrentName;}// Rest of the ViewModel... }觀察LiveData中的數據
通常情況下都是在組件的onCreate()方法中開始觀察數據,原因有以下兩點:
- 系統會多次調用onResume()方法。
- 確保Activity/Fragment在處于活躍狀態時立刻可以展示數據。
下面的代碼展示了如何觀察LiveData對象:
public class NameActivity extends AppCompatActivity {private NameViewModel mModel;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);// Other code to setup the activity...// Get the ViewModel.mModel = ViewModelProviders.of(this).get(NameViewModel.class);// Create the observer which updates the UI.final Observer<String> nameObserver = new Observer<String>() {@Overridepublic void onChanged(@Nullable final String newName) {// Update the UI, in this case, a TextView.mNameTextView.setText(newName);}};// Observe the LiveData, passing in this activity as the LifecycleOwner and the observer.mModel.getCurrentName().observe(this, nameObserver);} }更新LiveData對象
如果想要在UI Controller中改變LiveData中的值呢?(比如點擊某個Button把性別從男設置成女)。LiveData并沒有提供這樣的功能,但是Architecture Component提供了MutableLiveData這樣一個類,可以通過setValue(T)和postValue(T)方法來修改存儲在LiveData中的數據。MutableLiveData是LiveData的一個子類,從名稱上也能看出這個類的作用。舉個直觀點的例子:
?
mButton.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {String anotherName = "John Doe";mModel.getCurrentName().setValue(anotherName);} });調用setValue()方法就可以把LiveData中的值改為John Doe。同樣,通過這種方法修改LiveData中的值同樣會觸發所有對這個數據感興趣的類。那么setValue()和postValue()有什么不同呢?區別就是setValue()只能在主線程中調用,而postValue()可以在子線程中調用。
Room和LiveData配合使用
Room可以返回LiveData的數據類型。這樣對數據庫中的任何改動都會被傳遞出去。這樣修改完數據庫就能獲取最新的數據,減少了主動獲取數據的代碼。
public LiveData<User> getUser(String userId) {refreshUser(userId);// return a LiveData directly from the database.(從數據庫中直接返回LiveData)return userDao.load(userId);}?
繼承LiveData擴展功能
LiveData的活躍狀態包括:STARTED或者RESUMED兩種狀態。那么如何在活躍狀態下把數據傳遞出去呢?
public class StockLiveData extends LiveData<BigDecimal> {private StockManager mStockManager;private SimplePriceListener mListener = new SimplePriceListener() {@Overridepublic void onPriceChanged(BigDecimal price) {setValue(price);}};public StockLiveData(String symbol) {mStockManager = new StockManager(symbol);}@Overrideprotected void onActive() {mStockManager.requestPriceUpdates(mListener);}@Overrideprotected void onInactive() {mStockManager.removeUpdates(mListener);} }可以看到onActive()和onInactive()就表示了處于活躍和不活躍狀態的回調。
public class MyFragment extends Fragment {@Overridepublic void onActivityCreated(Bundle savedInstanceState) {super.onActivityCreated(savedInstanceState);LiveData<BigDecimal> myPriceListener = ...;myPriceListener.observe(this, price -> {// Update the UI.});} }如果把StockLiveData寫成單例模式,那么還可以在不同的組件間共享數據。
public class StockLiveData extends LiveData<BigDecimal> {private static StockLiveData sInstance;private StockManager mStockManager;private SimplePriceListener mListener = new SimplePriceListener() {@Overridepublic void onPriceChanged(BigDecimal price) {setValue(price);}};@MainThreadpublic static StockLiveData get(String symbol) {if (sInstance == null) {sInstance = new StockLiveData(symbol);}return sInstance;}private StockLiveData(String symbol) {mStockManager = new StockManager(symbol);}@Overrideprotected void onActive() {mStockManager.requestPriceUpdates(mListener);}@Overrideprotected void onInactive() {mStockManager.removeUpdates(mListener);} }轉換LiveData中的值(Transform LiveData)
這么說很容易和上文改變LiveData中的值搞混。這里的變換是指在LiveData的數據被分發到各個組件之前轉換值的內容,各個組件收到的是轉換后的值,但是LiveData里面數據本身的值并沒有改變。(和RXJava中map的概念很像)Lifecycle包中提供了Transformations來提供轉換的功能。
Transformations.map()
LiveData<User> userLiveData = ...; LiveData<String> userName = Transformations.map(userLiveData, user -> {user.name + " " + user.lastName });把原來是包含User的LiveData轉換成包含String的LiveData傳遞出去。
Transformations.switchMap()
private LiveData<User> getUser(String id) {...; }LiveData<String> userId = ...; LiveData<User> user = Transformations.switchMap(userId, id -> getUser(id) );和上面的map()方法很像。區別在于傳遞給switchMap()的函數必須返回LiveData對象。
和LiveData一樣,Transformation也可以在觀察者的整個生命周期中存在。只有在觀察者處于觀察LiveData狀態時,Transformation才會運算。Transformation是延遲運算的(calculated lazily),而生命周期感知的能力確保不會因為延遲發生任何問題。
如果在ViewModel對象的內部需要一個Lifecycle對象,那么使用Transformation是一個不錯的方法。舉個例子:假如有個UI組件接受輸入的地址,返回對應的郵政編碼。那么可以 實現一個ViewModel和這個組件綁定:
class MyViewModel extends ViewModel {private final PostalCodeRepository repository;public MyViewModel(PostalCodeRepository repository) {this.repository = repository;}private LiveData<String> getPostalCode(String address) {// DON'T DO THIS (不要這么干)return repository.getPostCode(address);} }看代碼中的注釋,有個// DON'T DO THIS (不要這么干),這是為什么?有一種情況是如果UI組件被回收后又被重新創建,那么又會觸發一次 repository.getPostCode(address)查詢,而不是重用上次已經獲取到的查詢。那么應該怎樣避免這個問題呢?看一下下面的代碼:
class MyViewModel extends ViewModel {private final PostalCodeRepository repository;private final MutableLiveData<String> addressInput = new MutableLiveData();public final LiveData<String> postalCode =Transformations.switchMap(addressInput, (address) -> {return repository.getPostCode(address);});public MyViewModel(PostalCodeRepository repository) {this.repository = repository}private void setInput(String address) {addressInput.setValue(address);} }合并多個LiveData中的數據
MediatorLiveData是LiveData的子類,可以通過MediatorLiveData合并多個LiveData來源的數據。同樣任意一個來源的LiveData數據發生變化,MediatorLiveData都會通知觀察他的對象。說的有點抽象,舉個例子。比如UI接收來自本地數據庫和網絡數據,并更新相應的UI。可以把下面兩個LiveData加入到MeidatorLiveData中:
- 關聯數據庫的LiveData
- 關聯聯網請求的LiveData
相應的UI只需要關注MediatorLiveData就可以在任意數據來源更新時收到通知。
?
ViewModel
ViewModel設計的目的就是存放和處理和UI相關的數據,并且這些數據不受配置變化(Configuration Changes,例如:旋轉屏幕,組件被系統回收)的影響。
ViewModel用于為UI組件提供數據,并且能夠在旋轉屏幕等Configuration Change發生時,仍能保持里面的數據。當UI組件恢復時,可以立刻向UI提供數據。
?
public class MyViewModel extends ViewModel {private MutableLiveData<List<User>> users;public LiveData<List<User>> getUsers() {if (users == null) {users = new MutableLiveData<List<Users>>();loadUsers();}return users;}private void loadUsers() {// do async operation to fetch users} }Activity訪問User List數據:
public class MyActivity extends AppCompatActivity {public void onCreate(Bundle savedInstanceState) {MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);model.getUsers().observe(this, users -> {// update UI});} }假如用戶按返回鍵,主動銷毀了這個Activity呢?這時系統會調用ViewModel的onCleared()方法,清除ViewModel中的數據。
?
在Fragments間分享數據
使用ViewModel可以很好的解決這個問題。假設有這樣兩個Fragment,一個Fragment提供一個列表,另一個Fragment提供點擊每個item現實的詳細信息。
public class SharedViewModel extends ViewModel {private final MutableLiveData<Item> selected = new MutableLiveData<Item>();public void select(Item item) {selected.setValue(item);}public LiveData<Item> getSelected() {return selected;} }public class MasterFragment extends Fragment {private SharedViewModel model;public void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);itemSelector.setOnClickListener(item -> {model.select(item);});} }public class DetailFragment extends LifecycleFragment {public void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);model.getSelected().observe(this, { item ->// update UI});} }兩個Fragment都是通過getActivity()來獲取ViewModelProvider。這意味著兩個Activity都是獲取的屬于同一個Activity的同一個ShareViewModel實例。
這樣做優點如下:
- Activity不需要寫任何額外的代碼,也不需要關心Fragment之間的通信。
- Fragment不需要處理除SharedViewModel以外其他的代碼。這兩個Fragment不需要知道對方是否存在。
- Fragment的生命周期不會相互影響
ViewModel的生命周期
ViewModel只有在Activity finish或者Fragment detach之后才會銷毀。
?
Room
Room在SQLite上提供了一個方便訪問的抽象層。App把經常需要訪問的數據存儲在本地將會大大改善用戶的體驗。這樣用戶在網絡不好時仍然可以瀏覽內容。當用戶網絡可用時,可以更新用戶的數據。
使用原始的SQLite可以提供這樣的功能,但是有以下兩個缺點:
- 沒有編譯時SQL語句的檢查。尤其是當你的數據庫表發生變化時,需要手動的更新相關代碼,這會花費相當多的時間并且容易出錯。
- 編寫大量SQL語句和Java對象之間相互轉化的代碼。
Room包含以下三個重要組成部分:
-
Database?創建數據庫。
-
Entities?數據庫表中對應的Java對象
-
DAOs?訪問數據庫
?
?
User.java
@Entity public class User {@PrimaryKeyprivate int uid;@ColumnInfo(name = "first_name")private String firstName;@ColumnInfo(name = "last_name")private String lastName;// Getters and setters are ignored for brevity, // but they're required for Room to work.//Getters和setters為了簡單起見就省略了,但是對Room來說是必須的 }UserDao.java
@Dao public interface UserDao {@Query("SELECT * FROM user")List<User> getAll();@Query("SELECT * FROM user WHERE uid IN (:userIds)")List<User> loadAllByIds(int[] userIds);@Query("SELECT * FROM user WHERE first_name LIKE :first AND "+ "last_name LIKE :last LIMIT 1")User findByName(String first, String last);@Insertvoid insertAll(User... users);@Deletevoid delete(User user); }AppDatabase.java
@Database(entities = {User.class}, version = 1) public abstract class AppDatabase extends RoomDatabase {public abstract UserDao userDao(); }在創建了上面三個文件后,就可以通過如下代碼創建數據庫了:
AppDatabase db = Room.databaseBuilder(getApplicationContext(),AppDatabase.class, "database-name").build();Entities
@Entity
如果上面的User類中包含一個字段是不希望存放到數據庫中的,那么可以用@Ignore注解這個字段:
Room持久化一個類的field必須要求這個field是可以訪問的。可以把這個field設為public或者設置setter和getter。
Primary Key 主鍵
每個Entity都必須定義一個field為主鍵,即使是這個Entity只有一個field。如果想要Room生成自動的primary key,可以使用@PrimaryKey的autoGenerate屬性。如果Entity的primary key是多個Field的復合Key,可以向下面這樣設置:
@Entity(primaryKeys = {"firstName", "lastName"}) class User {public String firstName;public String lastName;@IgnoreBitmap picture; }在默認情況下Room使用類名作為數據庫表的名稱。如果想要設置不同的名稱,可以參考下面的代碼,設置表名tableName為users:
@Entity(tableName = "users") class User {... }和設置tableName相似,Room默認使用field的名稱作為表的列名。如果想要使用不同的名稱,可以通過@ColumnInfo(name = "first_name")設置,代碼如下:
@Entity(tableName = "users") class User {@PrimaryKeypublic int id;@ColumnInfo(name = "first_name")public String firstName;@ColumnInfo(name = "last_name")public String lastName;@IgnoreBitmap picture; }和設置tableName相似,Room默認使用field的名稱作為表的列名。如果想要使用不同的名稱,可以通過@ColumnInfo(name = "first_name")設置,代碼如下:
@Entity(tableName = "users") class User {@PrimaryKeypublic int id;@ColumnInfo(name = "first_name")public String firstName;@ColumnInfo(name = "last_name")public String lastName;@IgnoreBitmap picture; }索引和唯一性
外鍵
例如,有一個Pet類需要和User類建立關系,可以通過@ForeignKey來達到這個目的,代碼如下:
@Entity(foreignKeys = @ForeignKey(entity = User.class,parentColumns = "id",childColumns = "user_id")) class Pet {@PrimaryKeypublic int petId;public String name;@ColumnInfo(name = "user_id")public int userId; }對象嵌套對象
class Address {public String street;public String state;public String city;@ColumnInfo(name = "post_code")public int postCode; }@Entity class User {@PrimaryKeypublic int id;public String firstName;@Embeddedpublic Address address; }?
Data Access Objects(DAOs)
DAOs是數據庫訪問的抽象層。
Dao可以是一個接口也可以是一個抽象類。如果是抽象類,那么它可以接受一個RoomDatabase作為構造器的唯一參數。
Room不允許在主線程中訪問數據庫,除非在builder里面調用allowMainThreadQueries() 。因為訪問數據庫是耗時的,可能阻塞主線程,引起UI卡頓。
?
添加方便使用的方法
Insert
使用?@Insert注解的方法,Room將會生成插入的代碼。
@Dao public interface MyDao {@Insert(onConflict = OnConflictStrategy.REPLACE)public void insertUsers(User... users);@Insertpublic void insertBothUsers(User user1, User user2);@Insertpublic void insertUsersAndFriends(User user, List<User> friends); }如果@Insert?方法只接受一個參數,那么將返回一個long,對應著插入的rowId。如果接受多個參數,或者數組,或者集合,那么就會返回一個long的數組或者list。
Update
@Dao public interface MyDao {@Updatepublic void updateUsers(User... users); }也可以讓update方法返回一個int型的整數,代表被update的行號。
@Dao public interface MyDao {@Deletepublic void deleteUsers(User... users); }?
Paging Library
Paging Library(分頁加載庫)用于逐步從數據源加載信息,而不會耗費過多的設備資源或者等待太長的時間。
總體概覽
一個常見的需求是獲取很多數據,但是同時也只展示其中的一小部分數據。這就會引起很多問題,比如浪費資源和流量等。
現有的Android APIs可以提供分頁加載的功能,但是也帶來了顯著的限制和缺點:
- CursorAdapter,使得從數據庫加載數據到ListView變得非常容易。但是這是在主線程中查詢數據庫,并且分頁的內容使用低效的 Cursor返回。更多使用CursorAdapter帶來的問題參考Large Database Queries on Android。
- AsyncListUtil提供基于位置的( position-based)分頁加載到 RecyclerView中,但是無法使用不基于位置(non-positional)的分頁加載,而且還強制把null作為占位符。
提供的類
DataSource
數據源。根據你想要訪問數據的方式,可以有兩種子類可供選擇:
- KeyedDataSource用于加載從第N到N+1數據。
- TiledDataSource 用于從任意位置的分頁數據。
例如使用 Room persistence library 就可以自動創建返回 TiledDataSource類型的數據:
@Query("select * from users WHERE age > :age order by name DESC, id ASC") TiledDataSource<User> usersOlderThan(int age);?
@Dao interface UserDao {@Query("SELECT * FROM user ORDER BY lastName ASC")public abstract LivePagedListProvider<Integer, User> usersByLastName(); }class MyViewModel extends ViewModel {public final LiveData<PagedList<User>> usersList;public MyViewModel(UserDao userDao) {usersList = userDao.usersByLastName().create(/* initial load position */ 0,new PagedList.Config.Builder().setPageSize(50).setPrefetchDistance(50).build());} }class MyActivity extends AppCompatActivity {@Overridepublic void onCreate(Bundle savedState) {super.onCreate(savedState);MyViewModel viewModel = ViewModelProviders.of(this).get(MyViewModel.class);RecyclerView recyclerView = findViewById(R.id.user_list);UserAdapter<User> adapter = new UserAdapter();viewModel.usersList.observe(this, pagedList -> adapter.setList(pagedList));recyclerView.setAdapter(adapter);} }class UserAdapter extends PagedListAdapter<User, UserViewHolder> {public UserAdapter() {super(DIFF_CALLBACK);}@Overridepublic void onBindViewHolder(UserViewHolder holder, int position) {User user = getItem(position);if (user != null) {holder.bindTo(user);} else {// Null defines a placeholder item - PagedListAdapter will automatically invalidate// this row when the actual object is loaded from the databaseholder.clear();}}public static final DiffCallback<User> DIFF_CALLBACK = new DiffCallback<User>() {@Overridepublic boolean areItemsTheSame(@NonNull User oldUser, @NonNull User newUser) {// User properties may have changed if reloaded from the DB, but ID is fixedreturn oldUser.getId() == newUser.getId();}@Overridepublic boolean areContentsTheSame(@NonNull User oldUser, @NonNull User newUser) {// NOTE: if you use equals, your object must properly override Object#equals()// Incorrectly returning false here will result in too many animations.return oldUser.equals(newUser);}} }?
?
?
?
總結
以上是生活随笔為你收集整理的Android Architecture Components 整理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: RxJava 2.x 入门
- 下一篇: Spring整理