apply from: "config.gradle"
buildscript {
ext.kotlin_version = '1.3.61'
repositories {
jcenter()
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
jcenter()
google()
}
}
apply plugin: 'kotlin-android'
android {
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.2.2'
}
interface ZhihuApiService {
companion object Factory {
val ZHIHU_BASE_URL = "http://news-at.zhihu.com/api/"
val mZhihuApiService = create()
fun create(): ZhihuApiService {
val okHttpClient = OkHttpClient.Builder().connectTimeout(10, TimeUnit.SECONDS).build()
val retrofit = Retrofit.Builder()
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.baseUrl(ZHIHU_BASE_URL)
.build()
return retrofit.create(ZhihuApiService::class.java)
}
}
@GET("3/news/hot")
suspend fun getHotCoroutine(): HotJson
}
private fun getHot() {
GlobalScope.launch(Dispatchers.Main) {
val hotJson = withContext(Dispatchers.IO){
val dailiesJson = ZhihuApiService.mZhihuApiService.getHotCoroutine()
dailiesJson
}
if (swipe_refresh_layout.isRefreshing) {
swipe_refresh_layout.isRefreshing = false
}
val hots = hotJson.hots
if (hots != null) {
hotAdapter!!.addList(hots)
hotAdapter!!.notifyDataSetChanged()
}
}
}
这个很基础的写法有个很基础的问题,如果网络报错,比如最简单的 Timeout 超时错误,会导致代码报错,应用崩溃。
所以需要用 try catch 包裹一层。
private fun getHot() {
GlobalScope.launch(Dispatchers.Main) {
try {
val hotJson = withContext(Dispatchers.IO){
val dailiesJson = ZhihuApiService.mZhihuApiService.getHotCoroutine()
dailiesJson
}
val hots = hotJson.hots
if (hots != null) {
hotAdapter!!.addList(hots)
hotAdapter!!.notifyDataSetChanged()
}
} catch (e: Exception) {
Log.e("YAO", e.toString())
} finally {
if (swipe_refresh_layout.isRefreshing) {
swipe_refresh_layout.isRefreshing = false
}
}
}
}
完整代码在这里
网上作者 秉心说 ,封装了一层,按照了这种流程进行了封装。代码可读性提高了一些。
WanService.kt
interface WanService {
@FormUrlEncoded
@POST("/user/login")
suspend fun login(@Field("username") userName: String, @Field("password") passWord: String): WanResponse
}
BaseRepository.kt
open class BaseRepository {
suspend fun apiCall(call: suspend () -> WanResponse): WanResponse {
return call.invoke()
}
suspend fun safeApiCall(call: suspend () -> Result, errorMessage: String): Result {
return try {
call()
} catch (e: Exception) {
// An exception was thrown when calling the API so we're converting this to an IOException
Result.Error(IOException(errorMessage, e))
}
}
}
LoginRepository.kt
class LoginRepository : BaseRepository() {
suspend fun login(userName: String, passWord: String): Result {
return safeApiCall(call = { requestLogin(userName, passWord) },
errorMessage = "登录失败!")
}
}
Result.kt
sealed class Result {
data class Success(val data: T) : Result()
data class Error(val exception: Exception) : Result()
override fun toString(): String {
return when (this) {
is Success<*> -> "Success[data=$data]"
is Error -> "Error[exception=$exception]"
}
}
}
LoginViewModel.kt
fun login() {
viewModelScope.launch(Dispatchers.Default) {
if (userName.get().isNullOrBlank() || passWord.get().isNullOrBlank()) return@launch
withContext(Dispatchers.Main) { showLoading() }
val result = repository.login(userName.get() ?: "", passWord.get() ?: "")
withContext(Dispatchers.Main) {
if (result is Result.Success) {
emitUiState(showSuccess = result.data, enableLoginButton = true)
} else if (result is Result.Error) {
emitUiState(showError = result.exception.message, enableLoginButton = true)
}
}
}
}
我大概理解一下:
1、用关键字 sealed class 声明一个「密封类 Result 」把 「请求正常返回的结果(泛型)」 和 「异常」绑在了一起。这样才可以实现调用 login 这样的业务请求方法统一返回一个结果。
2、网络请求通过 safeApiCall 进行了封装一层厚调用,safeApiCall 就是 try catch 的封装。
3、login 方法体,先运行在了一个 Dispatchers.Default 里,这是子线程。(我感觉运行在 Dispatchers.IO 里更合适)。网络请求前,先在主线程里显示加载画面,然后执行网络请求 repository.login。拿到结果后,又在主线程里,if&else 判断 Result 是密封类里的哪种类型,执行对应的UI更新。
可以看到用协程请求网络,缩进的情况也不少。并不能达到多少「减少回调从而减少嵌套&缩进,增加代码可读性」的效果。
所以比较一下下面 协程 和 RxJava 两种写法,对于 RxJava 使用熟练的开发者,反而觉得 RxJava 更好理解,更有规范,代码封装得更好。
而且 RxJava里面的操作符 比 Kotlin语言自身带有的操作符,更多更完善,能方便我们更好的写业务代码。
//协程版本
private fun getHot() {
GlobalScope.launch(Dispatchers.Main) {
try {
val hotJson = withContext(Dispatchers.IO){
val dailiesJson = ZhihuApiService.mZhihuApiService.getHotCoroutine()
dailiesJson
}
val hots = hotJson.hots
if (hots != null) {
hotAdapter!!.addList(hots)
hotAdapter!!.notifyDataSetChanged()
}
} catch (e: Exception) {
Log.e("YAO", e.toString())
} finally {
if (swipe_refresh_layout.isRefreshing) {
swipe_refresh_layout.isRefreshing = false
}
}
}
}
//Rxjava版本
private fun getHotBak() {
ZhihuHttp.mZhihuHttp.getHot().subscribe(object : Observer {
override fun onSubscribe(@NonNull d: Disposable) {
mDisposable = d
}
override fun onNext(hotJson: HotJson) {
val hots = hotJson.hots
if (hots != null) {
hotAdapter!!.addList(hots)
hotAdapter!!.notifyDataSetChanged()
}
}
override fun onComplete() {
}
override fun onError(e: Throwable) {
Logger.e(e, "Subscriber onError()")
}
})
}
kotlin 协程 是一个线程框架,协程就是切线程
参考
扔物线 Kotlin 的协程用力瞥一眼
Kotlin 协程的挂起好神奇好难懂?今天我把它的皮给扒了
到底什么是「非阻塞式」挂起?协程真的更轻量级吗?
真香!Kotlin+MVVM+LiveData+协程 打造 Wanandroid!