淺談Android Architecture Components
- 淺談Android Architecture Components
簡介
Google IO 2017發(fā)布Android Architecture Components,自己先研究了一下,蠻有意思的,特來記錄一下。本文內(nèi)容主要是參考官方文檔以及自己的理解,如有錯誤之處,懇請指出。
Android Architecture Components
Android Architecture Components是Google發(fā)布的一套新的架構(gòu)組件,使App的架構(gòu)更加健壯,后面簡稱AAC。
AAC主要提供了Lifecycle,ViewModel,LiveData,Room等功能,下面依次說明:
- Lifecycle
生命周期管理,把原先Android生命周期的中的代碼抽取出來,如將原先需要在onStart()等生命周期中執(zhí)行的代碼分離到Activity或者Fragment之外。
- LiveData
一個數(shù)據(jù)持有類,持有數(shù)據(jù)并且這個數(shù)據(jù)可以被觀察被監(jiān)聽,和其他Observer不同的是,它是和Lifecycle是綁定的,在生命周期內(nèi)使用有效,減少內(nèi)存泄露和引用問題。
- ViewModel
用于實現(xiàn)架構(gòu)中的ViewModel,同時是與Lifecycle綁定的,使用者無需擔(dān)心生命周期??梢栽诙鄠€Fragment之間共享數(shù)據(jù),比如旋轉(zhuǎn)屏幕后Activity會重新create,這時候使用ViewModel還是之前的數(shù)據(jù),不需要再次請求網(wǎng)絡(luò)數(shù)據(jù)。
- Room
谷歌推出的一個Sqlite ORM庫,不過使用起來還不錯,使用注解,極大簡化數(shù)據(jù)庫的操作,有點類似Retrofit的風(fēng)格。
AAC的架構(gòu)是這樣的:

- Activity/Fragment
UI層,通常是Activity/Fragment等,監(jiān)聽ViewModel,當(dāng)VIewModel數(shù)據(jù)更新時刷新UI,監(jiān)聽用戶事件反饋到ViewModel,主流的數(shù)據(jù)驅(qū)動界面。
- ViewModel
持有或保存數(shù)據(jù),向Repository中獲取數(shù)據(jù),響應(yīng)UI層的事件,執(zhí)行響應(yīng)的操作,響應(yīng)數(shù)據(jù)變化并通知到UI層。
- Repository
App的完全的數(shù)據(jù)模型,ViewModel交互的對象,提供簡單的數(shù)據(jù)修改和獲取的接口,配合好網(wǎng)絡(luò)層數(shù)據(jù)的更新與本地持久化數(shù)據(jù)的更新,同步等
- Data Source
包含本地的數(shù)據(jù)庫等,網(wǎng)絡(luò)api等,這些基本上和現(xiàn)有的一些MVVM,以及Clean架構(gòu)的組合比較相似
Gradle 集成
根目錄gradle文件中添加Google Maven Repository
allprojects {
repositories {
jcenter()
maven { url 'https://maven.google.com' }
}
}
在模塊中添加對應(yīng)的依賴
如使用Lifecycle,LiveData、ViewModel,添加如下依賴。
compile "android.arch.lifecycle:runtime:1.0.0-alpha1"
compile "android.arch.lifecycle:extensions:1.0.0-alpha1"
annotationProcessor "android.arch.lifecycle:compiler:1.0.0-alpha1"
如使用Room功能,添加如下依賴。
compile "android.arch.persistence.room:runtime:1.0.0-alpha1"
annotationProcessor "android.arch.persistence.room:compiler:1.0.0-alpha1"
// For testing Room migrations, add:
testCompile "android.arch.persistence.room:testing:1.0.0-alpha1"
// For Room RxJava support, add:
compile "android.arch.persistence.room:rxjava2:1.0.0-alpha1"
LifeCycles
Android開發(fā)中,經(jīng)常需要管理生命周期。舉個栗子,我們需要獲取用戶的地址位置,當(dāng)這個Activity在顯示的時候,我們開啟定位功能,然后實時獲取到定位信息,當(dāng)頁面被銷毀的時候,需要關(guān)閉定位功能。
下面是簡單的示例代碼。
class MyLocationListener {
public MyLocationListener(Context context, Callback callback) {
// ...
}
void start() {
// connect to system location service
}
void stop() {
// disconnect from system location service
}
}
class MyActivity extends AppCompatActivity {
private MyLocationListener myLocationListener;
public void onCreate(...) {
myLocationListener = new MyLocationListener(this, (location) -> {
// update UI
});
}
public void onStart() {
super.onStart();
myLocationListener.start();
}
public void onStop() {
super.onStop();
myLocationListener.stop();
}
}
上面只是一個簡單的場景,我們來個復(fù)雜一點的場景。當(dāng)定位功能需要滿足一些條件下才開啟,那么會變得復(fù)雜多了??赡茉趫?zhí)行Activity的stop方法時,定位的start方法才剛剛開始執(zhí)行,比如如下代碼,這樣生命周期管理就變得很麻煩了。
class MyActivity extends AppCompatActivity {
private MyLocationListener myLocationListener;
public void onCreate(...) {
myLocationListener = new MyLocationListener(this, location -> {
// update UI
});
}
public void onStart() {
super.onStart();
Util.checkUserStatus(result -> {
// what if this callback is invoked AFTER activity is stopped?
if (result) {
myLocationListener.start();
}
});
}
public void onStop() {
super.onStop();
myLocationListener.stop();
}
}
AAC中提供了Lifecycle,用來幫助我們解決這樣的問題。LifeCycle使用2個枚舉類來解決生命周期管理問題。一個是事件,一個是狀態(tài)。
事件:
生命周期事件由系統(tǒng)來分發(fā),這些事件對于與Activity和Fragment的生命周期函數(shù)。
狀態(tài):
Lifecycle的狀態(tài),用于追蹤中Lifecycle對象,如下圖所示。

