Code前端首页关于Code前端联系我们

Android开发:使用Kotlin协程(Coroutine)和Retrofit执行网络和取消请求

terry 2年前 (2023-09-23) 阅读数 74 #移动小程序

演示了如何使用Coroutine配合Retrofit来触发Android中如何优雅地取消一个已经使用协程时启动网络请求。

本文的演示标题:

创建协程作用域

在上一篇文章中,我写到方法是一个非常常用的协程构建器。因此,要使用协程,首先必须创建一个对象CoroutineScope。代码如下:

CoroutineScope( + Job())
复制代码

上面的代码创建了一个CoroutineScope对象,并指定其协程在主线程上执行。 ,并且分配了Job

。在演示中,我使用了MVP模式来编写,所以我把CoroutineScope的创建放在BasePresenter中。代码如下:

interfaceMvpViewinterfaceMvpPresenter<V: MvpView> {

    @UiThreadfunattachView(view: V)@UiThreadfundetachView()
}

openclassBasePresenter<V: MvpView> : MvpPresenter<V> {
    lateinitvar view: V
    val presenterScope: CoroutineScope by lazy {
        CoroutineScope( + Job())
    }

    overridefunattachView(view: V) {
        this.view = view
    }

    overridefundetachView() {
        ()
    }
}
复制代码

使用()取消协程

你应该看到上面中调用了()。那么这个方法的作用是什么呢?它是关于取消 presenterScope所有创建的协程及其子协程。

在上一篇文章中,我也介绍了launch在创建协程时的使用,Job对象将通过Job对象返回cancel方法也可以取消任务对应的协程,那我这里为什么不使用那个方法呢?

显然,如果使用()方法取消协程,那么我创建每个协程后,都需要保存返回的Job对象,然后取消它。显然如果想要更复杂的话,使用()一次性取消协程上下文创建的所有协程和子协程。该代码也可以轻松地提取到基类中,以便您稍后编写。编写业务代码时,无需担心协程和视图的生命周期。

其实看源码也可以发现,最后的()也是用来取消协程

的,可以看上面。 api接口定义大家应该很熟悉了。可以通过以下代码发起异步网络请求
().enqueue(object : Callback<T> {
    overridefunonFailure(call: Call<T>, t: Throwable) {
        
    }

    overridefunonResponse(call: Call<T>, response: Response<T>) {
        
    }
})
复制代码

上一篇文章介绍了协程可以让异步代码像编写同步代码一样方便。上面的异步代码可以使用协同程序吗?将其更改为编写同步代码块之类的内容怎么样?这显然是可能的。具体修改代码如下:


suspend fun<T> Call<T>.await(): T {
    return suspendCoroutine {
        enqueue(object : Callback<T> {
            overridefunonFailure(call: Call<T>, t: Throwable) {
                
                (t)
            }

            overridefunonResponse(call: Call<T>, response: Response<T>) {
                if() {
                   
                   (()!!)
                } else{
                   
                   (Throwable(()))
                }
            }
        })
    }
}
复制代码

上述代码扩展了hang函数await。执行该方法时,会执行一个异步请求,同时协程序会被挂起。运行此函数直到异步请求失败或发生错误,然后继续例程。

suspendCoroutine

全局函数,该函数可以获取当前方法所在协程的上下文并挂起当前协程,直到一定时间再恢复协程执行,但是这个时间其实是由开发者自己控制的,仅作为上面代码中的

发起请求,写为


 {
    val time = ()
    ()
    try {
        val ganks = queryGanks()
        (ganks)
    } catch (e: Throwable) {
        ()
    } finally {
        (TAG, "耗时:${() - time}")
    }
}

suspend funqueryGanks(): List<Gank> {
    returntry {
        val androidResult = ().await()
        
        val iosResult = ().await()

        val result = mutableListOf<Gank>().apply {
            addAll()
            addAll()
        }
        result
    } catch (e: Throwable) {
        
        ()
        throw e
    }
}
复制代码

从上面的代码中可以看到,协程使用了try-catch方法来处理异常。首先,这就是我到目前为止所想到的。方式。因此,在使用协程时,最好在异常捕获企业中适当的地方使用try-catch。否则,当例程执行过程中出现异常时,程序就会崩溃。

另外,上面代码的写法还有一个问题。由于执行临时函数时当前协程会暂停,上面的两个请求是顺序执行的,所以上面的方法queryGanks()实际上需要两个网络请求,因为两个android列表请求和ios列表请求它们不是并行的,所以这种写法肯定不是最优方案。

触发请求,写入方法2

我们换一种写入方法吧。

suspend funqueryGanks(): List<Gank> {
    return withContext() {
        try {
            val androidDeferred = async {
                val androidResult = ().await()
                androidResult
            }

            val iosDeferred = async {
                val iosResult = ().await()
                iosResult
            }

            val androidResult = ().results
            val iosResult = ().results

            val result = mutableListOf<Gank>().apply {
                addAll(iosResult)
                addAll(androidResult)
            }
            result
        } catch (e: Throwable) {
            ()
            throw e
        }
    }
}
复制代码

这种写法与之前的区别在于async构造函数创建了两个子例程,分别请求Android列表和IOS列表。同时,由于async构建器,当前协程在执行过程中不会被挂起,所以两个请求都是并行执行的,所以效率比之前的写法要高很多。

提交请求,第三种写法

第三种写法是从Retorfit打乱CallAdapter,通过自定义CallAdapter来实现工厂,将api。定义Call的结果直接转换为Deferred,这样Android列表请求和IOS列表就可以同时触发,然后通过获取请求结果。这种写法是第一种写法两种写法的结合。

这个写法Jake Wharton师父已经给我们实现了。地址在这里...

这里就不讲这个方案的具体实现了。有兴趣的同学可以查看其源码。

第三种写法的特殊代码如下:

val instance = ()
        .baseUrl("")
        .addCallAdapterFactory(CoroutineCallAdapterFactory())
        .addConverterFactory(())
        .build().create(CallAdapterApiService::class.java)
        
suspend funqueryGanks(): List<Gank> {
    return withContext() {
        try {
            val androidDeferred = ()

            val iosDeferred = ()

            val androidResult = ().results

            val iosResult = ().results

            val result = mutableListOf<Gank>().apply {
                addAll(iosResult)
                addAll(androidResult)
            }
            result
        } catch (e: Throwable) {
            ()
            throw e
        }
    }
}
复制代码

上面的第三种写法看起来更简洁,也是并行请求。取请求时间最长的请求时间,与第二种方法类似。

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门