OkHttp多个特性以及代码示例

文末附代码。
文章包含:OkHttp同步或者异步、OkHttp获取响应的头部信息、OkHttp提交一个markdown文档到web服务,以HTML方式渲染markdown、OkHttp以流的方式POST提交请求体、OkHttp提交文件照片、OkHttp使用Gson队JSON和Java对象转化、OkHttp配置缓存、OkHttp配置超时(OkHttp支持连接,读取和写入超时)、OkHttp取消请求、OkHttp单独配置单个请求、OkHttp触发401后、OkHttp拦截器、OkHttp证书

OkHttp同步或者异步:
同步:client.newCall(request)之后用的是execute()
异步:client.newCall(request)之后用的是enqueue(回调)

OkHttp获取响应的头部信息:
通过response.header(key)获取即可,如果key不存在返回""
如果用的是headers(key),key不存在返回[]

OkHttp提交一个markdown文档到web服务,以HTML方式渲染markdown:
MediaType指定渲染markdown
RequestBody.create(MediaType,body)是最终附加的核心api

OkHttp以流的方式POST提交请求体:
需要重写RequestBody,在writeTo,通过传递的BufferedSink(还存留),进行流的转化处理

OkHttp提交文件、照片
核心都是RequestBody.create(MediaType,body)是最终附加的核心api
照片需要用MultipartBody

OkHttp使用Gson队JSON和Java对象转化:
依赖:
implementation ‘com.google.code.gson:gson:2.8.0’
ResponseBody.charStream()使用响应头Content-Type指定的字符集来解析响应体。默认是UTF-8
核心api为:gson.fromJson(…)

OkHttp配置缓存:
val cacheSize = 10 * 1024 * 1024 // 10 MiB
val cache = Cache(File(“缓存目录”), cacheSize.toLong())
client = OkHttpClient.Builder() // 为OkHttpClient附加缓存配置
.cache(cache)
.build()

OkHttp配置超时(OkHttp支持连接,读取和写入超时):
client = OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build()

OkHttp取消请求:
拿到Call,通过call.cancel()取消
对于取消另外的请求,通过client.dispatcher().runningCalls()拿到正在请求中的call列表,然后比对tag,获取匹配的call然后取消
client.dispatcher().queuedCalls()能拿到队列中的call列表。
tag可以在Request.Builder()中通过tag(“tag”)加入

OkHttp单独配置单个请求:
核心api:OkHttpClient的newBuilder()
通过该api拿到全局的OkHttpClient,然后浅拷贝一个client,覆盖配置

OkHttp触发401后:
401 Not Authorized
就是请求没通过验证,OkHttp会自动重试未验证,所以一般通过response重新对其request附加证书即可
如果不打算自动验证,在新建Authenticator的时候返回null
通过Credentials.basic()创建证书实例,然后Builder附加进去即可
client = client.newBuilder() // 浅拷贝client
.authenticator { route, response ->
val credential = Credentials.basic(“证书账号”, “证书密码”) // 创建证书实例
response.request().newBuilder() // 注意:在response中拿到request,然后重新builder了headers
.header(“Authorization”, credential) // 附加证书
.build()
}
.build()

OkHttp拦截器:
拦截器继承Interceptor,实现的intercept的参数Interceptor.Chain可以通过 chain.request()拿到请求,
chain.proceed(request) 执行请求并拿到响应。
能拿到请求和响应,自然能够轻易的重写请求和响应,这就是拦截器的最大意义。
OkHttp里分为应用拦截器和网络拦截器,区别在于网络拦截器能够对重定向和重试等中间响应进行操作,
比如一个url会重定向,那么拦截器就会触发两次,但是如果是应用拦截器,就只有一次。
网络拦截器,在OkHttpClient的addNetworkInterceptor这个api里添加实例;
应用拦截器,在OkHttpClient的addInterceptor这个api里添加实例。

OkHttp证书:
官网连接:https://square.github.io/okhttp/features/https/

kt文件:

