簡介
背景
這幾年 MVP 架構(gòu)在安卓界非常流行屑那,幾乎已經(jīng)成為主流框架,它讓業(yè)務(wù)邏輯 和 UI操作相對獨(dú)立,使得代碼結(jié)構(gòu)更清晰扯再。
MVVM 在前端火得一塌糊涂,而在安卓這邊卻基本沒見到幾個(gè)人在用址遇,看到介紹 MVVM 也最多是講 DataBinding 或 介紹思想的熄阻。偶爾看到幾篇提到應(yīng)用的,還是對谷歌官網(wǎng)的Architecture Components 文章的翻譯倔约。
相信大家看別人博客或官方文檔的時(shí)候秃殉,總會(huì)碰到一些坑。要么入門教程寫得太復(fù)雜(無力吐槽浸剩,前面寫一堆原理钾军,各種高大上的圖,然并卵绢要,到實(shí)踐部分一筆帶過吏恭,你確定真的是入門教程嗎)。要么就是簡單得就是一個(gè) hello world重罪,然后就沒有下文了(看了想罵人)樱哼。
實(shí)在看不下去的我,決定插手你的人生蛆封。
目錄
《安卓-深入淺出MVVM教程》大致分兩部分:應(yīng)用篇唇礁、原理篇。
采用循序漸進(jìn)方式惨篱,內(nèi)容深入淺出盏筐,符合人類學(xué)習(xí)規(guī)律,希望大家用最少時(shí)間掌握 MVVM砸讳。
應(yīng)用篇:
01 Hello MVVM (快速入門)
02 Repository (數(shù)據(jù)倉庫)
03 Cache (本地緩存)
04 State Lcee (加載/空/錯(cuò)誤/內(nèi)容視圖)
05 Simple Data Source (簡單的數(shù)據(jù)源)
06 Load More (加載更多)
07 DataBinding (數(shù)據(jù)與視圖綁定)
08 RxJava2
09 Dragger2
10 Abstract (抽象)
11 Demo (例子)
12-n 待定(歡迎 github 提建議)
原理篇
01 MyLiveData(最簡單的LiveData)
02-n 待定(并不是解讀源碼琢融,那樣太無聊了界牡,打算帶你從0擼一個(gè) Architecture)
關(guān)于提問
本人水平和精力有限,如果有大佬發(fā)現(xiàn)哪里寫錯(cuò)了或有好的建議漾抬,歡迎在本教程附帶的 github倉庫 提issue宿亡。
What?為什么不在博客留言纳令?考慮到國內(nèi)轉(zhuǎn)載基本無視版權(quán)的情況挽荠,一般來說你都不是在源出處看到這篇文章,所以留言我也一般是看不到的平绩。
教程附帶代碼
https://github.com/ittianyu/MVVM
應(yīng)用篇放在 app 模塊下圈匆,原理篇放在 implementation 模塊下。
每一節(jié)代碼采用不同包名捏雌,相互獨(dú)立跃赚。
前言
快一年沒更新安卓技術(shù)棧了,今年年初就發(fā)現(xiàn)谷歌正在推 Architecture 性湿。這是谷歌推出的快速實(shí)現(xiàn) MVVM 架構(gòu)的類庫纬傲。目前已經(jīng)是RC版本了,正式版應(yīng)該不遠(yuǎn)了肤频。體驗(yàn)完這么一個(gè)架構(gòu)之后叹括,只能用一個(gè)字形容。
雖然不打算長久的做安卓開發(fā)着裹,但在其位謀其職领猾,考慮到安卓社區(qū)的發(fā)展,有必要給大家漲漲姿勢骇扇。
思想
什么是 MVVM?
很簡單面粮, Model(數(shù)據(jù)) View(視圖) ViewModel(數(shù)據(jù)視圖管理器)
能不能具體點(diǎn)少孝?
Model:bean(實(shí)體類)、Data Source(Http請求相關(guān)熬苍、數(shù)據(jù)庫相關(guān))
View:xml稍走、View、Activity柴底、Fragment 等UI相關(guān)
ViewModel:這正是我要說的婿脸,先簡單理解為管理器
執(zhí)行流程
ViewModel 負(fù)責(zé)調(diào)用 Model(可以稱之為數(shù)據(jù)源),拿到結(jié)果后柄驻,更新自身狐树。而 View 與 ViewModel 雙向綁定(后面會(huì)講怎么實(shí)現(xiàn)綁定的),所以 View 就會(huì)自動(dòng)更新鸿脓。
這就是 MVVM 大致的思想抑钟。
環(huán)境配置
首先需要引入 Lifecycles, LiveData and ViewModel 這三個(gè)全家桶涯曲,而這些庫是放在谷歌 maven 上,所以你得在項(xiàng)目根 gradle 文件中加上谷歌 maven
allprojects {
repositories {
jcenter()
maven { url 'https://maven.google.com' }
}
}
然后在 app 模塊的 gradle 文件中加上如下 3 個(gè)庫在塔,為了便于后面正式版發(fā)布后修改版本號幻件,我們通過引用變量。
// Lifecycles, LiveData and ViewModel
compile "android.arch.lifecycle:runtime:$rootProject.lifecycleRuntime"
compile "android.arch.lifecycle:extensions:$rootProject.lifecycle"
annotationProcessor "android.arch.lifecycle:compiler:$rootProject.lifecycle"
相應(yīng)的蛔溃,你需要在根 gradle 文件中加上附加屬性(gradle 的語法)
ext {
lifecycle = '1.0.0-rc1'
lifecycleRuntime = '1.0.3'
}
Model
為了降低學(xué)習(xí)難度绰沥,打算寫一個(gè)展示用戶 id 和 name 的demo。
所以 bean 真的是簡單得只有2個(gè)屬性贺待,省略了 get 和 set 方法(讀者練習(xí)時(shí)自己加上)揪利。
public class User implements Serializable {
private int id;
private String name;
// ...getter setter and toString...
}
Model 就寫完了,what狠持?說好的數(shù)據(jù)源呢疟位?
慌,不要慌喘垂!下次再講甜刻。
View
所以相應(yīng)的,我們定義 2 個(gè) TextView 來展示 id 和 name正勒。
a_activity_user.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="vertical"
android:id="@+id/v_root"
android:gravity="center"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/tv_id"
android:gravity="center"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/tv_name"
android:gravity="center"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
然后在 Activity 中初始化并獲得引用得院。
這里需要注意,如果你的 support lib >= 26.0.0章贞,則直接繼承 AppCompatActivity 就好祥绞。否則需要繼承 LifecycleActivity。
public class UserActivity extends AppCompatActivity {
private TextView tvId;
private TextView tvName;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.a_activity_user);
initView();
initData();
}
private void initView() {
tvId = (TextView) findViewById(R.id.tv_id);
tvName = (TextView) findViewById(R.id.tv_name);
}
private void initData() {
// todo
}
}
Lifecycle
上面提到 LifecycleActivity鸭限,實(shí)際上還有 LifecycleFragment蜕径,類似的,在 support lib >= 26.0.0 時(shí)败京,默認(rèn)的 Activity 和 Fragment 基類里面已經(jīng)集成了兜喻,不需要你做修改了。具體可以看官方文檔 Lifecycle
用于管理 Activity / Freagment 生命周期赡麦,方便查詢當(dāng)前組件生命周期的狀態(tài)朴皆。對應(yīng)的關(guān)系如下圖。
那到底有什么用泛粹?
只要持有 Lifecycle涣达,就等于知道了容器的生命周期食茎。
比如漱抓,實(shí)現(xiàn) LifecycleObserver 之后崩瓤,就可以在狀態(tài)發(fā)生改變時(shí)及時(shí)響應(yīng)。也就是等同于讓 Model或其他也擁有了生命周期。(還有這種操作珠增?)
定義一個(gè)定位監(jiān)聽器超歌,可以在生命周期不同狀態(tài)下,做對應(yīng)的操作蒂教,再也不用在 Activity 中加生命周期注冊或取消操作了巍举。
class MyLocationListener implements LifecycleObserver {
private boolean enabled = false;
public MyLocationListener(Context context, Lifecycle lifecycle, Callback callback) {
...
}
@OnLifecycleEvent(Lifecycle.Event.ON_START)
void start() {
if (enabled) {
// connect
}
}
public void enable() {
enabled = true;
if (lifecycle.getState().isAtLeast(STARTED)) {
// connect if not connected
}
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
void stop() {
// disconnect if connected
}
}
而在 Activity 中,只需要傳入 lifecycle 就可以了凝垛。優(yōu)雅得不像話懊悯。
myLocationListener = new MyLocationListener(this, getLifecycle(), location -> {
// update UI
});
ViewModel
ViewModel 就類似于 MVP 中的 Presenter 或者 MVC 中的 Controller。(不清楚什么是 MVC MVP 也沒關(guān)系梦皮,就是個(gè)概念而已)
現(xiàn)在我們完全沒有必要自己定義 ViewModel 來管理 UI 生命周期炭分,谷歌大禮包中就有 ViewModel
所以很自然的我們寫出這樣的一個(gè)類。
public class UserViewModel extends ViewModel {}
你可能好奇 ViewModel剑肯,點(diǎn)進(jìn)去看發(fā)現(xiàn)就是只有一個(gè)空方法的抽象類捧毛。可以让网,這很谷歌呀忧。但從 onCleared 這方法名可以看出,這還是有用處的溃睹。
LiveData
接下來就是要去調(diào)用 Model 并和 View 綁定而账,在繼續(xù)往下講之前,還需要介紹一下 LiveData因篇,沒錯(cuò)泞辐,就是它,可以輕松的實(shí)現(xiàn)和 View 的綁定竞滓。
它能自動(dòng)響應(yīng)生命周期方法咐吼,可被觀察(數(shù)據(jù)改變時(shí)通知觀察者),所以可以實(shí)現(xiàn) Model 和 View 的綁定虽界。
具有以下優(yōu)點(diǎn):
- 避免內(nèi)存泄露:由于 Observer 和 Lifecycle 綁定汽烦,當(dāng) Lifecycle 被銷毀后,Observer 自動(dòng)被清理莉御。
- 避免崩潰:避免在 Activity 被銷毀后更新數(shù)據(jù)導(dǎo)致的崩潰情況
- 數(shù)據(jù)可共享:數(shù)據(jù)可在多個(gè) fragment 中共享。甚至可以通過單例的 LiveData 實(shí)現(xiàn)全局?jǐn)?shù)據(jù)共享
- 不需要在 Activity 中處理生命周期
- 數(shù)據(jù)更新及時(shí):當(dāng)數(shù)據(jù)在 UI 不可見的時(shí)更新了俗冻,在可見時(shí)礁叔,數(shù)據(jù)會(huì)及時(shí)更新到UI上
具體用法
創(chuàng)建一個(gè)可變的 LiveData,也就是 MutableLiveData迄薄。
并通過一個(gè)方法返回琅关。沒錯(cuò),就是這么簡單,只需要 new 出來就行了涣易。
// Create a LiveData with a String
private MutableLiveData<String> mCurrentName;
public MutableLiveData<String> getCurrentName() {
if (mCurrentName == null) {
mCurrentName = new MutableLiveData<String>();
}
return mCurrentName;
}
在 Activity / Fragment 中 觀察 LiveData画机。也就是當(dāng) LiveData 數(shù)據(jù)發(fā)生變化時(shí),自動(dòng)回調(diào)方法來更新 UI新症。
final Observer<String> nameObserver = new Observer<String>() {
@Override
public void onChanged(@Nullable final String newName) {
// Update the UI, in this case, a TextView.
mNameTextView.setText(newName);
}
};
getCurrentName().observe(this, nameObserver);
那什么時(shí)候會(huì)更新數(shù)據(jù)步氏?
更新數(shù)據(jù)一般是 UI 這邊觸發(fā)的,比如我們給按鈕綁定一個(gè)事件徒爹,點(diǎn)擊之后去更改 name荚醒。
mButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
String anotherName = "John Doe";
getCurrentName().setValue(anotherName);
}
});
是的,你想更新 UI 上的文本隆嗅,只需要修改 LiveData 中的數(shù)據(jù)即可界阁。
ViewModel + LiveData
講完 LiveData,我們回到 ViewModel胖喳。
為了顯示 id 和 name泡躯,我們需要一個(gè) LiveData<User>。
對外公開一個(gè) getUser 方法 和 setUsername 方法丽焊。
這里你會(huì)發(fā)現(xiàn) getUser 并不是線程安全的较剃,因?yàn)檫@個(gè)案例不會(huì)有在多線程中 getUser,所以沒必要加粹懒,實(shí)際上還可以把 user 初始化放到成員變量定義的地方重付。
public class UserViewModel extends ViewModel {
private MutableLiveData<User> user;
public LiveData<User> getUser() {
if (user == null)
user = new MutableLiveData<>();
return user;
}
public void setUsername(String username) {
user.setValue(new User(1, username));
}
}
View 和 ViewModel 綁定
之前提到了 LiveData,它可以方便的被觀察凫乖。
還有 ViewModel确垫,可以自動(dòng)響應(yīng)生命周期。
所以就是這兩個(gè)東西帽芽,方便的實(shí)現(xiàn)綁定操作删掀。
之前我已經(jīng)初始化了 UserActivity 的View,現(xiàn)在來完善 initData导街。
通常來說披泪,一個(gè) View 只和一個(gè) ViewModel 綁定。所以我們需要一個(gè) Activity 級別的單例 ViewModel搬瑰,我們可以通過 ViewModelProviders.of(this).get(UserViewModel.class) 來簡單的實(shí)現(xiàn)款票。
然后通過 userViewModel 取得 LiveData 并添加監(jiān)聽(綁定操作)。
這個(gè)回調(diào)方法里面泽论,我們就可以更新與該數(shù)據(jù)相關(guān)的 UI艾少。
沒錯(cuò),綁定就是這么簡單便捷翼悴。
private UserViewModel userViewModel;
private void initData() {
userViewModel = ViewModelProviders.of(this).get(UserViewModel.class);
userViewModel.getUser().observe(this, new Observer<User>() {
@Override
public void onChanged(@Nullable User user) {
updateView(user);
}
});
userViewModel.setUsername("ittianyu");
}
private void updateView(User user) {
tvId.setText(user.getId() + "");
tvName.setText(user.getName());
}
總結(jié)
有了這樣一個(gè)構(gòu)架缚够,以后就不需要為 n 處都要修改 View 導(dǎo)致的復(fù)雜的 UI 邏輯而頭疼了。
而數(shù)據(jù)請求也可以更純粹,不需要管 UI 是怎么顯示的谍椅。
簡單來說就是 請求數(shù)據(jù)完成并更新數(shù)據(jù)后误堡,通過通知 UI 去根據(jù)數(shù)據(jù)來渲染界面。
沒錯(cuò)雏吭,今后安卓也可以像前端那樣愉快的寫 UI 了锁施。也可以像后端那樣層次分明的專心處理數(shù)據(jù)了,妙不可言思恐。