OkHttp3简单使用和封装使用

OkHttp简介

OkHttp是一个http协议网络请求的框架,OkHttp是一个高效的HTTP客户端,适用于Android和Java应用程序。从Android 4.4开始google已经开始将源码中的HttpURLConnection替换为OkHttp,而在Android 6.0之后的SDK中google更是移除了对于HttpClient的支持,而现在流行的Retrofit同样是使用OkHttp进行再次封装而来的。

其它文章

Retrofit2.0+RxJava2.0封装使用
Android开发 多语言、指纹登录、手势登录
Android使用IconFont阿里矢量图标
Android Studio 使用SVN 主干和分支合并代码

本文章主要讲的:

1.Okhttp3简单使用
2.Okhttp3封装使用

效果图

效果图.gif

项目地址:https://github.com/pengjunshan/UseOkhttp3

拿到代码后移到自己项目中根据自己项目需求修改即可使用。

OkHttp3特性
  • 支持http2,使得对同一个主机发出的所有请求都可以共享相同的socket套接字连接;
  • 使用连接池来复用连接以减少延迟、提高效率;
  • 支持Gzip压缩响应体,降低传输内容的大小;
  • 支持Http缓存,避免重复请求;
  • 请求失败时会自动重试主机中的其他IP地址自动重定向;
  • 使用Okio来简化数据的访问与存储,提高性能;
  • 使用了创建者设计模式;

Http简介

HTTP是一个属于应用层的面向对象的协议,由于其简捷、快速的方式,适用于分布式超媒体信息系统。它于1990年提出,经过几年的使用与发展,得到不断地完善和扩展。目前在WWW中使用的是HTTP/1.1版本。2.0版本目前也有在使用,只是使用不广泛。HTTP协议工作于客户端-服务端架构为上,我们把Http协议中通信的两方称作Client和Server,Client端向Server端通过http协议发送一个Request请求,Server端收到Client端发来的Request请求后经过一系列的处理返回Client一个Response,过程如下图。


Client客户端 - Server服务端
HTTP请求报文格式
  • HTTP请求报文主要由请求行、请求头部、请求正文3部分组成。

  • 请求行:由请求方法,URL,协议版本三部分构成。

    • URL是请求服务器地址。
    • 协议版本有HTTP1.0、HTTP1.1,目前HTTP2.0也有使用。
    • HTTP1.0定义了三种请求方法: GET, POST 和 HEAD方法。
    • HTTP1.1新增了五种请求方法:OPTIONS, PUT, DELETE, TRACE 和 CONNECT 方法。
    • 请求方法
  • 请求头部为请求报文添加了一些附加信息,由“名/值”对组成,每行一对,名和值之间使用冒号分隔。
    Host ----接受请求的服务器地址,可以是IP:端口号,也可以是域名
    User-Agent ----发送请求的应用程序名称
    Connection ---- 指定与连接相关的属性,如Connection:Keep-Alive
    Accept-Charset ---- 通知服务端可以发送的编码格式
    Accept-Encoding ---- 通知服务端可以发送的数据压缩格式
    Accept-Language ---- 通知服务端可以发送的语言

  • 请求正文,可选部分,GET请求就没有请求正文。

HTTP响应报文格式
  • HTTP响应报文主要由状态码、响应头部、响应正文3部分组成。
    • 常用状态码
      200:响应成功
      302:重定向跳转,跳转地址通过响应头中的Location属性指定
      400:客户端请求有语法错误,参数错误,不能被服务器识别
      403:服务器接收到请求,但是拒绝提供服务(认证失败)
      404:请求资源不存在
      500:服务器内部错误

    • 响应头部,与请求头部类似,为响应报文添加了一些附加信息
      Server - 服务器应用程序软件的名称和版本
      Content-Type - 响应正文的类型(是图片还是二进制字符串)
      Content-Length - 响应正文长度
      Content-Charset - 响应正文使用的编码
      Content-Encoding - 响应正文使用的数据压缩格式
      Content-Language - 响应正文使用的语言

    • 响应正文,是请求响应的最终结果,都在响应体里。可以是字符串可以是字符流。

配置
  • maven方式:
<dependency>
  <groupId>com.squareup.okhttp3</groupId>
  <artifactId>okhttp</artifactId>
  <version>3.12.0</version>
</dependency>
  • gradle方式:
   compile 'com.squareup.okhttp3:okhttp:3.12.0'
  • 自动下载okio资源


    Libraries资源
  • 联网权限
  <uses-permission android:name="android.permission.INTERNET"></uses-permission>

1.简单使用

HTTP工作中常用方式:
  • get请求
  • post请求
  • 文件上传
  • 文件下载
  • 图文混合上传

Android3.0 以后已经不允许在主线程访问网络。需要注意的是这个onResponse回调方法不是在主线程回调,可以使用runOnUIThread(new Runnable(){})更新UI,或者使用Handler在主线程中更新UI。

GET请求

