El uso y explicación detallada de OKHttp3

I. Resumen

OKHttp es un marco de trabajo de código abierto para procesar solicitudes de red. Andorid es actualmente el mejor marco de trabajo de red. La capa inferior de Retrofit también es OKHttp, que se usa para reemplazar HttpUrlConnection y Apache HttpClient (se eliminó API23 6.0).

En términos generales, OKHttp es un marco HTTP excelente. Admite solicitudes GET y POST, admite la carga y descarga de archivos Http, admite la carga de imágenes, admite la compresión GZIP transparente de archivos descargados, admite el almacenamiento en caché de respuestas para evitar solicitudes de red repetidas y admite el uso de Pool de conexiones para reducir el problema del retraso en la respuesta.

Ventajas de OKHttp:

1. Admite HTTP2/SPDY , lo que permite que todas las solicitudes al mismo host compartan la misma conexión de socket.

2. Si OkHttp no está disponible para HTTP2/SPDY , se usará un conjunto de conexiones para multiplexar conexiones para mejorar la eficiencia.

3. Proporciona   soporte predeterminado para GZIP para reducir el tamaño del contenido transmitido

4. Proporciona  un mecanismo de almacenamiento en caché para las respuestas HTTP , lo que puede evitar solicitudes de red innecesarias

5. Cuando hay un problema con la red, OkHttp volverá a intentar automáticamente varias direcciones IP de un host

2. Uso básico

2.1 Proyecto de configuración

(1) Primero agregue permisos de red en el archivo de manifiesto AndroidManifest.xml

 <uses-permission android:name="android.permission.INTERNET"/>
 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
 <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

(2) Agregue la dependencia okhttp en el archivo build.gradle:

implementation 'com.squareup.okhttp3:okhttp:4.2.0'

2.2 Pasos a utilizar:

(1) Crear una instancia de OkHttpClient

(2) Construya el objeto de solicitud Request a través de la clase auxiliar Builder

(3) La instancia de OkHttpClient devuelve la solicitud y obtiene el objeto Call

(4) Ejecutar la solicitud de forma sincrónica/asincrónica y obtener el objeto de Respuesta

2.3 Explicación detallada del método

(1) Crear una instancia de OkHttpClient

//方式一:创建OkHttpClient实例,使用默认构造函数,创建默认配置OkHttpClient(官方建议全局只有一个实例)
OkHttpClient okHttpClient = new OkHttpClient();
 
//方式二:通过new OkHttpClient.Builder() 一步步配置一个OkHttpClient实例
OkHttpClient httpClient = new OkHttpClient.Builder().connectTimeout(13, TimeUnit.SECONDS).build();
 
//方式三:如果要求使用现有的实例,可以通过newBuilder().build()方法进行构造
OkHttpClient client = okHttpClient.newBuilder().build();

Use el constructor predeterminado para crear una instancia de OkHttpClient y cree una configuración predeterminada de OkHttpClient (la recomendación oficial es que solo hay una instancia a nivel mundial). También puede configurar una instancia de OkHttpClient paso a paso a través de new OkHttpClient.Builder() .Si necesita usar una instancia existente, puede construirla a través del método newBuilder().build() .

(2) Configuración del tiempo de espera

Cuando creamos una instancia de OkHttpClient, también estableceremos propiedades relacionadas en forma de .Builder().build() , como la configuración de tiempo de espera:

    //1.构建OkHttpClient实例
    final OkHttpClient okHttpClient = new OkHttpClient.Builder()
            .connectTimeout(2, TimeUnit.SECONDS)//链接超时为2秒,单位为秒
            .writeTimeout(2, TimeUnit.SECONDS)//写入超时
            .readTimeout(2, TimeUnit.SECONDS)//读取超时
            .build();
 
    //2.通过Builder辅助类构建请求对象
    final Request request = new Request.Builder()
            .url("http://httpbin.org/delay/10")//URL地址
            .build();//构建
 
    //创建线程,在子线程中运行
    new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                //3.通过mOkHttpClient调用请求得到Call
                final Call call = okHttpClient.newCall(request);
                //4.执行同步请求,获取响应体Response对象
                Response response = call.execute();
                Log.e(TAG, "请求(超时)==" + response);
            } catch (IOException e) {
                e.printStackTrace();
                Log.e(TAG, "请求(超时)==" + e.toString());
            }
        }
    }).start();

Aquí configuro la solicitud para que sea exitosa después de 10 segundos. El tiempo de espera de la solicitud es de 2 segundos, y el tiempo de espera de lectura y escritura es de 2 segundos. La solicitud de red es una operación de solicitud que requiere mucho tiempo, lo que requiere que se ejecute otro subproceso, y se lanza una excepción de tiempo de espera Prueba el siguiente efecto:

