将kotlin协程用于网络请求---完整实例,看这一篇就够了

这篇具有很好参考价值的文章主要介绍了将kotlin协程用于网络请求---完整实例,看这一篇就够了。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言:关于kotlin协程的介绍网上一大堆,用于网络请求的介绍也是一大堆,此文章不讲解各种原理,只讲实例使用,只要你有kotlin基础保证能看懂,看完就可以实际将kotlin协程应用于网络请求,从此废弃掉回调地狱,让你的app飞起来吧

本文的网络请求使用了Retrofit2 + okhttp,因为使用的是协程,就再也不需要回调地狱了,所以抛弃了Rxjava

1.先集成相关sdk

在app模块目录build.gradle中添加

apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'

..........
dependencies {
    implementation 'androidx.core:core-ktx:1.1.0-alpha04'
    implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0-beta01'
    implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.0.0'
    implementation 'com.google.code.gson:gson:2.6.2'//解析接口返回的数据我用到了这个,如果你是用fastjson的话,可以忽略掉这个,继续用fastjson就行了
    implementation 'com.squareup.retrofit2:retrofit:2.9.0' //这个版本很重要,必须要大于2.6.0,如果你的项目是低于2.6.0的话,更新一下版本,否则实际运行时会出现错误
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.2"
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.2"
}

2.创建Retrofit公共请求类

通常我们同一个项目,和服务器后端应该存在一些固定约束,比如接口返回的固定字段,会有code、message、data这些,那么发送给接口的请求,也应该有一些固定约束,比如固定的header等,所以就需要将网络请求进行一层封装

class RetrofitClient {
    //实际项目应用中,应该存在至少dev环境和idc线上环境,笔者这里还有test环境,如果你的项目没有这些环境,那么可以直接return 唯一的地址,甚至这个方法都可以不需要,直接使用唯一的环境即可
    fun getCoroutineServiceApi():ServiceApi{
        if(HttpApi.baseIp == "xxx" || HttpApi.baseIp == "xxx"){
            return coroutineServiceApiDev
        }else{
            return coroutineServiceApiOnLine
        }
    }

    private val coroutineServiceApiDev: ServiceApi by lazy { //不清楚by lazy的自行去百度,这是委托模式,可以延迟加载,并且只在首次访问时计算值
        val retrofitClient = Retrofit.Builder()
            .baseUrl(HttpApi.baseIp)
            .client(OkHttpClient.Builder()
                .addInterceptor(HttpLoggingInterceptor(HttpLoggingInterceptor.Logger { message ->
                    Log.i(TAG, message)
                }).setLevel(HttpLoggingInterceptor.Level.BODY)
                ).addInterceptor(Interceptor { chain ->
                    //这里就可以添加一些通用请求头了
                    val request: Request = chain.request()
                        .newBuilder()
                        .addHeader("Content-Type", "application/json")
                        .addHeader("version", MyApplication.getInstances().getVersion())
                        .addHeader("deviceId", MyApplication.getInstances().getDeviceid())
                        .addHeader("osType", MyApplication.osType)
                        .addHeader("token",  token)
.addHeader("channelCode",MyApplication.getInstances().getChannelNo()+MyApplication.getInstances().getHumeChannel())
.addHeader("androidId",MyApplication.getInstances().getAndroidid())
                        .addHeader("ua",URLEncoder.encode(MyApplication.mUa,"UTF-8"))
                        .addHeader("oaid",MyApplication.getInstances().getOaid())
                        .addHeader("imei",MyApplication.getInstances().getIMEI())
                        .addHeader("uuid",MyApplication.getInstances().getuniqueId())
                        .addHeader("vendor",Build.MANUFACTURER)
                        .build()
                    KLogger.e("xiaolitest","当前uuid:"+MyApplication.getInstances().getuniqueId())
                    Log.e("xiaolitest","当前渠道:"+MyApplication.getInstances().getChannelNo())
                    chain.proceed(request)
                }).build())
            .addConverterFactory(DsGsonConverterFactory.create())//这里我是把接口返回的值序列化,就像上面说的,同一个项目,后台和客户端的数据返回应该有一些固定约束
            .build()
        retrofitClient.create(ServiceApi::class.java)
    }
    //coroutineServiceApiOnLine我就不贴了,写法完全一样,只是baseUrl不一样而已,一个测试环境的,一个线上环境的
}

3.创建ServiceApi

上文中创建了Retrofit的属性委托,返回的对象都是ServiceApi,那么就需要写这个ServiceApi了

