摘要:?jiǎn)栴}為了防止銷毀時(shí)異步任務(wù)仍然在進(jìn)行所導(dǎo)致的內(nèi)存泄露,我們都會(huì)在方法中去取消異步任務(wù)。總結(jié)層可以天然自動(dòng)監(jiān)視銷毀,我一直在找尋如何優(yōu)雅的自動(dòng)取消異步任務(wù),在目前來(lái)看是最佳的方案。協(xié)程絕對(duì)是最先進(jìn)的,效率最高,最優(yōu)雅的技術(shù)棧組合。
前提在Android MVVM模式,我使用了Jetpack包中的ViewModel來(lái)實(shí)現(xiàn)業(yè)務(wù)層,當(dāng)然你也可以使用DataBinding,關(guān)于Android業(yè)務(wù)層架構(gòu)的選擇我在這篇文章中有更詳細(xì)的說(shuō)明:Android開發(fā)中API層的最佳實(shí)踐。
業(yè)務(wù)層無(wú)非就是網(wǎng)絡(luò)請(qǐng)求,存儲(chǔ)操作和數(shù)據(jù)處理操作,然后將處理好的數(shù)據(jù)更新給LiveData,UI層則自動(dòng)更新。其中網(wǎng)絡(luò)請(qǐng)求我是使用的協(xié)程來(lái)進(jìn)行,而不是線程。
問(wèn)題為了防止UI銷毀時(shí)異步任務(wù)仍然在進(jìn)行所導(dǎo)致的內(nèi)存泄露,我們都會(huì)在onCleared()方法中去取消異步任務(wù)。如何取消異步任務(wù)呢?懶惰的我們當(dāng)然不會(huì)在每個(gè)ViewModel中去取消,而是去定義一個(gè)BaseVM類來(lái)存儲(chǔ)每個(gè)Job對(duì)象,然后統(tǒng)一取消。代碼如下:
open class BaseVM : ViewModel(){
val jobs = mutableListOf()
override fun onCleared() {
super.onCleared()
jobs.forEach { it.cancel() }
}
}
//UserVM
class UserVM : BaseVM() {
val userData = StateLiveData()
fun login() {
jobs.add(GlobalScope.launch {
userData.postLoading()
val result = "https://lixiaojun.xin/api/login".http(this).get>().await()
if (result != null && result.succeed) {
userData.postValueAndSuccess(result.data!!)
} else {
userData.postError()
}
})
}
fun register(){
//...
}
}
這樣寫看起來(lái)簡(jiǎn)潔統(tǒng)一,但并不是最優(yōu)雅的,它有兩個(gè)問(wèn)題:
需要我們手動(dòng)取消,現(xiàn)在是9102年,不該啊
不夠靈活,它會(huì)傻瓜式的取消所有VM的異步任務(wù),如果我們某個(gè)VM的某個(gè)異步任務(wù)的需求是即使UI銷毀也要在后臺(tái)進(jìn)行(比如后臺(tái)上傳數(shù)據(jù)),那這個(gè)就不滿足需求了
我所期待最好的樣子是: 我們只需專注地執(zhí)行異步邏輯,它能夠自動(dòng)的監(jiān)視UI銷毀去自動(dòng)干掉自己,讓我能多一點(diǎn)時(shí)間打Dota。
分析有了美好的愿景后來(lái)分析一下目前代碼存在的問(wèn)題,我們使用GlobalScope開啟的協(xié)程并不能監(jiān)視UI生命周期,如果讓父ViewModel負(fù)責(zé)管理和產(chǎn)生協(xié)程對(duì)象,子ViewModel直接用父類產(chǎn)生的協(xié)程對(duì)象開啟協(xié)程,而父ViewModel在onCleared中統(tǒng)一取消所有的協(xié)程,這樣不就能實(shí)現(xiàn)自動(dòng)銷毀協(xié)程么。
當(dāng)我開始動(dòng)手的時(shí)候,發(fā)現(xiàn)Jetpack的ViewModel模塊最新版本正好增加了這個(gè)功能,它給每個(gè)ViewModel增加了一個(gè)擴(kuò)展屬性viewModelScope,我們使用這個(gè)擴(kuò)展屬性來(lái)開啟的協(xié)程就能自動(dòng)在UI銷毀時(shí)干掉自己。
首先,添加依賴,注意一定要是androidx版本的哦:
def lifecycle_version = "2.2.0-alpha01"
// ViewModel and LiveData
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
重寫上面的代碼:
open class BaseVM : ViewModel(){
override fun onCleared() {
super.onCleared()
//父類啥也不用做
}
}
//UserVM
class UserVM : BaseVM() {
val userData = StateLiveData()
fun login() {
viewModelScope.launch {
userData.postLoading()
val result = "https://lixiaojun.xin/api/login".http(this).get>().await()
if (result != null && result.succeed) {
userData.postValueAndSuccess(result.data!!)
} else {
userData.postError()
}
}
}
}
這個(gè)代碼就足夠優(yōu)雅了,再也不用關(guān)心什么時(shí)候UI銷毀,協(xié)程會(huì)關(guān)心,再也不會(huì)有內(nèi)存泄露產(chǎn)生。如果我們希望某個(gè)異步任務(wù)在UI銷毀時(shí)也執(zhí)行的話,還是用GlobalScope來(lái)開啟即可。
原理分析:viewModelScope的核心代碼如下:
private const val JOB_KEY = "androidx.lifecycle.ViewModelCoroutineScope.JOB_KEY"
val ViewModel.viewModelScope: CoroutineScope
get() {
val scope: CoroutineScope");this.getTag(JOB_KEY)
if (scope != null) {
return scope
}
return setTagIfAbsent(JOB_KEY,
CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main))
}
internal class CloseableCoroutineScope(context: CoroutineContext) : Closeable, CoroutineScope {
override val coroutineContext: CoroutineContext = context
override fun close() {
coroutineContext.cancel()
}
它大概做了這樣幾個(gè)事情:
給ViewModel增加了擴(kuò)展屬性viewModelScope,這樣的好處是使用起來(lái)更方便。
然后重寫viewModelScope屬性的getter方法,根據(jù)JOB_KEY取出CoroutineScope對(duì)象,目前來(lái)看JOB_KEY是固定的,后期可能增加多個(gè)Key。
如果CoroutineScope對(duì)象為空,則創(chuàng)建CloseableCoroutineScope對(duì)象并通過(guò)setTagIfAbsent方法進(jìn)行緩存,根據(jù)方法名能看出是線程安全的操作。
CloseableCoroutineScope類是一個(gè)自定義的協(xié)程Scope對(duì)象,接收一個(gè)協(xié)程對(duì)象,它只有一個(gè)close()方法,在該方法中取消協(xié)程
然后看下ViewModel的核心代碼:
public abstract class ViewModel {
// Can"t use ConcurrentHashMap, because it can lose values on old apis (see b/37042460)
@Nullable
private final Map mBagOfTags = new HashMap<>();
private volatile boolean mCleared = false;
@SuppressWarnings("WeakerAccess")
protected void onCleared() {
}
@MainThread
final void clear() {
mCleared = true;
if (mBagOfTags != null) {
synchronized (mBagOfTags) {
for (Object value : mBagOfTags.values()) {
// see comment for the similar call in setTagIfAbsent
closeWithRuntimeException(value);
}
}
}
onCleared();
}
//線程安全的進(jìn)儲(chǔ)協(xié)程對(duì)象
T setTagIfAbsent(String key, T newValue) {
T previous;
synchronized (mBagOfTags) {
//noinspection unchecked
previous = (T) mBagOfTags.get(key);
if (previous == null) {
mBagOfTags.put(key, newValue);
}
}
T result = previous == null ");if (mCleared) {
closeWithRuntimeException(result);
}
return result;
}
/**
* Returns the tag associated with this viewmodel and the specified key.
*/
@SuppressWarnings("TypeParameterUnusedInFormals")
T getTag(String key) {
//noinspection unchecked
synchronized (mBagOfTags) {
return (T) mBagOfTags.get(key);
}
}
private static void closeWithRuntimeException(Object obj) {
if (obj instanceof Closeable) {
try {
((Closeable) obj).close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
正如我們所想,ViewModel做了這樣幾個(gè)事情:
提供一個(gè)Map來(lái)存儲(chǔ)協(xié)程Scope對(duì)象,并提供了用來(lái)set和get的方法
在onCleared遍歷所有的Scope對(duì)象,調(diào)用他們的close,取消協(xié)程的執(zhí)行
整個(gè)執(zhí)行過(guò)程跟我們之前的分析差不多,通過(guò)讓父類來(lái)管理協(xié)程對(duì)象,并在onCleared中去干掉這些協(xié)程。
總結(jié)VM層可以天然自動(dòng)監(jiān)視UI銷毀,我一直在找尋如何優(yōu)雅的自動(dòng)取消異步任務(wù),viewModelScope在目前來(lái)看是最佳的方案。
有些人說(shuō)老子用MVP,不用MVVM。MVP架構(gòu)下邏輯層和UI層交互有這樣幾個(gè)方式:
為了解耦,定義接口互調(diào),調(diào)來(lái)調(diào)去繞彎子
用EventBus發(fā)消息,代碼大的話會(huì)有幾百個(gè)標(biāo)識(shí),很難管理
Kotlin的協(xié)程和高階函數(shù)也完全能夠碾壓它
如果3年前我會(huì)推薦你使用MVP,現(xiàn)在的話,相信我,用MVVM吧。ViewModel + Kotlin + 協(xié)程絕對(duì)是最先進(jìn)的,效率最高,最優(yōu)雅的技術(shù)棧組合。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/7845.html
前言 RxHttp截止本文發(fā)表已經(jīng)推廣了4個(gè)禮拜,目前已經(jīng)有了141個(gè)star,如下: showImg(https://user-gold-cdn.xitu.io/2019/5/20/16ad5f3b6d10d9be); 其中一文,Android 史上最優(yōu)雅的實(shí)現(xiàn)文件上傳、下載及進(jìn)度的監(jiān)聽更是得到了大神劉皇叔微信公眾號(hào)的推送,歡迎讀者關(guān)注劉皇叔微信公眾號(hào)「劉望舒」,每天都有精彩的文章推送,真的很棒...
摘要:跨域總結(jié)跨域思路跨域解決方案一般分為兩種前端解決,后端解決前端解決方案通過(guò)前端解決的思想就是,通過(guò)設(shè)置中間件把跨域的請(qǐng)求轉(zhuǎn)發(fā)一下,其實(shí)就是反向代理,比如想要訪問(wèn)豆瓣的接口很會(huì)有跨域問(wèn)題,但是如果請(qǐng)求的是就不存在跨域反向代理就是截取之后的請(qǐng)求 跨域總結(jié) 1.跨域思路 跨域解決方案一般分為兩種:前端解決,后端解決 1.1 前端解決方案 通過(guò)前端解決的思想就是,通過(guò)設(shè)置中間件把跨域的請(qǐng)求轉(zhuǎn)發(fā)...
摘要:接下來(lái)繼續(xù)介紹三種架構(gòu)模式,分別是查詢分離模式微服務(wù)模式多級(jí)緩存模式。分布式應(yīng)用程序可以基于實(shí)現(xiàn)諸如數(shù)據(jù)發(fā)布訂閱負(fù)載均衡命名服務(wù)分布式協(xié)調(diào)通知集群管理選舉分布式鎖和分布式隊(duì)列等功能。 SpringCloud 分布式配置 SpringCloud 分布式配置 史上最簡(jiǎn)單的 SpringCloud 教程 | 第九篇: 服務(wù)鏈路追蹤 (Spring Cloud Sleuth) 史上最簡(jiǎn)單的 S...
摘要:例如,在方面它主要能夠幫助你解決以下兩個(gè)問(wèn)題在主線程中執(zhí)行耗時(shí)任務(wù)導(dǎo)致的主線程阻塞,從而使發(fā)生。提供主線程安全,同時(shí)對(duì)來(lái)自于主線程的網(wǎng)絡(luò)回調(diào)磁盤操提供保障。在線程通過(guò)從數(shù)據(jù)庫(kù)取數(shù)據(jù),一旦數(shù)據(jù)返回,在主線程進(jìn)行處理。 showImg(https://segmentfault.com/img/bVbuqpM?w=800&h=320); 今天我們來(lái)聊聊Kotlin Coroutine,如果你...
閱讀 2836·2021-10-08 10:04
閱讀 3306·2021-09-10 11:20
閱讀 547·2019-08-30 10:54
閱讀 3354·2019-08-29 17:25
閱讀 2323·2019-08-29 16:24
閱讀 918·2019-08-29 12:26
閱讀 1495·2019-08-23 18:35
閱讀 1971·2019-08-23 17:53