1.第一步创建OkHttpClient对象
2.如果需要拼接参数 (一般有参数的都会用Post请求,除非参数不重要)
3.第二步创建request对象
4.新建一个Call对象
5.同步请求网络execute()
6.异步请求网络enqueue(Callback)

      /**
     * 获取轮播图接口
     * GET请求
     */
   private void requestBannerApi(){
        //1.第一步创建OkHttpClient对象
        final OkHttpClient okHttpClient = new OkHttpClient();

        String url ="http://www.wanandroid.com/banner/json";

        //2. 如果需要拼接参数 (一般有参数的都会用Post请求,除非参数不重要)
//        Map<String, String> params = new HashMap<>();
//        params.put("movieid", "246363");
//        params.put("limit", "3");
//        params.put("offset", "5");
//        url = appendParams(url,params);

        //3.第二步创建request
        Request.Builder builder = new Request.Builder();
        final Request request = builder.url(url)
                .get()
                .build();

        //4.新建一个Call对象
        final Call call = okHttpClient.newCall(request);

        //5.同步请求网络execute()
           new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Response response = call.execute();
                    if(response.isSuccessful()){
                        Log.e("Benner请求成功同步=",response.body().string());
                    }else{
                        throw new IOException("Unexpected code " + response);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        }).start();


        //6.异步请求网络enqueue(Callback)
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                Log.e("TAG", "Benner请求失败="+e.getMessage());
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                String json = response.body().string();
                Log.e("TAG", "Benner请求成功异步="+json);
            }
        });

    }

    /**
     * 拼接参数
     * @param url
     * @param params
     * @return
     */
    private String appendParams(String url, Map<String, String> params) {
        StringBuilder sb = new StringBuilder();
        sb.append(url + "?");
        if (params != null && !params.isEmpty()) {
            for (String key : params.keySet()) {
                sb.append(key).append("=").append(params.get(key)).append("&");
            }
        }
        sb = sb.deleteCharAt(sb.length() - 1);
        return sb.toString();
    }

注意execute()同步方式会阻塞调用线程,所以在Android中应放在子线程中执行,否则有可能引起ANR异常。一般都会使用enqueue()异步请求服务器。

POST请求(键值对 key-value)

1.拿到okhttpClient对象
2.创建 FormBody 添加需要的键值对
3.构造Request
4.创建一个Call对象
5.异步请求enqueue(Callback)

/**
    * 登录接口
    * POST请求
    * @param account 用户名
    * @param pwd 密码
    */
   private void requestLoginApi(String account, String pwd) {
       // 1.拿到okhttpClient对象
       OkHttpClient okHttpClient = new OkHttpClient();

       //2.创建 FormBody 添加需要的键值对
       FormBody formBody = new FormBody.Builder()
               .add("username",account)
               .add("password",pwd)
               .build();
    
       // 3.构造Request
       Request.Builder builder = new Request.Builder();
       Request request = builder.url("http://www.wanandroid.com/user/login")
               .post(formBody)//键值对
               .build();

       //4.创建一个Call对象
       Call call = okHttpClient.newCall(request);

       //5.异步请求enqueue(Callback)
       call.enqueue(new Callback() {
           @Override
           public void onFailure(Call call, IOException e) {
               Log.e("TAG", "登录失败="+e.getMessage());
           }

           @Override
           public void onResponse(Call call, Response response) throws IOException {
               String json = response.body().string();
               UserInfo userInfo = new Gson().fromJson(json,UserInfo.class);
               if(userInfo!=null) {
                   if(userInfo.getErrorCode()!=0) {
                       Log.e("TAG", userInfo.getErrorMsg());
                   }else {
                       Log.e("TAG", "登录成功="+json);
                   }
               }

           }
       });

   }

如果Post提交的数据是键值对就构造一个FormBody对象,可以添加N个键值对。

POST请求(json字符串)

1.拿到okhttpClient对象
2.设置提交类型MediaType+json字符串
3.构造Request
4.创建一个Call对象
5.异步请求enqueue(Callback)

   private void requestLoginApi(String account, String pwd) {
       // 1.拿到okhttpClient对象
       OkHttpClient okHttpClient = new OkHttpClient();
       //需要提交的json字符串
      String jsonStr = "hahaha";
      //2.创建 RequestBody 设置提交类型MediaType+json字符串
     RequestBody requestBody =  RequestBody.create(MediaType.parse("application/json"),jsonStr);

       // 3.构造Request
       Request.Builder builder = new Request.Builder();
       Request request = builder.url("http://www.wanandroid.com/user/login")
               .post(requestBody)//字符串
               .build();

       //4.创建一个Call对象
       Call call = okHttpClient.newCall(request);

       //5.异步请求enqueue(Callback)
       call.enqueue(new Callback() {
           @Override
           public void onFailure(Call call, IOException e) {
               Log.e("TAG", "登录失败="+e.getMessage());
           }

           @Override
           public void onResponse(Call call, Response response) throws IOException {
               String json = response.body().string();
               UserInfo userInfo = new Gson().fromJson(json,UserInfo.class);
               if(userInfo!=null) {
                   if(userInfo.getErrorCode()!=0) {
                       Log.e("TAG", userInfo.getErrorMsg());
                   }else {
                       Log.e("TAG", "登录成功="+json);
                   }
               }

           }
       });

   }

如果提交json字符串需要构造一个RequestBody对象,用它来携带我们要提交的json字符串数据。在构造 RequestBody 需要指定MediaType,用于描述请求/响应 body 的内容类型。

RequstBody的几种构造方式
POST上传(文件)

