/ 今日科技快讯 /
近日,苹果首席执行官蒂姆·库克接受了简短专访,其间他谈到了科技平台应该承担的社会责任,以及社交媒体应用Parler的潜在回归。在接受专访时,库克重申了苹果的立场,称其将继续与其他平台所有者大力打击仇恨言论等内容。此外,在决定将Parler因严重的审查失误而从应用商店移除后,苹果正做出更积极的应对。
/ 作者简介 /
本篇文章来自1个懒人的投稿,分享了他如何利用Retrofit和Coroutine重构Android中网络通信框架,相信会对大家有所帮助!同时也感谢作者贡献的精彩文章!
1个懒人的博客地址:
https://blog.csdn.net/taotao110120119
/ 前言 /
本文会讲解Coroutine的优点,以及一步步的从零开始改造 Retrofit+Coroutine,对改造中的关键问题进行讲解,给出详细可运行的示例代码。最后会给出Demo,Demo经过简单修改可以直接运用在自己的实际项目中。
/ Kotlin Coroutine简介 /
Kotlin coroutines let you convert callback-based code to sequential code. Code written sequentially is typically easier to read, and can even use language features such as exceptions.
Coroutine可以让你把基于callback的代码转换成顺序代码。
使用Coroutine会有以下几个好处:
详细的Coroutine介绍,参考官方教程。
/ Retrofit + Coroutine /
如果将Coroutine与Retrofit结合起来,就能将Coroutine的优点用于网络访问代码。
Retrofit 从 2.6.0版本开始支持Coroutine。
/ 开始搭建 /
我们用Retrofit + Coroutine来写一个API的示例,其中会用到Jetpack的ViewModel,LiveData等组件。
http://fanyi.youdao.com/translate?doctype=json&i=Hello%20world
此时会返回:
{
"type": "EN2ZH_CN",
"errorCode": 0,
"elapsedTime": 1,
"translateResult": [
[
{
"src": "Hello world",
"tgt": "你好,世界"
}
]
]
}
我们来实现这个API。
NetworkBase.kt
package com.jst.network
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
private const val BASE_URL = "http://fanyi.youdao.com/"
val retrofit = Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(BASE_URL)
.build()
将retrofit实例对象直接定义在包下面,这样其他类在用的时候可以直接用"retrofit",不用使用 “类名.retrofit”,十分方便。
interface TranslateApiService {
@FormUrlEncoded
@POST("translate?doctype=json")
suspend fun translate(@Field("i")i:String):Result
}
data class Result(
val type: String,
val elapsedTime: Int,
val translateResult: List<List<TranslateResult>>
) {
data class TranslateResult(
val src:String,
val tgt:String
)
}
object TranslateApi{
val retrofitService: TranslateApiService by lazy { retrofit.create(TranslateApiService::class.java) }
}
retrofit.create(TranslateApiService::class.java)
class MainViewModel : ViewModel() {
private val _translateResult: MutableLiveData<String> = MutableLiveData()
val translateResult: LiveData<String>
get() = _translateResult
fun translate(word: String) {
viewModelScope.launch {
val result = TranslateApi.retrofitService.translate(word)
_translateResult.value = result.translateResult[0][0].tgt
}
}
}
class MainFragment : Fragment() {
companion object {
fun newInstance() = MainFragment()
}
private val viewModel: MainViewModel by viewModels()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View {
return inflater.inflate(R.layout.main_fragment, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
val word = "Hello world"
textview.text = "正在翻译……"
viewModel.translateResult.observe(viewLifecycleOwner){
textview.text = "原词: $word \n翻译: $it"
}
viewModel.translate(word)
}
}
val result = TranslateApi.retrofitService.translate(word)
val result = TranslateApi.retrofitService.translate(word)
class MainViewModel : ViewModel() {
private val _translateResult: MutableLiveData<String> = MutableLiveData()
val translateResult: LiveData<String>
get() = _translateResult
fun translate(word: String) {
viewModelScope.launch {
try {
val result = TranslateApi.retrofitService.translate(word)
_translateResult.value = result.translateResult[0][0].tgt
} catch (e: Exception) {
_translateResult.value = e.message
}
}
}
}
我们的网络框架依赖于try catch的话就会很容易出错。比如,开发人员很容易忘了try catch,而且这种情况下也没有编译提示。他测试的时候网络很好,所以一切正常,但是到用户那一旦网络不好就崩溃了。
此时的e.message里没有我们想展示给用户的提示信息。我们通常的网络框架在异常的时候,需要提供errorCode 和errorMsg,目前这种try catch的方案无法满足我们的需求。
/**
* Creates {@link CallAdapter} instances based on the return type of {@linkplain
* Retrofit#create(Class) the service interface} methods.
*/
abstract class Factory {
/**
* Returns a call adapter for interface methods that return {@code returnType}, or null if it
* cannot be handled by this factory.
*/
public abstract @Nullable CallAdapter<?, ?> get(
Type returnType, Annotation[] annotations, Retrofit retrofit);
/**
* Extract the upper bound of the generic parameter at {@code index} from {@code type}. For
* example, index 1 of {@code Map<String, ? extends Runnable>} returns {@code Runnable}.
*/
protected static Type getParameterUpperBound(int index, ParameterizedType type) {
return Utils.getParameterUpperBound(index, type);
}
/**
* Extract the raw class type from {@code type}. For example, the type representing {@code
* List<? extends Runnable>} returns {@code List.class}.
*/
protected static Class<?> getRawType(Type type) {
return Utils.getRawType(type);
}
}
/**
* Adapts a {@link Call} with response type {@code R} into the type of {@code T}. Instances are
* created by {@linkplain Factory a factory} which is {@linkplain
* Retrofit.Builder#addCallAdapterFactory(Factory) installed} into the {@link Retrofit} instance.
*/
public interface CallAdapter<R, T> {
/**
* Returns the value type that this adapter uses when converting the HTTP response body to a Java
* object. For example, the response type for {@code Call<Repo>} is {@code Repo}. This type is
* used to prepare the {@code call} passed to {@code #adapt}.
*
* <p>Note: This is typically not the same type as the {@code returnType} provided to this call
* adapter's factory.
*/
Type responseType();
/**
* Returns an instance of {@code T} which delegates to {@code call}.
*
* <p>For example, given an instance for a hypothetical utility, {@code Async}, this instance
* would return a new {@code Async<R>} which invoked {@code call} when run.
*
* <pre><code>
* @Override
* public <R> Async<R> adapt(final Call<R> call) {
* return Async.create(new Callable<Response<R>>() {
* @Override
* public Response<R> call() throws Exception {
* return call.execute();
* }
* });
* }
* </code></pre>
*/
T adapt(Call<R> call);
}
T adapt(Call<R> call);
/**
* An invocation of a Retrofit method that sends a request to a webserver and returns a response.
* Each call yields its own HTTP request and response pair. Use {@link #clone} to make multiple
* calls with the same parameters to the same webserver; this may be used to implement polling or to
* retry a failed call.
*
* <p>Calls may be executed synchronously with {@link #execute}, or asynchronously with {@link
* #enqueue}. In either case the call can be canceled at any time with {@link #cancel}. A call that
* is busy writing its request or reading its response may receive a {@link IOException}; this is
* working as designed.
*
* @param <T> Successful response body type.
*/
public interface Call<T> extends Cloneable {
/**
* Synchronously send the request and return its response.
*
* @throws IOException if a problem occurred talking to the server.
* @throws RuntimeException (and subclasses) if an unexpected error occurs creating the request or
* decoding the response.
*/
Response<T> execute() throws IOException;
/**
* Asynchronously send the request and notify {@code callback} of its response or if an error
* occurred talking to the server, creating the request, or processing the response.
*/
void enqueue(Callback<T> callback);
/**
* Returns true if this call has been either {@linkplain #execute() executed} or {@linkplain
* #enqueue(Callback) enqueued}. It is an error to execute or enqueue a call more than once.
*/
boolean isExecuted();
/**
* Cancel this call. An attempt will be made to cancel in-flight calls, and if the call has not
* yet been executed it never will be.
*/
void cancel();
/** True if {@link #cancel()} was called. */
boolean isCanceled();
/**
* Create a new, identical call to this one which can be enqueued or executed even if this call
* has already been.
*/
Call<T> clone();
/** The original HTTP request. */
Request request();
/**
* Returns a timeout that spans the entire call: resolving DNS, connecting, writing the request
* body, server processing, and reading the response body. If the call requires redirects or
* retries all must complete within one timeout period.
*/
Timeout timeout();
}
interface MyService {
@GET("/user")
Observable<User> getUser();
}
sealed class ApiResult<out T> {
data class Success<out T>(val data: T?):ApiResult<T>()
data class Failure(val errorCode:Int,val errorMsg:String):ApiResult<Nothing>()
}
interface TranslateApiService {
@FormUrlEncoded
@POST("translate?doctype=json")
suspend fun translate(@Field("i")i:String):ApiResult<Result>
}
call.enqueue(new Callback<T>() {
//请求成功时候的回调
@Override
public void onResponse(Call<T> call, Response<T> response) {
// 将 response.body() 作为suspend方法成功时的返回值
}
//请求失败时候的回调
@Override
public void onFailure(Call<Translation> call, Throwable throwable) {
// 将 throwable 作为suspend方法失败时的异常抛出
}
});
interface TranslateApiService {
@FormUrlEncoded
@POST("translate?doctype=json")
suspend fun translate(@Field("i")i:String):ApiResult<Result>
}
interface TranslateApiService {
@FormUrlEncoded
@POST("translate?doctype=json")
fun translate(@Field("i")i:String):Call<ApiResult<Result>>
}
fun adapt(call: Call<T>): Call<ApiResult<T>>
package com.jst.network.calladapter
import com.jst.network.ApiError
import com.jst.network.ApiResult
import com.jst.network.exception.ApiException
import okhttp3.Request
import okio.Timeout
import retrofit2.*
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
class ApiResultCallAdapterFactory : CallAdapter.Factory() {
override fun get(returnType: Type, annotations: Array<Annotation>, retrofit: Retrofit): CallAdapter<*, *>? {
/*凡是检测不通过的,直接抛异常,提示使用者返回值类型格式不对
因为ApiResultCallAdapterFactory是使用者显式设置使用的*/
//以下是检查是否是 Call<ApiResult<T>> 类型的returnType
//检查returnType是否是Call<T>类型的
check(getRawType(returnType) == Call::class.java) { "$returnType must be retrofit2.Call." }
check(returnType is ParameterizedType) { "$returnType must be parameterized. Raw types are not supported" }
//取出Call<T> 里的T,检查是否是ApiResult<T>
val apiResultType = getParameterUpperBound(0, returnType)
check(getRawType(apiResultType) == ApiResult::class.java) { "$apiResultType must be ApiResult." }
check(apiResultType is ParameterizedType) { "$apiResultType must be parameterized. Raw types are not supported" }
//取出ApiResult<T>中的T 也就是API返回数据对应的数据类型
val dataType = getParameterUpperBound(0, apiResultType)
return ApiResultCallAdapter<Any>(dataType)
}
}
class ApiResultCallAdapter<T>(private val type: Type) : CallAdapter<T, Call<ApiResult<T>>> {
override fun responseType(): Type = type
override fun adapt(call: Call<T>): Call<ApiResult<T>> {
return ApiResultCall(call)
}
}
class ApiResultCall<T>(private val delegate: Call<T>) : Call<ApiResult<T>> {
/**
* 该方法会被Retrofit处理suspend方法的代码调用,并传进来一个callback,如果你回调了callback.onResponse,那么suspend方法就会成功返回
* 如果你回调了callback.onFailure那么suspend方法就会抛异常
*
* 所以我们这里的实现是永远回调callback.onResponse,只不过在请求成功的时候返回的是ApiResult.Success对象,
* 在失败的时候返回的是ApiResult.Failure对象,这样外面在调用suspend方法的时候就不会抛异常,一定会返回ApiResult.Success 或 ApiResult.Failure
*/
override fun enqueue(callback: Callback<ApiResult<T>>) {
//delegate 是用来做实际的网络请求的Call<T>对象,网络请求的成功失败会回调不同的方法
delegate.enqueue(object : Callback<T> {
/**
* 网络请求成功返回,会回调该方法(无论status code是不是200)
*/
override fun onResponse(call: Call<T>, response: Response<T>) {
if (response.isSuccessful) {//http status 是200+
//这里担心response.body()可能会为null(还没有测到过这种情况),所以做了一下这种情况的处理,
// 处理了这种情况后还有一个好处是我们就能保证我们传给ApiResult.Success的对象就不是null,这样外面用的时候就不用判空了
val apiResult = if (response.body() == null) {
ApiResult.Failure(ApiError.dataIsNull.errorCode, ApiError.dataIsNull.errorMsg)
} else {
ApiResult.Success(response.body()!!)
}
callback.onResponse(this@ApiResultCall, Response.success(apiResult))
} else {//http status错误
val failureApiResult = ApiResult.Failure(ApiError.httpStatusCodeError.errorCode, ApiError.httpStatusCodeError.errorMsg)
callback.onResponse(this@ApiResultCall, Response.success(failureApiResult))
}
}
/**
* 在网络请求中发生了异常,会回调该方法
*
* 对于网络请求成功,但是业务失败的情况,我们也会在对应的Interceptor中抛出异常,这种情况也会回调该方法
*/
override fun onFailure(call: Call<T>, t: Throwable) {
val failureApiResult = if (t is ApiException) {//Interceptor里会通过throw ApiException 来直接结束请求 同时ApiException里会包含错误信息
ApiResult.Failure(t.errorCode, t.errorMsg)
} else {
ApiResult.Failure(ApiError.unknownException.errorCode, ApiError.unknownException.errorMsg)
}
callback.onResponse(this@ApiResultCall, Response.success(failureApiResult))
}
})
}
override fun clone(): Call<ApiResult<T>> = ApiResultCall(delegate.clone())
override fun execute(): Response<ApiResult<T>> {
throw UnsupportedOperationException("ApiResultCall does not support synchronous execution")
}
override fun isExecuted(): Boolean {
return delegate.isExecuted
}
override fun cancel() {
delegate.cancel()
}
override fun isCanceled(): Boolean {
return delegate.isCanceled
}
override fun request(): Request {
return delegate.request()
}
override fun timeout(): Timeout {
return delegate.timeout()
}
}
{
"type": "EN2ZH_CN",
"errorCode": 0,
"elapsedTime": 1,
"translateResult": [
[
{
"src": "Hello world",
"tgt": "你好,世界"
}
]
]
}
{
"errorCode": 50,
"errorMsg": "xxx",
}
/**
* 业务错误 Interceptor
* 对于request: 无
* 对于response:负责解析业务错误(在http status 成功的前提下)
*/
class BusinessErrorInterceptor :Interceptor{
override fun intercept(chain: Interceptor.Chain): Response {
var response = chain.proceed(chain.request())
//http status不是成功的情况下,我们不处理
if (!response.isSuccessful){
return response
}
//因为response.body().string() 只能调用一次,所以这里读取responseBody不使用response.body().string(),原因:https://juejin.im/post/6844903545628524551
//以下读取resultString的代码节选自
//https://github.com/square/okhttp/blob/master/okhttp-logging-interceptor/src/main/kotlin/okhttp3/logging/HttpLoggingInterceptor.kt
val responseBody = response.body()!!
val source = responseBody.source()
source.request(Long.MAX_VALUE) // Buffer the entire body.
var buffer = source.buffer
val contentType = responseBody.contentType()
val charset: Charset = contentType?.charset(UTF_8) ?: UTF_8
val resultString = buffer.clone().readString(charset)
val jsonObject = JSONObject(resultString)
if (!jsonObject.has("errorCode")) {
return response
}
val errorCode = jsonObject.optInt("errorCode")
//对于业务成功的情况不做处理
if (errorCode == 0) {
return response
}
//我们的示例里服务器没有返回errorMsg,一般实际应用中服务器都会有errorMsg
throw ApiException(errorCode, "some error msg")
}
}
https://juejin.im/post/6844903545628524551
我们读取resultString的代码节选自okhttp官方的HttpLoggingInterceptor.kt。地址为:
https://github.com/square/okhttp/blob/master/okhttp-logging-interceptor/src/main/kotlin/okhttp3/logging/HttpLoggingInterceptor.kt
class ApiException(val errorCode:Int,val errorMsg:String): IOException()
/**
* 在网络请求中发生了异常,会回调该方法
*
* 对于网络请求成功,但是业务失败的情况,我们也会在对应的Interceptor中抛出异常,这种情况也会回调该方法
*/
override fun onFailure(call: Call<T>, t: Throwable) {
val failureApiResult = if (t is ApiException) {//Interceptor里会通过throw ApiException 来直接结束请求 同时ApiException里会包含错误信息
ApiResult.Failure(t.errorCode, t.errorMsg)
} else {
ApiResult.Failure(ApiError.unknownException.errorCode, ApiError.unknownException.errorMsg)
}
callback.onResponse(this@ApiResultCall, Response.success(failureApiResult))
}
private const val BASE_URL = "http://fanyi.youdao.com/"
private val okHttpClient = OkHttpClient.Builder()
.addInterceptor(BusinessErrorInterceptor())
.build()
val retrofit = Retrofit.Builder()
.addCallAdapterFactory(ApiResultCallAdapterFactory())
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(BASE_URL)
.client(okHttpClient)
.build()
class MainViewModel : ViewModel() {
private val _translateResult: MutableLiveData<String> = MutableLiveData()
val translateResult: LiveData<String>
get() = _translateResult
fun translate(word: String) {
viewModelScope.launch {
when (val result = TranslateApi.retrofitService.translate(word)) {
is ApiResult.Success -> {
_translateResult.value = result.data.translateResult1[0][0].tgt
}
is ApiResult.Failure -> {
_translateResult.value = "errorCode: ${result.errorCode} errorMsg: ${result.errorMsg}"
}
}
}
}
}
{
"type": "UNSUPPORTED",
"errorCode": 40,
"elapsedTime": 0,
"translateResult": [
[
{
"src": "IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII",
"tgt": "IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII"
}
]
]
}
https://github.com/ShuangtaoJia/RetrofitWithCoroutineDemo