協程最佳實踐 android官網地址
這些實踐可以讓你的程式在使用協程的時候更加的易擴展和易測驗
1.注入調度器
不要在創建一個協程的時候或者呼叫withContext,硬編碼來指定調度器 比如這樣的
class NewsRepository {
// DO NOT use Dispatchers.Default directly, inject it instead
suspend fun loadNews() = withContext(Dispatchers.Default) { /* ... */ }
}
而應該進行注入
class NewsRepository(
private val defaultDispatcher: CoroutineDispatcher = Dispatchers.Default
) {
suspend fun loadNews() = withContext(defaultDispatcher) { /* ... */ }
}
原因:依賴注入的模式可以讓你在測驗的時候容易更換調度器 詳細參考Android中簡易的協程
2. 掛起函式在實作的時候,應該保證對主執行緒是安全的
比如這樣的:
suspend fun fetchLatesNews():ListArtical{
withContext(Dispatchers.IO){
}
}
主執行緒呼叫的時候
suspend operator fun updateContent(){
val news = fetchLatesNews()
}
這樣可以保證你的App是易擴展的,類掛起方法呼叫的時候,不需要擔心執行緒是在哪個環境調度的,由具體實作類中的方法來確保執行緒調度的安全
3. viewModle 應該去創建一個協程
viewModle更應該去創建一個協程,而不是去暴露一個suspend方法, 比如應該是這樣:
//示例代碼,viewModle內去創建一個協程
class LastestNewsViewModel{
//內部維護了一個可觀察的帶狀態的資料
private val _uiState = MutableStateFlow<LatestNewsUiState>(LatestNewsUiState.Loading)
val uiState:StateFlow<LatestNewsUiState> = _uiState
//重點來了,這里不是一個suspend方法
fun loadNews(){
viewModleScope.lanuch{
val lastestNewsWithAuthors = getLatestNewsWithAuthors()
_uiState.valule = LastestNewUiState.Success(lastestNewsWithAuthors)
}
}
}
而不是這樣的
class LastestNewsViewModel():ViewModel{
//這種是直接回傳了一個suspend方法
suspend fun loadNews() = getLatestNewsWithAuthors()
}
除非不需要呼叫知道資料流的狀態,而只需要發射一個單獨的資料,(個人理解,是保持viewModle中的定義,維護一個可觀察的帶狀態的資料,而不是直接扔原始資料出來)
4.不要暴露可修改的引數型別
應該對其他類暴露不可修改的的型別,這樣所有可變型別資料的變更都集中在一個類里,如果有問題的時候,更容易除錯(也是迪米特原則) 比如應該是這樣的
class LastestNewsViewModel : ViewModel{
//可修改型別
private val _uiState = _MutalbeStateFlwow(LastestNewsViewModel.Loading)
//對外暴露不可修改型別資料(對外不提供修改功能)
val uiState : StateFlow<LatestNewsUiState> = _uiState
}
5. 資料和業務層應該暴露掛起函式 或 Flow
資料層和業務層通常需要暴露方法,去執行一次性的呼叫或者需要持續接收資料的變化,這時候應該提供為一次性呼叫提供掛起函式 或者 提供Flow來幫忙觀察資料的變化操作 比如這樣的:
class ExampleRepository{
//為一次性的呼叫提供 suspend方法
suspend fun makeNetworkRequest(){}
//為一需要觀察的資料提供Flow物件
fun getExamples():Flow<Example>{}
}
最佳的實踐可以使呼叫者通常是業務層,能夠控制業務的執行和生命周期的運轉,并且在需要的時候可以取消任務
6. 在業務和資料層創建協程
在資料和業務層需要創建協程的原因可能有不幾的原因,下邊是一些可能的選項
- 如果協程的任務是相關的,且只在用戶在當前界面時才顯示,那么它需要關聯呼叫者的生命周期,這個呼叫者通常就是ViewModel,在這種 情況下, 應該使用coroutineScope 和 supervisorScope
示例代碼:
class GetAllBooksAndAuthorsUseCase(
private val booksRepository:BooksRepository,
private val authorsRepository:AuthorsRepository,
private val defaultDispatcher:CoroutineDispatcher = Dispatchers.Default
){
suspend fun getBookdAndAuthors():BookAndAuthors{
//平行的情況需要等待結果,書籍串列和作者串列需要同時準備好之后再回傳
return coroutineScope{
val books = async(defaultDispatcher){
booksRepository.getAllBooks()
}
val authors = async(defaultDispatcher){
authorsRepository.getallAuthors()
}
//準備好資料之后再回傳
BookAndAuthors(books.await(),authors.await())
}
}
}
- 如果這個任務是在App開啟期間需要執行,這個任務也不系結到某一個具體的界面,這時候任務是需要在超出呼叫者的生命周期的,這種場景下,需要用到
external 的 CoroutineScope ,詳細可參考 協程設計模式之任務不應該被取消
參考示例代碼:
class ArticalesRepository(
private val articlesDataSource: ArticlesDataSource,
private val externalScope:CoroutineScope,
private val defaultDispatcher:CoroutineDispatcher = Dispatchers.Default
){
//這個場景是這樣的,即使我們離開的螢屏,也希望這個預訂操作是能夠被完整執行的,那么這任務齋要在外部域開啟一個新的協程里來完成這wh
suspend fun bookmarkArtical(artical:Article){
externalScope.lanuch(defaultDispatcher){
articlesDataSource.bookmarkArticle(article)
}.join() //等待協程執行完畢
}
}
說明: 外部域需要被一個比當前界面的生命周期更長的一個類來創建,比如說 Application或者是一個navigatin grah的ViewModel
7. 避免使用GlobalScope全域作用域
就像最佳實踐里邊的注入調度器,如果用了GlobalScope,那就是在類里邊使用硬編碼,可能會有以下幾個負面影響
- 硬編碼,
- 難以測驗
8. 協程需要可以被取消
取消操作也是一種協程的操作,意思是說當協程被取消的時候,協程并沒有直接被取消,除非它在 掛起 或者 有取消操作,如果你的協程是在操作一個阻塞的操作,需要確保協程是中途可以被取消的, 舉個例子,如果你正在讀取多個檔案,需要在讀取每個檔案之前,檢查下協程是否已經被取消了,一個檢查協程是否被取消的方法就是 呼叫 ensureActivite方法,(或者還有isActive可用) 參考示例代碼:
someScope.lanuch{
ensureActive()//檢查協程是否已經被取消
readFile(file)
}
更多詳細的描述資訊可以參考 取消協程
9. 協程的例外處理
如果協程拋出的例外處理不當,可能會導致你的App崩潰,如果例外出現了,就在協程里就捕獲好例外并進行處理
參考示例代碼:
class LoginViewModel(
private val loginRepository:LoginRepository
):ViewModel(){
fun login(username:String,token:String){
viewModleScope.lanuch{
try{
loginRepository.login(username,token)
//通知界面登錄成功
}catch(error:Throwable){
//通知view 登錄操作失敗
}
}
}
}
更多協程例外的處理,或者其他場景需要用到CoroutineExceptionHandler,可以參考 協程例外處理
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/296912.html
標籤:其他