Aquí hay varias funciones importantes de las solicitudes http, Request es la solicitud de acceso de OKHttp, Builder es la clase auxiliar de acceso y Response es la respuesta a la solicitud de OKHttp.

Solicitud (request): Cada solicitud HTTP debe contener una URL, un método GET o POST, Header y otros parámetros, y también puede contener flujos de datos de tipos de contenido específicos.

Respuestas (response): la respuesta contiene un código de respuesta (200 significa éxito, 404 significa no encontrado), encabezado y cuerpo opcional personalizado.
Además, el código de estado devuelto se puede obtener de acuerdo con response.code().

OKHttp: En pocas palabras, se puede enviar una solicitud http a través de OkHttpClient y se puede leer la respuesta de la solicitud. Es una fábrica para producir llamadas. Benefíciese de factores como una caché de respuesta compartida/grupo de subprocesos/enlaces reutilizados, la mayoría de las aplicaciones utilizan una instancia de OKHttpClient para satisfacer las solicitudes Http de toda la aplicación

(3) Configuración y lectura de encabezados HTTP

La estructura de datos del encabezado HTTP es de tipo Map<String, List<String>> , es decir, para cada encabezado HTTP, puede haber múltiples valores, pero la mayoría de los encabezados HTTP tienen un solo valor y solo algunos HTTP. Encabezados Se permiten varios valores Para obtener la descripción del valor del nombre, consulte: Descripción del parámetro del encabezado HTTP

El método de procesamiento de OKHTTP es:

  • encabezado (nombre, valor) para establecer el valor único del encabezado HTTP, si ya hay información de respuesta en la solicitud, reemplácela directamente;
  • addHeader(nombre, valor) para complementar el nuevo valor. Si el nombre-valor de nombre ya existe en el encabezado de la solicitud, se seguirá agregando. Habrá varios "pares clave-valor" con el mismo nombre y valores diferentes en el encabezado de la solicitud;
  • header(name, value) lee un valor único o el último de múltiples valores;
  • headers(name) obtiene todos los valores.
    Request request = new Request.Builder()
            .url("https://api.github.com/repos/square/okhttp/issues")
            .header("User-Agent", "OkHttp Headers.java")//设置唯一值
            .addHeader("Server", "application/json; q=0.5")//设置新值
            .addHeader("Server", "application/vnd.github.v3+json")//设置新值
            .build();
 
    mOkHttpClient.newCall(request).enqueue(new Callback() {
        @Override
        public void onFailure(@NotNull Call call, @NotNull IOException e) {
            Log.e(TAG, "Post请求(HTTP头)异步响应failure==" + e.getMessage());
        }
 
        @Override
        public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
            Log.e(TAG, "header:Date==" + response.header("Date"));
            Log.e(TAG, "header:User-Agent==" + response.header("User-Agent"));
            Log.e(TAG, "headers:Server==" + response.headers("Server"));
            Log.e(TAG, "headers:Vary==" + response.headers("Vary"));
 
            Log.e(TAG, "Post请求(HTTP头)异步响应Success==" + response.body().string());
        }
    });

En la Solicitud de solicitud , configure la URL (dirección de solicitud) en forma de .Builder().build(), y también puede configurar la información del encabezado de la solicitud. Hay muchos métodos de salida para el cuerpo de la respuesta, y string() es solo uno de ellos. Tenga en cuenta Si es un archivo de descarga, es response.body().bytes(). Echemos un vistazo a los resultados impresos:

(4) solicitud GET (síncrona)

