摘要:問題為了防止銷毀時異步任務仍然在進行所導致的內存泄露,我們都會在方法中去取消異步任務。總結層可以天然自動監視銷毀,我一直在找尋如何優雅的自動取消異步任務,在目前來看是最佳的方案。協程絕對是最先進的,效率最高,最優雅的技術棧組合。
前提在Android MVVM模式,我使用了Jetpack包中的ViewModel來實現業務層,當然你也可以使用DataBinding,關于Android業務層架構的選擇我在這篇文章中有更詳細的說明:Android開發中API層的最佳實踐。
業務層無非就是網絡請求,存儲操作和數據處理操作,然后將處理好的數據更新給LiveData,UI層則自動更新。其中網絡請求我是使用的協程來進行,而不是線程。
問題為了防止UI銷毀時異步任務仍然在進行所導致的內存泄露,我們都會在onCleared()方法中去取消異步任務。如何取消異步任務呢?懶惰的我們當然不會在每個ViewModel中去取消,而是去定義一個BaseVM類來存儲每個Job對象,然后統一取消。代碼如下:
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(){
//...
}
}
這樣寫看起來簡潔統一,但并不是最優雅的,它有兩個問題:
需要我們手動取消,現在是9102年,不該啊
不夠靈活,它會傻瓜式的取消所有VM的異步任務,如果我們某個VM的某個異步任務的需求是即使UI銷毀也要在后臺進行(比如后臺上傳數據),那這個就不滿足需求了
我所期待最好的樣子是: 我們只需專注地執行異步邏輯,它能夠自動的監視UI銷毀去自動干掉自己,讓我能多一點時間打Dota。
分析有了美好的愿景后來分析一下目前代碼存在的問題,我們使用GlobalScope開啟的協程并不能監視UI生命周期,如果讓父ViewModel負責管理和產生協程對象,子ViewModel直接用父類產生的協程對象開啟協程,而父ViewModel在onCleared中統一取消所有的協程,這樣不就能實現自動銷毀協程么。
當我開始動手的時候,發現Jetpack的ViewModel模塊最新版本正好增加了這個功能,它給每個ViewModel增加了一個擴展屬性viewModelScope,我們使用這個擴展屬性來開啟的協程就能自動在UI銷毀時干掉自己。
首先,添加依賴,注意一定要是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()
}
}
}
}
這個代碼就足夠優雅了,再也不用關心什么時候UI銷毀,協程會關心,再也不會有內存泄露產生。如果我們希望某個異步任務在UI銷毀時也執行的話,還是用GlobalScope來開啟即可。
原理分析: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()
}
它大概做了這樣幾個事情:
給ViewModel增加了擴展屬性viewModelScope,這樣的好處是使用起來更方便。
然后重寫viewModelScope屬性的getter方法,根據JOB_KEY取出CoroutineScope對象,目前來看JOB_KEY是固定的,后期可能增加多個Key。
如果CoroutineScope對象為空,則創建CloseableCoroutineScope對象并通過setTagIfAbsent方法進行緩存,根據方法名能看出是線程安全的操作。
CloseableCoroutineScope類是一個自定義的協程Scope對象,接收一個協程對象,它只有一個close()方法,在該方法中取消協程
然后看下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();
}
//線程安全的進儲協程對象
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做了這樣幾個事情:
提供一個Map來存儲協程Scope對象,并提供了用來set和get的方法
在onCleared遍歷所有的Scope對象,調用他們的close,取消協程的執行
整個執行過程跟我們之前的分析差不多,通過讓父類來管理協程對象,并在onCleared中去干掉這些協程。
總結VM層可以天然自動監視UI銷毀,我一直在找尋如何優雅的自動取消異步任務,viewModelScope在目前來看是最佳的方案。
有些人說老子用MVP,不用MVVM。MVP架構下邏輯層和UI層交互有這樣幾個方式:
為了解耦,定義接口互調,調來調去繞彎子
用EventBus發消息,代碼大的話會有幾百個標識,很難管理
Kotlin的協程和高階函數也完全能夠碾壓它
如果3年前我會推薦你使用MVP,現在的話,相信我,用MVVM吧。ViewModel + Kotlin + 協程絕對是最先進的,效率最高,最優雅的技術棧組合。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/7845.html
前言 RxHttp截止本文發表已經推廣了4個禮拜,目前已經有了141個star,如下: showImg(https://user-gold-cdn.xitu.io/2019/5/20/16ad5f3b6d10d9be); 其中一文,Android 史上最優雅的實現文件上傳、下載及進度的監聽更是得到了大神劉皇叔微信公眾號的推送,歡迎讀者關注劉皇叔微信公眾號「劉望舒」,每天都有精彩的文章推送,真的很棒...
摘要:跨域總結跨域思路跨域解決方案一般分為兩種前端解決,后端解決前端解決方案通過前端解決的思想就是,通過設置中間件把跨域的請求轉發一下,其實就是反向代理,比如想要訪問豆瓣的接口很會有跨域問題,但是如果請求的是就不存在跨域反向代理就是截取之后的請求 跨域總結 1.跨域思路 跨域解決方案一般分為兩種:前端解決,后端解決 1.1 前端解決方案 通過前端解決的思想就是,通過設置中間件把跨域的請求轉發...
摘要:例如,在方面它主要能夠幫助你解決以下兩個問題在主線程中執行耗時任務導致的主線程阻塞,從而使發生。提供主線程安全,同時對來自于主線程的網絡回調磁盤操提供保障。在線程通過從數據庫取數據,一旦數據返回,在主線程進行處理。 showImg(https://segmentfault.com/img/bVbuqpM?w=800&h=320); 今天我們來聊聊Kotlin Coroutine,如果你...
閱讀 2809·2021-10-08 10:04
閱讀 3198·2021-09-10 11:20
閱讀 524·2019-08-30 10:54
閱讀 3306·2019-08-29 17:25
閱讀 2302·2019-08-29 16:24
閱讀 885·2019-08-29 12:26
閱讀 1447·2019-08-23 18:35
閱讀 1931·2019-08-23 17:53