前言
說起程式員人們的第一印象就是工資高、加班兇、話少錢多頭發少。再加上現在科技網際網路公司太吃香,bat、華為小米等公司程式員加班情況被廣泛傳播,程式員用生命在敲代碼的印象刻在了很多人的心裡。

與其它行業一樣,凡是有進階和普通,雖然都是敲代碼但也有大牛和普通之分,大牛程式員,一個人比一個團隊做項目都做得快,最為出名的當屬十幾年前求伯君在做wps時,一個人完成了微軟二十人團隊沒有完成的項目需求,也讓wps在與微軟的競争中站穩了腳跟。程式員的能力差距真的比貧富的差距還要大。
1、MVVM架構模式概覽
這是使用MVVM架構模式+Kotlin協程+JetPack(ViewModel+LiveData)+Retrofit的架構,實作WanAndroid登入接口的小DEMO,後續會慢慢完善WanAndroid用戶端
1、ViewModel
為了從界面控制器Activity/Fragment邏輯中分離出視圖View資料所有權,架構元件為界面控制器提供了 ViewModel 輔助程式類,該類負責為界面準備資料。在配置更改期間會自動保留 ViewModel 對象,以便它們存儲的資料立即可供下一個 Activity 或 Fragment 執行個體使用。
2、LiveData
LiveData 是一種可觀察的資料存儲器類,具有生命周期感覺能力,意指它遵循其他應用元件如 Activity、Fragment 或 Service 生命周期,可確定 LiveData 僅更新處于活躍生命周期狀态的應用元件觀察者。LiveData 對象通常存儲在 ViewModel 對象中,并可通過 getter 方法進行通路。
3、Kotlin協程
協程依附線上程上,可以實作順序編寫異步代碼,自動進行線程切換。并且ViewModelScope為應用中的每個 ViewModel 定義了 ViewModelScope。如果 ViewModel 已清除,則在此範圍内啟動的協程都會自動取消。
4、Retrofit
将服務接口中的網絡請求函數聲明為suspend挂起接口函數,以支援Kotlin線程,并将suspend函數結果作為 LiveData 對象傳送。
2、ViewModel
//擷取ViewModel
viewModel = ViewModelProvider(this).get(MainViewModel::class.java)`
ViewModel 對象存在的時間範圍是擷取 ViewModel 時傳遞給 ViewModelProvider 的 Lifecycle。ViewModel 将一直留在記憶體中,直到限定其存在時間範圍的 Lifecycle 永久消失:對于 Activity,是在 Activity 完成時;而對于 Fragment,是在 Fragment 分離時。
3、LiveData
//對User資料進行觀察
viewModel.user.observe(this, Observer {
//展示登入結果
if (it.errorCode == 0) {
Toast.makeText(this, it.data?.nickname, Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(this, it.errorMsg, Toast.LENGTH_SHORT).show()
}
})
使用 LiveData 具有以下優勢:確定界面符合資料狀态
LiveData 遵循觀察者模式。當生命周期狀态發生變化時,LiveData 會通知 Observer 對象。您可以整合代碼以在這些 Observer 對象中更新界面。觀察者可以在每次發生更改時更新界面,而不是在每次應用資料發生更改時更新界面。
不會發生記憶體洩漏
觀察者會綁定到 Lifecycle 對象,并在其關聯的生命周期遭到銷毀後進行自我清理。
不會因 Activity 停止而導緻崩潰
如果觀察者的生命周期處于非活躍狀态(如傳回棧中的 Activity),則它不會接收任何 LiveData 事件。
不再需要手動處理生命周期
界面元件隻是觀察相關資料,不會停止或恢複觀察。LiveData 将自動管理所有這些操作,因為它在觀察時可以感覺相關的生命周期狀态變化。
資料始終保持最新狀态
如果生命周期變為非活躍狀态,它會在再次變為活躍狀态時接收最新的資料。例如,曾經在背景的 Activity 會在傳回前台後立即接收最新的資料。
适當的配置更改
如果由于配置更改(如裝置旋轉)而重新建立了 Activity 或 Fragment,它會立即接收最新的可用資料。
共享資源
可以使用單一執行個體模式擴充 LiveData 對象以封裝系統服務,以便在應用中共享它們。
4、Kotlin協程
4.1、異步的本質
什麼是異步?
異步就是同時進行一個以上彼此目的不同的任務。
但是對于有前後依賴關系的任務,異步該如何處理呢?
利用異步中的回調機制處理。
為什麼需要異步回調機制?
因為不同的任務之間存在前後的依賴關系。
異步回調機制有什麼缺點?
代碼結構過分耦合,遇到多重函數回調的嵌套耦合,也就是回調地獄,代碼會難以維護。
解決回調地獄的方案有什麼?
鍊式調用結構。
常見方式就是使用RxJava,它是反應函數式程式設計在Java中的實作。
但是RxJava中流的建立、轉化與消費都需要使用到各種類和豐富的操作符,加大了RxJava的學習成本。
減少在無封裝情況下使用RxJava,因為你無法保證團隊裡面的每一個成員都能看懂它,并且在修改時都能做出正确選擇。
在串行的執行中,雖然代碼确實是順序執行的,但其實是在不同的線程上順序執行的。那為什麼在串行的執行中代碼執行順序一緻,卻還要使用回調呢?
因為串行的執行中,執行是阻塞式的,主線程的阻塞會導緻很嚴重的問題,是以所有的耗時操作不能在主線程中執行,是以就需要多線程并行來執行。
在并行的執行中,異步回調其實就是代碼的多線程順序執行。那能不能既按照順序的方式編寫代碼,又可以讓代碼在不同的線程順序執行,自動完成線程的切換工作呢?
那就是Kotlin協程。
Kotlin 的協程是一種無棧協程的實作,它的控制流轉依靠對協程體本身編譯生成的狀态機的狀态流轉來實作,變量儲存也是通過閉包文法來實作的。
結論:
異步回調就是代碼的多線程順序執行,而Kotlin協程可以實作順序編寫異步代碼,自動進行線程切換。
那麼協程自動進行線程切換的原理是什麼?
Yield:讓出CPU,放棄排程控制權,回到上一次Resume的地方
Resume:擷取排程控制權,繼續執行程式,到上一次Yield的地方
例子:
1. GlobalScope.launch發起了一個協程,并在IO線程上執行,
2\. 在協程裡,去調用接口擷取結果。
3. 拿到結果,使用withContext(Dispatchers.Main)切換到主線程并更新界面
4.2、協程的類型
是協程範圍,指的是協程内的代碼運作的時間周期範圍,如果超出了指定的協程範圍,協程會被取消執行。
GlobalScope
指的是與應用程序相同的協程範圍,也就是在程序沒有結束之前協程内的代碼都可以運作。
JetPack中提供的生命周期感覺型協程範圍:
ViewModelScope,為應用中的每個 ViewModel 定義了 ViewModelScope。如果 ViewModel 已清除,則在此範圍内啟動的協程都會自動取消。
LifecycleScope,為每個 Lifecycle 對象定義了 LifecycleScope。在此範圍内啟動的協程會在 Lifecycle 被銷毀時取消。
使用 LiveData 時,可能需要異步計算值。可以使用 liveData 建構器函數調用 suspend 函數,并将結果作為 LiveData 對象傳送。
相關連結:https://developer.android.google.cn/topic/libraries/architecture/coroutines
4.3、協程的啟動
launch方法:
/**
* 重要知識:ViewModel+協程
*/
fun ViewModel.launch(
block: suspend CoroutineScope.() -> Unit,
onError: (e: Throwable) -> Unit = {},
onComplete: () -> Unit = {}
) {
viewModelScope.launch(CoroutineExceptionHandler { _, e -> onError(e) }) {
try {
block.invoke(this)
} finally {
onComplete()
}
}
}
源碼:
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job {
val newContext = newCoroutineContext(context)
val coroutine = if (start.isLazy)
LazyStandaloneCoroutine(newContext, block) else
StandaloneCoroutine(newContext, active = true)
coroutine.start(start, coroutine, block)
return coroutine
}
4.3.1、launch方法解釋
context
協程上下文,可以指定協程運作的線程。預設與指定的CoroutineScope中的coroutineContext保持一緻,比如GlobalScope預設運作在一個背景工作線程内。也可以通過顯示指定參數來更改協程運作的線程,Dispatchers提供了幾個值可以指定:Dispatchers.Default、Dispatchers.Main、Dispatchers.IO、Dispatchers.Unconfined。
start
協程的啟動模式。預設的CoroutineStart.DEFAULT是指協程立即執行,除此之外還有CoroutineStart.LAZY、CoroutineStart.ATOMIC、CoroutineStart.UNDISPATCHED。
block
協程主體。也就是要在協程内部運作的代碼,可以通過lamda表達式的方式友善的編寫協程内運作的代碼。
CoroutineExceptionHandler
指定CoroutineExceptionHandler來處理協程内部的異常。
Job
傳回值,對目前建立的協程的引用。可以通過Job的start、cancel、join等方法來控制協程的啟動和取消。
4.4、suspend挂起函數
suspend關鍵字隻起到了标志這個函數是一個耗時操作,必須放在協程中執行的作用,而withContext方法則進行了線程的切換工作。
協程中的代碼自動地切換到其他線程之後又自動地切換回了主線程!順序編寫保證了邏輯上的直覺性,協程的自動線程切換又保證了代碼的非阻塞性。挂起函數必須在協程或者其他挂起函數中被調用,也就是挂起函數必須直接或者間接地在協程中執行。
那為什麼協程中的代碼沒有在主線程中執行呢?而且執行完畢為什麼還會自動地切回主線程呢?
協程的挂起可以了解為協程中的代碼離開協程所線上程的過程,協程的恢複可以了解為協程中的代碼重新進入協程所線上程的過程。協程就是通過的這個挂起恢複機制進行線程的切換。
4.5、async await方法
用async方法包裹了suspend方法來執行并發請求,并發結果都傳回之後,切換到主線程,接着再用await方法來擷取并發請求結果。
5、Retrofit
HTTP接口suspend挂起函數:
interface ApiService {
@FormUrlEncoded
@POST("user/login")
suspend fun loginForm(@Field("username") username: String,@Field("password") password: String): BaseResponse<User>
}
kotlin泛型:
data class BaseResponse<T>(
val errorCode: Int=0,
val errorMsg:String? = null,
var data: T? = null
)
這是使用MVVM架構模式+Kotlin協程+JetPack(ViewModel+LiveData)+Retrofit的架構,實作WanAndroid登入接口的小DEMO,後續會慢慢完善WanAndroid用戶端
最後相關架構及資料領取方式:
點選我的GitHub免費領取擷取往期Android進階架構資料、源碼、筆記、視訊。進階UI、性能優化、架構師課程、NDK、混合式開發(ReactNative+Weex)微信小程式、Flutter全方面的Android進階實踐技術,群内還有技術大牛一起讨論交流解決問題。
a120464/Android-P7/blob/master/Android%E5%BC%80%E5%8F%91%E4%B8%8D%E4%BC%9A%E8%BF%99%E4%BA%9B%EF%BC%9F%E5%A6%82%E4%BD%95%E9%9D%A2%E8%AF%95%E6%8B%BF%E9%AB%98%E8%96%AA%EF%BC%81.md)免費領取擷取往期Android進階架構資料、源碼、筆記、視訊。進階UI、性能優化、架構師課程、NDK、混合式開發(ReactNative+Weex)微信小程式、Flutter全方面的Android進階實踐技術,群内還有技術大牛一起讨論交流解決問題。**