new Thread(new Runnable() {
        @Override
        public void run() {
            //通过Builder辅助类构建请求对象
            Request request = new Request.Builder()
                    .get()//get请求
                    .url("https://www.baidu.com")//请求地址
                    .build();//构建
 
            try {
                //通过mOkHttpClient调用请求得到Call
                final Call call = mOkHttpClient.newCall(request);
                //执行同步请求,获取Response对象
                Response response = call.execute();
 
                if (response.isSuccessful()) {//如果请求成功
                    String string = response.body().string();
                    Log.e(TAG, "get同步请求success==" + string);
                    //响应体的string()对于小文档来说十分方便高效,但是如果响应体太大(超过1M),应避免使用string()方法,
                    //因为它会把整个文档加载到内存中,对用超多1M的响应body,应该使用流的方式来处理。
 
                    //response.body().bytes();//字节数组类型
                    //response.body().byteStream();//字节流类型
                    //response.body().charStream();//字符流类型
 
                    printHeads(response.headers());
                } else {
                    Log.e(TAG, "get同步请求failure==");
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }).start();
 
   /**
     * 打印请求头信息
     *
     * @param headers 请求头集合
     */
    private void printHeads(Headers headers) {
        if (headers == null) return;
        for (int i = 0; i < headers.size(); i++) {
            Log.e(TAG, "请求头==" + headers.name(i) + ":" + headers.value(i));
        }
    }

Declararlo como una solicitud GET en la solicitud, establecer la dirección de la solicitud de URL, llamar a la instancia mOkHttpClient.new Call(request) para llamar a la solicitud, devolver la llamada y ejecutarla sincrónicamente a través del método sincrónico call.execute() . , es necesario abrir un subproceso para ejecutarlo. Debe juzgar manualmente si la solicitud es exitosa a través de response.isSuccessful() y, al mismo tiempo, hacer que Heander imprima la información relevante del encabezado de la solicitud a través del cuerpo de la respuesta y ver el resultado:

(5) Solicitud GET (asincrónica)

La diferencia entre síncrono y asíncrono es el método invocado por el objeto Call . call.enqueue(callback) implementa los dos métodos de éxito y fracaso de devolución de llamada de función. El método asíncrono se puede ejecutar sin abrir un subproceso.

Nota: La sincronización se bloquea, se ejecuta en serie y la sincronización se produce en el subproceso actual. La asincronía es concurrente y los subprocesos se crearán nuevamente para manejar las operaciones que consumen mucho tiempo.

    //通过Builder辅助类构建Request对象,链式编程
    Request request = new Request.Builder()
            .url("https://www.baidu.com")
            .get()
            .build();
    //异步
    mOkHttpClient.newCall(request).enqueue(new Callback() {
        @Override
        public void onFailure(@NotNull Call call, @NotNull IOException e) {
            e.printStackTrace();
            Log.e(TAG, "get异步响应失败==" + e.toString());
        }
 
        @Override
        public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
            //主线程中更新UI
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    //TODO 在主线程中更新UI的操作
                }
            });
 
            Log.e(TAG, "get异步当前线程,线程id==" + Thread.currentThread().getId());
            String result = response.body().string();
            Log.e(TAG, "get异步响应成功==" + result);
            printHeads(response.headers());
        }
    });
 
    Log.e(TAG, "主线程,线程id==" + Thread.currentThread().getId());

Imprima el registro de la siguiente manera:

onFailure() y onResponse() son métodos que se llamarán cuando la solicitud falle y tenga éxito, respectivamente. Hay un punto a tener en cuenta aquí. onFailure() y onResponse() se ejecutan en un subproceso asíncrono, por lo que si escribe la operación de actualización de la interfaz de usuario en estos dos métodos en Android, se informará un error. En este momento, puede usar runOnUiThread Este método realiza una operación de actualización de la interfaz de usuario.

(6) POST envía una solicitud de cadena (síncrona)

La solicitud de creación de solicitud posterior es la misma que get, excepto que la solicitud posterior debe enviar un formulario, que es RequestBody. Hay muchas formas de formularios. Los parámetros de envío de la solicitud posterior deben construir un objeto RequestBody . El proceso de envío posterior debe encapsular el contenido enviado en un RequestBody y, al mismo tiempo, debe establecer el tipo MediaType . MediaType se usa para describir el tipo de contenido de la solicitud Http y el cuerpo de la respuesta, es decir, Content-Type, URL (dirección de solicitud) y RequestBody (contenedor de parámetros) se establecen en forma de .Builder().build() .

    //构建RequestBody对象,post提交的过程需要将提交的内容封装到一个RequestBody中
    //MediaType用于描述Http请求和响应体的内容类型,也就是Content-Type
    MediaType mediaType = MediaType.parse("text/plain; charset=utf-8");
    RequestBody requestBody = RequestBody.create("提交的内容", mediaType);
    final Request request = new Request.Builder()
            .post(requestBody)
            .url("https://api.github.com/markdown/raw")
            .build();
 
    new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                Response response = mOkHttpClient.newCall(request).execute();
                if (response.isSuccessful()) {
                    Log.e(TAG, "Post请求String同步响应success==" + response.body().string());
                } else {
                    Log.e(TAG, "Post请求String同步响应failure==" + response.body().string());
                }
            } catch (IOException e) {
                e.printStackTrace();
                Log.e(TAG, "Post请求String同步响应failure==" + e.getMessage());
            }
        }
    }).start();

El resultado de la solicitud es el siguiente:

