淺談Android Architecture Components
- 淺談Android Architecture Components
簡(jiǎn)介
Google IO 2017發(fā)布Android Architecture Components拇舀,自己先研究了一下逻族,蠻有意思的,特來(lái)記錄一下骄崩。本文內(nèi)容主要是參考官方文檔以及自己的理解聘鳞,如有錯(cuò)誤之處,懇請(qǐng)指出要拂。
Android Architecture Components
Android Architecture Components是Google發(fā)布的一套新的架構(gòu)組件抠璃,使App的架構(gòu)更加健壯,后面簡(jiǎn)稱AAC脱惰。
AAC主要提供了Lifecycle搏嗡,ViewModel,LiveData枪芒,Room等功能彻况,下面依次說(shuō)明:
- Lifecycle
生命周期管理谁尸,把原先Android生命周期的中的代碼抽取出來(lái),如將原先需要在onStart()等生命周期中執(zhí)行的代碼分離到Activity或者Fragment之外纽甘。
- LiveData
一個(gè)數(shù)據(jù)持有類良蛮,持有數(shù)據(jù)并且這個(gè)數(shù)據(jù)可以被觀察被監(jiān)聽,和其他Observer不同的是悍赢,它是和Lifecycle是綁定的决瞳,在生命周期內(nèi)使用有效,減少內(nèi)存泄露和引用問(wèn)題左权。
- ViewModel
用于實(shí)現(xiàn)架構(gòu)中的ViewModel皮胡,同時(shí)是與Lifecycle綁定的,使用者無(wú)需擔(dān)心生命周期赏迟÷藕兀可以在多個(gè)Fragment之間共享數(shù)據(jù),比如旋轉(zhuǎn)屏幕后Activity會(huì)重新create锌杀,這時(shí)候使用ViewModel還是之前的數(shù)據(jù)甩栈,不需要再次請(qǐng)求網(wǎng)絡(luò)數(shù)據(jù)。
- Room
谷歌推出的一個(gè)Sqlite ORM庫(kù)糕再,不過(guò)使用起來(lái)還不錯(cuò)量没,使用注解,極大簡(jiǎn)化數(shù)據(jù)庫(kù)的操作突想,有點(diǎn)類似Retrofit的風(fēng)格殴蹄。
AAC的架構(gòu)是這樣的:
- Activity/Fragment
UI層,通常是Activity/Fragment等猾担,監(jiān)聽ViewModel袭灯,當(dāng)VIewModel數(shù)據(jù)更新時(shí)刷新UI,監(jiān)聽用戶事件反饋到ViewModel垒探,主流的數(shù)據(jù)驅(qū)動(dòng)界面妓蛮。
- ViewModel
持有或保存數(shù)據(jù),向Repository中獲取數(shù)據(jù)圾叼,響應(yīng)UI層的事件蛤克,執(zhí)行響應(yīng)的操作,響應(yīng)數(shù)據(jù)變化并通知到UI層夷蚊。
- Repository
App的完全的數(shù)據(jù)模型构挤,ViewModel交互的對(duì)象,提供簡(jiǎn)單的數(shù)據(jù)修改和獲取的接口惕鼓,配合好網(wǎng)絡(luò)層數(shù)據(jù)的更新與本地持久化數(shù)據(jù)的更新筋现,同步等
- Data Source
包含本地的數(shù)據(jù)庫(kù)等,網(wǎng)絡(luò)api等,這些基本上和現(xiàn)有的一些MVVM矾飞,以及Clean架構(gòu)的組合比較相似
Gradle 集成
根目錄gradle文件中添加Google Maven Repository
allprojects {
repositories {
jcenter()
maven { url 'https://maven.google.com' }
}
}
在模塊中添加對(duì)應(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)常需要管理生命周期瞒津。舉個(gè)栗子,我們需要獲取用戶的地址位置括尸,當(dāng)這個(gè)Activity在顯示的時(shí)候巷蚪,我們開啟定位功能,然后實(shí)時(shí)獲取到定位信息濒翻,當(dāng)頁(yè)面被銷毀的時(shí)候屁柏,需要關(guān)閉定位功能。
下面是簡(jiǎ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();
}
}
上面只是一個(gè)簡(jiǎn)單的場(chǎng)景前联,我們來(lái)個(gè)復(fù)雜一點(diǎn)的場(chǎng)景。當(dāng)定位功能需要滿足一些條件下才開啟娶眷,那么會(huì)變得復(fù)雜多了⌒ネ危可能在執(zhí)行Activity的stop方法時(shí)届宠,定位的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,用來(lái)幫助我們解決這樣的問(wèn)題灯萍。LifeCycle使用2個(gè)枚舉類來(lái)解決生命周期管理問(wèn)題轧铁。一個(gè)是事件,一個(gè)是狀態(tài)旦棉。
事件:
生命周期事件由系統(tǒng)來(lái)分發(fā)齿风,這些事件對(duì)于與Activity和Fragment的生命周期函數(shù)。
狀態(tài):
Lifecycle的狀態(tài)绑洛,用于追蹤中Lifecycle對(duì)象救斑,如下圖所示。
上面的定位功能代碼真屯,使用LifeCycle實(shí)現(xiàn)以后是這樣的脸候,實(shí)現(xiàn)一個(gè)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
接口對(duì)象泵额,LifecycleOwner
是一個(gè)只有一個(gè)方法的接口getLifecycle()
,需要由子類來(lái)實(shí)現(xiàn)携添。
在Lib中已經(jīng)有實(shí)現(xiàn)好的子類嫁盲,我們可以直接拿來(lái)使用。比如LifecycleActivity
和LifecycleFragment
薪寓,我們只需要繼承此類就行了亡资。
當(dāng)然實(shí)際開發(fā)的時(shí)候我們會(huì)自己定義BaseActivity,Java是單繼承模式向叉,那么需要自己實(shí)現(xiàn)LifecycleRegistryOwner
接口锥腻。
如下所示即可,代碼很近簡(jiǎn)單
public class BaseFragment extends Fragment implements LifecycleRegistryOwner {
LifecycleRegistry lifecycleRegistry = new LifecycleRegistry(this);
@Override
public LifecycleRegistry getLifecycle() {
return lifecycleRegistry;
}
}
LiveData
LiveData 是一個(gè) Data Holder 類母谎,可以持有數(shù)據(jù)瘦黑,同時(shí)這個(gè)數(shù)據(jù)可以被監(jiān)聽的,當(dāng)數(shù)據(jù)改變的時(shí)候奇唤,可以觸發(fā)回調(diào)幸斥。但是又不像普通的Observable
,LiveData綁定了App的組件咬扇,LiveData可以指定在LifeCycle的某個(gè)狀態(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()
這個(gè)方法在LiveData在被激活的狀態(tài)下執(zhí)行懈贺,我們可以開始執(zhí)行一些操作经窖。
- onActive()
這個(gè)方法在LiveData在的失去活性狀態(tài)下執(zhí)行,我們可以結(jié)束執(zhí)行一些操作梭灿。
- setValue()
執(zhí)行這個(gè)方法的時(shí)候画侣,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
對(duì)象皮迟,也就是說(shuō)添加的對(duì)象必須是可以被LifeCycle管理的搬泥。
如果LifeCycle沒(méi)有觸發(fā)對(duì)對(duì)應(yīng)的狀態(tài)(STARTED or RESUMED),它的值被改變了万栅,那么Observe就不會(huì)被執(zhí)行佑钾,
如果LifeCycle被銷毀了,那么Observe將自動(dòng)被刪除烦粒。
實(shí)際上LiveData就提供一種新的供數(shù)據(jù)共享方式休溶〈蓿可以用它在多個(gè)Activity、Fragment等其他有生命周期管理的類中實(shí)現(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)點(diǎn):
沒(méi)有內(nèi)存泄露的風(fēng)險(xiǎn)窖壕,全部綁定到對(duì)應(yīng)的生命周期,當(dāng)LifeCycle被銷毀的時(shí)候杉女,它們也自動(dòng)被移除
降低Crash瞻讽,當(dāng)Activity被銷毀的時(shí)候,LiveData的Observer自動(dòng)被刪除熏挎,然后UI就不會(huì)再接受到通知
實(shí)時(shí)數(shù)據(jù)速勇,因?yàn)長(zhǎng)iveData是持有真正的數(shù)據(jù)的,所以當(dāng)生命周期又重新開始的時(shí)候坎拐,又可以重新拿到數(shù)據(jù)
正常配置改變烦磁,當(dāng)Activity或者Fragment重新創(chuàng)建的時(shí)候,可以從LiveData中獲取上一次有用的數(shù)據(jù)
不再需要手動(dòng)的管理生命周期
Transformations
有時(shí)候需要對(duì)一個(gè)LiveData做Observer哼勇,但是這個(gè)LiveData是依賴另外一個(gè)LiveData都伪,有點(diǎn)類似于RxJava中的操作符,我們可以這樣做积担。
- Transformations.map()
用于事件流的傳遞陨晶,用于觸發(fā)下游數(shù)據(jù)。
LiveData<User> userLiveData = ...;
LiveData<String> userName = Transformations.map(userLiveData, user -> {
user.name + " " + user.lastName
});
- Transformations.switchMap()
這個(gè)和map類似帝璧,只不過(guò)這個(gè)是用來(lái)觸發(fā)上游數(shù)據(jù)珍逸。
private LiveData<User> getUser(String id) {
// ...
}
LiveData<String> userId = ...;
LiveData<User> user = Transformations.switchMap(userId, id -> getUser(id) );
ViewModel
ViewModel是用來(lái)存儲(chǔ)UI層的數(shù)據(jù),以及管理對(duì)應(yīng)的數(shù)據(jù)聋溜,當(dāng)數(shù)據(jù)修改的時(shí)候,可以馬上刷新UI叭爱。
Android系統(tǒng)提供控件撮躁,比如Activity和Fragment,這些控件都是具有生命周期方法买雾,這些生命周期方法被系統(tǒng)調(diào)用把曼。
當(dāng)這些控件被銷毀或者被重建的時(shí)候,如果數(shù)據(jù)保存在這些對(duì)象中漓穿,那么數(shù)據(jù)就會(huì)丟失嗤军。比如在一個(gè)界面,保存了一些用戶信息晃危,當(dāng)界面重新創(chuàng)建的時(shí)候叙赚,就需要重新去獲取數(shù)據(jù)老客。當(dāng)然了也可以使用控件自動(dòng)再帶的方法,在onSaveInstanceState
方法中保存數(shù)據(jù)震叮,在onCreate中重新獲得數(shù)據(jù)胧砰,但這僅僅在數(shù)據(jù)量比較小的情況下。如果數(shù)據(jù)量很大苇瓣,這種方法就不能適用了尉间。
另外一個(gè)問(wèn)題就是,經(jīng)常需要在Activity中加載數(shù)據(jù)击罪,這些數(shù)據(jù)可能是異步的哲嘲,因?yàn)楂@取數(shù)據(jù)需要花費(fèi)很長(zhǎng)的時(shí)間。那么Activity就需要管理這些數(shù)據(jù)調(diào)用媳禁,否則很有可能會(huì)產(chǎn)生內(nèi)存泄露問(wèn)題眠副。最后需要做很多額外的操作,來(lái)保證程序的正常運(yùn)行损话。
同時(shí)Activity不僅僅只是用來(lái)加載數(shù)據(jù)的侦啸,還要加載其他資源,做其他的操作丧枪,最后Activity類變大光涂,就是我們常講的上帝類。也有不少架構(gòu)是把一些操作放到單獨(dú)的類中拧烦,比如MVP就是這樣忘闻,創(chuàng)建相同類似于生命周期的函數(shù)做代理,這樣可以減少Activity的代碼量恋博,但是這樣就會(huì)變得很復(fù)雜齐佳,同時(shí)也難以測(cè)試。
AAC中提供ViewModel可以很方便的用來(lái)管理數(shù)據(jù)债沮。我們可以利用它來(lái)管理UI組件與數(shù)據(jù)的綁定關(guān)系炼吴。ViewModel提供自動(dòng)綁定的形式,當(dāng)數(shù)據(jù)源有更新的時(shí)候疫衩,可以自動(dòng)立即的更新UI硅蹦。
下面是一個(gè)簡(jiǎn)單的代碼示例。
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實(shí)例的時(shí)候,ViewModel是通過(guò)ViewModelProvider保存在LifeCycle中,ViewModel會(huì)一直保存在LifeCycle中岳颇,直到Activity或者Fragment銷毀了粟矿,觸發(fā)LifeCycle被銷毀,那么ViewModel也會(huì)被銷毀的。下面是ViewModel的生命周期圖。
Room
Room是一個(gè)持久化工具,和ormlite生音、greenDao類似宁否,都是ORM工具。在開發(fā)中我們可以利用Room來(lái)操作sqlite數(shù)據(jù)庫(kù)久锥。
Room主要分為三個(gè)部分:
- Database
使用注解申明一個(gè)類家淤,注解中包含若干個(gè)Entity類,這個(gè)Database類主要負(fù)責(zé)創(chuàng)建數(shù)據(jù)庫(kù)以及獲取數(shù)據(jù)對(duì)象的瑟由。
- Entity
表示每個(gè)數(shù)據(jù)庫(kù)的總的一個(gè)表結(jié)構(gòu)絮重,同樣也是使用注解表示,類中的每個(gè)字段都對(duì)應(yīng)表中的一列歹苦。
- DAO
DAO是 Data Access Object的縮寫青伤,表示從從代碼中直接訪問(wèn)數(shù)據(jù)庫(kù),屏蔽sql語(yǔ)句殴瘦。
下面是官方給的結(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對(duì)象
AppDatabase db = Room.databaseBuilder(getApplicationContext(), AppDatabase.class, "database-name").build();
注意: Database最好設(shè)計(jì)成單利模式,否則對(duì)象太多會(huì)有性能的影響蚪腋。
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用來(lái)注解一個(gè)實(shí)體類丰歌,對(duì)應(yīng)數(shù)據(jù)庫(kù)一張表。默認(rèn)情況下屉凯,Room為實(shí)體中定義的每個(gè)成員變量在數(shù)據(jù)中創(chuàng)建對(duì)應(yīng)的字段立帖,我們可能不想保存到數(shù)據(jù)庫(kù)的字段,這時(shí)候就要用道@Ignore注解悠砚。
注意: 為了保存每一個(gè)字段晓勇,這個(gè)字段需要有可以訪問(wèn)的gettter/setter方法或者是public的屬性
Entity的參數(shù) primaryKeys
每個(gè)實(shí)體必須至少定義1個(gè)字段作為主鍵,即使只有一個(gè)成員變量灌旧,除了使用@PrimaryKey 將字段標(biāo)記為主鍵的方式之外绑咱,還可以通過(guò)在@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ù)庫(kù)表名枢泰。如果你想表都有一個(gè)不同的名稱描融,就可以在@Entity中使用tableName參數(shù)指定
@Entity(tableName = "users")
class User {
...
}
和tableName作用類似; @ColumnInfo注解是改變成員變量對(duì)應(yīng)的數(shù)據(jù)庫(kù)的字段名稱衡蚂。
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í),數(shù)據(jù)庫(kù)中某些字段或字段組必須是唯一的讳窟。通過(guò)將@Index的unique 設(shè)置為true,可以強(qiáng)制執(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
因?yàn)镾QLite是一個(gè)關(guān)系型數(shù)據(jù)庫(kù)丽啡,你可以指定對(duì)象之間的關(guān)系。盡管大多數(shù)ORM庫(kù)允許實(shí)體對(duì)象相互引用硬猫,但Room明確禁止补箍。實(shí)體之間沒(méi)有對(duì)象引用改执。
不能使用直接關(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;
}
外鍵是非常強(qiáng)大的辈挂,因?yàn)樗试S指定引用實(shí)體更新時(shí)發(fā)生的操作。例如裹粤,級(jí)聯(lián)刪除终蒂,你可以告訴SQLite刪除所有書籍的用戶如果用戶對(duì)應(yīng)的實(shí)例是由包括OnDelete =CASCADE在@ForeignKey注釋。ON_CONFLICT : @Insert(onConflict=REPLACE) REMOVE 或者 REPLACE
有時(shí)候可能還需要對(duì)象嵌套這時(shí)候可以用@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ù)訪問(wèn)對(duì)象Data Access Objects (DAOs)是一種抽象訪問(wèn)數(shù)據(jù)庫(kù)的干凈的方式遥诉。
DAO的Insert 操作
當(dāng)創(chuàng)建DAO方法并用@Insert注釋它時(shí)拇泣,生成一個(gè)實(shí)現(xiàn)時(shí)會(huì)在一個(gè)事務(wù)中完成插入所有參數(shù)到數(shù)據(jù)庫(kù)。
@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 操作
一個(gè)簡(jiǎn)單查詢示例
@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);
}
也可以帶多個(gè)參數(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);
}
返回子集
上面示例都是查詢一個(gè)表中的所有字段,結(jié)果用對(duì)應(yīng)的Entity即可苞笨,但是如果我只要其中的幾個(gè)字段债朵,那么該怎么使用呢?
比如上面的User瀑凝,我只需要firstName和lastName序芦,首先定義一個(gè)子集,然后結(jié)果改成對(duì)應(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ù)
有個(gè)這樣一個(gè)查詢需求芝加,比如要查詢某兩個(gè)地區(qū)的所有用戶,直接用sql中的in即可射窒,但是如果這個(gè)地區(qū)是程序指定的藏杖,個(gè)數(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是另外一個(gè)異步操作庫(kù)来吩,同樣也是支持的。
@Dao
public interface MyDao {
@Query("SELECT * from user where id = :id LIMIT 1")
public Flowable<User> loadUserById(int id);
}
支持Cursor
原始的Android系統(tǒng)查詢結(jié)果是通過(guò)Cursor來(lái)獲取的蔽莱,同樣也支持弟疆。
@Dao
public interface MyDao {
@Query("SELECT * FROM user WHERE age > :minAge LIMIT 5")
public Cursor loadRawUsersOlderThan(int minAge);
}
多表查詢
有時(shí)候數(shù)據(jù)庫(kù)存在范式相關(guān),數(shù)據(jù)拆到了多個(gè)表中盗冷,那么就需要關(guān)聯(lián)多個(gè)表進(jìn)行查詢怠苔,如果結(jié)果只是一個(gè)表的數(shù)據(jù),那么很簡(jiǎn)單仪糖,直接用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é)果是部分字段迫肖,同上面一樣,需要單獨(dú)定義一個(gè)POJO攒驰,來(lái)接受數(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)換
有時(shí)候Java定義的數(shù)據(jù)類型和數(shù)據(jù)庫(kù)中存儲(chǔ)的數(shù)據(jù)類型是不一樣的,Room提供類型轉(zhuǎn)換玻粪,這樣在操作數(shù)據(jù)庫(kù)的時(shí)候隅津,可以自動(dòng)轉(zhuǎn)換。
比如在Java中奶段,時(shí)間用Date表示饥瓷,但是在數(shù)據(jù)庫(kù)中類型確實(shí)long,這樣有利于存儲(chǔ)痹籍。
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ù)庫(kù)時(shí)候需要指定類型轉(zhuǎn)換呢铆,同時(shí)定義好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ù)庫(kù)升級(jí)
版本迭代中蹲缠,我們不可避免的會(huì)遇到數(shù)據(jù)庫(kù)升級(jí)問(wèn)題棺克,Room也為我們提供了數(shù)據(jù)庫(kù)升級(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");
}
};
遷移過(guò)程結(jié)束后线定,Room將驗(yàn)證架構(gòu)以確保遷移正確發(fā)生娜谊。如果Room發(fā)現(xiàn)問(wèn)題,則拋出包含不匹配信息的異常斤讥。
警告: 如果不提供必要的遷移纱皆,Room會(huì)重新構(gòu)建數(shù)據(jù)庫(kù),這意味著將丟失數(shù)據(jù)庫(kù)中的所有數(shù)據(jù)芭商。
輸出模式
可以在gradle中設(shè)置開啟輸出模式派草,便于我們調(diào)試,查看數(shù)據(jù)庫(kù)表情況铛楣,以及做數(shù)據(jù)庫(kù)遷移近迁。
android {
...
defaultConfig {
...
javaCompileOptions {
annotationProcessorOptions {
arguments = ["room.schemaLocation":
"$projectDir/schemas".toString()]
}
}
}
}
Sample
這里是官方示例,本人自己參考官方文檔和示例Android Architecture Components samples后簸州,也寫出了一個(gè)類似的示例項(xiàng)目XiaoxiaZhihu_AAC鉴竭,還請(qǐng)多多指教。
總結(jié)
原先IO是在5月底已經(jīng)結(jié)束岸浑,本來(lái)想盡快參照官方文檔和示例搏存,把API擼一遍,然后寫個(gè)Demo和文章來(lái)介紹一下矢洲。代碼示例早已經(jīng)寫出來(lái)了璧眠,但6月分工作太忙,然后又出差到北京,最后等到了6月底了蛆橡,才把這篇文章給寫出來(lái)了。中間可能有內(nèi)容以及改變掘譬,如果有發(fā)現(xiàn)穩(wěn)重有錯(cuò)誤泰演,請(qǐng)及時(shí)指出,不理賜教葱轩。同時(shí)以后做事一定要有始有終睦焕,確定的事一定要堅(jiān)持。
相關(guān)資料
Android Architecture Components
簡(jiǎn)單聊聊Android Architecture Componets
Android Architecture Components samples