前言:关于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来处理并发,试想一下,原本繁重的一个接口回调后再调另外一个接口,变成了如下代码,是不是代码量减少了,可读性也越高了?文章来源:https://www.toymoban.com/news/detail-411331.html
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模板网!