El formato de datos de RequestBody debe especificar el tipo de contenido, y hay tres comunes:

  • application/x-www-form-urlencoded data es una forma ordinaria;
  • Hay archivos en los datos multipart/form-data;
  • application/json data es un json.
 MediaType mediaType = MediaType.parse("image/png");
 RequestBody requestBody = RequestBody.create(xxx.png, mediaType);

Se pueden configurar diferentes tipos de contenido cambiando el contenido en MediaType. Por ejemplo, image/png significa que Portable Network Graphics (PNG) es un formato de gráficos de mapa de bits comprimido sin pérdidas que admite esquemas de color de índice, escala de grises y RGB. Y funciones como canales alfa .

(7) POST envía una solicitud de cadena (asincrónica)

    RequestBody requestBody = RequestBody.create("提交内容", MediaType.parse("text/plain; charset=utf-8"));
    Request request = new Request.Builder()
            .url("https://api.github.com/markdown/raw")
            .post(requestBody)
            .build();
        mOkHttpClient.newCall(request).enqueue(new Callback() {
        @Override
        public void onFailure(@NotNull Call call, @NotNull IOException e) {
            Log.e(TAG, "Post请求String异步响应failure==" + e.getMessage());
        }
 
        @Override
        public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
            String string = response.body().string();
            Log.e(TAG, "Post请求String异步响应success==" + string);
        }
    });

Las solicitudes asíncronas tienen principalmente un método de devolución de llamada, que es similar a otras partes y sincronización. Echemos un vistazo al efecto:

(8) POST envía una solicitud de par clave-valor (asincrónico)

Se requiere FormBody para enviar pares clave-valor para los parámetros de solicitud FormBody hereda de RequestBody y se agrega en forma de .add("key", "value") :

    //提交键值对需要用到FormBody,FormBody继承自RequestBody
    FormBody formBody = new FormBody.Builder()
            //添加键值对(通多Key-value的形式添加键值对参数)
            .add("key", "value")
            .build();
    final Request request = new Request.Builder()
            .post(formBody)
            .url("url")
            .build();
 
        mOkHttpClient.newCall(request).enqueue(new Callback() {
        @Override
        public void onFailure(@NotNull Call call, @NotNull IOException e) {
            Log.e(TAG, "Post请求(键值对)异步响应failure==" + e.getMessage());
        }
 
        @Override
        public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
            String result = response.body().string();
            Log.e(TAG, "Post请求(键值对)异步响应Success==" + result);
        }
    });

(9) Solicitud de archivo de envío POST (asíncrono)

Post envía el archivo y pasa el archivo a RequestBody :

    RequestBody requestBody = RequestBody.create(MediaType.parse("text/plain; charset=utf-8"), new File("text.txt"));
    Request request = new Request.Builder()
            .post(requestBody)
            .url("https://api.github.com/markdown/raw")
            .build();
        mOkHttpClient.newCall(request).enqueue(new Callback() {
        @Override
        public void onFailure(@NotNull Call call, @NotNull IOException e) {
            Log.e(TAG, "Post请求(文件)异步响应failure==" + e.getMessage());
        }
 
        @Override
        public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
            String result = response.body().string();
            Log.e(TAG, "Post请求(文件)异步响应Success==" + result);
        }

El efecto de la solicitud es el siguiente: hay ejemplos en el código fuente

(10) Solicitud de formulario de envío POST (asíncrono)

 Utilice FormEncodingBuilder para construir el cuerpo de la solicitud con el mismo efecto que las etiquetas HTML, y los pares clave-valor se codificarán mediante una forma de codificación de URL compatible con HTML.

   //使用FormEncodingBuilder来构建和HTML标签相同效果的请求体,键值对将使用一种HTML兼容形式的URL编码来进行编码
    FormBody formBody = new FormBody.Builder()
            .add("search", "Jurassic Park")
            .build();
    Request request = new Request.Builder()
            .url("https://en.wikipedia.org/w/index.php")
            .post(formBody)
            .build();
 
        mOkHttpClient.newCall(request).enqueue(new Callback() {
        @Override
        public void onFailure(@NotNull Call call, @NotNull IOException e) {
            Log.e(TAG, "Post请求(表单)异步响应failure==" + e.getMessage());
        }
 
        @Override
        public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
            String result = response.body().string();
            Log.e(TAG, "Post请求(表单)异步响应Success==" + result);
        }
    });

(11) Solicitud de flujo de envío POST (síncrono)