package com.example.wantv

import android.os.Bundle
import android.util.Log
import com.example.view.base.BaseActivity
import com.example.wantv.databinding.ActivityTestBinding
import com.google.gson.Gson
import okhttp3.*
import okhttp3.internal.Internal.logger
import okio.BufferedSink
import java.io.File
import java.io.IOException
import java.util.concurrent.TimeUnit


/**
 * Create by smy on 2022/2/9
 * Note:测试类
 */
class TestActivity : BaseActivity<ActivityTestBinding>() {
    var client: OkHttpClient = OkHttpClient()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mBinding.btGet.setOnClickListener { getRequest() }
        mBinding.btPost.setOnClickListener { postRequest() }
        mBinding.btSnycDownFile.setOnClickListener { syncDownFile() }
        mBinding.btAsnycDownFile.setOnClickListener { asnycDownFile() }
        mBinding.btGetResponseHeaders.setOnClickListener { getResponseHeaders() }
        mBinding.btPostMoreLineText.setOnClickListener { postMoreLineText() }
        mBinding.btPostTextByFlow.setOnClickListener { postTextByFlow() }
        mBinding.btCommitFile.setOnClickListener { commitFile() }
        mBinding.btCommitImg.setOnClickListener { commitImg() }
        mBinding.btGson.setOnClickListener { gson() }
        mBinding.btCache.setOnClickListener { cache() }
        mBinding.btCancelRequest.setOnClickListener { cancelRequest() }
        mBinding.btTimeoutSetting.setOnClickListener { timeoutSetting() }
        mBinding.btCustomCall.setOnClickListener { customCall() }
        mBinding.btAutoAuthentication.setOnClickListener { autoAuthentication() }
        mBinding.btInterceptor.setOnClickListener { interceptor() }
        mBinding.btInterceptorNetwork.setOnClickListener { interceptorNetwork() }
    }

    /**
     * 网络拦截器
     * 核心api:addNetworkInterceptor来添加
     *
     * 与应用拦截器区别:能够对重定向和重试等中间响应进行操作
     */
    private fun interceptorNetwork() {
        client = client.newBuilder().addNetworkInterceptor(LoggingInterceptor()).build()  // 加入拦截器
        // TODO: 下面正常请求即可
        /**
         * 当我们运行这段代码时,拦截器会运行两次。
         * 一次用于初始请求http://www.publicobject.com/helloworld.txt,
         * 另一个用于重定向到https://publicobject.com/helloworld.txt
         */
    }

    /**
     * 应用拦截器
     * 核心api:addInterceptor来添加
     */
    private fun interceptor() {
        client = client.newBuilder().addInterceptor(LoggingInterceptor()).build()  // 加入拦截器
        val request: Request = Request.Builder()
            // http://www.publicobject.com/helloworld.txt 这个会重定向到https://publicobject.com/helloworld.txt
            // 可以打断点查看到拦截器中request和response 的 url不一样
            .url("http://www.publicobject.com/helloworld.txt")
            .header("User-Agent", "OkHttp Example")
            .build()

        val response = client.newCall(request).execute()
        response.body().close()
    }

    internal class LoggingInterceptor : Interceptor {
        @Throws(IOException::class)
        override fun intercept(chain: Interceptor.Chain): Response {
            val request = chain.request()   // chain是关键
            /**
             * 这里可以看到拦截器能拿到request和response
             * 所以可以轻易的重写请求和响应
             */
            val t1 = System.nanoTime()
            logger.info(    //记录传出
                java.lang.String.format(
                    "Sending request %s on %s%n%s",
                    request.url(), chain.connection(), request.headers()
                )
            )
            val response = chain.proceed(request)  // 拦截传入的response
            val t2 = System.nanoTime()
            logger.info(   // 传出传入做差 记录传出请求和传入响应
                String.format(
                    "Received response for %s in %.1fms%n%s",
                    response.request().url(), (t2 - t1) / 1e6, response.headers()
                )
            )
            return response
        }
    }

    /**
     * 401 Not Authorized
     * 就是请求没通过验证,OkHttp会自动重试未验证,所以一般通过response重新对其request附加证书即可
     * 如果不打算自动验证,在新建Authenticator的时候返回null
     * 通过Credentials.basic()创建证书实例,然后Builder
     */
    private fun autoAuthentication() {
        client = client.newBuilder()  // 浅拷贝client
            .authenticator { route, response ->
                println("response: $response")
                val credential = Credentials.basic("证书账号", "证书密码")  // 创建证书实例
                response.request().newBuilder()   // 注意:在response中拿到request,然后重新builder了headers
                    .header("Authorization", credential)  // 附加证书
                    .build()
            }
            .build()
        // TODO: 写请求即可
        // 不打算自动验证
        client = OkHttpClient.Builder() // 直接重新实例化一个client
            .authenticator(Authenticator { route, response ->
                if (response.request().header("Authorization") != null) {
                    return@Authenticator null // 返回null直接不自动验证
                }
                val credential = Credentials.basic("证书账号", "证书密码")  // 创建证书实例
                response.request().newBuilder()   // 注意:在response中拿到request,然后重新builder了headers
                    .header("Authorization", credential)  // 附加证书
                    .build()
            })
            .build()
        // TODO: 写请求即可
    }

    /**
     * 核心api:OkHttpClient的newBuilder()
     */
    private fun customCall() {
        val request: Request = Request.Builder()
            .url("xxxxurl")
            .build()

        try {
            val copy = client.newBuilder()          // 浅拷贝 通过newBuilder
                .readTimeout(500, TimeUnit.MILLISECONDS)
                .build()
            val response = copy.newCall(request).execute()
            println("Response 1 succeeded: $response")
        } catch (e: IOException) {
            println("Response 1 failed: $e")
        }
    }

    /**
     * 没有响应时使用超时结束call
     * OkHttp支持连接,读取和写入超时
     */
    private fun timeoutSetting() {
        client = OkHttpClient.Builder()
            .connectTimeout(10, TimeUnit.SECONDS)
            .writeTimeout(10, TimeUnit.SECONDS)
            .readTimeout(30, TimeUnit.SECONDS)
            .build()
        // TODO: 后面就是正常请求了
    }


    /**
     * 不管同步还是异步的call都可以取消,核心api:call.cancel()
     * 取消另外的请求,一般采用client.dispatcher()拿到全部的请求然后去比对,最后取消
     * tag可以在Request.Builder()中通过tag("tag")加入
     */
    private fun cancelRequest() {
        val request: Request = Request.Builder()
            .url("xxxx") // This URL is served with a 2 second delay.
            .build()
        val call: Call = client.newCall(request)
        call.cancel()   // 取消

        // 下面是按照tag取消请求
        for (call in client.dispatcher().queuedCalls()) {
            if ("你的tag".equals(call.request().tag())) {
                call.cancel();
            }
        }
        for (call in client.dispatcher().runningCalls()) {
            if ("你的tag".equals(call.request().tag())) {
                call.cancel();
            }
        }
        // 下面是取消全部
        for (call in client.dispatcher().queuedCalls()) {
            call.cancel();
        }
        for (call in client.dispatcher().runningCalls()) {
            call.cancel();
        }

    }

    /**
     * CacheControl.FORCE_NETWORK 可以通过这个让某个请求不使用缓存,不过会返回504
     * 没试验,待定
     */
    private fun cache() {
        val cacheSize = 10 * 1024 * 1024 // 10 MiB
        val cache = Cache(File("缓存目录"), cacheSize.toLong())
        client = OkHttpClient.Builder()  // 为OkHttpClient附加缓存配置
            .cache(cache)
            .build()
        // TODO: 之后就是正常请求
    }

    private fun gson() {
        val gson = Gson()
        val request: Request = Request.Builder()
            .url("https://api.github.com/gists/c2a7c39532239ff261be")
            .build()
        client.newCall(request).enqueue(object : Callback {
            override fun onFailure(call: Call?, e: IOException?) {
                e?.printStackTrace()
            }

            override fun onResponse(call: Call?, response: Response?) {
                if (!response!!.isSuccessful) throw IOException("Unexpected code $response")

                val gist: Gist = gson.fromJson(
                    response.body().charStream(),
                    Gist::class.java
                )  // 核心gson.fromJson
                for ((key, value) in gist.files!!.entries) {
                    println("$key : ${value.content}")
                }
            }
        })
    }

    class Gist {
        var files: Map<String, GistFile>? = null
    }

    class GistFile {
        var content: String? = null
    }

    private fun commitImg() {
        val MEDIA_TYPE_PNG: MediaType = MediaType.parse("image/png")
        val requestBody: RequestBody = MultipartBody.Builder()  // 需要用到MultipartBody
            .setType(MultipartBody.FORM)
            .addFormDataPart("title", "Square Logo")
            .addFormDataPart(
                "image", "logo-square.png",
                RequestBody.create(MEDIA_TYPE_PNG, File("Android设备中照片的路径"))
            )
            .build()

        val request: Request = Request.Builder()
            .header("Authorization", "Client-ID ...")
            .url("https://api.imgur.com/3/image")
            .post(requestBody)
            .build()

        client.newCall(request).enqueue(object : Callback {
            override fun onFailure(call: Call?, e: IOException?) {
                e?.printStackTrace()
            }

            override fun onResponse(call: Call?, response: Response?) {
                if (!response!!.isSuccessful) throw IOException("Unexpected code $response")
                println(response.body().string())
            }
        })
    }

    private fun commitFile() {
        val MEDIA_TYPE_MARKDOWN: MediaType = MediaType.parse("text/x-markdown; charset=utf-8")
        val file = File("Android设备文件路径,需要权限")
        val request: Request = Request.Builder()
            .url("https://api.github.com/markdown/raw")
            .post(RequestBody.create(MEDIA_TYPE_MARKDOWN, file))  // 核心都是RequestBody.create
            .build()

        client.newCall(request).enqueue(object : Callback {
            override fun onFailure(call: Call?, e: IOException?) {
                e?.printStackTrace()
            }

            override fun onResponse(call: Call?, response: Response?) {
                if (!response!!.isSuccessful) throw IOException("Unexpected code $response")
                println(response.body().string())
            }
        })
    }

    /**
     * 请求体的内容由流写入产生
     * 流直接写入Okio的BufferedSink
     * 如果是OutputStream,可以使用BufferedSink.outputStream()来获取
     */
    private fun postTextByFlow() {
        val MEDIA_TYPE_MARKDOWN: MediaType = MediaType.parse("text/x-markdown; charset=utf-8")

        val requestBody: RequestBody = object : RequestBody() {  // 核心 重写改请求体
            override fun contentType(): MediaType {
                return MEDIA_TYPE_MARKDOWN
            }

            override fun writeTo(sink: BufferedSink?) {
                sink!!.writeUtf8("Numbers\n")
                sink.writeUtf8("-------\n")
                for (i in 2..100) {
                    sink.writeUtf8(String.format(i.toString() + "\n"))
                }
            }
        }

        val request: Request = Request.Builder()
            .url("https://api.github.com/markdown/raw")
            .post(requestBody)
            .build()
        client.newCall(request).enqueue(object : Callback {
            override fun onFailure(call: Call?, e: IOException?) {
                e?.printStackTrace()
            }

            override fun onResponse(call: Call?, response: Response?) {
                if (!response!!.isSuccessful) throw IOException("Unexpected code $response")
                println(response.body().string())
            }
        })
    }

    /**
     * 多行文本的定义如同python的一般
     * MediaType指定渲染markdown
     * RequestBody.create是最终附加的核心api
     * 因为整个请求体都在内存中,因此避免使用此api提交大文档(大于1MB)
     */
    private fun postMoreLineText() {
        val postBody =
            """
            Releases
            --------
            
             * _1.0_ May 6, 2013
             * _1.1_ June 15, 2013
             * _1.2_ August 11, 2013
            """

        val MEDIA_TYPE_MARKDOWN: MediaType = MediaType.parse("text/x-markdown; charset=utf-8")
        val request: Request = Request.Builder()
            .url("https://api.github.com/markdown/raw")
            .post(RequestBody.create(MEDIA_TYPE_MARKDOWN, postBody))
            .build()

        client.newCall(request).enqueue(object : Callback {
            override fun onFailure(call: Call?, e: IOException?) {
                e?.printStackTrace()
            }

            override fun onResponse(call: Call?, response: Response?) {
                if (!response!!.isSuccessful) throw IOException("Unexpected code $response")
                println(response.body().string())
            }
        })
    }

    /**
     * 通过response.header(key)获取即可,如果key不存在返回""
     * 如果用的是headers(key),key不存在返回[]
     */
    private fun getResponseHeaders() {
        val request: Request = Request.Builder()
            .url("https://api.github.com/repos/square/okhttp/issues")
            .header("User-Agent", "OkHttp Headers.java")
            .addHeader("Accept", "application/json; q=0.5")
            .addHeader("Accept", "application/vnd.github.v3+json")
            .build()

        val response = client.newCall(request).enqueue(object : Callback {
            override fun onFailure(call: Call?, e: IOException?) {
                e?.printStackTrace()
            }

            override fun onResponse(call: Call?, response: Response?) {
                if (!response!!.isSuccessful) throw IOException("Unexpected code $response")

                println("Server: " + response.header("Server"))
                println("Date: " + response.header("Date"))
                println("Vary: " + response.headers("Vary"))
                println("Hahsahdasdas: " + response.headers("Hahsahdasdas"))
            }

        })
    }

    /**
     * 异步拉文件,通过响应的回调
     */
    private fun asnycDownFile() {
        val request: Request =
            Request.Builder().url("http://publicobject.com/helloworld.txt").build()
        client.newCall(request).enqueue(object : Callback {
            override fun onFailure(call: Call?, e: IOException?) {
                e?.printStackTrace()
            }

            override fun onResponse(call: Call?, response: Response?) {
                if (!response!!.isSuccessful) {
                    throw IOException("$response")
                }
                val headers: Headers = response.headers()
                for (i in 0 until headers.size()) {
                    Log.d("TestActivity", headers.name(i) + " : " + headers.value(i))
                }
                Log.e("TestActivity", response.body().toString())
            }
        })
        Log.d("TestActivity", "main thread is run...")
    }

    /**
     * 同步拉文件,必须开子线程
     * 会将把整个文档加载到内存中。对于超过1MB的响应body,应使用流的方式来处理body
     */
    private fun syncDownFile() {
        Thread {
            run {
                val request: Request =
                    Request.Builder().url("http://publicobject.com/helloworld.txt").build()
                val response: Response = client.newCall(request).execute()
                if (!response.isSuccessful) {
                    throw IOException("$response")
                }
                val headers: Headers = response.headers()
                for (i in 0 until headers.size()) {
                    Log.d("TestActivity", headers.name(i) + " : " + headers.value(i))
                }
                Log.e("TestActivity", response.body().toString())
            }
        }.start()

    }

    private fun postRequest() {
        val body: RequestBody = FormBody.Builder().add("", "").build()
        val request: Request = Request.Builder()
            .url("http://www.baidu.com")
            .post(body)
            .build()
        Thread {
            run {
                try {
                    val response: Response = client.newCall(request).execute()
                    if (response.isSuccessful) {
                        Log.d("TestActivity and post ", response.body().string())
                    } else {
                        throw IOException("" + response)
                    }
                } catch (e: IOException) {
                    e.printStackTrace()
                }
            }
        }.start()
    }

    private fun getRequest() {
        val request: Request = Request.Builder()
            .get()
            .tag(this)
            .url("http://www.baidu.com")
            .build()
        Thread {
            run {
                try {
                    val response: Response = client.newCall(request).execute()
                    if (response.isSuccessful) {
                        Log.d("TestActivity and get", response.body().string())
                    } else {
                        throw IOException("" + response)
                    }
                } catch (e: IOException) {
                    e.printStackTrace()
                }
            }
        }.start()
    }

}