上面的定位功能代碼,使用LifeCycle實現(xiàn)以后是這樣的,實現(xiàn)一個LifecycleObserver接口,然后用注解標(biāo)注狀態(tài),最后在LifecycleOwner中添加監(jiān)聽。
public class MyObserver implements LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
public void onResume() {
}
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
public void onPause() {
}
}
aLifecycleOwner.getLifecycle().addObserver(new MyObserver());
上面代碼中用到了aLifecycleOwner是LifecycleOwner接口對象,LifecycleOwner是一個只有一個方法的接口getLifecycle(),需要由子類來實現(xiàn)。
在Lib中已經(jīng)有實現(xiàn)好的子類,我們可以直接拿來使用。比如LifecycleActivity和LifecycleFragment,我們只需要繼承此類就行了。
當(dāng)然實際開發(fā)的時候我們會自己定義BaseActivity,Java是單繼承模式,那么需要自己實現(xiàn)LifecycleRegistryOwner接口。
如下所示即可,代碼很近簡單
public class BaseFragment extends Fragment implements LifecycleRegistryOwner {
LifecycleRegistry lifecycleRegistry = new LifecycleRegistry(this);
@Override
public LifecycleRegistry getLifecycle() {
return lifecycleRegistry;
}
}
LiveData
LiveData 是一個 Data Holder 類,可以持有數(shù)據(jù),同時這個數(shù)據(jù)可以被監(jiān)聽的,當(dāng)數(shù)據(jù)改變的時候,可以觸發(fā)回調(diào)。但是又不像普通的Observable,LiveData綁定了App的組件,LiveData可以指定在LifeCycle的某個狀態(tài)被觸發(fā)。比如LiveData可以指定在LifeCycle的 STARTED 或 RESUMED狀體被觸發(fā)。
public class LocationLiveData extends LiveData<Location> {
private LocationManager locationManager;
private SimpleLocationListener listener = new SimpleLocationListener() {
@Override
public void onLocationChanged(Location location) {
setValue(location);
}
};
public LocationLiveData(Context context) {
locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
}
@Override
protected void onActive() {
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, listener);
}
@Override
protected void onInactive() {
locationManager.removeUpdates(listener);
}
}
- onActive()
這個方法在LiveData在被激活的狀態(tài)下執(zhí)行,我們可以開始執(zhí)行一些操作。
- onActive()
這個方法在LiveData在的失去活性狀態(tài)下執(zhí)行,我們可以結(jié)束執(zhí)行一些操作。
- setValue()
執(zhí)行這個方法的時候,LiveData可以觸發(fā)它的回調(diào)。
LocationLiveData可以這樣使用。
public class MyFragment extends LifecycleFragment {
public void onActivityCreated (Bundle savedInstanceState) {
LiveData<Location> myLocationListener = ...;
Util.checkUserStatus(result -> {
if (result) {
myLocationListener.addObserver(this, location -> {
// update UI
});
}
});
}
}
注意,上面的addObserver方法,必須傳LifecycleOwner對象,也就是說添加的對象必須是可以被LifeCycle管理的。
如果LifeCycle沒有觸發(fā)對對應(yīng)的狀態(tài)(STARTED or RESUMED),它的值被改變了,那么Observe就不會被執(zhí)行,
如果LifeCycle被銷毀了,那么Observe將自動被刪除。
實際上LiveData就提供一種新的供數(shù)據(jù)共享方式??梢杂盟诙鄠€Activity、Fragment等其他有生命周期管理的類中實現(xiàn)數(shù)據(jù)共享。
還是上面的定位例子。
public class LocationLiveData extends LiveData<Location> {
private static LocationLiveData sInstance;
private LocationManager locationManager;
@MainThread
public static LocationLiveData get(Context context) {
if (sInstance == null) {
sInstance = new LocationLiveData(context.getApplicationContext());
}
return sInstance;
}
private SimpleLocationListener listener = new SimpleLocationListener() {
@Override
public void onLocationChanged(Location location) {
setValue(location);
}
};
private LocationLiveData(Context context) {
locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
}
@Override
protected void onActive() {
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, listener);
}
@Override
protected void onInactive() {
locationManager.removeUpdates(listener);
}
}
然后在Fragment中調(diào)用。
public class MyFragment extends LifecycleFragment {
public void onActivityCreated (Bundle savedInstanceState) {
Util.checkUserStatus(result -> {
if (result) {
LocationLiveData.get(getActivity()).observe(this, location -> {
// update UI
});
}
});
}
}
從上面的示例,可以得到使用LiveData優(yōu)點:
沒有內(nèi)存泄露的風(fēng)險,全部綁定到對應(yīng)的生命周期,當(dāng)LifeCycle被銷毀的時候,它們也自動被移除
降低Crash,當(dāng)Activity被銷毀的時候,LiveData的Observer自動被刪除,然后UI就不會再接受到通知
實時數(shù)據(jù),因為LiveData是持有真正的數(shù)據(jù)的,所以當(dāng)生命周期又重新開始的時候,又可以重新拿到數(shù)據(jù)
正常配置改變,當(dāng)Activity或者Fragment重新創(chuàng)建的時候,可以從LiveData中獲取上一次有用的數(shù)據(jù)
不再需要手動的管理生命周期
Transformations
有時候需要對一個LiveData做Observer,但是這個LiveData是依賴另外一個LiveData,有點類似于RxJava中的操作符,我們可以這樣做。
- Transformations.map()
用于事件流的傳遞,用于觸發(fā)下游數(shù)據(jù)。
LiveData<User> userLiveData = ...;
LiveData<String> userName = Transformations.map(userLiveData, user -> {
user.name + " " + user.lastName
});
- Transformations.switchMap()
這個和map類似,只不過這個是用來觸發(fā)上游數(shù)據(jù)。
private LiveData<User> getUser(String id) {
// ...
}
LiveData<String> userId = ...;
LiveData<User> user = Transformations.switchMap(userId, id -> getUser(id) );
ViewModel
ViewModel是用來存儲UI層的數(shù)據(jù),以及管理對應(yīng)的數(shù)據(jù),當(dāng)數(shù)據(jù)修改的時候,可以馬上刷新UI。
Android系統(tǒng)提供控件,比如Activity和Fragment,這些控件都是具有生命周期方法,這些生命周期方法被系統(tǒng)調(diào)用。
當(dāng)這些控件被銷毀或者被重建的時候,如果數(shù)據(jù)保存在這些對象中,那么數(shù)據(jù)就會丟失。比如在一個界面,保存了一些用戶信息,當(dāng)界面重新創(chuàng)建的時候,就需要重新去獲取數(shù)據(jù)。當(dāng)然了也可以使用控件自動再帶的方法,在onSaveInstanceState方法中保存數(shù)據(jù),在onCreate中重新獲得數(shù)據(jù),但這僅僅在數(shù)據(jù)量比較小的情況下。如果數(shù)據(jù)量很大,這種方法就不能適用了。
另外一個問題就是,經(jīng)常需要在Activity中加載數(shù)據(jù),這些數(shù)據(jù)可能是異步的,因為獲取數(shù)據(jù)需要花費很長的時間。那么Activity就需要管理這些數(shù)據(jù)調(diào)用,否則很有可能會產(chǎn)生內(nèi)存泄露問題。最后需要做很多額外的操作,來保證程序的正常運行。
同時Activity不僅僅只是用來加載數(shù)據(jù)的,還要加載其他資源,做其他的操作,最后Activity類變大,就是我們常講的上帝類。也有不少架構(gòu)是把一些操作放到單獨的類中,比如MVP就是這樣,創(chuàng)建相同類似于生命周期的函數(shù)做代理,這樣可以減少Activity的代碼量,但是這樣就會變得很復(fù)雜,同時也難以測試。
AAC中提供ViewModel可以很方便的用來管理數(shù)據(jù)。我們可以利用它來管理UI組件與數(shù)據(jù)的綁定關(guān)系。ViewModel提供自動綁定的形式,當(dāng)數(shù)據(jù)源有更新的時候,可以自動立即的更新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
}
}
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
});
}
}
當(dāng)我們獲取ViewModel實例的時候,ViewModel是通過ViewModelProvider保存在LifeCycle中,ViewModel會一直保存在LifeCycle中,直到Activity或者Fragment銷毀了,觸發(fā)LifeCycle被銷毀,那么ViewModel也會被銷毀的。下面是ViewModel的生命周期圖。