1.创建OkHttpClient对象
2.获取文件地址,设置上传文件类型,构造RequestBody对象
3.构造Requst对象
4.构造Call对象进行 异步请求enqueue(Callback)

 /**
     * 提交txt文件
     * POST请求
     */
    private void requestPostFileTxt(){

        //1.创建OkHttpClient对象
        OkHttpClient okHttpClient = new OkHttpClient();

        //2.获取文件地址,设置上传文件类型,构造RequestBody对象
        File fileAdress = new File("/sdcard/wangshu.txt");
        MediaType mediaType = MediaType.parse("text/plain; charset=utf-8");
        RequestBody requestBody = RequestBody.create(mediaType,fileAdress);

        //3.构造Requst对象
        Request request = new Request.Builder()
                .url("http://www.baidu.com")
                .post(requestBody)
                .build();

        //4.构造Call对象进行 异步请求enqueue(Callback)
        okHttpClient.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                Log.e("TAG", "post"+e.getMessage());
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                String json = response.body().string();
                Log.e("TAG", "Benner请求成功异步="+json);
            }
        });

    }

上传文件本身也是一个post请求,向服务器发送文件时需要备注文件类型Content-Type,可以用MultipartBody上传多个文件。如果没有添加charset也没关系,RequestBody 中已经帮我们写好了。

charset

常用的Content-Type:
text/plain :纯文本格式 .txt
text/xml : XML格式 .xml
image/gif :gif图片格式 .gif
image/jpeg :jpg图片格式 .jpg
image/png:png图片格式 .png
audio/mp3 : 音频mp3格式 .mp3
audio/rn-mpeg :音频mpga格式 .mpga
video/mpeg4 : 视频mp4格式 .mp4
video/x-mpg : 视频mpa格式 .mpg
video/x-mpeg :视频mpeg格式 .mpeg
video/mpg : 视频mpg格式 .mpg
以application开头的媒体格式类型:
application/xhtml+xml :XHTML格式
application/xml : XML数据格式
application/atom+xml :Atom XML聚合格式
application/json : JSON数据格式
application/pdf :pdf格式
application/msword : Word文档格式
application/octet-stream : 二进制流数据(如常见的文件下载)

POST上传图片

1.创建OkHttpClient对象
2.设置文件类型
3.构造RequestBody 指定文件类型和文件
4.创建Request对象
5.异步请求newCall(Callback)

    /**
     * 上传图片
     * @param file
     */
    private void requestPostImg( File file) {

        //1.创建OkHttpClient对象
        OkHttpClient okHttpClient = new OkHttpClient();

        //2.设置文件类型
        MediaType mediaType = MediaType.parse("image/png");

        if (file != null && file.exists()) {

            //3.构造RequestBody 指定文件类型和文件
            RequestBody image = RequestBody.create(mediaType, file);
            RequestBody requestBody = new MultipartBody.Builder()
                    .setType(MultipartBody.FORM)
                    .addFormDataPart("img", file.getName(), image)
                    .build();

            //4.创建Request对象
            Request request = new Request.Builder()
                    .header("Authorization", "Client-ID " + "...")
                    .url("www.baidu.login")
                    .post(multipartBody)
                    .build();

            //5.异步请求newCall(Callback)
            okHttpClient.newCall(request).enqueue(new Callback() {
                @Override
                public void onFailure(Call call, IOException e) {
                    Log.e("TAG", "图片上传失败="+e.getMessage());
                }

                @Override
                public void onResponse(Call call, Response response) throws IOException {
                    String result = response.body().string();
                    Log.e("TAG", "成功上传图片=" + result);
                }
            });

        }
    }

MultipartBody继承RequestBody,具有自己的contentType+BufferedSink,是POST请求的最外层封装,需要添加多个Part
Part对象组成:Headers+RequestBody。是MultipartBody的成员变量,需要写入MultipartBody的BufferedSink中。

GET下载图片

1.创建OkHttpClient对象
2.创建Request对象
3.异步请求newCall(Callback)
4.用文件流下载在本地文件夹下

 /**
   * 上传图片
   * 没有测试服务器地址
   */
  public void PostImgRequet(View view) {
    //1.创建OkHttpClient对象
    OkHttpClient okHttpClient = new OkHttpClient();

    //2.设置文件类型
    MediaType mediaType = MediaType.parse("image/png");

    if (file != null && file.exists()) {

      //3.构造RequestBody 指定文件类型和文件
      RequestBody image = RequestBody.create(mediaType, file);
      RequestBody requestBody = new MultipartBody.Builder()
          .setType(MultipartBody.FORM)
          .addFormDataPart("img", file.getName(), image)
          .build();

      //4.创建Request对象
      Request request = new Request.Builder()
          .header("Authorization", "Client-ID " + "...")
          .url("www.baidu.login")
          .post(requestBody)
          .build();

      //5.异步请求newCall(Callback)
      okHttpClient.newCall(request).enqueue(new Callback() {
        @Override
        public void onFailure(Call call, IOException e) {
          mHandler.sendEmptyMessage(0);
          Log.e("TAG", "图片上传失败="+e.getMessage());
        }

        @Override
        public void onResponse(Call call, Response response) throws IOException {
          String result = response.body().string();
          mHandler.sendEmptyMessage(1);
          Log.e("TAG", "成功上传图片=" + result);
        }
      });

    }
  }

  /**
   * 下载图片
   */
  public void GetImgRequetSimpleness(View view) {
    //1.创建OkHttpClient对象
    OkHttpClient okHttpClient = new OkHttpClient();
    String url = "http://p0.meituan.net/165.220/movie/7f32684e28253f39fe2002868a1f3c95373851.jpg";
    //2.创建Request对象
    Request request  = new Request.Builder()
        .url(url)
        .build();
    //3.异步请求newCall(Callback)
    okHttpClient.newCall(request).enqueue(new Callback() {
      @Override
      public void onFailure(Call call, IOException e) {
        Log.e("TAG", "下载失败");
        mHandler.sendEmptyMessage(0);
      }
      @Override
      public void onResponse(Call call, Response response) throws IOException {
        runOnUiThread(new Runnable() {
          @Override
          public void run() {
            Toast.makeText(SimplenessActivity.this, "下载图片成功", Toast.LENGTH_SHORT).show();
          }
        });
        /**
         * 用java文件输入流下载图片
         */
               /* InputStream inputStream = response.body().byteStream();
                FileOutputStream fileOutputStream = null;
                try {
                    fileOutputStream = new FileOutputStream(new File("/sdcard/okhttp.jpg"));
                    byte[] buffer = new byte[2048];
                    int len = 0;
                    while ((len = inputStream.read(buffer)) != -1) {
                        fileOutputStream.write(buffer, 0, len);
                    }
                    fileOutputStream.flush();
                } catch (IOException e) {
                    Log.i("TAG", "IOException");
                    e.printStackTrace();
                }*/

        //方法一,获取byte数组,然后转换成图片
        byte[] bytes = response.body().bytes();
        Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);

        //方法二,可以获取字节流,然后转换成图片
//        InputStream inputStream = response.body().byteStream();
//        Bitmap bitmap = BitmapFactory.decodeStream(inputStream);

        /**
         * 保存图片
         */
 /*       File file=new File("/sdcard/okhttp.jpg");
        file.createNewFile();
        //创建文件输出流对象用来向文件中写入数据
        FileOutputStream out=new FileOutputStream(file);
        //将bitmap存储为jpg格式的图片
        bitmap.compress(Bitmap.CompressFormat.JPEG,100,out);
        //刷新文件流
        out.flush();
        out.close();*/

        if(bitmap!=null) {
          Log.e("TAG", "图片下载成功");
        }

      }
    });
  }

