コルーチン時代のAndroidネットワークリクエストをナビゲートするためのRetrofitのエレガントな使用(3)

序文:フレームワーク自体は常に反復しているため、記事のコードの一部が更新または古くなっている可能性があります。ソースコードを読んだり、プロジェクトでコードが実際にどのように使用されているかを確認したい場合は、次のような作成プロジェクトを表示できます。著者は現在次のことを維持しています:Spacecraft: "Spacecraft-My Android Technology Practice Platform"-コードを表示するには開発ブランチに入ります(gitee.com)

この記事の内容は、主にネットワークフレームワークの全体的な状況に関連するロジックを実装することです。前の記事を読んでいない場合は、コルーチン時代のAndroidネットワークリクエストを移動するためのRetrofitのエレガントな使用にジャンプしてください(2) -ナゲッツ(juejin.cn))

自然言語情報に対するネットワーク例外

  前のセクションでは、提供されていないコードをいくつか残しました。ここで確認します

/**
 * 网络请求出现异常
 */
data class Exception<T>(val exception: Throwable) : Failure<T>() {
    //分析异常类型,返回自然语言错误信息
    val exceptionMessage:String by lazy {
        //TODO 下文讲解
    }
}

  この自然言語の意味は、ユーザーが直接読むことができるエラーメッセージです。別の角度から考えてみましょう。プログラムがネットワークタイムアウト例外SocketTimeoutExceptionの情報をToastなどのユーザーに直接スローした場合、どうなりますか。起こる?ユーザーは非常に混乱する可能性があるため(プログラマーでない場合)、プログラムの大まかなエラースタック情報を人間が読める形式の情報に変換する必要があります。

まだナンセンスを話している!上記のコード。

/**
 * 网络异常解析器
 */
interface HttpExceptionParser{
    /**
     * 将一个异常转化为自然语言报错
     */
    fun parse(throwable: Throwable?): String
    /**
     * 默认实现
     */
    companion object{
        val DEFAULT_PARSER= object : HttpExceptionParser {
            override fun parse(throwable: Throwable?): String {
                return when (throwable) {
                    is ConnectException, is SocketException, is HttpException, is UnknownHostException -> "网络连接失败"
                    is SSLHandshakeException -> "证书验证失败"
                    is JSONException, is ParseException, is JsonParseException -> "解析报文失败"
                    is SocketTimeoutException -> "连接超时"
                    is MsgException -> throwable.tip
                    else -> "未知错误"
                }
            }
        }
    }
}

  コードは非常に単純です。つまり、インターフェイスにはthrowableを文字列に変換するメソッドが含まれ、インターフェイスにはデフォルトの実装が含まれます(ここで必要なものに変更できます)。

  次に、Holderを実装してパーサーを保持し、HolderにParserメソッド(エージェントモード)を実装させてから、実際のパーサーを呼び出して解析します(パーサーを置き換えて、必要なカスタムロジックを実装できます)。

/**
 * 异常处理器持有者
 */
object HttpExceptionParserHolder:HttpExceptionParser {

    var parser=DEFAULT_PARSER

    override fun parse(throwable: Throwable?): String {
        return parser.parse(throwable)
    }

}

  このようにして、元のコードに戻り、ホルダーを渡して論理的な閉ループを完了します。これにより、ユーザーには奇妙なネットワークの例外やエラーが表示されなくなります。

/**
 * 网络请求出现异常
 */
data class Exception<T>(val exception: Throwable) : Failure<T>() {
    val exceptionMessage: String by lazy {
        HttpExceptionParserHolder.parse(exception)
    }
}

