面對愈來愈複雜的 App 需求,Google 官方發佈了Android 框架組件庫(Android Architecture Components )。爲開發者更好的開發 App 提供了很是好的樣本。這個框架裏的組件是配合 Android 組件生命週期的,因此它可以很好的規避組件生命週期管理的問題。今天咱們就來看看這個庫的使用。html
官方建議在架構 App 的時候遵循如下兩個準則:java
關注分離android
其中早期開發 App 最多見的作法是在 Activity 或者 Fragment 中寫了大量的邏輯代碼,致使 Activity 或 Fragment 中的代碼很臃腫,十分不易維護。如今不少 App 開發者都注意到了這個問題,因此前兩年 MVP 結構就很是有市場,目前普及率也很高。git
模型驅動UIgithub
模型持久化的好處就是:即便系統回收了 App 的資源用戶也不會丟失數據,並且在網絡不穩定的狀況下 App 依然能夠正常地運行。從而保證了 App 的用戶體驗。web
框架提供瞭如下幾個核心組件,咱們將經過一個實例來講明這幾個組件的使用。數據庫
假設要實現一個用戶信息展現頁面。這個用戶信息是經過REST API 從後臺獲取的。緩存
咱們使用 fragment (UserProfileFragment.java) 來實現用戶信息的展現頁面。爲了驅動 UI,咱們的數據模型須要持有如下兩個數據元素微信
咱們要建立 ViewModel 對象用於保存以上數據。網絡
那什麼是 ViewModel 呢?
A ViewModel provides the data for a specific UI component, such as a fragment or activity, and handles the communication with the business part of data handling, such as calling other components to load the data or forwarding user modifications. The ViewModel does not know about the View and is not affected by configuration changes such as recreating an activity due to rotation.
ViewModel 是一個框架組件。它爲 UI 組件 (fragment或activity) 提供數據,而且能夠調用其它組件加載數據或者轉發用戶指令。ViewModel 不會關心 UI 長什麼樣,也不會受到 UI 組件配置改變的影響,例如不會受旋轉屏幕後 activity 從新啓動的影響。所以它是一個與 UI 組件無關的。
public class UserProfileViewModel extends ViewModel { private String userId; private User user; public void init(String userId) { this.userId = userId; } public User getUser() { return user; } }
public class UserProfileFragment extends LifecycleFragment { private static final String UID_KEY = "uid"; private UserProfileViewModel viewModel; @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); String userId = getArguments().getString(UID_KEY); viewModel = ViewModelProviders.of(this).get(UserProfileViewModel.class); viewModel.init(userId); } @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { return inflater.inflate(R.layout.user_profile, container, false); } }
須要的是:因爲框架組件目前還處於預覽版本,這裏 UserProfileFragment
是繼承於 LifecycleFragment
而不是 Fragment
。待正式發佈版本以後 Android Support 包中的 Fragment
就會默認實現 LifecycleOwner
接口。而 LifecycleFragment
也是實現了 LifecycleOwner
接口的。即正式版本發佈時 Support 包中的 UI 組件類就是支持框架組件的。
如今已經有了 UI 組件和 ViewModel,那麼咱們如何將它們進行鏈接呢?這時候就須要用到 LiveData 組件了。
LiveData is an observable data holder. It lets the components in your app observe
LiveData
objects for changes without creating explicit and rigid dependency paths between them. LiveData also respects the lifecycle state of your app components (activities, fragments, services) and does the right thing to prevent object leaking so that your app does not consume more memory.
LiveData 的使用有點像 RxJava。所以徹底可使用 RxJava 來替代 LiveData 組件。
如今咱們修改一下 UserProfileViewModel
類
public class UserProfileViewModel extends ViewModel { ... private LiveData<User> user; public LiveData<User> getUser() { return user; } }
將 Useruser
替換成 LiveData<User>user
而後再修改 UserProfileFragment
類中
@Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); viewModel.getUser().observe(this, user -> { // update UI }); }
當用戶數據發生改變時,就會通知 UI 進行更新。ViewModel 與 UI 組件的交互就是這麼簡單。
但細心的朋友可能發現了:fragment 在 onActivityCreated
方法中添加了相應的監聽,可是沒有在其它對應的生命週期中移除監聽。有經驗的朋友就會以爲這是否是有可能會發生引用泄露問題呢?其實否則,LiveData 組件內部已經爲開發者作了這些事情。即 LiveData 會再正確的生命週期進行回調。
如今已經成功的把 ViewModel 與 UI 組件(fragment)進行了通訊。那麼 ViewModel 又是如何獲取數據的呢?
假設咱們的數據是經過REST API 從後天獲取的。咱們使用 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); }
ViewModel 能夠引用 Webservice
接口,可是這樣作違背了咱們在上文提到的關注分離準則。由於咱們推薦使用 Repository
模型對 Webservice
進行封裝。
Repository modules are responsible for handling data operations. They provide a clean API to the rest of the app. They know where to get the data from and what API calls to make when data is updated. You can consider them as mediators between different data sources (persistent model, web service, cache, etc.).
關於 Repository 模式能夠參考個人上一篇《App 組件化/模塊化之路——Repository模式》
如下是使用 Repository 封裝 WebService
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>() { @Override public void onResponse(Call<User> call, Response<User> response) { // error case is left out for brevity data.setValue(response.body()); } }); return data; } }
使用 Respository 模式抽象數據源接口,也能夠很方便地替換其它數據。這樣 ViewModel 也不用知道數據源究竟是來自哪裏。
從上文咱們知道 UserRepository
類須要有一個 WebService
實例才能工做。咱們能夠直接建立它,但這麼作咱們就必須知道它的依賴,並且會由不少重複的建立對象的代碼。這時候咱們可使用依賴注入。本例中咱們將使用 Dagger 2 來管理依賴。
修改 UserProfileViewModel
類,引用 Repository 而且經過 Dagger 2 對 Repository 的依賴進行管理。
public class UserProfileViewModel extends ViewModel { private LiveData<User> user; private UserRepository userRepo; @Inject // UserRepository parameter is provided by Dagger 2 public 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 change return; } user = userRepo.getUser(userId); } public LiveData<User> getUser() { return this.user; } }
前面咱們實現的 Repository 是隻有一個網絡數據源的。這樣作每次進入用戶信息頁面都須要去查詢網絡,用戶須要等待,體驗很差。所以在 Repository 中加一個緩存數據。
@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>() { @Override public void onResponse(Call<User> call, Response<User> response) { data.setValue(response.body()); } }); return data; } }
Android 框架提供了 Room 組件,爲 App 數據持久化提供瞭解決方案。
Room is an object mapping library that provides local data persistence with minimal boilerplate code. At compile time, it validates each query against the schema, so that broken SQL queries result in compile time errors instead of runtime failures. Room abstracts away some of the underlying implementation details of working with raw SQL tables and queries. It also allows observing changes to the database data (including collections and join queries), exposing such changes via LiveData objects. In addition, it explicitly defines thread constraints that address common issues such as accessing storage on the main thread.
Room 組件提供了數據庫操做,配合 LiveData 使用能夠監聽數據庫的變化,進而更新 UI 組件。
要使用 Room 組件,須要如下步驟:
@Entity
定義實體RoomDatabase
子類RoomDatabase
中引用 DAO 1. 用註解 @Entity
定義實體類
@Entity class User { @PrimaryKey private 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 { }
須要注意的是 MyDatabase
是抽象類,Room 組件爲咱們提供具體的實現。
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; @Inject public 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. 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 data Response 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()); } }); } }
目前爲止咱們的代碼就基本完成了。UI 組件經過 ViewModel 訪問數據,而 ViewModel 經過 LiveData 監聽數據的變化,而且使用 Repository 模式封裝數據源。這些數據源能夠是網絡數據,緩存以及持久化數據。
參考文檔:
https://developer.android.com/topic/libraries/architecture/guide.html#recommendedapparchitecture
https://github.com/googlesamples/android-architecture-components
微信關注咱們,能夠獲取更多