下载图片可以用java文件输入流下载图片、BitmapFactory.decodeByteArray、 BitmapFactory.decodeStream,根据自己的需求来使用。

图文混合上传

1.创建OkHttpClient对象
2.构建多部件builder
3.创建 Map 添加需要的键值对
4.获取要上传的图片集合
5.获取参数并放到请求体中
6.添加图片集合到请求体中
7.构造Request
8.异步请求enqueue(Callback)

/**
   * 图文混合上传
   * @param view
   */
  public void PostImgKeyValueRequet(View view) {

    //1.创建OkHttpClient对象
    OkHttpClient okHttpClient = new OkHttpClient();

    //2.构建多部件builder
    MultipartBody.Builder bodyBuilder = new MultipartBody.Builder().setType(MultipartBody.FORM);

    //3.创建 Map 添加需要的键值对
    Map<String, String> params = new HashMap<>();
    params.put("username","15294792877");
    params.put("password","15294792877pp");

    //4.获取要上传的图片集合
    List<File> fileList = new ArrayList<>();

    //5.获取参数并放到请求体中
    try {
      if (params != null) {
        for (Map.Entry<String, String> entry : params.entrySet()) {
          //将请求参数逐一遍历添加到我们的请求构建类中
          bodyBuilder.addFormDataPart(entry.getKey(), entry.getValue());
        }
      }
    } catch (Exception e) {
      e.printStackTrace();
    }


    //6.添加图片集合到请求体中
    if (fileList != null) {
      for (File f : fileList) {
        bodyBuilder.addFormDataPart("files", f.getName(),
            RequestBody.create(MediaType.parse("image/png"), f));
      }
    }

    //7.构造Request
    Request request = new Request.Builder()
        .url("https://www.wanandroid.com/user/login")
        .post(bodyBuilder.build())
        .build();

    //8.异步请求enqueue(Callback)
    okHttpClient.newCall(request).enqueue(new Callback() {
      @Override
      public void onFailure(Call call, IOException e) {
        mHandler.sendEmptyMessage(0);
        Log.e("TAG", "失败="+e.getMessage());
      }

      @Override
      public void onResponse(Call call, Response response) throws IOException {
        String json = response.body().string();
        Log.e("TAG", "成功="+json);
      }
    });

  }

要构建一个多部件MultipartBody.Builder,设置其类型为FORM("multipart/form-data")。然后把需要上传的key-value键值对和图片都通过addFormDataPart()方法添加进去。添加图片时name要和后端接口指定name相同,还要添加RequestBody指定类型("image/png")。

封装使用

如果不封装使用起来还是很繁琐的,比如:写重复的代码、增加类的代码量、不易维护、回调函数不在主线程... 接下来我们就来封装一个。(加泛型使用)

先看下封装后请求代码

一个get请求,一个post请求,和上面没有封装时相比是不是代码很简洁清晰。下面就简单的讲一下封装的过程,想详细的看封装过程请下载demo查看,每个类都有注解。