Post envía el cuerpo de la solicitud en forma de transmisión, y el contenido del cuerpo de la solicitud se genera mediante la escritura de la transmisión. Aquí, la transmisión se escribe directamente en BufferedSink de OKIO . Su programa puede usar OutputStream , puede usar BufferedSink.outputStream() para obtenerlo, aquí necesita reescribir varios métodos en RequestBody, colocar los datos locales en el cuerpo de solicitud del protocolo Http y luego enviarlo al servidor.

    //以流的方式Post提交请求体,请求体的内容由流写入产生,这里是流直接写入OKIO的BufferedSink。
    // 你的程序可能会使用OutputStream, 你可以使用BufferedSink.outputStream()来获取
    final MediaType mediaType = MediaType.parse("text/x-markdown; charset=utf-8");
    //重写RequestBody中的几个方法,将本地数据放入到Http协议的请求体中,然后发送到服务器
    final RequestBody requestBody = new RequestBody() {
        @Nullable
        @Override
        public MediaType contentType() {
            //返回内容类型
            return mediaType;
        }
 
        @Override
        public void writeTo(@NotNull BufferedSink bufferedSink) throws IOException {
            //输入数据头
            bufferedSink.writeUtf8("Numbers\\n");
            bufferedSink.writeUtf8("-------\\n");
 
            //构造数据
            for (int i = 2; i < 997; i++) {
                bufferedSink.writeUtf8(String.format(" * %s = %s\n", i, factor(i)));
            }
        }
    };
 
    //构建请求
    final Request request = new Request.Builder().
            url("https://api.github.com/markdown/raw")
            .post(requestBody)
            .build();
        //开启线程
        new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                Response response = mOkHttpClient.newCall(request).execute();
                if (response.isSuccessful()) {
                    String result = response.body().toString();
                    Log.e(TAG, "Post请求(流)异步响应Success==" + result);
                } else {
                    Log.e(TAG, "Post请求(流)异步响应failure==" + response);
                    throw new IOException("Unexpected code " + response);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }).start();
 
    private String factor(int n) {
        for (int i = 2; i < n; i++) {
            int x = n / i;
            if (x * i == n) {
                return factor(x) + "x" + i;
            }
        }
        return Integer.toString(n);
    }

El resultado de la solicitud es el siguiente:

(12) Solicitud de bloqueo de envío POST (asíncrono)

MultipartBuilder puede crear cuerpos de solicitud complejos, que son compatibles con la carga de archivos HTML. Cada cuerpo de solicitud en el encabezado de solicitud de varias partes es un cuerpo de solicitud, y puede definir su propio cuerpo de solicitud. Estos cuerpos de solicitud se pueden usar para describir esta solicitud. Por ejemplo , su Disposición de contenido, si Content-Length y Content-Type están disponibles, se agregarán automáticamente al encabezado de la solicitud.

    MediaType mediaType = MediaType.parse("image/png");
    String IMGUR_CLIENT_ID = "...";
    //构建body
    MultipartBody multipartBody = new MultipartBody.Builder()
            .setType(MultipartBody.FORM)
            .addFormDataPart("title", "Square Logo")
            .addFormDataPart("image", "logo-square.png", RequestBody.create(mediaType,
                    new File("website/static/logo-square.png")))
            .build();
    
    //构建请求
    Request request = new Request.Builder()
            .header("Authorization", "Client-ID " + IMGUR_CLIENT_ID)
            .url("https://api.imgur.com/3/image")
            .post(multipartBody)
            .build();
        
    //执行请求
        mOkHttpClient.newCall(request).enqueue(new Callback() {
        @Override
        public void onFailure(@NotNull Call call, @NotNull IOException e) {
            Log.e(TAG, "Post请求(分块)异步响应failure==" + e.getMessage());
        }
 
        @Override
        public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
            String result = response.body().string();
            Log.e(TAG, "Post请求(分块)异步响应Success==" + result);
        }
    });

El resultado de la solicitud es el siguiente:

Si carga un archivo que no es una imagen, pero no sabe qué completar en la "imagen/png" en MediaType.parse ("imagen/png"), puede consultar esta página .

Dirección de la fuente


Artículos relacionados:

Explicación detallada y uso sencillo de Retrofit2

  • Introducción y uso sencillo de Retrofit2

El uso y explicación detallada de OKHttp3

  • Introducción y análisis del uso de OKHttp3

Explicación detallada del código fuente de OKHttp3

  • Explicar el proceso clave y las operaciones importantes de OKHttp3 desde la perspectiva del código fuente

Reimpreso: https://blog.csdn.net/m0_37796683/article/details/101029208

Supongo que te gusta

Origin blog.csdn.net/gqg_guan/article/details/126603225
Recomendado
Clasificación