interface ServiceApi {
 @POST("card-user/xxx") //这个不用描述吧?懂retrofit的都知道,代表的是请求方式以及请求地址
 suspend fun getUserGotoTest():BaseResult<UserJumpConfigBean> //注意到开头的suspend关键字了吗?它很重要,因为协程体调用外部的方法,它必须是suspend的,否则会报错
}


//贴一下BaseResult代码,大部分的约束也应该如此
data class BaseResult <T>(
    val code :String,
    val success:Boolean,
    val message:String,
    val time:String,
    val data: T

)

4.创建viewmodel,并在里面生命协程作用域

viewmodel并不是协程所必须创建的,但属于lifecycle的viewmodel,能感知生命周期,在生命周期的末尾取消掉协程,都不用去管内存泄漏这些问题了,它不香么?

class MyViewModel:ViewModel() {
 /**
  * 这是此 ViewModel 运行的所有协程所用的任务。
  * 终止这个任务将会终止此 ViewModel 开始的所有协程。
  */
    private val viewModelJob = SupervisorJob()

 val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
 val ioScope = CoroutineScope(Dispatchers.IO + viewModelJob)
 override fun onCleared() { //不清楚的去百度一下viewmodel生命周期
  super.onCleared()
  viewModelJob.cancel()
 }

    //协程一共有几种调度器,是有对应区别的,如下:
/*
Dispatchers 决定协程在哪个线程或线程池上运行(启动和恢复)。最重要的选项有:

    Dispatchers.Defualt:被用来执行 CPU 密集型操作
    Dispatchers.Main:被我们用来访问主线程,例如在 Android、Swing 或者 JavaFX 上
    Dispatchers.Main.immediate:它和 Dispatchers.Main 运行在同一个线程上,但如果没有必要,它不会重新调度
    Dispatchers.IO:被用来执行一些阻塞线程的操作
    调用了 limitParallelism的 Dispatchers.IO 或者带有自定义线程池的 Dispatcher.IO:我们用来处理大量的阻塞调用
    调用了 limitParallelism 并设置为1的 Dispatchers.Default 或 Dispatchers.IO,或具有单个线程的自定义调度器:被用来修改共享状态
    Dispatchers.Unconfined:当我们不关心协程在哪个线程上被挂起时使用
*/
}

5.创建一个BaseActivity吧,通常我们的项目不都有它么,在里面设置我们将要用到的viewmodel以及serviceapi

abstract class BaseActivity  : AppCompatActivity(){
//熟悉吧,又用到了委托属性,它只会加载一次,不用每次都计算,不香么
val myViewModel: MyViewModel by lazy {
        ViewModelProviders.of(this).get(MyViewModel::class.java)
    }
    val coroutineServiceApi: ServiceApi by lazy {
        RetrofitClient.getRetrofitClient().getCoroutineServiceApi()
    }
    //其它的代码我就省略了,不重要
}

6.一切都有了,那么使用吧!

class TestKT :BaseActivity(){
    override fun getLayoutId(): Int {
        return R.layout.item_hot_cake_text
    }

    override fun initView() {
        myViewModel.ioScope.launch{ //这里我用的是Dispatchers.IO调度器,因为只是请求网络读写数据呀,不用放到主线程
            var data = coroutineServiceApi.getUserGotoTest()
            initData(data)
        }
    }

//留意到了吗?又是suspend关键字,协程体调用外部方法必须是suspend的,否则会报错
    suspend fun initData(data: BaseResult<UserJumpConfigBean>){
        withContext(Dispatchers.Main){ //因为上面的调度器是IO线程的,但UI只能在主线程更新,所以这里要生命Main调度器,否则会报错,如果上面调用的是uiScope那么这里就可以不用写这个了
            tv_content.text = data.message
        }
    }

}

7.贴一下序列化的代码

楼下有网友评论说Gson序列化失败就会崩溃,这是因为你序列化的代码没有对接口的异常进行判断,这个问题比较初级哈,鉴于此篇文章就是给初步使用协程请求网络用的,就贴一下序列化的代码吧,其实这个代码是通用的,还用的是java版本

public class DsGsonConverterFactory extends Converter.Factory {

    public static final String TAG = DsGsonConverterFactory.class.getSimpleName();
    private final Gson gson;

    private DsGsonConverterFactory(Gson gson) {
        if (gson == null) throw new NullPointerException("gson == null");
        this.gson = gson;
    }

