一文了解OKHttp3全(使用篇)

5,631 阅读6分钟

1.简介

okhttp3现在基本都在用的一个底层网络框架。这篇博客主要的目的就是将OKHttp3这个框架在开发中能用到的地方都记录下来,也当一个工具文档为日后使用时查找方便。

如果已经会了,那么请移步一文了解OKHttp3全(大话原理篇)

2.环境搭建

首先记得在build.gradle 和 配置文件分别加上依赖和网络权限

    implementation 'com.squareup.okhttp3:okhttp:3.8.0'
    implementation 'com.squareup.okio:okio:1.12.0'

以及权限

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

完事了,接下来介绍使用

3.Get使用

 OkHttpClient mClient = new OkHttpClient.Builder() // 构建者模式,创建实例
                .connectTimeout(20, TimeUnit.SECONDS) // 设置连接超时时间
                .build();
             
        Request mRequest = new Request.Builder() // 构建者模式,创建请求信息
                .get()
                .url("https://www.baidu.com")
                .build();

        Call call = mClient.newCall(mRequest); // 将request转换成call

        call.enqueue(new Callback() { // 执行call

            @Override
            public void onFailure(Call call, IOException e) {
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {

                final String strByNet = response.body().string();

                // 切换到主线程
                runOnUiThread(new Runnable() {

                    @Override
                    public void run() {

                        tv_msg.setText(strByNet);
                    }
                });
            }
        });

好了,这就是get的简单使用,不过说了是工具,自然要列出扩展 OkHttpClient.Builder的扩展属性

 * final Dispatcher dispatcher;  //重要:分发器,分发执行和关闭由request构成的Call
 * final Proxy proxy;  //代理
 * final List<Protocol> protocols; //协议
 * final List<ConnectionSpec> connectionSpecs; //传输层版本和连接协议
 * final List<Interceptor> interceptors; //重要:拦截器
 * final List<Interceptor> networkInterceptors; //网络拦截器
 * final ProxySelector proxySelector; //代理选择
 * final CookieJar cookieJar; //cookie
 * final Cache cache; //缓存
 * final InternalCache internalCache;  //内部缓存
 * final SocketFactory socketFactory;  //socket 工厂
 * final SSLSocketFactory sslSocketFactory; //安全套接层socket 工厂,用于HTTPS
 * final CertificateChainCleaner certificateChainCleaner; // 验证确认响应证书 适用 HTTPS 请求连接的主机名。
 * final HostnameVerifier hostnameVerifier;    //  主机名字确认
 * final CertificatePinner certificatePinner;  //  证书链
 * final Authenticator proxyAuthenticator;     //代理身份验证
 * final Authenticator authenticator;      // 本地身份验证
 * final ConnectionPool connectionPool;    //连接池,复用连接
 * final Dns dns;  //域名
 * final boolean followSslRedirects;  //安全套接层重定向
 * final boolean followRedirects;  //本地重定向
 * final boolean retryOnConnectionFailure; //重试连接失败
 * final int connectTimeout;    //连接超时
 * final int readTimeout; //read 超时
 * final int writeTimeout; //write 超时

4.服务器搭建

为了测试接下来的功能,我们自己搭建一个服务器。 先搭建环境,配置tomcat 作者的是mac所以就介绍下mac下配置tomcat的方式,Windows的小伙伴们可以参考这个(www.cnblogs.com/beginner-bo…)

  1. 下载Tomcat
  1. 下载文件解压
  1. 打开终端输入命令,简单办法,终端输入cd,然后直接将bin文件夹直接拖拽到终端

  2. cd /Library/Tomcat/bin

  3. 将目标文件授权,终端输入命令

    chmod +x *.sh

  4. 启动tomcat

    ./startup.sh

  5. 浏览器中输入:http://localhost:8080/

到这里,恭喜你tomcat配置成功

服务端作者使用的是IDEA,怎么在这里面配置Tomcat,以及后面要使用的Servlet可看这个文章,会用的小伙伴可以跳过(www.cnblogs.com/wfhking/p/9…

好了,到这里,服务端已经搭建完毕,接下来,我们书写服务端程序吧!


5.post使用

服务端

@WebServlet(name = "TestPost")
public class TestPost extends HttpServlet {

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        response.setHeader("Access-Control-Allow-Origin", "*"); // 跨域
        response.setContentType("text/html"); // 设置响应内容类型
        response.setCharacterEncoding("UTF-8"); // 指定编码

        // 获取前端传入的数据
        BufferedReader br = request.getReader();
        String line;
        StringBuffer mStringBuff = new StringBuffer();
        while ((line = br.readLine()) != null){

            mStringBuff.append(line);
        }
        //设置逻辑实现
        PrintWriter out = response.getWriter();
        String jsonStr = "服务器收到信息并返回:\n" + mStringBuff.toString();
        out.println(jsonStr);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    }
}

好了,这样就行了,接下来继续实现我们的客户端的post

客户端

  private void doPost(String username, String pass, String hobby) {

        // 编码集
        final MediaType FORM_CONTENT_TYPE = MediaType.parse("application/json; charset=utf-8");

        // 接口地址
        final String uri = "http://192.168.0.102:8081/TestOkhttp3_war_exploded/TestPost";

        // 创建实例
        OkHttpClient okhttp = new OkHttpClient.Builder()
                .build();

        // 创建表单及数据
        HashMap<String, String> map = new HashMap<>();
        map.put("username", username);
        map.put("password", pass);
        map.put("hobby", hobby);
        String jsonStr = new Gson().toJson(map);

        RequestBody formBody = RequestBody.create(FORM_CONTENT_TYPE, jsonStr);

        // 创建请求实例
        Request request = new Request.Builder()
                .url(uri)
                .post(formBody)
                .build();

        Call call = okhttp.newCall(request);
        call.enqueue(new Callback() {

            @Override
            public void onFailure(Call call, IOException e) {
                Log.e("lybj", "接口调用失败");
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {

                final String strByNet = response.body().string();
                // 切换到主线程
                runOnUiThread(new Runnable() {

                    @Override
                    public void run() {

                        tv_msg.setText(strByNet);
                    }
                });
            }
        });
    }

当然这么写了之后,会出现一个异常

CLEARTEXT communication ** not permitted by network security policy 这是因为,Android高版本后限制了HTTP访问权限。

解决方案有2个,要么采用https,要么采用下面的方法 在res里面新建xml文件夹,创建文件network_security_config.xml

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config cleartextTrafficPermitted="true" />
</network-security-config>

在application中引用它

    <application
        ...
        android:networkSecurityConfig="@xml/network_security_config">

ok了,post可以正常使用了

6.post上传多文件和参数

在project settings -> Artifacts -> 选择自己的工程 -> 右边OutPut directory 中看到自己的输出路径,然后找到该路径,可查看到自己提交的文件及控制台输出的参数信息。

服务端

 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        // 设置编码
        request.setCharacterEncoding("UTF-8");
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter pw = response.getWriter();

        try {
            // 设置系统环境
            DiskFileItemFactory factory = new DiskFileItemFactory();

            // 文件存储的路径
            String storePath = getServletContext().getRealPath("/WEB-INF/files");
            if(!new File(storePath).exists()){
                new File(storePath).mkdirs();
            }

            ServletFileUpload upload = new ServletFileUpload(factory);
            upload.setFileSizeMax(4 * 1024 * 1024); // 设置单个文件大小不能超过4M
            upload.setSizeMax(4 * 1024 * 1024); // 设置总文件上传大小不能超过6M

            // 解析
            List<FileItem> items = upload.parseRequest(request);
            for (FileItem item : items) {

                // 普通字段,表单提交过来的
                if (item.isFormField()){

                    String name = item.getFieldName();
                    String value = item.getString("UTF-8");
                    System.out.println(name + "==" + value);
                } else { // 解析上传的文件
//                  String mimeType = item.getContentType(); 获取上传文件类型
//                  if(mimeType.startsWith("image")){
                    InputStream in = item.getInputStream();
                    String fileName = item.getName();
                    if (fileName == null || "".equals(fileName.trim())) {
                        continue;
                    }
                    fileName = fileName.substring(fileName.lastIndexOf("\\") + 1);
                    fileName = UUID.randomUUID() + "_" + fileName;

                    // 按日期来建文件夹
                    String storeFile = storePath + "/" + fileName;
                    OutputStream out = new FileOutputStream(storeFile);
                    byte[] b = new byte[1024];
                    int len = -1;
                    while ((len = in.read(b)) != -1) {
                        out.write(b, 0, len);
                    }
                    in.close();
                    out.close();
                    item.delete(); // 删除临时文件
                }
            }
            PrintWriter out = response.getWriter();
            out.println("上传成功");
        } catch (org.apache.commons.fileupload.FileUploadBase.FileSizeLimitExceededException e) {

            pw.write("单个文件不能超过4M");
        } catch (org.apache.commons.fileupload.FileUploadBase.SizeLimitExceededException e) {

            pw.write("总文件不能超过6M");
        } catch (FileUploadException e) {
            e.printStackTrace();
        }
    }

客户端

   /**
     * 上传文件
     * */
    private void doUpload(File file, String userId, String msg){

        // 接口地址
        OkHttpClient client = new OkHttpClient.Builder()
                .connectTimeout(30, TimeUnit.SECONDS)
                .build();

        RequestBody fileRequestBody1 = RequestBody.create(MediaTypeUtils.UPLOAD_FILE.value, file);

        // 可传多个
        MultipartBody body = new MultipartBody.Builder()
                .setType(MultipartBody.FORM)
                .addFormDataPart("userId", userId)
                .addFormDataPart("msg", msg)
                .addFormDataPart("file", "myFileName", fileRequestBody1)
                .build();

        Request rb = new Request.Builder()
                .header("Authorization", "Client-ID " + UUID.randomUUID())
                .url(ApiUtils.TestPostUpload)
                .post(body)
                .build();

        Call call = client.newCall(rb);
        call.enqueue(new Callback() {

            @Override
            public void onFailure(Call call, IOException e) {

                Log.e("lybj", "接口调用失败");
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {

                final String string = response.body().string();
                runOnUiThread(new Runnable() {

                    @Override
                    public void run() {

                        tv_msg.setText(string);
                    }
                });
            }
        });
    }

接口封装了下

public interface ApiUtils {

    String BaseUri = "http://192.168.0.102:8081/TestOkhttp3_war_exploded/";

    // post提交json
    String TestPost = BaseUri + "TestPost";

    // 上传文件
    String TestPostUpload = BaseUri + "TestPostUpload";
}

定义一个枚举类型,用于存储上传使用的信息

public enum MediaTypeUtils {

    JSON_UTF_8(MediaType.parse("application/json; charset=utf-8")),  // 设置Json数据传输并指定Utf-8为编码集
    UPLOAD_FILE(MediaType.parse("multipart/form-data")); // 上传文件

    public MediaType value;

    MediaTypeUtils(MediaType value) {
        this.value = value;
    }
}

好了,这就可以了

7.post下载

简单起见,我们直接下载个网上的APK

客户端

   /**
     * 下载文件
     * */
    private void doDownload(){

        // 接口地址
        OkHttpClient client = new OkHttpClient.Builder()
                .connectTimeout(60, TimeUnit.SECONDS)
                .build();

        Request rb = new Request.Builder()
                .get()
                .url(ApiUtils.TestPostDownload)
                .build();

        Call call = client.newCall(rb);
        call.enqueue(new Callback() {

            @Override
            public void onFailure(Call call, IOException e) {

                Log.e("lybj", "接口调用失败");
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {

                writeFile(response);
            }
        });
    }

   /**
     * 下载文件
     * */
    private void writeFile(Response response) {

        InputStream is = null;
        FileOutputStream fos = null;
        is = response.body().byteStream();
        String path = Environment.getExternalStorageDirectory().getAbsolutePath();
        File file = new File(path, "hehe.apk");
        try {
            fos = new FileOutputStream(file);
            byte[] bytes = new byte[1024];
            int len = 0;
            // 获取下载的文件的大小
            long fileSize = response.body().contentLength();
            long sum = 0;
            int porSize = 0;
            while ((len = is.read(bytes)) != -1) {
                fos.write(bytes);
                sum += len;
                porSize = (int) ((sum * 1.0f / fileSize) * 100);
                Message message = handler.obtainMessage(1);
                message.arg1 = porSize;
                handler.sendMessage(message);
            }
        } catch (Exception e) {

            e.printStackTrace();

        } finally {
            try {
                if (is != null) {
                    is.close();
                }
                if (fos != null) {
                    fos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        Log.i("myTag", "下载成功");
    }

  Handler handler = new Handler() {

        @Override
        public void handleMessage(Message msg) {

            if (msg.what == 1) {
                pro.setProgress(msg.arg1);
            }
        }
    };

好了,到了这里,下载也OK了,上传和下载之前,别忘记动态申请Android权限哈。

8.Gzip压缩

为了优化(扯淡的故事)接口要求压缩上传的数据,好了直接贴代码

build.gradle

   implementation 'com.zhouyou:rxeasyhttp:2.1.2'
 OkHttpClient client = new OkHttpClient.Builder()
                .connectTimeout(60, TimeUnit.SECONDS)
                  .addInterceptor(new GzipRequestInterceptor())
                .build();
                  

当然,需要服务器支持,OK,完事了

9.源码下载

客户端的源码

服务端的源码