/**
   * GET请求 
   * 返回类型要Json字符串
   */
  public void GetRequet(View view) {
    HttpRequest.getBannerApi(null, new ResponseCallback<String>() {
      @Override
      public void onSuccess(String s) {
        Toast.makeText(EncapsulationActivity.this, "请求成功" + s.toString(), Toast.LENGTH_SHORT)
            .show();
      }

      @Override
      public void onFailure(OkHttpException failuer) {
        Toast.makeText(EncapsulationActivity.this, "请求失败=" + failuer.getEmsg(), Toast.LENGTH_SHORT)
            .show();
      }
    });
  }

  /**
   * POST请求 
   * 返回类型我要实体类
   */
  public void PostKeyValueRequet(View view) {
    RequestParams params = new RequestParams();
    params.put("username", "15294792877");
    params.put("password", "15294792877pp");
    HttpRequest.postLoginApi(params, new ResponseCallback<BaseBean<Info>>() {
      @Override
      public void onSuccess(BaseBean<Info> infoBaseBean) {
        Toast.makeText(EncapsulationActivity.this, "成功=" + infoBaseBean.toString(),
            Toast.LENGTH_SHORT).show();
      }

      @Override
      public void onFailure(OkHttpException failuer) {
        Toast.makeText(EncapsulationActivity.this, "失败=" + failuer.getEmsg(), Toast.LENGTH_SHORT)
            .show();
      }
    });
  }

  /**
   * 下载图片
   *  可以用GET方式||POST方式,一般是用POST方式 除非你们公司不注重隐式,
   *   本案例用的是GET方式,因为没有找到免费的POST请求api。
   *
   * @param view
   */
  public void GetImgRequet(View view) {

    HttpRequest.getImgApi(null, String.valueOf(System.currentTimeMillis()) + ".png",
        new ResponseByteCallback() {
          @Override
          public void onSuccess(File file) {
            Toast.makeText(EncapsulationActivity.this, "图片下载成功="+file.getAbsolutePath(), Toast.LENGTH_SHORT).show();
            Log.e("TAG", "图片下载成功="+file.getAbsolutePath());
          }

          @Override
          public void onFailure(String failureMsg) {
            Toast.makeText(EncapsulationActivity.this, "图片下载失败="+failureMsg, Toast.LENGTH_SHORT).show();
            Log.e("TAG", "图片下载失败="+failureMsg);
          }
        });

  }

  /**
   * 图文混合
   * @param view
   */
  public void PostImgKeyValueRequet(View view) {
    RequestParams params = new RequestParams();
    params.put("name", "aaaaaaa");
    //添加图片
    List<File> fileList = new ArrayList<>();
//    HttpRequest.postMultipartApi(params, fileList, new ResponseCallback() {
//      @Override
//      public void onSuccess(Object responseObj) {
//
//      }
//
//      @Override
//      public void onFailure(OkHttpException failuer) {
//
//      }
//    });
  }
请求日志.png
OkHttpClient对象

初始化全局OkHttpClient对象,为我们的Client配置参数,使用静态语句块来配置,只执行一次,运行一开始就开辟了内存,内存放在全局。主要设置有缓存、超时时间、重定向、拦截器、Https支持,根据自己项目需求类配置就行了。

/**
 * @author:PengJunShan.
 * 时间:On 2019-05-05.
 * 描述:OkHttpClient对象
 */
public class CommonOkHttpClient {

    /**
     * 超时时间
     */
    private static final int TIME_OUT = 30;
    private static OkHttpClient mOkHttpClient;

    /**
     * 为我们的Client配置参数,使用静态语句块来配置
     * 只执行一次,运行一开始就开辟了内存,内存放在全局
     */
    static {
        //获取缓存路径
        File cacheDir = MyApplication.context.getExternalCacheDir();

        //设置缓存的大小
        int cacheSize = 10 * 1024 *1024 ;
        //创建我们Client对象的构建者
        OkHttpClient.Builder okHttpBuilder = new OkHttpClient.Builder();
        okHttpBuilder
                //为构建者设置超时时间
                .connectTimeout(TIME_OUT, TimeUnit.SECONDS)
                .readTimeout(TIME_OUT, TimeUnit.SECONDS)
                .writeTimeout(TIME_OUT, TimeUnit.SECONDS)
                ////websocket轮训间隔(单位:秒)
                .pingInterval(20, TimeUnit.SECONDS)
                //设置缓存
                .cache(new Cache(cacheDir.getAbsoluteFile(), cacheSize))
                //允许重定向
                .followRedirects(true)
                //设置拦截器
                .addInterceptor(new RequetInterceptor())
                //添加https支持
                .hostnameVerifier(new HostnameVerifier() {
                    @Override
                    public boolean verify(String s, SSLSession sslSession) {
                        return true;
                    }
                })
                .sslSocketFactory(HttpsUtils.initSSLSocketFactory(), HttpsUtils.initTrustManager());

        mOkHttpClient = okHttpBuilder.build();
    }

    /**
     * 发送具体的HTTP以及Https请求
     */
    public static Call sendRequest(Request request, CommonJsonCallback commonCallback) {
        Call call = mOkHttpClient.newCall(request);
        call.enqueue(commonCallback);
        return call;
    }

    /**
     * GET请求
     */
    public static Call get(Request request, ResposeDataHandle handle) {
        Call call = mOkHttpClient.newCall(request);
        call.enqueue(new CommonJsonCallback(handle));
        return call;
    }

    /**
     * POST请求
     */
    public static Call post(Request request, ResposeDataHandle handle) {
        Call call = mOkHttpClient.newCall(request);
        call.enqueue(new CommonJsonCallback(handle));
        return call;
    }

