淺談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)是這樣的:

新的架構(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的狀態(tài)

上面的定位功能代碼,使用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());

上面代碼中用到了aLifecycleOwnerLifecycleOwner接口對象,LifecycleOwner是一個只有一個方法的接口getLifecycle(),需要由子類來實現(xiàn)。

在Lib中已經(jīng)有實現(xiàn)好的子類,我們可以直接拿來使用。比如LifecycleActivityLifecycleFragment,我們只需要繼承此類就行了。

當(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

XiaoxiaZhihu_AAC

淺談Android Architecture Components

Room ORM 數(shù)據(jù)庫框架

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容