    public static DsGsonConverterFactory create() {
        return create(new Gson());
    }

    public static DsGsonConverterFactory create(Gson gson) {
        return new DsGsonConverterFactory(gson);
    }


    @Nullable
    @Override
    public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
        TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
        return new GsonResponseBodyConverter<>(gson, adapter);
    }

    @Nullable
    @Override
    public Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
        TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
        return new GsonRequestBodyConverter(gson, adapter);
    }

    private final static class GsonRequestBodyConverter<T> implements Converter<T, RequestBody> {

        private static final MediaType MEDIA_TYPE = MediaType.parse("application/json;charset=UTF-8");
        private static final Charset UTF_8 = Charset.forName("UTF-8");

        private final Gson gson;
        private final TypeAdapter<T> adapter;

        GsonRequestBodyConverter(Gson gson, TypeAdapter<T> adapter) {
            this.gson = gson;
            this.adapter = adapter;
        }

        @Override
        public RequestBody convert(T value) throws IOException {
            Buffer buffer = new Buffer();
            Writer writer = new OutputStreamWriter(buffer.outputStream(), UTF_8);

            JsonWriter jsonWriter = gson.newJsonWriter(writer);
            adapter.write(jsonWriter, value);
            jsonWriter.close();
            return RequestBody.create(MEDIA_TYPE, buffer.readByteString());
        }
    }

    private final static class GsonResponseBodyConverter<T> implements Converter<ResponseBody, T> {
        private final Gson gson;
        private final TypeAdapter<T> adapter;
        private final TypeAdapter<BaseResult> mExceptionAdapter;

        GsonResponseBodyConverter(Gson gson, TypeAdapter<T> adapter) {
            this.gson = gson;
            this.adapter = adapter;
            this.mExceptionAdapter = (TypeAdapter<BaseResult>) adapter;
        }

        @Override
        public T convert(ResponseBody value) throws IOException {
            String response = value.string();
            BaseResult baseResult = gson.fromJson(response, BaseResult.class);

            MediaType contentType = value.contentType();
            Charset charset = contentType != null ? contentType.charset(UTF_8) : UTF_8;
            InputStream inputStream = new ByteArrayInputStream(response.getBytes());
            Reader reader = new InputStreamReader(inputStream, charset);
            JsonReader jsonReader = gson.newJsonReader(reader);
            T entity = null;
            try {
                entity = adapter.read(jsonReader);
            } catch (JsonSyntaxException jsonSyntaxException) { //类型转换错误
                KLogger.INSTANCE.e("json解析错误:" + jsonSyntaxException.getMessage());
                entity = (T) baseResult;
            } catch (Exception e) {
                e.printStackTrace();
                throw e;
            } finally {
                value.close();
                return entity;
            }
        }
    }

}

NOTO::写在最后,至此,只要你有kotlin基础,用过retrofit +okhttp,那么你现在已经可以将网络请求应用到实际项目中了。但是!但是!这还远远不够的,本文的目的是期望你能快速上手运用协程,将异步的代码用同步的逻辑来写,这样会使得代码更简洁,可读性也更高,而且完全废弃掉了Rxjava,也不用管什么回调地狱了,但你在会使用协程之后,就应该去关注一下更细节的东西;

比如:协程的作用域、协程里面需要再启动协程、必要时取消协程等等。举个实际项目中的例子,假设我们有两个网络请求,分别是接口A和接口B,接口B的请求依赖于接口A返回的字段,那么如果不用协程,我们是不是要写接口A的回调,然后在接口A里面去调用接口B。如果不是两个网络请求,是三个或者更多呢?代码是不是看起来就很臃肿?那么协程就真香了,因为协程有async/await来处理并发,试想一下,原本繁重的一个接口回调后再调另外一个接口,变成了如下代码,是不是代码量减少了,可读性也越高了?

coroutineScope.launch(Dispatchers.IO) {
	val a1 = async{ 接口A() }
	val userInfo = a1.await()
	val a2 = async{ 接口B(userInfo.token) }
	val msgList = a2.await()
}

还有上面我不止一次提到的suspend关键字,它到底有什么用呢?文章来源地址https://www.toymoban.com/news/detail-411331.html

