Architecture Components 是在 2017 年 Google I/O 大會上腌闯,Google 官方推出的一個構(gòu)建 Android 應用架構(gòu)的庫燎悍。它可以幫你避免在 Android 應用開發(fā)中常見的一些問題,比如:內(nèi)存泄露筹燕,管理組件生命周期等等。本文將介紹如何利用 Architecture Components 庫開發(fā)一個實際的 Android 應用 ArchitecturePractice,歡迎 fork 和 star舞丛。
本文主要分為以下兩部分:
- 介紹 Architecture Components 庫;
- 如何使用 Architecture Components 庫開發(fā)應用果漾。
關于 Architecture Components
應用開發(fā)者所面對的問題
在資源有限的移動設備中球切,在任何時候,系統(tǒng)都有可能為新的應用殺死一些原來的應用绒障,那么在原來應用中的組件(Activity吨凑、Fragment、Service等等)也會被銷毀户辱,這些組件的生命周期不受開發(fā)者控制鸵钝,而是由系統(tǒng)控制的,所以不要在應用程序組件中存儲任何應用數(shù)據(jù)和狀態(tài)焕妙,并且應用程序組件之間相互不要依賴蒋伦。
常見的構(gòu)建原則
如果不可以在應用程序組件中存儲應用數(shù)據(jù)和狀態(tài),那么該如何構(gòu)建應用呢焚鹊?這兒有兩條常見的構(gòu)建原則:
- 關注點分離:一個常見的錯誤是在 Activity 和 Fragment 中編寫所有的代碼痕届。任何和UI或者操作系統(tǒng)交互無關的代碼都盡量不要出現(xiàn)在這些類中韧献,盡量保持這些類的精簡會幫助你避免很多和生命周期相關的問題。最好減少對它們的依賴以提供一個穩(wěn)定的用戶體驗研叫。
- 通過 Model 驅(qū)動 UI锤窑,最好是持久化的 Model。最好使用持久化的數(shù)據(jù)有兩個原因:a. 如果系統(tǒng)銷毀應用釋放資源嚷炉,用戶也不用擔心丟失數(shù)據(jù)渊啰; b. 即使網(wǎng)絡連接不可靠或者斷網(wǎng),應用仍將繼續(xù)運行申屹。Model 是負責處理應用數(shù)據(jù)的組件绘证,Model 獨立運行于應用中的 View 和應用程序中的其他組件,因此 Model 和其他應用程序組件的生命周期無關哗讥∪履牵基于 Model 構(gòu)建的應用程序,其管理數(shù)據(jù)的職責明確杆煞,所以更容易測試魏宽,而且穩(wěn)定性更高。
主要內(nèi)容
處理生命周期
在 android.arch.lifecycle 包中提供了可以構(gòu)建生命周期感知的組件的類和接口决乎,這些組件可以根據(jù) Activity/Fragment 的生命周期自動調(diào)整它的行為队询。
- Lifecycle:它是一個持有 Activity/Fragment 生命周期狀態(tài)信息的類,并且允許其他對象觀察此狀態(tài)构诚。
-
LifecycleOwner:是一個具有單一方法的接口蚌斩。如果一個類實現(xiàn)了此接口,則該類中需要持有一個
Lifecycle
對象范嘱,并通過LifecycleOwner#getLifecycle()
方法返回該對象凳寺。
并不是只有 Activity 和 Fragment 才可以實現(xiàn) LifecycleOwner 接口的,任何和 Activity/Fragment 生命周期有關系的類都可以實現(xiàn)此接口彤侍。通過實現(xiàn)此接口肠缨,該類完全是生命周期可感知的,只需要對它進行初始化盏阶,它就可以進行自己的初始化和清理操作晒奕,而不受其 Activity/Fragment 的管理。詳細可以參看官方文檔說明:LifecycleOwner 實踐
LiveData
LiveData
是一個數(shù)據(jù)持有類名斟,它持有一個值并且該值可以被觀察脑慧。不同于普通的可觀察者,LiveData
遵從應用組件的生命周期砰盐,這樣 Observer
便可以指定一個其應該遵循的 Lifecycle
闷袒。
如果 Observer
所依附的 Lifecycle
處于 STARTED
或者 RESUMED
狀態(tài),則 LiveData
認為 Observer
處于活躍狀態(tài)岩梳。
可以感知組件生命周期的 LiveData
給我們提供了一種可能:可以在多個 Activity
囊骤、Fragment
之間共享它晃择。
使用 LiveData
會有以下幾個優(yōu)勢:
- 避免內(nèi)存泄露:因為
Observer
是綁定到Lifecycle
對象上的,當Lifecycle
對象被銷毀的時候也物,LiveData
對象也會被自動清除 - 不會因為
Activity
停止而使應用崩潰:如果Observer
所綁定的Lifecycle
處于閑置狀態(tài)(例如:Activity
處于后臺運行時)宫屠,他們不會接收到改變的事件 - 始終保持最新的數(shù)據(jù):如果一個
Lifecycle
重新啟動以后(例如:Activity
從后臺重新開始運行于前臺),它會接收到最新的數(shù)據(jù)(除非沒有最新的數(shù)據(jù)) - 正確處理配置改變:如果一個
Activity
或者Fragment
以為配置改變(例如:旋轉(zhuǎn)屏幕)被重建以后滑蚯,LiveData
將會接收到最新的數(shù)據(jù) - 資源共享:通過單例模式浪蹂,可以在多個
Activity
或者Fragment
之間共享LiveData
數(shù)據(jù)。 - 不再手動的處理生命周期:
Fragment
只有在處于活躍的時候才會觀察LiveData
數(shù)據(jù)告材。由于Fragment
提供了Lifecycle
對象坤次,所以LiveData
會管理這一切。
有時候斥赋,也許想在 LiveData
被下發(fā)到 Observer
之前浙踢,改變 LiveData
的值,或者是基于當前的 LiveData
下發(fā)另一個不同的 LiveData
值灿渴。Lifecycle
包中的 Transformations
可以實現(xiàn)這樣的功能。
-
Transformations.map()
胰舆,使用此方法骚露,可以將LiveData
傳遞到下游
LiveData<User> userLiveData = ...;
LiveData<String> userName = Transformations.map(userLiveData, user -> {
user.name + " " + user.lastName
});
-
Transformations.switchMap()
,和map()
方法類似缚窿,使用switchMap()
應用于LiveData
的值并解包棘幸,然后將結(jié)果傳遞到下游。傳遞給switchMap()
的方法必須返回一個Lifecycle
private LiveData<User> getUser(String id) {
...;
}
LiveData<String> userId = ...;
LiveData<User> user = Transformations.switchMap(userId, id -> getUser(id) );
使用這兩個轉(zhuǎn)換倦零,允許在整個調(diào)用鏈中攜帶觀察者的 Lifecycle
信息误续,這樣的話,只有在觀察者觀察到 LiveData
的返回值時扫茅,才會運算這些轉(zhuǎn)換蹋嵌。
當你需要在 ViewModel
中添加一個 Lifecycle
對象時,Transformations
或許是一個好的解決辦法葫隙。
ViewModel
ViewModel 類是用來存儲和管理 UI 相關的數(shù)據(jù)栽烂,這樣在配置發(fā)生變化(例如:屏幕旋轉(zhuǎn))時,數(shù)據(jù)就不會丟失恋脚。
由于應用程序組件(例如:Activity腺办、Fragment),具有一個由 Android Framework 管理的生命周期糟描,Activity 或 Fragment 在某些情況下(比如:內(nèi)存緊張或者屏幕旋轉(zhuǎn))會發(fā)生銷毀或者重新創(chuàng)建的情況怀喉。這樣就會帶來一些問題:
- 由于 Activity 或者 Fragment 有可能會被銷毀或重新創(chuàng)建,所以保存于其中的數(shù)據(jù)有可能會丟失
- 在 Activity 或者 Fragment 中會經(jīng)常發(fā)起一些需要一定時間才會返回結(jié)果的異步請求調(diào)用
- 如果把處理應用數(shù)據(jù)船响、完成響應用戶操作躬拢、處理系統(tǒng)通信工作的代碼都寫在 Activity 或者 Fragment 中躲履,那么 Activity 或者 Fragment 將會變得非常的臃腫,給維護工作帶來一定的困難
針對以上問題估灿,Lifecycle 提供了一個叫 ViewModel 的類崇呵,一個 UI 控制器的幫助類,用來為 UI 準備數(shù)據(jù)馅袁。
在配置更改的時候域慷,ViewModel 會被保留,以便其保存的數(shù)據(jù)可以立即傳遞給重新創(chuàng)建的 Activity 或者 Fragment 實例中汗销。如果 Activity 被重新創(chuàng)建犹褒,它將會收到由之前的 Activity 或者 Fragment 創(chuàng)建的 ViewModel 實例。當所有者 Activity 被銷毀以后弛针,F(xiàn)ramework 會調(diào)用 ViewModel#onCleared() 清楚系統(tǒng)資源叠骑。
在多個 Fragment 之間共享數(shù)據(jù)
在同一個 Activity 中的多個 Fragment 之間進行通信是十分常見的需求,目前通常的做法是新建一個接口削茁,并且用 Activity 將多個 Fragment 聯(lián)系起來宙枷。如果需要通信的數(shù)據(jù)比較多,就會出現(xiàn)接口泛濫的情況茧跋。
使用 ViewModel 可以解決這個痛點慰丛。在同一個 Activity 中的 Fragment 可以使用此 Activity 限定的 ViewModel 來處理該通訊。比如如下代碼所示:
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 onActivityCreated() {
model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
itemSelector.setOnClickListener(item -> {
model.select(item);
});
}
}
public class DetailFragment extends LifecycleFragment {
public void onActivityCreated() {
SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
model.getSelected().observe(this, { item ->
// 更新 UI
});
}
}
在上面示例代碼中瘾杭,獲取 ViewModelProvider 時兩個 Fragment 都使用 getActivity() 方法诅病,這就意味著,它們會收到同一個 Activity 限制的同一個 ViewModel 實例對象粥烁。這樣做有以下幾個優(yōu)點:
- Activity 不需要知道該通信的任何事情
- Fragment 之間不受相互影響贤笆。除了 ViewModel 之外,F(xiàn)ragment 不需要了解彼此讨阻,就算一個 Fragment 被銷毀了芥永,另一個也可以正常工作。而且每個 Fragment 都有自己獨立的生命周期钝吮,不受其他 Fragment 的影響恤左。
ViewModel 的生命周期
ViewModel 對象存在于內(nèi)存當中,直到傳遞給它的 Lifecycle 對象被完成的銷毀(Activity:被完全銷毀搀绣,F(xiàn)ragment:被完成移除)飞袋。其生命周期圖如下所示:
ViewModel vs SavedInstanceState
- ViewModels 提供了一種在配置更改時保存數(shù)據(jù)的簡便方式,但是如果應用進程被操作系統(tǒng)殺死链患,那么數(shù)據(jù)則沒有機會被恢復巧鸭。
- 通過 SavedInstanceState 保存的數(shù)據(jù),存在于操作系統(tǒng)進程的內(nèi)存中麻捻。當用戶離開應用數(shù)個小時之后纲仍,應用的進程很有可能被操作系統(tǒng)殺死呀袱,通過 SavedInstanceState 保存的數(shù)據(jù),則可以在 Activity 或者 Fragment 重新創(chuàng)建的時候郑叠,在其中的
onCreate()
方法中通過 Bundle 恢復數(shù)據(jù)夜赵。
Room Persistence Library
Room 在 SQLite 之上提供了一個抽象層,以便在利用 SQLite 全部功能的同時也可以流暢發(fā)訪問數(shù)據(jù)庫乡革。
在 Room 中有非常重要的三個類:
- Database:可以使用此組件創(chuàng)建一個數(shù)據(jù)庫持有者寇僧。使用注解定義實體列表,通過類的內(nèi)容定義數(shù)據(jù)庫中數(shù)據(jù)訪問對象列表沸版。它也是底層連接的主要切入點嘁傀。
注解的類應該是一個繼承了
RoomDatabase
的抽象類。在運行時视粮,可以通過Room.databaseBuilder()
或者Room.inMemoryDatabaseBuilder()
方法獲取單例细办。
- Entity:該組件代表了一個表示數(shù)據(jù)庫中某個數(shù)據(jù)表某一行的類。對于每個 Entity 類蕾殴,都會在數(shù)據(jù)庫中創(chuàng)建一個數(shù)據(jù)表保存該類的對象笑撞。必須通過
Database
中的entities
字段引用 Entity 類。Entity 類中的每個字段都會持久化到數(shù)據(jù)中钓觉,除非使用@Ignore
注解修飾 - DAO:該組件表示一個數(shù)據(jù)訪問對象(DAO)的類或者接口茴肥。DAO 是 Room 的主要組件,其職責是定義方法來訪問數(shù)據(jù)庫议谷。被
@Database
注解修飾的類必須包含一個沒有參數(shù)的抽象方法,該方法的返回值是被@Dao
注解的類堕虹。在編譯時生成代碼時卧晓,Room 創(chuàng)建該類的實現(xiàn)。
Room 中的三大組件與應用程序中其他部分的關系如下圖所示:
關于 Room Persistence Library 更加詳細的內(nèi)容赴捞,請參閱 Room Persistence Library 官方說明文檔逼裆。
Architecture Components 的使用
添加組件到項目
添加 Google Maven 倉庫
在應用工程的 build.gradle
文件中添加依賴對 Google Maven 的依賴:
allprojects {
repositories {
jcenter()
maven { url 'https://maven.google.com' }
}
}
添加 Architecture Components 組件
在應用或者模塊的 build.gradle
文件中添加對 Architecture Components 的依賴,如下所示:
// Lifecycles, LiveData 和 ViewModel
compile "android.arch.lifecycle:runtime:1.0.0-alpha5"
compile "android.arch.lifecycle:extensions:1.0.0-alpha5"
annotationProcessor "android.arch.lifecycle:compiler:1.0.0-alpha5"
// Room
compile "android.arch.persistence.room:runtime:1.0.0-alpha5"
annotationProcessor "android.arch.persistence.room:compiler:1.0.0-alpha5"
// 對 RxJava 的支持
compile "android.arch.persistence.room:rxjava2:1.0.0-alpha5"
// 對測試 Room 的支持
testCompile "android.arch.persistence.room:testing:1.0.0-alpha5"
具體應用
假如在項目中有類似于下面知乎列表這樣的一個頁面:
關于該頁面有如下兩個接口:
// 請求最新的知乎列表(下拉刷新)
https://news-at.zhihu.com/api/4/news/latest
// 上拉加載歷史列表(上拉加載更多)
https://news-at.zhihu.com/api/4/news/before/{date}
根據(jù)上面兩個接口和 “UI設計稿”赦政,再根據(jù) Architecture Components 組件中提供的類胜宇,我們一步步完成此頁面。首先我們分三步:
- UI 界面的實現(xiàn)
- 中間層 ViewModel(UI 界面和數(shù)據(jù)層連接的橋梁)
- 數(shù)據(jù)層(本地持久化數(shù)據(jù)和服務器端的網(wǎng)絡數(shù)據(jù))
View 界面
此頁面使用 Fragment 控制并顯示恢着,將其命名為 ZhihuListFragment.java
桐愉,其布局文件 fragment_zhihu_list.xml
如下所示:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/rl_zhihu_root"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/srl_zhihu"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/rv_zhihu_list"
android:name="com.lijiankun24.architecturepractice.fragment.GirlFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="LinearLayoutManager"
tools:context=".ui.fragment.GirlListFragment"
tools:listitem="@layout/fragment_girl_list_item"/>
</android.support.v4.widget.SwipeRefreshLayout>
<!-- ProgressBar顏色更改 http://www.voidcn.com/blog/dongbeitcy/article/p-5781104.html -->
<ProgressBar
android:id="@+id/bar_load_more_zhihu"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:indeterminate="true"
android:indeterminateTint="@color/colorPrimaryDark"
android:indeterminateTintMode="src_atop"/>
</RelativeLayout>
fragment_zhihu_list.xml
布局文件比較簡單,不需要多講掰派。
ZhihuListFragment.java
代碼如下所示:
/**
* ZhihuListFragment.java
* <p>
* Created by lijiankun on 17/7/30.
*/
public class ZhihuListFragment extends LifecycleFragment {
// ZhihuListFragment 所對應的 ViewModel 類的對象
private ZhihuListViewModel mListViewModel = null;
private SwipeRefreshLayout mRefreshLayout = null;
private ZhihuListAdapter mAdapter = null;
private ProgressBar mLoadMorebar = null;
private View mRLZhihuRoot = null;
// 自定義接口从诲,將 RecyclerView 的 Adapter 對其中每個 Item 的點擊事件會傳到 ZhihuListFragment 中。
private final OnItemClickListener<ZhihuStory> mZhihuOnItemClickListener =
new OnItemClickListener<ZhihuStory>() {
@Override
public void onClick(ZhihuStory zhihuStory) {
if (Util.isNetworkConnected(MyApplication.getInstance())) {
ZhihuActivity.startZhihuActivity(getActivity(), zhihuStory.getId(),
zhihuStory.getTitle());
} else {
Util.showSnackbar(mRLZhihuRoot, getString(R.string.network_error));
}
}
};
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_zhihu_list, container, false);
initView(view);
return view;
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
subscribeUI();
}
/**
* 將 ZhihuListFragment 對應的 ZhihuListViewModel 類中的 LiveData 添加注冊監(jiān)聽到
* 此 ZhihuListFragment
*/
private void subscribeUI() {
// 通過 ViewModelProviders 創(chuàng)建對應的 ZhihuListViewModel 對象
ZhihuListViewModel.Factory factory = new ZhihuListViewModel
.Factory(MyApplication.getInstance()
, Injection.getDataRepository(MyApplication.getInstance()));
mListViewModel = ViewModelProviders.of(this, factory).get(ZhihuListViewModel.class);
mListViewModel.getZhihuList().observe(this, new Observer<List<ZhihuStory>>() {
@Override
public void onChanged(@Nullable List<ZhihuStory> stories) {
if (stories == null || stories.size() <= 0) {
return;
}
L.i("size is " + stories.size());
mAdapter.setStoryList(stories);
}
});
mListViewModel.isLoadingZhihuList().observe(this, new Observer<Boolean>() {
@Override
public void onChanged(@Nullable Boolean aBoolean) {
if (aBoolean == null) {
return;
}
L.i("state " + aBoolean);
mRefreshLayout.setRefreshing(false);
mLoadMorebar.setVisibility(aBoolean ? View.VISIBLE : View.INVISIBLE);
}
});
mListViewModel.refreshZhihusData();
}
/**
* 初始化頁面 UI
*
* @param view Fragment 的 View
*/
private void initView(View view) {
if (view == null) {
return;
}
LinearLayoutManager layoutManager = new LinearLayoutManager(getContext());
mAdapter = new ZhihuListAdapter(getContext(), mZhihuOnItemClickListener);
RecyclerView recyclerView = view.findViewById(R.id.rv_zhihu_list);
recyclerView.setAdapter(mAdapter);
recyclerView.setLayoutManager(layoutManager);
recyclerView.addOnScrollListener(new ZhihuOnScrollListener());
mRefreshLayout = view.findViewById(R.id.srl_zhihu);
mRefreshLayout.setOnRefreshListener(new ZhihuSwipeListener());
mRefreshLayout.setColorSchemeResources(
android.R.color.holo_blue_bright,
android.R.color.holo_green_light,
android.R.color.holo_orange_light,
android.R.color.holo_red_light);
mLoadMorebar = view.findViewById(R.id.bar_load_more_zhihu);
mRLZhihuRoot = view.findViewById(R.id.rl_zhihu_root);
}
/**
* ZhihuSwipeListener 用于 SwipeRefreshLayout 下拉刷新操作
*/
private class ZhihuSwipeListener implements SwipeRefreshLayout.OnRefreshListener {
@Override
public void onRefresh() {
mAdapter.clearStoryList();
mListViewModel.refreshZhihusData();
}
}
/**
* ZhihuOnScrollListener 用于 RecyclerView 下拉到最低端時的上拉加載更多操作
*/
private class ZhihuOnScrollListener extends RecyclerView.OnScrollListener {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
LinearLayoutManager layoutManager = (LinearLayoutManager)
recyclerView.getLayoutManager();
int lastPosition = layoutManager
.findLastCompletelyVisibleItemPosition();
if (lastPosition == mAdapter.getItemCount() - 1) {
// 上拉加載更多數(shù)據(jù)
mListViewModel.loadNextPageZhihu();
}
}
}
}
在 ZhihuListFragment.java
中注釋已經(jīng)比較清楚靡羡,關于 UI 方面的不多講系洛,其中最重要的一個方法是 subscribeUI()
方法俊性。在該方法中,創(chuàng)建 ZhihuListViewModel 對象之后描扯,對 ZhihuListViewModel 中兩個重要的數(shù)據(jù)進行注冊觀察并更新 UI定页,兩個重要的數(shù)據(jù)分別是:LiveData<List<ZhihuStory>>
和 LiveData<Boolean>
:
-
LiveData<List<ZhihuStory>>
:表示從數(shù)據(jù)層獲取到的知乎列表的數(shù)據(jù) -
LiveData<Boolean>
:表示是否正在獲取數(shù)據(jù)的狀態(tài),以控制界面中加載動畫的顯示和隱藏
ViewModel 控制層
在 ZhihuListViewModel
類中需要三個 LiveData 類型的屬性绽诚。LiveData 類型的數(shù)據(jù)和 RxJava 中的 Observables
工作模式類似典徊,當 LiveData 持有的數(shù)據(jù)發(fā)生變化時,通知觀察者憔购。如下面代碼所示:
public class ZhihuListViewModel extends AndroidViewModel {
// 請求接口中查詢的日期參數(shù)
private MutableLiveData<String> mZhihuPageDate = new MutableLiveData<>();
// Zhihu 列表的數(shù)據(jù)
private final LiveData<List<ZhihuStory>> mZhihuList;
// 是否正在進行網(wǎng)絡請求的狀態(tài)參數(shù)
private final LiveData<Boolean> mIsLoadingZhihuList;
......
private ZhihuListViewModel(Application application) {
super(application);
// 使用 Transformations.switchMap() 方法宫峦,表示當 View 改變 mZhihuPageDate 參數(shù)的值時,則進行 zhihu 列表數(shù)據(jù)的請求
mZhihuList = Transformations.switchMap(mZhihuPageDate, new Function<String, LiveData<List<ZhihuStory>>>() {
@Override
public LiveData<List<ZhihuStory>> apply(String input) {
......
}
});
}
public LiveData<List<ZhihuStory>> getZhihuList() {
return mZhihuList;
}
public LiveData<Boolean> isLoadingZhihuList() {
return mIsLoadingZhihuList;
}
/**
* 下拉刷新玫鸟,獲取最新的 Zhihu 列表數(shù)據(jù)
*/
public void refreshZhihusData() {
mZhihuPageDate.setValue("today");
}
/**
* 上拉加載更多時导绷,獲取 Zhihu 歷史列表數(shù)據(jù)
*
* @param positon 表示列表滑動到最后一項
*/
public void loadNextPageZhihu(int positon) {
if (!Util.isNetworkConnected(MyApplication.getInstance())) {
return;
}
mZhihuPageDate.setValue(String.valueOf(positon));
}
public static class Factory extends ViewModelProvider.NewInstanceFactory {
@NonNull
private final Application mApplication;
public Factory(@NonNull Application application) {
mApplication = application;
}
@Override
public <T extends ViewModel> T create(Class<T> modelClass) {
return (T) new ZhihuListViewModel(mApplication);
}
}
}
注:由于 ViewModel 存活的時間可能會比個別的 activity 和 fragment 實例更長,所以它決不能引用 View屎飘,或任何持任何 activity(context)妥曲。如果 ViewModel 需要 Application 的 context(如:調(diào)用系統(tǒng)服務),可以繼承 AndroidViewModel 類钦购,可以在構(gòu)造函數(shù)中接受 Application檐盟。
上面示例的 ZhihuListViewModel 類是功能并不完整的 ViewModel 類,因為它只是向 View 層提供了操作 Zhihu 列表數(shù)據(jù)和監(jiān)聽數(shù)據(jù)請求狀態(tài)的接口押桃,那么 ZhihuListViewModel 該從哪里獲取數(shù)據(jù)呢葵萎?換句話說,數(shù)據(jù)源在哪里唱凯?
在 ArchitecturePractice 項目中羡忘,封裝了 DataRepository 類,表示所有數(shù)據(jù)的源頭磕昼。
那 ZhihuListViewModel 應該持有一個 DataRepository
對象卷雕,來獲取數(shù)據(jù)。完整的 ZhihuListViewModel
類如下所示:
/**
* ZhihuListViewModel.java
* <p>
* Created by lijiankun on 17/7/30.
*/
public class ZhihuListViewModel extends AndroidViewModel {
// 請求接口中查詢的日期參數(shù)
private MutableLiveData<String> mZhihuPageDate = new MutableLiveData<>();
// Zhihu 列表的數(shù)據(jù)
private final LiveData<List<ZhihuStory>> mZhihuList;
// 數(shù)據(jù)源
private DataRepository mDataRepository = null;
private ZhihuListViewModel(Application application, DataRepository dataRepository) {
super(application);
mDataRepository = dataRepository;
// 使用 Transformations.switchMap() 方法票从,當 View 改變 mZhihuPageDate 參數(shù)的值時漫雕,則進行 zhihu 列表數(shù)據(jù)的請求
mZhihuList = Transformations.switchMap(mZhihuPageDate, new Function<String, LiveData<List<ZhihuStory>>>() {
@Override
public LiveData<List<ZhihuStory>> apply(String input) {
return mDataRepository.getZhihuList(input);
}
});
}
/**
* 獲取 Zhihu 列表數(shù)據(jù)
*
* @return Zhihu 列表數(shù)據(jù)
*/
public LiveData<List<ZhihuStory>> getZhihuList() {
return mZhihuList;
}
/**
* 數(shù)據(jù)請求狀態(tài)由 DataRepository 控制堵未,包括下拉刷新和上拉加載更多
*
* @return 是否在進行數(shù)據(jù)請求
*/
public LiveData<Boolean> isLoadingZhihuList() {
return mDataRepository.isLoadingZhihuList();
}
/**
* 下拉刷新捌刮,獲取最新的 Zhihu 列表數(shù)據(jù)
*/
public void refreshZhihusData() {
mZhihuPageDate.setValue("today");
}
/**
* 上拉加載更多時,獲取 Zhihu 歷史列表數(shù)據(jù)
*
* @param positon 表示列表滑動到最后一項
*/
public void loadNextPageZhihu(int positon) {
if (!Util.isNetworkConnected(MyApplication.getInstance())) {
return;
}
mZhihuPageDate.setValue(String.valueOf(positon));
}
public static class Factory extends ViewModelProvider.NewInstanceFactory {
@NonNull
private final Application mApplication;
private final DataRepository mGirlsDataRepository;
public Factory(@NonNull Application application, DataRepository girlsDataRepository) {
mApplication = application;
mGirlsDataRepository = girlsDataRepository;
}
@Override
public <T extends ViewModel> T create(Class<T> modelClass) {
return (T) new ZhihuListViewModel(mApplication, mGirlsDataRepository);
}
}
}
Model 數(shù)據(jù)層
正如在 ViewModel 控制層中介紹的钞支,ArchitecturePractice 中所有的數(shù)據(jù)均由 DataRepository
類中獲取吟榴。在 DataRepository
中有兩個數(shù)據(jù)源:本地數(shù)據(jù)庫和遠端服務器发框,如果有網(wǎng),則從服務器獲取最新數(shù)據(jù),并保存在本地數(shù)據(jù)庫中梅惯;如果沒有網(wǎng)宪拥,則從本地數(shù)據(jù)庫中加載數(shù)據(jù)并顯示。則 DataRepository
的初步實現(xiàn)是這樣的:
/**
* DataRepository.java
* <p>
* Created by lijiankun on 17/7/7.
*/
public class DataRepository {
private static DataRepository INSTANCE = null;
// 從服務器獲取數(shù)據(jù)
private final DataSource mRemoteDataSource;
// 從本地數(shù)據(jù)庫獲取數(shù)據(jù)
private final DataSource mLocalDataSource;
private static Application sApplication = null;
private DataRepository(@NonNull DataSource remoteDataSource,
@NonNull DataSource localDataSource) {
mRemoteDataSource = remoteDataSource;
mLocalDataSource = localDataSource;
}
static DataRepository getInstance(@NonNull DataSource remoteDataSource,
@NonNull DataSource localDataSource,
Application application) {
if (INSTANCE == null) {
synchronized (DataRepository.class) {
if (INSTANCE == null) {
INSTANCE = new DataRepository(remoteDataSource, localDataSource);
sApplication = application;
}
}
}
return INSTANCE;
}
public LiveData<List<ZhihuStory>> getZhihuList(@NonNull String date) {
if (Util.isNetworkConnected(sApplication.getApplicationContext())) {
if (date.equals("today")) {
return mRemoteDataSource.getLastZhihuList();
} else {
return mRemoteDataSource.getMoreZhihuList(date);
}
} else {
if (date.equals("today")) {
return mLocalDataSource.getLastZhihuList();
} else {
return mLocalDataSource.getMoreZhihuList(date);
}
}
}
public LiveData<Boolean> isLoadingZhihuList() {
if (Util.isNetworkConnected(sApplication.getApplicationContext())) {
return mRemoteDataSource.isLoadingZhihuList();
} else {
return mLocalDataSource.isLoadingZhihuList();
}
}
}
其中的 DataSource
表示獲取數(shù)據(jù)的抽象層铣减,如下所示:
public interface DataSource {
......
/**
* Zhihu 相關方法
*/
LiveData<List<ZhihuStory>> getLastZhihuList();
LiveData<List<ZhihuStory>> getMoreZhihuList(String date);
LiveData<Boolean> isLoadingZhihuList();
}
此外還有兩個非常重要的類:RemoteDataSource
和 LocalDataSource
她君,這兩個類分別實現(xiàn)了 DataSource
接口。
RemoteDataSource
類代碼如下所示葫哗,從遠端服務器獲取數(shù)據(jù)使用的是 Retrofit
缔刹,并且對網(wǎng)絡請求進行簡單封裝,由 ApiManager 統(tǒng)一向外提供網(wǎng)絡請求接口:
/**
* RemoteDataSource.java
* <p>
* Created by lijiankun on 17/7/7.
*/
public class RemoteDataSource implements DataSource {
private static RemoteDataSource INSTANCE = null;
private final MutableLiveData<Boolean> mIsLoadingZhihuList;
private final MutableLiveData<List<ZhihuStory>> mZhihuList;
private final ApiZhihu mApiZhihu;
private String mZhihuPageDate;
{
mIsLoadingZhihuList = new MutableLiveData<>();
mZhihuList = new MutableLiveData<>();
}
private RemoteDataSource() {
mApiZhihu = ApiManager.getInstance().getApiZhihu();
}
public static RemoteDataSource getInstance() {
if (INSTANCE == null) {
synchronized (RemoteDataSource.class) {
if (INSTANCE == null) {
INSTANCE = new RemoteDataSource();
}
}
}
return INSTANCE;
}
@Override
public LiveData<List<ZhihuStory>> getLastZhihuList() {
mIsLoadingZhihuList.setValue(true);
mApiZhihu.getLatestNews()
.enqueue(new Callback<ZhihuData>() {
@Override
public void onResponse(Call<ZhihuData> call, Response<ZhihuData> response) {
if (response.isSuccessful()) {
mZhihuList.setValue(response.body().getStories());
refreshLocalZhihuList(response.body().getStories());
mZhihuPageDate = response.body().getDate();
}
mIsLoadingZhihuList.setValue(false);
}
@Override
public void onFailure(Call<ZhihuData> call, Throwable t) {
mIsLoadingZhihuList.setValue(false);
}
});
return mZhihuList;
}
@Override
public LiveData<List<ZhihuStory>> getMoreZhihuList(String date) {
mIsLoadingZhihuList.setValue(true);
mApiZhihu.getTheDaily(mZhihuPageDate)
.enqueue(new Callback<ZhihuData>() {
@Override
public void onResponse(Call<ZhihuData> call, Response<ZhihuData> response) {
if (response.isSuccessful()) {
mZhihuList.setValue(response.body().getStories());
refreshLocalZhihuList(response.body().getStories());
mZhihuPageDate = response.body().getDate();
}
mIsLoadingZhihuList.setValue(false);
}
@Override
public void onFailure(Call<ZhihuData> call, Throwable t) {
mIsLoadingZhihuList.setValue(false);
}
});
return mZhihuList;
}
@Override
public MutableLiveData<Boolean> isLoadingZhihuList() {
return mIsLoadingZhihuList;
}
private void refreshLocalZhihuList(List<ZhihuStory> zhihuStoryList) {
if (zhihuStoryList == null || zhihuStoryList.isEmpty()) {
return;
}
AppDatabaseManager.getInstance().insertZhihuList(zhihuStoryList);
}
}
LocalDataSource
類代碼如下所示劣针,從本地數(shù)據(jù)庫獲取數(shù)據(jù)使用的是 Architecture Components 中的 Room
庫校镐,簡單封裝為 AppDatabaseManager ,向外提供統(tǒng)一的方法:
/**
* LocalDataSource.java
* <p>
* Created by lijiankun on 17/7/7.
*/
public class LocalDataSource implements DataSource {
private static LocalDataSource INSTANCE = null;
private LocalDataSource() {
}
public static LocalDataSource getInstance() {
if (INSTANCE == null) {
synchronized (LocalDataSource.class) {
if (INSTANCE == null) {
INSTANCE = new LocalDataSource();
}
}
}
return INSTANCE;
}
......
@Override
public LiveData<List<ZhihuStory>> getLastZhihuList() {
return AppDatabaseManager.getInstance().loadZhihuList();
}
@Override
public LiveData<List<ZhihuStory>> getMoreZhihuList(String date) {
return null;
}
@Override
public LiveData<Boolean> isLoadingZhihuList() {
return AppDatabaseManager.getInstance().isLoadingZhihuList();
}
}
使用到的 ApiManager
用于統(tǒng)一管理 Retrofit 網(wǎng)絡請求捺典,AppDatabaseManager
則是對 Room 數(shù)據(jù)庫的統(tǒng)一管理鸟廓,關于 Retrofit 和 Room 的使用就不再多說。這樣一個向外提供干凈可靠 API 的數(shù)據(jù)源 DataRepository 模塊則完成了襟己,DataRepository 主要負責處理數(shù)據(jù)的獲取引谜。
至此,關于 Architecture Components 組件的介紹和實踐都全部介紹完畢擎浴,本文中用于舉例的 ArchitecturePractice 在 GitHub 上员咽,歡迎 star 和 fork,也歡迎通過下面二維碼下載 APK 體驗贮预,如果有什么問題歡迎指出贝室。我的工作郵箱:jiankunli24@gmail.com
參考資料:
Android Architecture Components 官方文檔
Google 官方推出應用開發(fā)架構(gòu)指南 -- Hevin
譯 Architecture Components 之 Guide to App Architecture -- zly394