NetworkResultインターセプター

  在本网络框架的网络请求中,在服务器无异常且拿到服务器报文后,逻辑会执行到Success中去,但是有时候我们有这样的需求,报文中有个字段,例如Code,如果Code不为1,则说明业务报错,因此我们还需要进一步判断Code的值,否则会出现BUG。

  那么问题来了,我们如果实现全局逻辑而不是在具体的某个请求的Success中回调呢? 笔者参考过很多人的解决方案,大部分是对OKhttp的拦截器进行处理,即在okhttp拦截器中进行校验,但是这样有个缺陷是,你需要多执行一遍实体类型的转换(因为Retrofit层已经有一层),性能差而且存在代码维护的难度(因为存在两套实体类转换逻辑,okhttp和retrofit各一套),因此我们希望统一在Retrofit层面进行校验。

  让我们回到第一节的代码(第一节传送门:优雅使用Retrofit,在协程时代遨游安卓网络请求(一) - 掘金 (juejin.cn)

  在第一节中,笔者提供了一个将Retrofit的Response转成NetworkResult的扩展方法,我们的关键点就是这里。

fun <T> Response<T>.toNetworkResult(): NetworkResult<T> =
    //此处可以做全局转换
   try {
        if (isSuccessful) {
            toSuccessResult()
        } else {
            toServerErrorResult()
        }
    } catch (t: Throwable) {
        t.toExceptionResult()
    }

  这个方法的返回值是NetworkResult,因此可以返回Success,Failure中的任意一种,我们只需要在这里插入转换的逻辑,将部分Success的转成Failure即可(当然你可以实现任意的转换,甚至将错误转成正确都可以,非常的任性)。

  那么如何实现的,废话不多说,直接上代码!

interface GlobalNetworkResultInterceptor {

    /**
     * 拦截网络请求,
     */
    fun <T> onIntercept(networkResult: NetworkResult<T>): NetworkResult<T> {
        return networkResult
    }

    //默认实现
    object DefaultGlobalNetworkResultInterceptor : GlobalNetworkResultInterceptor

}

  和异常解析器非常类似,也是一接口带着一个默认实现,方法就是将一个NetworkResult变成另外一个NetworkResult,默认就是不转。

  为了方便读者理解,我们用玩安卓的api来辅助说明,首先玩安卓的api接口返回值全是一个模板(大部分公司都类似),用实体类表示如下:

/**
 * 带壳的相应bean
 * @param T data实体类
 * @property data T 报文中对应data的部分
 * @property errorCode Int 报文中对应errorCode的部分
 * @property errorMsg String 报文中对应errorMsg的部分
 * @constructor
 */
data class WanBeanWrapper<T>(
    val data: T,
    val errorCode: Int = -1,
    val errorMsg: String = ""
) {

    companion object {
        const val SUCCESS_CODE = 0
        const val LOGIN_ERROR_CODE = -1001
    }

    /**
     * 请求是否成功
     * @return Boolean true:成功
     */
    fun isSuccessful(): Boolean {
        return errorCode == SUCCESS_CODE
    }

    /**
     * 登陆失败
     * @return Boolean true:登陆失败
     */
    fun isLoginError(): Boolean {
        return errorCode == LOGIN_ERROR_CODE
    }

}

  グローバルインターセプターの場合、このシェルに注意を払うだけで十分です。ロジックを実装する必要があるとします。errorCodeがSUCCESS_CODEと等しくない場合、SuccessタイプのNetworkResultをErrorタイプのNetworkResultに変換する必要があります。

  プロジェクトで作成者によって実装されたインターセプターを見てみましょう。

class WanGlobalNetworkResultInterceptor @Inject constructor() : GlobalNetworkResultInterceptor {

    override fun <T> onIntercept(networkResult: NetworkResult<T>): NetworkResult<T> {
        return if (
            //只有成功才转换
            networkResult is Success &&
            //只转换这种类型的Bean
            networkResult.responseBody is WanBeanWrapper<*>
        ) {
            //类型强转
            val wanBeanWrapper = (networkResult.responseBody as WanBeanWrapper<*>)
            //如果不成功
            if (!wanBeanWrapper.isSuccessful()) {
                return MsgException(wanBeanWrapper.errorMsg).toExceptionResult()
            }
            networkResult
        } else {
            networkResult
        }
    }

}

NetworkResultタイプ  の変換が完了していることがわかります。これは、最初はSuccessタイプであり、メッセージのerrorCodeをチェックして、バックグラウンドからエラーが報告されていることを確認することで、MsgExceptionに基づいています。 (IoExceptionの単純なサブクラス、作成者okhttpのインターセプトでいくつかのカスタムエラーを報告するために使用されます。ExceptionResultをカプセル化する任意の例外を使用できます)新しいExceptionResultを生成ますこのようにして、成功したはずのロジックがエラーのロジックになり、呼び出し元は、ネットワーク要求が成功するたびにerrorCodeが正常であるかどうかを手動で判断する必要がありません。

第1章のコードに戻り、いくつかの変更を加えましょう。

//扩展方法新增一个参数
fun <T> Response<T>.toNetworkResult(interceptor: GlobalNetworkResultInterceptor): NetworkResult<T> =
    interceptor.onIntercept(
        try {
            if (isSuccessful) {
                toSuccessResult()
            } else {
                toServerErrorResult()
            }
        } catch (t: Throwable) {
            t.toExceptionResult()
        }
    )


//代理类
internal class ApexResponseCallDelegate<T>(
    private val proxyCall: Call<T>,
    //新增参数,传入全局拦截器
    private val interceptor: GlobalNetworkResultInterceptor
) :
    Call<NetworkResult<T>> {

    override fun enqueue(callback: Callback<NetworkResult<T>>) =
        proxyCall.enqueue(object : Callback<T> {
            override fun onResponse(call: Call<T>, response: Response<T>) {
                callback.onResponse(
                    this@ApexResponseCallDelegate,
                    Response.success(
                        //将参数传入到这里使用
                        response.toNetworkResult(interceptor)
                    )
                )
            }

            //省略部分代码

        })
        

このようにして、元のコードを低コストで変更でき、グローバルリターン結果のインターセプトと変更が完了します。

これを読んでいただきありがとうございます。この簡単なシリーズは終了しました。さらに質問がある場合は、コメント領域に作成者にメッセージを残すことができます。ソースコードを確認したい場合は、作成者のオープンソースプロジェクトにアクセスすることもできます。関連するコードを検索します(クラス名の検索は「はい」です。一部のクラス名は変更される場合があります)。

おすすめ

転載: juejin.im/post/7118380454633439240