前言:關(guān)于kotlin協(xié)程的介紹網(wǎng)上一大堆,用于網(wǎng)絡請求的介紹也是一大堆,此文章不講解各種原理,只講實例使用,只要你有kotlin基礎保證能看懂,看完就可以實際將kotlin協(xié)程應用于網(wǎng)絡請求,從此廢棄掉回調(diào)地獄,讓你的app飛起來吧
本文的網(wǎng)絡請求使用了Retrofit2 + okhttp,因為使用的是協(xié)程,就再也不需要回調(diào)地獄了,所以拋棄了Rxjava
1.先集成相關(guān)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'//解析接口返回的數(shù)據(jù)我用到了這個,如果你是用fastjson的話,可以忽略掉這個,繼續(xù)用fastjson就行了
implementation 'com.squareup.retrofit2:retrofit:2.9.0' //這個版本很重要,必須要大于2.6.0,如果你的項目是低于2.6.0的話,更新一下版本,否則實際運行時會出現(xiàn)錯誤
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.創(chuàng)建Retrofit公共請求類
通常我們同一個項目,和服務器后端應該存在一些固定約束,比如接口返回的固定字段,會有code、message、data這些,那么發(fā)送給接口的請求,也應該有一些固定約束,比如固定的header等,所以就需要將網(wǎng)絡請求進行一層封裝
class RetrofitClient {
//實際項目應用中,應該存在至少dev環(huán)境和idc線上環(huán)境,筆者這里還有test環(huán)境,如果你的項目沒有這些環(huán)境,那么可以直接return 唯一的地址,甚至這個方法都可以不需要,直接使用唯一的環(huán)境即可
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())//這里我是把接口返回的值序列化,就像上面說的,同一個項目,后臺和客戶端的數(shù)據(jù)返回應該有一些固定約束
.build()
retrofitClient.create(ServiceApi::class.java)
}
//coroutineServiceApiOnLine我就不貼了,寫法完全一樣,只是baseUrl不一樣而已,一個測試環(huán)境的,一個線上環(huán)境的
}
3.創(chuàng)建ServiceApi
上文中創(chuàng)建了Retrofit的屬性委托,返回的對象都是ServiceApi,那么就需要寫這個ServiceApi了
interface ServiceApi {
@POST("card-user/xxx") //這個不用描述吧?懂retrofit的都知道,代表的是請求方式以及請求地址
suspend fun getUserGotoTest():BaseResult<UserJumpConfigBean> //注意到開頭的suspend關(guān)鍵字了嗎?它很重要,因為協(xié)程體調(diào)用外部的方法,它必須是suspend的,否則會報錯
}
//貼一下BaseResult代碼,大部分的約束也應該如此
data class BaseResult <T>(
val code :String,
val success:Boolean,
val message:String,
val time:String,
val data: T
)
4.創(chuàng)建viewmodel,并在里面生命協(xié)程作用域
viewmodel并不是協(xié)程所必須創(chuàng)建的,但屬于lifecycle的viewmodel,能感知生命周期,在生命周期的末尾取消掉協(xié)程,都不用去管內(nèi)存泄漏這些問題了,它不香么?
class MyViewModel:ViewModel() {
/**
* 這是此 ViewModel 運行的所有協(xié)程所用的任務。
* 終止這個任務將會終止此 ViewModel 開始的所有協(xié)程。
*/
private val viewModelJob = SupervisorJob()
val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
val ioScope = CoroutineScope(Dispatchers.IO + viewModelJob)
override fun onCleared() { //不清楚的去百度一下viewmodel生命周期
super.onCleared()
viewModelJob.cancel()
}
//協(xié)程一共有幾種調(diào)度器,是有對應區(qū)別的,如下:
/*
Dispatchers 決定協(xié)程在哪個線程或線程池上運行(啟動和恢復)。最重要的選項有:
Dispatchers.Defualt:被用來執(zhí)行 CPU 密集型操作
Dispatchers.Main:被我們用來訪問主線程,例如在 Android、Swing 或者 JavaFX 上
Dispatchers.Main.immediate:它和 Dispatchers.Main 運行在同一個線程上,但如果沒有必要,它不會重新調(diào)度
Dispatchers.IO:被用來執(zhí)行一些阻塞線程的操作
調(diào)用了 limitParallelism的 Dispatchers.IO 或者帶有自定義線程池的 Dispatcher.IO:我們用來處理大量的阻塞調(diào)用
調(diào)用了 limitParallelism 并設置為1的 Dispatchers.Default 或 Dispatchers.IO,或具有單個線程的自定義調(diào)度器:被用來修改共享狀態(tài)
Dispatchers.Unconfined:當我們不關(guān)心協(xié)程在哪個線程上被掛起時使用
*/
}
5.創(chuàng)建一個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調(diào)度器,因為只是請求網(wǎng)絡讀寫數(shù)據(jù)呀,不用放到主線程
var data = coroutineServiceApi.getUserGotoTest()
initData(data)
}
}
//留意到了嗎?又是suspend關(guān)鍵字,協(xié)程體調(diào)用外部方法必須是suspend的,否則會報錯
suspend fun initData(data: BaseResult<UserJumpConfigBean>){
withContext(Dispatchers.Main){ //因為上面的調(diào)度器是IO線程的,但UI只能在主線程更新,所以這里要生命Main調(diào)度器,否則會報錯,如果上面調(diào)用的是uiScope那么這里就可以不用寫這個了
tv_content.text = data.message
}
}
}
7.貼一下序列化的代碼
樓下有網(wǎng)友評論說Gson序列化失敗就會崩潰,這是因為你序列化的代碼沒有對接口的異常進行判斷,這個問題比較初級哈,鑒于此篇文章就是給初步使用協(xié)程請求網(wǎng)絡用的,就貼一下序列化的代碼吧,其實這個代碼是通用的,還用的是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) { //類型轉(zhuǎn)換錯誤
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,那么你現(xiàn)在已經(jīng)可以將網(wǎng)絡請求應用到實際項目中了。但是!但是!這還遠遠不夠的,本文的目的是期望你能快速上手運用協(xié)程,將異步的代碼用同步的邏輯來寫,這樣會使得代碼更簡潔,可讀性也更高,而且完全廢棄掉了Rxjava,也不用管什么回調(diào)地獄了,但你在會使用協(xié)程之后,就應該去關(guān)注一下更細節(jié)的東西;
比如:協(xié)程的作用域、協(xié)程里面需要再啟動協(xié)程、必要時取消協(xié)程等等。舉個實際項目中的例子,假設我們有兩個網(wǎng)絡請求,分別是接口A和接口B,接口B的請求依賴于接口A返回的字段,那么如果不用協(xié)程,我們是不是要寫接口A的回調(diào),然后在接口A里面去調(diào)用接口B。如果不是兩個網(wǎng)絡請求,是三個或者更多呢?代碼是不是看起來就很臃腫?那么協(xié)程就真香了,因為協(xié)程有async/await來處理并發(fā),試想一下,原本繁重的一個接口回調(diào)后再調(diào)另外一個接口,變成了如下代碼,是不是代碼量減少了,可讀性也越高了?文章來源:http://www.zghlxwxcb.cn/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關(guān)鍵字,它到底有什么用呢?文章來源地址http://www.zghlxwxcb.cn/news/detail-411331.html
到了這里,關(guān)于將kotlin協(xié)程用于網(wǎng)絡請求---完整實例,看這一篇就夠了的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!