到了这里,关于将kotlin协程用于网络请求---完整实例,看这一篇就够了的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【Linux】在ubuntu18.04系统中配置网络信息(超有用,完美解决,只看这一篇就行了)

    最近用centos搭建hadoop集群已经熟练了,但是学习场景中更多的使用的是ubuntu环境,就安装了ubuntu的环境进行搭建,结果ubuntu在很多地方与centos操作有较大区别,首先网络配置就让我折腾了半天,现在我将我已成功配置好的经验分享给大家 1.首先打开我们的VMware虚拟机,左上角

    2024年02月13日
    浏览(47)
  • 【kotlin 协程】万字协程 一篇完成kotlin 协程进阶

    Kotlin 中的协程提供了一种全新处理并发的方式,可以在 Android 平台上使用它来简化异步执行的代码。协程是从 Kotlin 1.3 版本开始引入,但这一概念在编程世界诞生的黎明之际就有了,最早使用协程的编程语言可以追溯到 1967 年的 Simula 语言。 在过去几年间,协程这个概念发展

    2024年02月07日
    浏览(52)
  • 微信小程序:服务器请求、上传图片和提交表单开发完整代码实例

    该示例涉及服务器请求、上传图片、展示上传的图片,并提交表单,同时配合使用 WXML(微信小程序的前端页面结构)、WXSS(样式表)、以及 JavaScript(逻辑控制)。请注意,服务器端的实现将不在本示例范围内,您需要根据实际需求创建后端 API 来处理请求和上传的操作。

    2024年02月06日
    浏览(56)
  • Android---Retrofit实现网络请求:Kotlin版

    在 Android 开发中,网络请求是一个极为关键的部分。Retrofit 作为一个强大的网络请求库,能够简化开发流程,提供高效的网络请求能力。 Retrofit 是一个建立在 OkHttp 基础之上的网络请求库,能够将我们定义的 Java 接口转化为相应的 HTTP请求,Retrofit 是适用于 Android 和 Java 的类

    2024年02月20日
    浏览(43)
  • Kotlin 协程一 —— 协程 Coroutine

    1.1.1基本定义 进程 进程是一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程,是操作系统进行资源分配和调度的一个独立单位,是应用程序运行的载体。 进程是资源分配的最小单位,在单核CPU中,同一时刻只有一个程序在内存中被CPU调用运行。 线程 基本的

    2024年02月05日
    浏览(49)
  • [论文阅读]PANet(PAFPN)——用于实例分割的路径聚合网络

    Path Aggregation Network for Instance Segmentation 用于实例分割的路径聚合网络 论文网址:PANet 这篇论文提出了Path Aggregation Network (PANet),目的是增强基于proposal的实例分割框架中的信息流动。具体来说,论文提出了以下几点改进: 增加自底向上的路径(bottom-up path augmentation),用低层中的精确

    2024年02月05日
    浏览(36)
  • kotlin语法进阶 - 协程(一)协程基础

    协程并不是一个新的概念,而是一个非常老的概念,很多语言都支持协程,建议去浏览器去了解一下协程的历史和基本概念,这里我们只讲一下kotlin中的协程的作用。 从代码实现角度来看:kotlin协程底层是用线程实现的,是一个封装完善供开发者使用的线程框架。kotlin的一个

    2024年02月09日
    浏览(45)
  • web3Js(干货)(多签的流程原理)看完这一篇就懂了(波场网络-请勿用于除学习外其他用途)

    连接波场网络: 其中APIKEY可以在官网获取; 可以使用tronWeb.isConnected()判断是否连接成功 创建离线波场地址: 该地址未激活,如果需要激活, 通常需要一定数量的 TRX(TRON 的本地代币)用于支付激活费用; 等待区块确定 就可以查看激活信息; 创建随机助记词与私钥: 如何让

    2024年02月04日
    浏览(88)
  • Kotlin协程-从一到多

    上一篇文章,我介绍了Kotlin协程的创建,使用,协作等内容。本篇将引入更多的使用场景,继续带你走进协程世界。 常用编程语言都会内置对同一类型不同对象的数据集表示,我们通常称之为容器类。不同的容器类适用于不同的使用场景。Kotlin的 Flow 就是在异步计算的需求下

    2024年02月09日
    浏览(55)
  • Kotlin协程学习之-02

    协程的基本使用 GlobalScope.launch 生命周期与进程一致,且无法取消 runBlocking 会阻塞线程,一般在测试阶段可以使用 val coroutineScope = CoroutineScope(context) coroutineScope.launch 通过context参数去管理和控制协程的生命周期 用法 val coroutineScope = CoroutineScope(context) coroutineScope.launch(Dispatche

    2024年01月22日
    浏览(44)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包