xml文件:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/bt_get"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Get请求"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/bt_post"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="20dp"
        android:text="Post请求"
        app:layout_constraintLeft_toRightOf="@id/bt_get"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/bt_snyc_down_file"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="20dp"
        android:text="同步拉文件"
        app:layout_constraintLeft_toRightOf="@id/bt_post"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/bt_asnyc_down_file"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="20dp"
        android:text="异步拉文件"
        app:layout_constraintLeft_toRightOf="@id/bt_snyc_down_file"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/bt_get_response_headers"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="20dp"
        android:text="获取响应头信息"
        app:layout_constraintLeft_toRightOf="@id/bt_asnyc_down_file"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/bt_post_more_line_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="20dp"
        android:text="POST多行文本给Server"
        app:layout_constraintLeft_toRightOf="@id/bt_get_response_headers"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/bt_post_text_by_flow"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="POST多行文本给Server通过流"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toBottomOf="@id/bt_get" />

    <Button
        android:id="@+id/bt_commit_file"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="20dp"
        android:text="POST提交文件"
        app:layout_constraintLeft_toRightOf="@id/bt_post_text_by_flow"
        app:layout_constraintTop_toBottomOf="@id/bt_get" />

    <Button
        android:id="@+id/bt_commit_img"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="20dp"
        android:text="POST提交照片"
        app:layout_constraintLeft_toRightOf="@id/bt_commit_file"
        app:layout_constraintTop_toBottomOf="@id/bt_get" />

    <Button
        android:id="@+id/bt_gson"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="20dp"
        android:text="Gson解析"
        app:layout_constraintLeft_toRightOf="@id/bt_commit_img"
        app:layout_constraintTop_toBottomOf="@id/bt_get" />

    <Button
        android:id="@+id/bt_cache"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="缓存"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toBottomOf="@id/bt_post_text_by_flow" />

    <Button
        android:id="@+id/bt_cancel_request"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="20dp"
        android:text="取消请求"
        app:layout_constraintLeft_toRightOf="@id/bt_cache"
        app:layout_constraintTop_toBottomOf="@id/bt_post_text_by_flow" />

    <Button
        android:id="@+id/bt_timeout_setting"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="20dp"
        android:text="超时配置"
        app:layout_constraintLeft_toRightOf="@id/bt_cancel_request"
        app:layout_constraintTop_toBottomOf="@id/bt_post_text_by_flow" />

    <Button
        android:id="@+id/bt_custom_call"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="20dp"
        android:text="浅拷贝连接自定义单个请求"
        app:layout_constraintLeft_toRightOf="@id/bt_timeout_setting"
        app:layout_constraintTop_toBottomOf="@id/bt_post_text_by_flow" />

    <Button
        android:id="@+id/bt_auto_authentication"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="20dp"
        android:text="401自动验证"
        app:layout_constraintLeft_toRightOf="@id/bt_custom_call"
        app:layout_constraintTop_toBottomOf="@id/bt_post_text_by_flow" />

    <Button
        android:id="@+id/bt_interceptor"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="拦截器"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toBottomOf="@id/bt_cache" />

    <Button
        android:id="@+id/bt_interceptor_network"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="20dp"
        android:text="网络拦截器"
        app:layout_constraintLeft_toRightOf="@id/bt_interceptor"
        app:layout_constraintTop_toBottomOf="@id/bt_cache" />

</androidx.constraintlayout.widget.ConstraintLayout>

这里mBinding我提前和用viewBinding把V和M关联了,阅读代码只需要把mBinding.btGet看成xml里面的bt_get按钮就行。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值