 /**
   * POST请求图片
   */
  public static Call downLadImg(Request request, final String imgPath,
      final ResponseByteCallback callback) {
    Call call = mOkHttpClient.newCall(request);
    call.enqueue(new Callback() {
      @Override
      public void onFailure(Call call, IOException e) {
        Log.e("TAG", "下载图片失败=" + e.getMessage());
        new Handler().post(new Runnable() {
          @Override
          public void run() {
            callback.onFailure(e.getMessage());
          }
        });

      }

      @Override
      public void onResponse(Call call, Response response) throws IOException {
        Log.e("TAG", "下载图片成功=" + response);
        File file = null;
        try {
          InputStream is = response.body().byteStream();
          int len = 0;
          // 文件夹路径
          String pathUrl =
              Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator
                  + "/sgcc/";
          File filepath = new File(pathUrl);
          if (!filepath.exists()) {
            filepath.mkdirs();// 创建文件夹
          }
          file = new File(pathUrl, imgPath);

          FileOutputStream fos = new FileOutputStream(file);

          byte[] buf = new byte[2048];
          while ((len = is.read(buf)) != -1) {
            fos.write(buf, 0, len);
          }
          fos.flush();
          fos.close();
          is.close();
          File finalFile = file;
          new Handler(Looper.getMainLooper()).post(new Runnable() {
            @Override
            public void run() {
              callback.onSuccess(finalFile);
            }
          });
        } catch (final Exception e) {
          new Handler(Looper.getMainLooper()).post(new Runnable() {
            @Override
            public void run() {
              callback.onFailure(e.getMessage());
            }
          });
         
        }

      }
    });
    return call;
  }

}

公共入参(CommonRequest)

我们每次请求都会创建Request对象,写着重复的代码,那我们就写一个类专门处理入参然后返回Request对象。

/**
 * 创建: PengJunShan
 * 描述: 公共入参
 */

public class CommonRequest {

  /**
   * 创建Get请求的Request
   */
  public static Request createGetRequest(String url, RequestParams params) {
    StringBuilder urlBuilder = new StringBuilder(url).append("?");

    if (params != null) {
      for (Map.Entry<String, String> entry : params.urlParams.entrySet()) {
        urlBuilder
            .append(entry.getKey())
            .append("=")
            .append(entry.getValue())
            .append("&");
      }
    }

    return new Request.Builder().url(urlBuilder.substring(0, urlBuilder.length() - 1))
        .get().build();
  }

  /**
   * 创建Post请求的Request
   *
   * @return 返回一个创建好的Request对象
   */
  public static Request createPostRequest(String url, RequestParams params) {
    FormBody.Builder mFromBodyBuilder = new FormBody.Builder();

    //将请求参数逐一遍历添加到我们的请求构建类中
    for (Map.Entry<String, String> entry : params.urlParams.entrySet()) {
      mFromBodyBuilder.add(entry.getKey(), entry.getValue());
    }

    //通过请求构建类的build方法获取到真正的请求体对象
    FormBody mFormBody = mFromBodyBuilder.build();
    Request request = new Request.Builder()
        .url(url)
        .post(mFormBody)
        .build();

    return request;
  }


  /**
   * 混合form和图片
   * @return 返回一个创建好的Request对象
   */
  public static Request createMultipartRequest(String url, RequestParams params, List<File> files) {

    //构建多部件builder
    MultipartBody.Builder bodyBuilder = new MultipartBody.Builder().setType(MultipartBody.FORM);
    //获取参数并放到请求体中
    try {
      if (params != null) {
        JSONObject jsonObject = new JSONObject();
        for (Map.Entry<String, String> entry : params.urlParams.entrySet()) {
          //将请求参数逐一遍历添加到我们的请求构建类中
          bodyBuilder.addFormDataPart(entry.getKey(), entry.getValue());
          jsonObject.put(entry.getKey(), entry.getValue());
        }
        Log.e("TAG", "入参:   " + jsonObject.toString());
      }
    } catch (JSONException e) {
      e.printStackTrace();
    }

    //添加图片集合放到请求体中
    if (files != null) {
      for (File f : files) {
        bodyBuilder.addFormDataPart("files", f.getName(),
            RequestBody.create(MediaType.parse("image/png"), f));
      }
    }

    Request request = new Request.Builder()
        .url(url)
        .post(bodyBuilder.build())
        .build();

    return request;
  }

}

请求模式(RequestMode)

实际工作中常用的请求模式有:get(无参)、post(key-value)、图文混合、图片下载。

/**
 * 创建: PengJunShan
 * 描述:请求模式
 */

public class RequestMode {

  /**
   * GET请求
   * @param url URL请求地址
   * @param params 入参
   * @param callback 回调接口
   * @param clazz 需要解析的实体类
   */
  public static void getRequest(String url, RequestParams params,
      ResponseCallback callback, Class<?> clazz) {
    CommonOkHttpClient.get(CommonRequest.createGetRequest(url, params),
        new ResposeDataHandle(callback, clazz));
  }

  /**
   * POST请求
   * @param url URL请求地址
   * @param params 入参
   * @param callback 回调接口
   * @param clazz 需要解析的实体类
   */
  public static void postRequest(String url, RequestParams params,
      ResponseCallback callback, Class<?> clazz) {
    CommonOkHttpClient.post(CommonRequest.createPostRequest(url, params),
        new ResposeDataHandle(callback, clazz));
  }

  /**
   * 下载图片 Get方式
   */
  public static void getLoadImg(String url,RequestParams params,String imgPath, ResponseByteCallback callback){
    CommonOkHttpClient.downLadImg(CommonRequest.createGetRequest(url, params),imgPath,callback);
  }

  /**
   * 下载图片 Post方式
   */
  public static void postLoadImg(String url,RequestParams params,String imgPath, ResponseByteCallback callback){
    CommonOkHttpClient.downLadImg(CommonRequest.createPostRequest(url, params),imgPath,callback);
  }