Room
Room是一個持久化工具,和ormlite、greenDao類似,都是ORM工具。在開發(fā)中我們可以利用Room來操作sqlite數(shù)據(jù)庫。
Room主要分為三個部分:
- Database
使用注解申明一個類,注解中包含若干個Entity類,這個Database類主要負(fù)責(zé)創(chuàng)建數(shù)據(jù)庫以及獲取數(shù)據(jù)對象的。
- Entity
表示每個數(shù)據(jù)庫的總的一個表結(jié)構(gòu),同樣也是使用注解表示,類中的每個字段都對應(yīng)表中的一列。
- DAO
DAO是 Data Access Object的縮寫,表示從從代碼中直接訪問數(shù)據(jù)庫,屏蔽sql語句。
下面是官方給的結(jié)構(gòu)圖。

代碼示例:
// User.java
@Entity
public class User {
@PrimaryKey
private 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.
}
// 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);
@Insert
void insertAll(User... users);
@Delete
void delete(User user);
}
// AppDatabase.java
@Database(entities = {User.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
public abstract UserDao userDao();
}
最后在代碼中調(diào)用Database對象
AppDatabase db = Room.databaseBuilder(getApplicationContext(), AppDatabase.class, "database-name").build();
注意: Database最好設(shè)計成單利模式,否則對象太多會有性能的影響。
Entities
@Entity
class User {
@PrimaryKey
public int id;
@ColumnInfo(name = "first_name")
public String firstName;
@ColumnInfo(name = "last_name")
public String lastName;
@Ignore
Bitmap picture;
}
@Entity用來注解一個實體類,對應(yīng)數(shù)據(jù)庫一張表。默認(rèn)情況下,Room為實體中定義的每個成員變量在數(shù)據(jù)中創(chuàng)建對應(yīng)的字段,我們可能不想保存到數(shù)據(jù)庫的字段,這時候就要用道@Ignore注解。
注意: 為了保存每一個字段,這個字段需要有可以訪問的gettter/setter方法或者是public的屬性
Entity的參數(shù) primaryKeys
每個實體必須至少定義1個字段作為主鍵,即使只有一個成員變量,除了使用@PrimaryKey 將字段標(biāo)記為主鍵的方式之外,還可以通過在@Entity注解中指定參數(shù)的形式
@Entity(primaryKeys = {"firstName", "lastName"})
class User {
public String firstName;
public String lastName;
@Ignore
Bitmap picture;
}
Entity的參數(shù) tableName
默認(rèn)情況下,Room使用類名作為數(shù)據(jù)庫表名。如果你想表都有一個不同的名稱,就可以在@Entity中使用tableName參數(shù)指定
@Entity(tableName = "users")
class User {
...
}
和tableName作用類似; @ColumnInfo注解是改變成員變量對應(yīng)的數(shù)據(jù)庫的字段名稱。
Entity的參數(shù) indices
indices的參數(shù)值是@Index的數(shù)組,在某些情況寫為了加快查詢速度我們可以需要加入索引
@Entity(indices = {@Index("name"), @Index("last_name", "address")})
class User {
@PrimaryKey
public int id;
public String firstName;
public String address;
@ColumnInfo(name = "last_name")
public String lastName;
@Ignore
Bitmap picture;
}
有時,數(shù)據(jù)庫中某些字段或字段組必須是唯一的。通過將@Index的unique 設(shè)置為true,可以強制執(zhí)行此唯一性屬性。
下面的代碼示例防止表有兩行包含F(xiàn)irstName和LastName列值相同的一組:
@Entity(indices = {@Index(value = {"first_name", "last_name"}, unique = true)})
class User {
@PrimaryKey
public int id;
@ColumnInfo(name = "first_name")
public String firstName;
@ColumnInfo(name = "last_name")
public String lastName;
@Ignore
Bitmap picture;
}
Entity的參數(shù) foreignKeys
因為SQLite是一個關(guān)系型數(shù)據(jù)庫,你可以指定對象之間的關(guān)系。盡管大多數(shù)ORM庫允許實體對象相互引用,但Room明確禁止。實體之間沒有對象引用。
不能使用直接關(guān)系,所以就要用到foreignKeys(外鍵)。
@Entity(foreignKeys = @ForeignKey(entity = User.class,
parentColumns = "id",
childColumns = "user_id"))
class Book {
@PrimaryKey
public int bookId;
public String title;
@ColumnInfo(name = "user_id")
public int userId;
}
外鍵是非常強大的,因為它允許指定引用實體更新時發(fā)生的操作。例如,級聯(lián)刪除,你可以告訴SQLite刪除所有書籍的用戶如果用戶對應(yīng)的實例是由包括OnDelete =CASCADE在@ForeignKey注釋。ON_CONFLICT : @Insert(onConflict=REPLACE) REMOVE 或者 REPLACE
有時候可能還需要對象嵌套這時候可以用@Embedded注解
class Address {
public String street;
public String state;
public String city;
@ColumnInfo(name = "post_code")
public int postCode;
}
@Entity
class User {
@PrimaryKey
public int id;
public String firstName;
@Embedded
public Address address;
}
Dao
@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);
@Insert
void insertAll(User... users);
@Delete
void delete(User user);
}
數(shù)據(jù)訪問對象Data Access Objects (DAOs)是一種抽象訪問數(shù)據(jù)庫的干凈的方式。
DAO的Insert 操作
當(dāng)創(chuàng)建DAO方法并用@Insert注釋它時,生成一個實現(xiàn)時會在一個事務(wù)中完成插入所有參數(shù)到數(shù)據(jù)庫。
@Dao
public interface MyDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
public void insertUsers(User... users);
@Insert
public void insertBothUsers(User user1, User user2);
@Insert
public void insertUsersAndFriends(User user, List<User> friends);
}
DAO的Update、Delete操作
與上面類似
@Dao
public interface MyDao {
@Update
public void updateUsers(User... users);
@Delete
public void deleteUsers(User... users);
}
DAO的Query 操作
一個簡單查詢示例
@Dao
public interface MyDao {
@Query("SELECT * FROM user")
public User[] loadAllUsers();
}
稍微復(fù)雜的,帶參數(shù)的查詢操作
@Dao
public interface MyDao {
@Query("SELECT * FROM user WHERE age > :minAge")
public User[] loadAllUsersOlderThan(int minAge);
}
也可以帶多個參數(shù)
@Dao
public interface MyDao {
@Query("SELECT * FROM user WHERE age BETWEEN :minAge AND :maxAge")
public User[] loadAllUsersBetweenAges(int minAge, int maxAge);
@Query("SELECT * FROM user WHERE first_name LIKE :search OR last_name LIKE :search")
public List<User> findUserWithName(String search);
}
返回子集
上面示例都是查詢一個表中的所有字段,結(jié)果用對應(yīng)的Entity即可,但是如果我只要其中的幾個字段,那么該怎么使用呢?
比如上面的User,我只需要firstName和lastName,首先定義一個子集,然后結(jié)果改成對應(yīng)子集即可。
public class NameTuple {
@ColumnInfo(name="first_name")
public String firstName;
@ColumnInfo(name="last_name")
public String lastName;
}
@Dao
public interface MyDao {
@Query("SELECT first_name, last_name FROM user")
public List<NameTuple> loadFullName();
}
支持集合參數(shù)
有個這樣一個查詢需求,比如要查詢某兩個地區(qū)的所有用戶,直接用sql中的in即可,但是如果這個地區(qū)是程序指定的,個數(shù)不確定,那么改怎么辦?
@Dao
public interface MyDao {
@Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)")
public List<NameTuple> loadUsersFromRegions(List<String> regions);
}
支持Observable
前面提到了LiveData,可以異步的獲取數(shù)據(jù),那么我們的Room也是支持異步查詢的。
@Dao
public interface MyDao {
@Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)")
public LiveData<List<User>> loadUsersFromRegionsSync(List<String> regions);
}
支持RxJava
RxJava是另外一個異步操作庫,同樣也是支持的。
@Dao
public interface MyDao {
@Query("SELECT * from user where id = :id LIMIT 1")
public Flowable<User> loadUserById(int id);
}
支持Cursor
原始的Android系統(tǒng)查詢結(jié)果是通過Cursor來獲取的,同樣也支持。
@Dao
public interface MyDao {
@Query("SELECT * FROM user WHERE age > :minAge LIMIT 5")
public Cursor loadRawUsersOlderThan(int minAge);
}
多表查詢
有時候數(shù)據(jù)庫存在范式相關(guān),數(shù)據(jù)拆到了多個表中,那么就需要關(guān)聯(lián)多個表進(jìn)行查詢,如果結(jié)果只是一個表的數(shù)據(jù),那么很簡單,直接用Entity定義的類型即可。
@Dao
public interface MyDao {
@Query("SELECT * FROM book "
+ "INNER JOIN loan ON loan.book_id = book.id "
+ "INNER JOIN user ON user.id = loan.user_id "
+ "WHERE user.name LIKE :userName")
public List<Book> findBooksBorrowedByNameSync(String userName);
}
如果結(jié)果是部分字段,同上面一樣,需要單獨定義一個POJO,來接受數(shù)據(jù)。
public class UserPet {
public String userName;
public String petName;
}
@Dao
public interface MyDao {
@Query("SELECT user.name AS userName, pet.name AS petName "
+ "FROM user, pet "
+ "WHERE user.id = pet.user_id")
public LiveData<List<UserPet>> loadUserAndPetNames();
}
類型轉(zhuǎn)換
有時候Java定義的數(shù)據(jù)類型和數(shù)據(jù)庫中存儲的數(shù)據(jù)類型是不一樣的,Room提供類型轉(zhuǎn)換,這樣在操作數(shù)據(jù)庫的時候,可以自動轉(zhuǎn)換。
比如在Java中,時間用Date表示,但是在數(shù)據(jù)庫中類型確實long,這樣有利于存儲。
public class Converters {
@TypeConverter
public static Date fromTimestamp(Long value) {
return value == null ? null : new Date(value);
}
@TypeConverter
public static Long dateToTimestamp(Date date) {
return date == null ? null : date.getTime();
}
}
定義數(shù)據(jù)庫時候需要指定類型轉(zhuǎn)換,同時定義好Entity和Dao。
@Database(entities = {User.java}, version = 1)
@TypeConverters({Converter.class})
public abstract class AppDatabase extends RoomDatabase {
public abstract UserDao userDao();
}
@Entity
public class User {
...
private Date birthday;
}
@Dao
public interface UserDao {
...
@Query("SELECT * FROM user WHERE birthday BETWEEN :from AND :to")
List<User> findUsersBornBetweenDates(Date from, Date to);
}
數(shù)據(jù)庫升級
版本迭代中,我們不可避免的會遇到數(shù)據(jù)庫升級問題,Room也為我們提供了數(shù)據(jù)庫升級的處理接口。
Room.databaseBuilder(getApplicationContext(), MyDb.class, "database-name").addMigrations(MIGRATION_1_2, MIGRATION_2_3).build();
static final Migration MIGRATION_1_2 = new Migration(1, 2) {
@Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("CREATE TABLE `Fruit` (`id` INTEGER, `name` TEXT, PRIMARY KEY(`id`))");
}
};
static final Migration MIGRATION_2_3 = new Migration(2, 3) {
@Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("ALTER TABLE Book ADD COLUMN pub_year INTEGER");
}
};
遷移過程結(jié)束后,Room將驗證架構(gòu)以確保遷移正確發(fā)生。如果Room發(fā)現(xiàn)問題,則拋出包含不匹配信息的異常。
警告: 如果不提供必要的遷移,Room會重新構(gòu)建數(shù)據(jù)庫,這意味著將丟失數(shù)據(jù)庫中的所有數(shù)據(jù)。
輸出模式
可以在gradle中設(shè)置開啟輸出模式,便于我們調(diào)試,查看數(shù)據(jù)庫表情況,以及做數(shù)據(jù)庫遷移。
android {
...
defaultConfig {
...
javaCompileOptions {
annotationProcessorOptions {
arguments = ["room.schemaLocation":
"$projectDir/schemas".toString()]
}
}
}
}
Sample
這里是官方示例,本人自己參考官方文檔和示例Android Architecture Components samples后,也寫出了一個類似的示例項目XiaoxiaZhihu_AAC,還請多多指教。
總結(jié)
原先IO是在5月底已經(jīng)結(jié)束,本來想盡快參照官方文檔和示例,把API擼一遍,然后寫個Demo和文章來介紹一下。代碼示例早已經(jīng)寫出來了,但6月分工作太忙,然后又出差到北京,最后等到了6月底了,才把這篇文章給寫出來了。中間可能有內(nèi)容以及改變,如果有發(fā)現(xiàn)穩(wěn)重有錯誤,請及時指出,不理賜教。同時以后做事一定要有始有終,確定的事一定要堅持。
相關(guān)資料
Android Architecture Components
簡單聊聊Android Architecture Componets
Android Architecture Components samples