我所理解的 Android Architecture Components
寫(xiě)在前面:
Android Architecture Components 是 Google 2017 年 I/O 大會(huì)提出的一種架構(gòu)解決方案。在此之前痕檬,Android 應(yīng)用大多數(shù)以 MVC MVP MVVM 等比較常見(jiàn)的架構(gòu)方式被構(gòu)建霎槐。看到這樣各自為戰(zhàn)的情況梦谜,再加上開(kāi)發(fā)者們強(qiáng)烈的意愿丘跌,Google 自然也坐不住了,推出了 AAC 這種架構(gòu)方式唁桩,看它的命名闭树,Android Architecture Components,是不是大有一統(tǒng)江湖的意思荒澡。
架構(gòu)變遷
在介紹 AAC 架構(gòu)之前报辱,想和大家聊聊之前常見(jiàn)架構(gòu)模式的變遷。
在軟件行業(yè)单山,無(wú)論是什么形式的架構(gòu)碍现,大家都有一個(gè)共識(shí),如果實(shí)踐是檢驗(yàn)真理的唯一標(biāo)準(zhǔn)米奸,那可測(cè)試性就是檢驗(yàn)架構(gòu)好壞的唯一標(biāo)準(zhǔn)昼接。好的架構(gòu)會(huì)盡量讓邏輯開(kāi)發(fā)者關(guān)注邏輯本身,并且減少模板代碼悴晰。
無(wú)架構(gòu)模式 -- MVC
大家可以想象一下慢睡,我們從把所有代碼寫(xiě)在一團(tuán)到有了 MVC 架構(gòu)模式逐工,有哪些提升?
- 可測(cè)試性顯著提升(雖然我知道很多公司和開(kāi)發(fā)者都沒(méi)有寫(xiě)單元測(cè)試的習(xí)慣)
- 數(shù)據(jù)隔離漂辐、關(guān)注點(diǎn)隔離
我們從沒(méi)有架構(gòu)泪喊,到 MVC,終于能做到「分層」了髓涯。
MVC - MVP
之后我們寫(xiě)著寫(xiě)著又發(fā)現(xiàn)袒啼,MVP 對(duì)比 MVC 更有優(yōu)勢(shì):
- 面向接口編程
- 將邏輯從 UI 組件(Activity、Fragment)轉(zhuǎn)移到 P 層复凳,進(jìn)一步提升可測(cè)試性
為什么邏輯放在 P 層要更合理呢瘤泪?因?yàn)槭紫?UI 組件是連接你和 OS 的那一層,也就是說(shuō)它并不完完全全屬于開(kāi)發(fā)者本身育八,會(huì)受到系統(tǒng)的影響,比如配置的更改赦邻、內(nèi)存不足重啟等等髓棋,所以盡量保證 UI 組件整潔,絕對(duì)會(huì)提升代碼的可測(cè)試性和穩(wěn)健性惶洲。
其次也符合 Passive View Pattern 的這種架構(gòu)建議模式按声,其實(shí)我們也可以把 Passive View Pattern 這種模式直接類(lèi)比到后臺(tái)和前端的關(guān)系,這也是為什么大多數(shù)公司后臺(tái)開(kāi)發(fā)者的數(shù)量大于前端開(kāi)發(fā)者的數(shù)量恬吕。
現(xiàn)存問(wèn)題
無(wú)論是 MVC MVP MVVM 它們存在什么問(wèn)題呢签则?
他們的模塊通信方式,始終是持有對(duì)象(或接口)铐料。對(duì)于 Android 系統(tǒng)來(lái)說(shuō)渐裂,每一個(gè)你在 Manifest 聲明的四大組件都有可能會(huì)突然死掉,這也造成了:
- 持有對(duì)象可能會(huì)有 NPE
- 會(huì)有可能內(nèi)存泄漏
- 會(huì)寫(xiě)很多生命周期相關(guān)的模板代碼
為了掩蓋或解決以上種種問(wèn)題钠惩,AAC 架構(gòu)就應(yīng)運(yùn)而生了柒凉,AAC 架構(gòu)包括了一系列組件,比如 LifeCycle Room LiveData ViewModel Paging 等等篓跛。其實(shí)無(wú)論是 MVC MVP MVVM 或者是其他的架構(gòu)方式膝捞,數(shù)據(jù)層的爭(zhēng)議是最小的,AAC 也提出了一種數(shù)據(jù)層的構(gòu)建方式 -- Repository愧沟,我們可以先來(lái)看看 Repository蔬咬。
Repository
一個(gè)可擴(kuò)展的、高內(nèi)聚的數(shù)據(jù)層應(yīng)該有哪些特性沐寺?
- 涵蓋多個(gè)數(shù)據(jù)來(lái)源(Network林艘、Database、File芽丹、Cache)
- 對(duì)數(shù)據(jù)進(jìn)行必要的邏輯處理(緩存北启、格式轉(zhuǎn)換等)
- 對(duì)外提供單一的數(shù)據(jù)出口(對(duì)外的方法應(yīng)該是 getData,而不是 getNetData/getCacheData)
舉個(gè)例子?
public class TasksRepository {
private final TasksDataSource mTasksRemoteDataSource;
private final TasksDataSource mTasksLocalDataSource;
// Prevent direct instantiation.
private TasksRepository(@NonNull TasksDataSource tasksRemoteDataSource,
@NonNull TasksDataSource tasksLocalDataSource) {
mTasksRemoteDataSource = checkNotNull(tasksRemoteDataSource);
mTasksLocalDataSource = checkNotNull(tasksLocalDataSource);
}
}
TasksRepository 是對(duì)外提供 Task 的數(shù)據(jù)層咕村,可以看到他持有了 tasksRemoteDataSource
和 tasksLocalDataSource
兩個(gè)對(duì)象场钉,這就屬于我們剛才提到的,多個(gè)數(shù)據(jù)來(lái)源懈涛。并且他們都實(shí)現(xiàn)了 TasksDataSource
符合面向接口編程逛万。
再來(lái)看它的一個(gè)對(duì)數(shù)據(jù)操作的方法:
/**
* Gets tasks from local data source (sqlite) unless the table is new or empty. In that case it
* uses the network data source. This is done to simplify the sample.
* <p>
* Note: {@link GetTaskCallback#onDataNotAvailable()} is fired if both data sources fail to
* get the data.
*/
@Override
public void getTask(@NonNull final String taskId, @NonNull final GetTaskCallback callback) {
checkNotNull(taskId);
checkNotNull(callback);
Task cachedTask = getTaskWithId(taskId);
// Respond immediately with cache if available
if (cachedTask != null) {
callback.onTaskLoaded(cachedTask);
return;
}
// Load from server/persisted if needed.
// Is the task in the local data source? If not, query the network.
mTasksLocalDataSource.getTask(taskId, new GetTaskCallback() {
@Override
public void onTaskLoaded(Task task) {
// Do in memory cache update to keep the app UI up to date
if (mCachedTasks == null) {
mCachedTasks = new LinkedHashMap<>();
}
mCachedTasks.put(task.getId(), task);
callback.onTaskLoaded(task);
}
@Override
public void onDataNotAvailable() {
mTasksRemoteDataSource.getTask(taskId, new GetTaskCallback() {
@Override
public void onTaskLoaded(Task task) {
// Do in memory cache update to keep the app UI up to date
if (mCachedTasks == null) {
mCachedTasks = new LinkedHashMap<>();
}
mCachedTasks.put(task.getId(), task);
callback.onTaskLoaded(task);
}
@Override
public void onDataNotAvailable() {
callback.onDataNotAvailable();
}
});
}
});
}
可以看到這個(gè)方法符合剛才所說(shuō)的,只對(duì)調(diào)用者提供單一的數(shù)據(jù)出口批钠,調(diào)用方根本不需要知道數(shù)據(jù)來(lái)自于緩存還是網(wǎng)絡(luò)還是數(shù)據(jù)庫(kù)宇植,并且也隱藏了緩存數(shù)據(jù)的邏輯,這就是一個(gè)典型的 Repository 構(gòu)建方式埋心。這個(gè)例子來(lái)自于 google sample todo-mvp
在 AAC 架構(gòu)中指郁,Repository 中數(shù)據(jù)庫(kù)為 Room,不過(guò)如果你有已經(jīng)在使用的拷呆,穩(wěn)定的數(shù)據(jù)庫(kù)解決方案闲坎,其實(shí)是沒(méi)有必要替換的,所以 Room 的使用不是本文的重點(diǎn)茬斧。數(shù)據(jù)庫(kù)中讀出來(lái)的數(shù)據(jù)腰懂,自然可以配合 LiveData,關(guān)于 LiveData 的特性项秉,一會(huì)再談绣溜。
所以以上所說(shuō)的 Repository 應(yīng)該是如圖所示:
ViewModel
可以試想一下,假如我們把 Repository 直接寫(xiě)在 UI 組件(Activity娄蔼、Fragment)里面怖喻,會(huì)造成哪些問(wèn)題呢?
- 違背了 UI 層盡量簡(jiǎn)單的原則贷屎,并不能很好的進(jìn)行測(cè)試
- 因?yàn)?Activity罢防、Fragment 可能因?yàn)榕渲酶摹?nèi)存不足唉侄,導(dǎo)致了視圖銷(xiāo)毀或者重建咒吐,此時(shí)就會(huì)產(chǎn)生大量的冗余邏輯代碼
為了解決以上問(wèn)題,需要一個(gè)數(shù)據(jù)和 UI 組件的中間層 -- ViewModel
官方文檔對(duì) ViewModel 的概括:
The ViewModel class is designed to store and manage UI-related data in a lifecycle conscious way. The ViewModel class allows data to survive configuration changes such as screen rotations.
也就是說(shuō) ViewModel 是基于生命周期感知的属划,來(lái)持有并管理 UI 層相關(guān)數(shù)據(jù)的類(lèi)恬叹,并且它可以在 app 的 config 發(fā)生改變重建時(shí),始終存活下來(lái)同眯。
看了 ViewModel 的描述和它的生命周期圖绽昼,可以總結(jié)下 ViewModel 的特性:
- 生命周期感知。一般在 UI 組件的
onCreate
方法中由開(kāi)發(fā)者手動(dòng)的創(chuàng)建须蜗,隨著 UI 組件真正銷(xiāo)毀調(diào)用onDestory
時(shí)硅确,調(diào)用onCleared
方法銷(xiāo)毀目溉。 - 與 UI 組件解耦。無(wú)論由于配置更改 UI 組件反復(fù)創(chuàng)建了多少個(gè)實(shí)例菱农,對(duì) ViewModel 來(lái)說(shuō)都是未知的缭付,ViewModel 也不需要關(guān)心,UI 組件最終拿到的都是第一次創(chuàng)建的那個(gè)保持原有數(shù)據(jù)的 ViewModel循未。并且 ViewModel 中也不應(yīng)該有任何 android * 包下的引用(除了 android * arch)陷猫,如果需要一個(gè) Application 的 Context,啟動(dòng)一些 Service 的話(huà)的妖,使用 AndroidViewModel 這個(gè)類(lèi)就可以了绣檬。
來(lái)看一個(gè)簡(jiǎn)單的例子:
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 Fragment {
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);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
User user = viewModel.getUser();
}
}
也許你覺(jué)得這個(gè) demo 看起來(lái)平平無(wú)奇,但是 ViewModel 自身的框架已經(jīng)為你做了以下工作:
- 隨著 Fragment 的
onDestory
嫂粟,ViewModel 自動(dòng)onCleared
- 配置更改 Fragment 重啟時(shí)娇未,ViewModel 未感知,始終攜帶數(shù)據(jù)存活
- ViewModel 不持有任何 View 相關(guān)的引用星虹。(在任何位置持有 Activity忘蟹、Fragment 都是有風(fēng)險(xiǎn)的,有可能造成 NPE搁凸、內(nèi)存泄漏)
肯定有同學(xué)會(huì)問(wèn),你 ViewModel 都不持有 View 的對(duì)象狠毯,那數(shù)據(jù)更改了护糖,怎么通知 View 呢?對(duì)了就是我們接下來(lái)要說(shuō)的觀察者模式嚼松,觀察者模式對(duì)于 MVC MVP 這種持有對(duì)象和接口的模式來(lái)說(shuō)嫡良,簡(jiǎn)直就是降維打擊,可以試想一下献酗,如果我的 View 層去觀察 ViewModel 數(shù)據(jù)的變化寝受,當(dāng) View 層被殺死,那最多也就是不再去觀察 ViewModel 了罕偎,ViewModel 對(duì)此也不在意很澄,所以就會(huì)減少特別多的 NPE 和內(nèi)存泄漏。
在 AAC 架構(gòu)中颜及,觀察者模式是通過(guò) LiveData 這個(gè)類(lèi)配合完成的甩苛。
LiveData
LiveData is an observable data holder class. Unlike a regular observable, LiveData is lifecycle-aware, meaning it respects the lifecycle of other app components, such as activities, fragments, or services. This awareness ensures LiveData only updates app component observers that are in an active lifecycle state.
從官方文檔的描述中,可以總結(jié)出被 LiveData 包裝的數(shù)據(jù)俏站,有這樣的特性:
- 生命周期感知讯蒲,LiveData 僅僅會(huì)在你 UI 組件處于 active 狀態(tài)時(shí),notify 數(shù)據(jù)肄扎,這也為我們避免了很多 NPE 和內(nèi)存泄漏
- 數(shù)據(jù)自動(dòng)更新墨林,當(dāng) UI 組件從 inactive 到 active 狀態(tài)時(shí)赁酝,假設(shè)在此期間有數(shù)據(jù)更新,LiveData 會(huì)將最新的數(shù)據(jù) notify 給 UI 組件旭等,比如 App 回到 Home 頁(yè)再切換回來(lái)酌呆。
- 自動(dòng)清除引用,當(dāng) UI 組件調(diào)用
onDestory
時(shí)辆雾,LiveData 會(huì)清除 LifeCycle 相關(guān)的引用和注冊(cè)
可以來(lái)看一下 ViewModel 配合 LiveData 應(yīng)該是怎樣的一個(gè)效果:
public class NameViewModel extends ViewModel {
// Create a LiveData with a String
private MutableLiveData<String> mCurrentName;
public MutableLiveData<String> getCurrentName() {
if (mCurrentName == null) {
mCurrentName = new MutableLiveData<String>();
}
return mCurrentName;
}
// Rest of the ViewModel...
}
public class NameActivity extends AppCompatActivity {
private NameViewModel mModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Other code to setup the activity...
// Get the ViewModel.
mModel = ViewModelProviders.of(this).get(NameViewModel.class);
// Create the observer which updates the 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);
}
};
// Observe the LiveData, passing in this activity as the LifecycleOwner and the observer.
mModel.getCurrentName().observe(this, nameObserver);
}
}
mCurrentName
被 LiveData 包裹肪笋,這個(gè)時(shí)候就賦予了 mCurrentName
被觀察的能力。當(dāng)數(shù)據(jù)發(fā)生更改時(shí)度迂,如果 NameActivity
是 active 狀態(tài)藤乙,則會(huì)回調(diào) onChanged
方法。
自然惭墓,我也可以主動(dòng)地去更改 LiveData 中的數(shù)據(jù)的值:
mButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
String anotherName = "John Doe";
mModel.getCurrentName().setValue(anotherName);
}
});
除此以外坛梁,還有 postValue
方法,將值直接拋到 UI 線程腊凶。setSource
方法划咐,綁定觀察數(shù)據(jù)源
上文中,提到了很多次生命周期的監(jiān)測(cè)钧萍,其實(shí)是與 LifeCycle 這個(gè)組件相關(guān)的褐缠,LifeCycle 這個(gè)組件就是典型的為了減少模板代碼而生的。同理如果你是在開(kāi)發(fā)邏輯中风瘦,寫(xiě)了很多模板型的代碼队魏,這個(gè)時(shí)候你就要考慮從架構(gòu)中優(yōu)化,減少這些不必要的模板代碼万搔。關(guān)于 Lifecycle胡桨,其實(shí)可說(shuō)的點(diǎn)并不是很多,有興趣可以去官方文檔查看瞬雹。
到目前為止昧谊,我的 UI 層和 ViewModel 通過(guò) LiveData 建立了可靠的觀察者模式:
當(dāng)發(fā)生了 View 層的銷(xiāo)毀,會(huì)有怎樣的結(jié)果呢酗捌?
這就是觀察者模式的優(yōu)勢(shì)所在呢诬,View 層銷(xiāo)毀,對(duì) ViewModel 來(lái)說(shuō)只是少了一個(gè)觀察者而已意敛,我不持有你的對(duì)象馅巷,我肯定不會(huì)內(nèi)存泄漏,也不會(huì)有什么 NPE〔菀觯現(xiàn)在看起來(lái) View 層和 ViewModel 層的通信方式已經(jīng)升級(jí)為觀察者模式了钓猬,那在另一側(cè),ViewModel 層和 Repository 也應(yīng)該有觀察者模式這種高級(jí)通信方式吧撩独。
@Dao
public interface ProductDao {
@Query("SELECT * FROM products")
LiveData<List<ProductEntity>> loadAllProducts();
}
可以看到我們從 Room 數(shù)據(jù)庫(kù)取出的數(shù)據(jù)直接拿 LiveData 包裝后敞曹,作為返回值拋出账月。
public class DataRepository {
private MediatorLiveData<List<ProductEntity>> mObservableProducts;
private DataRepository(final AppDatabase database) {
mDatabase = database;
mObservableProducts = new MediatorLiveData<>();
mObservableProducts.addSource(mDatabase.productDao().loadAllProducts(),
productEntities -> {
if (mDatabase.getDatabaseCreated().getValue() != null) {
mObservableProducts.postValue(productEntities);
}
});
}
/**
* Get the list of products from the database and get notified when the data changes.
*/
public LiveData<List<ProductEntity>> getProducts() {
return mObservableProducts;
}
}
DataRepository 從數(shù)據(jù)庫(kù)中取出數(shù)據(jù),構(gòu)建成 MediatorLiveData<List<ProductEntity>> mObservableProducts
對(duì)象澳迫,通過(guò) getProducts
方法對(duì) ViewModel 暴露數(shù)據(jù)局齿。
public class ProductListViewModel extends AndroidViewModel {
// MediatorLiveData can observe other LiveData objects and react on their emissions.
private final MediatorLiveData<List<ProductEntity>> mObservableProducts;
public ProductListViewModel(Application application) {
super(application);
mObservableProducts = new MediatorLiveData<>();
// set by default null, until we get data from the database.
mObservableProducts.setValue(null);
LiveData<List<ProductEntity>> products = ((BasicApp) application).getRepository()
.getProducts();
// observe the changes of the products from the database and forward them
mObservableProducts.addSource(products, mObservableProducts::setValue);
}
/**
* Expose the LiveData Products query so the UI can observe it.
*/
public LiveData<List<ProductEntity>> getProducts() {
return mObservableProducts;
}
}
通過(guò)代碼可以看到,通過(guò)一系列 LiveData 對(duì)象橄登,將 ViewModel 和 Repository 也建立了觀察者模式:
作為開(kāi)發(fā)者抓歼,我只需要在 ViewModel 調(diào)用 onCleared
方法時(shí),去掉 Repository 的引用就好了拢锹。
其他
1.ViewModel 是 onSaveInstanceState
方法的替代方案嗎谣妻?
ViewModel 的數(shù)據(jù)是在內(nèi)存層面持有的,官方文檔也反復(fù)提到過(guò)卒稳,ViewModel 也僅僅是在配置更改 UI 層重建時(shí)蹋半,可以存活的。當(dāng) App 由于系統(tǒng)內(nèi)存不足被殺死時(shí)充坑,ViewModel 也會(huì)銷(xiāo)毀减江,這個(gè)時(shí)候就需要借助 onSaveInstanceState
來(lái)保存一些輕量的數(shù)據(jù)。所以數(shù)據(jù)持久化這塊捻爷,ViewModel 解決了配置更改重啟的問(wèn)題辈灼,但是除此之外的,依然要依靠原有的方案也榄。
2.ViewModel 是 Fragment.setRetainInstance(true)
的替代方案嗎茵休?
Fragment.setRetainInstance(true)
這個(gè)方法的目的就是在配置更改時(shí),保留 Fragment 的實(shí)例手蝎,所以 ViewModel 完全可以成為這個(gè)方法的替代方案。
3.看起來(lái) RxJava 一樣可以完成 LiveData 所做的事
是的俐芯,如果你 RxJava 玩得好棵介,一樣能做出和 LiveData 一樣的效果,官網(wǎng)也明確表明吧史,如果你執(zhí)意使用 RxJava 替代 LiveData邮辽,請(qǐng)保證你能處理 UI 的生命周期,使你的 data streams 配合 UI 組件狀態(tài)正確地 pause destory贸营。(何必為難自己呢吨述,用 LiveData 多好),Google 也出了一個(gè)類(lèi)可以將二者配合使用 LiveDataReactiveStreams
總結(jié)
所以完整的 AAC 架構(gòu)圖應(yīng)為:
要想打造一個(gè)能處理繁重業(yè)務(wù)量的架構(gòu)是很不容易的钞脂,我也準(zhǔn)備在公司項(xiàng)目中揣云,使用 AAC 重構(gòu)一個(gè)模塊,有任何心得感受或者采坑了冰啃,會(huì)更新到這個(gè)文章中邓夕。
參考資料:
https://developer.android.com/topic/libraries/architecture/
https://github.com/googlesamples/android-architecture-components