  /**
   * 表单和媒体 图文混合
   */
  public static void postMultipart(String url, RequestParams params,
      List<File> files, ResponseCallback callback, Class<?> clazz) {
    CommonOkHttpClient.post(CommonRequest.createMultipartRequest(url, params, files),
        new ResposeDataHandle(callback, clazz));
  }

}
HttpRequest

HttpRequest存放所有的请求接口,我们在activity中请求接口最先就是调用的这个类中的方法。

/**
 * 作者:PengJunShan.
 * 时间:On 2019-05-05.
 * 描述:所有的请求接口
 */
public class HttpRequest {


  /**
   * @param params 入参
   * @param callback 回调接口
   */
  public static void getBannerApi(RequestParams params, ResponseCallback<String> callback) {
    RequestMode.getRequest("https://www.wanandroid.com/banner/json", params, callback);
  }

  /**
   * @param params 入参
   * @param callback 回调接口
   */
  public static void postLoginApi(RequestParams params, ResponseCallback<BaseBean<Info>> callback) {
    RequestMode.postRequest("https://www.wanandroid.com/user/login", params, callback);
  }

  /**
   * 下载图片 Get方式
   * @param params 入参
   * @param imgPath 存储地址
   * @param callback 回调接口
   */
  public static void getImgApi(RequestParams params,String imgPath, ResponseByteCallback callback) {
    RequestMode.getLoadImg("http://p0.meituan.net/165.220/movie/7f32684e28253f39fe2002868a1f3c95373851.jpg",params,imgPath,callback);
  }

  /**
   * 下载图片 Post方式
   * @param params 入参
   * @param imgPath 存储地址
   * @param callback 回调接口
   */
  public static void postImgApi(RequestParams params,String imgPath, ResponseByteCallback callback) {
    RequestMode.postLoadImg("url地址",params,imgPath,callback);
  }

  /**
   * 图文混合上传服务器
   * @param params
   * @param files
   * @param callback
   */
  public static void postMultipartApi(RequestParams params, List<File> files, ResponseCallback callback) {
    RequestMode.postMultipart("url地址", params, files, callback, null);
  }

}
回调ResponseCallback<T>

每次请求api都创建这个抽象类并实现其抽象方法,通过回传把数据回调。

/**
 * 创建: PengJunShan 描述:回调 使用泛型
 */

public abstract class ResponseCallback<T> {

  Type mType;

  public ResponseCallback() {

    //Type是 Java 编程语言中所有类型的公共高级接口。它们包括原始类型、参数化类型、数组类型、类型变量和基本类型。
    Type superclass = getClass().getGenericSuperclass();

    if (superclass instanceof Class) {
//      throw new RuntimeException("请传入实体类");
      mType = null;
    } else {
      //ParameterizedType参数化类型,即泛型
      ParameterizedType parameterized = (ParameterizedType) superclass;

      //getActualTypeArguments获取参数化类型的数组,泛型可能有多个
      //将Java 中的Type实现,转化为自己内部的数据实现,得到gson解析需要的泛型
      mType = $Gson$Types.canonicalize(parameterized.getActualTypeArguments()[0]);
    }

  }

  //请求成功回调事件处理
  public abstract void onSuccess(T t);

  //请求失败回调事件处理
  public abstract void onFailure(OkHttpException failuer);

}
处理JSON数据(CommonJsonCallback)

当我们请求到Json数据后不是直接返给最前端的接口,而是先进行解析处理。让这个类继承Callback接口,实现onFailure()、onResponse()方法。如果走了onFailure()失败中,通过Exception类型判断失败原因。如果走了onResponse()中,首先是获取errorMsg值来判断是否成功然后解析数据。大家都知道CallBck的回调是在子线程中不能操作UI,那该怎么办呢?我们在创建Handler时通过Looper.getMainLooper(),获得主线程的Looper,将其绑定到此Handler对象上,这种情况下,Runnable对象是运行在主线程中可以更新UI操作。

/**
 * 创建: PengJunShan
 * 描述:专门处理JSON数据的回调响应
 */

public class CommonJsonCallback<T> implements Callback {

  /**
   * errorCode是根据接口返回的标识 实际根据自己接口返回为准
   */
  protected final String RESULT_CODE = "errorCode";
  protected final int RESULT_CODE_VALUE = 0;

  /**
   * errorMsg字段提示信息,实际根据自己接口返回为准
   */
  protected final String ERROR_MSG = "errorMsg";

  protected final String NETWORK_MSG = "请求失败";
  protected final String JSON_MSG = "解析失败";

  /**
   * 自定义异常类型
   */
  protected final int NETWORK_ERROR = -1; //网络失败
  protected final int JSON_ERROR = -2; //解析失败
  protected final int OTHER_ERROR = -3; //未知错误
  protected final int TIMEOUT_ERROR = -4; //请求超时

  private Handler mDeliveryHandler; //进行消息的转发
  private ResponseCallback<T> mListener;

  public CommonJsonCallback(ResposeDataHandle handle) {
    this.mListener = handle.mListener;
    this.mDeliveryHandler = new Handler(Looper.getMainLooper());
  }

  /**
   * 请求失败的处理
   */
  @Override
  public void onFailure(@NonNull Call call, @NonNull final IOException e) {
    Log.e("TAG", "请求失败=" + e.getMessage());
    mDeliveryHandler.post(new Runnable() {
      @Override
      public void run() {
        if (!Utils.isConnected(MyApplication.context)) {
          mListener.onFailure(new OkHttpException(NETWORK_ERROR, "请检查网络"));
        } else if (e instanceof SocketTimeoutException) {
          //判断超时异常
          mListener.onFailure(new OkHttpException(TIMEOUT_ERROR, "请求超时"));
        } else if (e instanceof ConnectException) {
          //判断超时异常
          mListener.onFailure(new OkHttpException(OTHER_ERROR, "请求服务器失败"));
        } else {
          mListener.onFailure(new OkHttpException(NETWORK_ERROR, e.getMessage()));
        }

      }
    });
  }

  /**
   * 请求成功的处理 回调在主线程
   */
  @Override
  public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
    final String result = response.body().string();
    mDeliveryHandler.post(new Runnable() {
      @Override
      public void run() {
        handleResponse(result);
      }
    });
  }

  /**
   * 处理Http成功的响应
   */
  private void handleResponse(Object responseObj) {
    if (responseObj == null && responseObj.toString().trim().equals("")) {
      mListener.onFailure(new OkHttpException(NETWORK_ERROR, NETWORK_MSG));
      return;
    }

    try {
      JSONObject result = new JSONObject(responseObj.toString());
      if (result.has(RESULT_CODE)) {
        //从JSON对象中取出我们的响应码,如果为0,则是正确的响应 (实际情况按你们接口文档)
        if (result.getInt(RESULT_CODE) == RESULT_CODE_VALUE) {

          /**
           * 判断是否需要解析成实体类还是json字符串
           * class com.google.gson.internal.$Gson$Types$ParameterizedTypeImpl
           */
          Gson gson = new GsonBuilder().serializeNulls().create();
          T obj = null;
          if (!mListener.mType.getClass().equals("java.lang.Class")) {
            obj = gson.fromJson((String) responseObj, mListener.mType);
          } else {
            obj = (T) responseObj;
          }

          if (obj != null) {
            mListener.onSuccess(obj);
          } else {
            mListener.onFailure(new OkHttpException(JSON_ERROR, JSON_MSG));
          }
        } else { //将服务端返回的异常回调到应用层去处理
          mListener.onFailure(new OkHttpException(OTHER_ERROR, result.get(ERROR_MSG) + ""));
          Log.e("TAG", "onResponse处理失败");
        }
      }
    } catch (Exception e) {
      e.printStackTrace();
      mListener.onFailure(new OkHttpException(OTHER_ERROR, e.getMessage()));
      Log.e("TAG", "onResponse处理失败" + e.getMessage());
    }
  }

}
日志拦截器(RequetInterceptor)

拦截器的作用还是很大的,一般我们工作中请求头部都会传入token、用户id标识认证参数。连接器中可以拦截到Request对象,然后添加头部公共参数。通过获取FormBody可以打印出入参参数,通过获取Response可以打印出出参参数。不能直接使用response.body().string()的方式输出日志,因为response.body().string()之后,response中的流会被关闭,我们需要创建出一个新的response给应用层处理。

/**
 * @author:PengJunShan.
 * 时间:On 2019-05-05.
 * 描述:日志拦截器
 */
public class RequetInterceptor implements Interceptor {

  /**
   * 这个chain里面包含了request和response,所以你要什么都可以从这里拿
   */
  @Override
  public Response intercept(Chain chain) throws IOException {

    /**
     * 可以添加公共头部参数如token
     */
    Request request = chain.request()
        .newBuilder()
//        .header("TOKEN", token)
//        .header("ID", id)
        .build();

    /**
     * 开始时间
     */
    long startTime = System.currentTimeMillis();
    Log.e("TAG","\n"+"requestUrl=" + request.url());
    String method = request.method();

    if ("POST".equals(method)) {
      try {
        JSONObject jsonObject = new JSONObject();
        if (request.body() instanceof FormBody) {
          FormBody body = (FormBody) request.body();
          for (int i = 0; i < body.size(); i++) {
            jsonObject.put(body.encodedName(i), body.encodedValue(i));
          }
          Log.e("TAG","入参JSON= " + jsonObject.toString());
        }
      } catch (JSONException e) {
        e.printStackTrace();
      }
    }

    Response response = chain.proceed(request);
    /**
     * 这里不能直接使用response.body().string()的方式输出日志
     * 因为response.body().string()之后,response中的流会被关闭,程序会报错,我们需要创建出一个新的response给应用层处理
     */
    ResponseBody responseBody = response.peekBody(1024 * 1024);
    Log.e("TAG","出参JSON=" + responseBody.string());
    long endTime = System.currentTimeMillis();
    long duration = endTime - startTime;
    Log.e("TAG","----------" + "耗时:" + duration + "毫秒----------");
    return response;
  }

}
结束

网上有很多别人封装好的库,但是符不符合自己的项目使用就是另外一回事了。还不如自己花点时间封装一个既简单又符合自己项目使用的代码,文章中贴出来的代码是核心代码并不是所有的代码,下载代码根据自己项目需求修改一下就可以用的。下篇我要写一个以retrofit2+rxjava2进行网络请求封装。

项目地址:https://github.com/pengjunshan/UseOkhttp3

拿到代码后移到自己项目中根据自己项目需求修改即可使用。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,165评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,503评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,295评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,589评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,439评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,342评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,749评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,397评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,700评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,740评论 2 313
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,523评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,364评论 3 314
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,755评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,024评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,297评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,721评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,918评论 2 336