OkHttp3使用介绍


首先引入okhttp框架

 compile 'com.squareup.okhttp3:okhttp:3.7.0'
 compile 'com.squareup.okio:okio:1.13.0'

另外,别忘了添加这些权限

<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"/>

GET请求

private void demo1() {
    try {
    	//get请求的参数拼在url后,需要编码,同时服务器也需要解码
    	String url ="http://192.168.30.217/shopping/test/demo1?username=" + 
                URLEncoder.encode("胜哥", "utf-8") + "&age=22"
    	//构建请求对象
        Request request = new Request.Builder()
                .url(url) //请求的url设置
                //.method("GET", null)// method GET must not have a request body.
                .get()//等于method("GET", null)
                .header("Cookie", "session=123123") //添加请求头
                .build();
		
		//创建okhttp
        OkHttpClient client = new OkHttpClient();
        //创建call
        Call call = client.newCall(request);
        //异步执行请求
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                System.out.println(e.getMessage());
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                System.out.println(response.body().string());//输出字符串结果
            }
        });
    } catch (UnsupportedEncodingException e) {
        e.printStackTrace();
    }
}

POST请求

private void demo2() {
   //application/x-www-form-urlencoded 普通表单提交,添加参数
   RequestBody body = new FormBody.Builder()
           .add("username", "胜哥")
           .add("age", "22")
           .add("hobby", "吃")
           .add("hobby", "喝")
           .add("hobby", "玩")
           .build();

	//构建请求对象        
   Request request = new Request.Builder()
           .url("http://192.168.30.217/shopping/test/demo1")
           .post(body) //post的body
           //.method("POST", body) 可以直接用post方法替代
           .header("Cookie", "session=123123") //添加请求头
           .build();
   //创建okhttp
   OkHttpClient client = new OkHttpClient();
   //创建call
   final Call call = client.newCall(request);

   //execute是同步的方法,必须在子线程中运行
   new Thread(new Runnable() {
       @Override
       public void run() {
           try {
               Response response = call.execute(); //同步执行
               System.out.println(response.body().string()); //输出字符串结果
           } catch (IOException e) {
               e.printStackTrace();
           }

       }
   }).start();
}

POST上传各种类型的文件

private void demo3() {
    //File file = createFile("a.txt");
    //File file = createFile("timg.gif");
    //File file = createFile("b.jpg");
    File file = createFile("c.mp4");
    //构建文件body
    RequestBody body = RequestBody.create(MediaType.parse("text/x-markdown;charset=utf-8"), file);
    //或者
    //RequestBody body = RequestBody.create(MediaType.parse("multipart/form-data; charset=utf-8"), file);
    
    //构建文件上传表单参数
    RequestBody multiBody = new MultipartBody.Builder()
            .setType(MultipartBody.FORM) //必须设置,multipart/form-data 才支持文件上传
            .addFormDataPart("upload", file.getName(), body) //name是表单提交的name
            .addFormDataPart("username", "胜哥")
            .addFormDataPart("age", "22")
            .addFormDataPart("hobby", "吃")
            .addFormDataPart("hobby", "喝")
            .addFormDataPart("hobby", "玩")
            .build();
    
    //构建请求对象        
    Request request = new Request.Builder()
            .url("http://192.168.30.217/shopping/test/demo1")
            .post(multiBody)//设置请求体数据
            .build();
	//创建okhttp
    OkHttpClient client = new OkHttpClient();
    //创建call
    Call call = client.newCall(request);
    //异步执行请求
    call.enqueue(new Callback() {
        @Override
        public void onFailure(Call call, IOException e) {
            System.out.println("onFailure:" + e.getMessage());
        }

        @Override
        public void onResponse(Call call, Response response) throws IOException {
            System.out.println("onResponse:" + response.body().string());
        }
    });

}


//将asset目录下的文件转成file保存到手机存储,并返回
private File createFile(String fileName) {
    File file = null;
    if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
        file = new File(Environment.getExternalStorageDirectory(), fileName);
    } else {
        file = new File(getCacheDir(), fileName);
    }
    if (!file.exists() || file.length() == 0) {
        try {
            BufferedInputStream bis = new BufferedInputStream(getAssets().open(fileName));
            BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file));
            int b;
            while ((b = bis.read()) != -1) {
                bos.write(b);
            }
            bos.flush();
            bos.close();
            bis.close();

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    return file;

}

POST提交字符串

private void demo4() {
    RequestBody body = RequestBody.create(MediaType.parse("text/x-markdown;charset=utf-8"), "天上飞的是什么?");
    Request request = new Request.Builder()
            .url("http://192.168.30.217/shopping/test/demo2")
            .post(body)
            .build();
    OkHttpClient client = new OkHttpClient();
    Call call = client.newCall(request);
    call.enqueue(new Callback() {
        @Override
        public void onFailure(Call call, IOException e) {
            System.out.println("onFailure:" + e.getMessage());
        }
        @Override
        public void onResponse(Call call, Response response) throws IOException {
            System.out.println("onResponse:" + response.body().string());
        }
    });
}

POST提交json

private void demo5() {
	String jsonStr = "{\"username\":\"胜哥\",\"age\":20}";
	RequestBody body = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), jsonStr);
    Request request = new Request.Builder()
            .url("http://192.168.30.217/shopping/test/demo2")
            .post(body)
            .build();
    OkHttpClient client = new OkHttpClient();
    Call call = client.newCall(request);
    call.enqueue(new Callback() {
        @Override
        public void onFailure(Call call, IOException e) {
            System.out.println("onFailure:" + e.getMessage());
        }
        @Override
        public void onResponse(Call call, Response response) throws IOException {
            System.out.println("onResponse:" + response.body().string());
        }
    });
}

POST提交byte数据

private void demo6() {
    byte[] bytes = "天上飞的是什么?".getBytes();
    RequestBody body = RequestBody.create(MediaType.parse("text/x-markdown; charset=utf-8"), bytes);
    Request request = new Request.Builder()
            .url("http://192.168.30.217/shopping/test/demo2")
            .post(body)
            .build();
    OkHttpClient client = new OkHttpClient();
    Call call = client.newCall(request);
    call.enqueue(new Callback() {
        @Override
        public void onFailure(Call call, IOException e) {
            System.out.println("onFailure:" + e.getMessage());
        }
        @Override
        public void onResponse(Call call, Response response) throws IOException {
            System.out.println("onResponse:" + response.body().string());
        }
    });
}

POST上传流

private void demo7() {
    OkHttpClient client = new OkHttpClient();
    RequestBody body = new RequestBody() {
        @Override
        public MediaType contentType() {
            return MediaType.parse("text/x-markdown; charset=utf-8");
        }
        @Override
        public void writeTo(BufferedSink sink) throws IOException {
            sink.writeUtf8("天上飞的是什么?");
        }
    };
    Request request = new Request.Builder().url("http://192.168.30.217/shopping/test/demo2").post(body).build();
    Call call = client.newCall(request);
    call.enqueue(new Callback() {
        @Override
        public void onFailure(Call call, IOException e) {
            System.out.println("onFailure:" + e.getMessage());
        }
        @Override
        public void onResponse(Call call, Response response) throws IOException {
            System.out.println("onResponse:" + response.body().string());
        }
    });
}

获取流

Response可以通过body获取到流,在回调结果中才能拿到Response对象

@Override
public void onResponse(Call call, Response response) throws IOException {
    //获取流
    InputStream stream = response.body().byteStream();
    BufferedReader br = new BufferedReader(new InputStreamReader(stream));
    StringBuilder sb = new StringBuilder();
    String line = null;
    while ((line = br.readLine()) != null) {
        sb.append(line);
    }
    System.out.println(sb);
}

获取字节数组

Response也可以获取字节数组

@Override
public void onResponse(Call call, Response response) throws IOException {
    //获取字节数组
    byte[] bytes = response.body().bytes();
    System.out.println(new String(bytes, "utf-8"));
}

设置超时时间和缓存

private OkHttpClient getOkHttpClient() {
    int cacheSize = 10 * 1024 * 1024; //设置缓存大小
    File cacheFile; //缓存的路径
    if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
        cacheFile = new File(getExternalCacheDir(), "okCache");
    } else {
        cacheFile = new File(getCacheDir(), "okCache");
    }
    return new OkHttpClient.Builder()
            .connectTimeout(15, TimeUnit.SECONDS) //连接超时
            .writeTimeout(20, TimeUnit.SECONDS) //写超时
            .readTimeout(20, TimeUnit.SECONDS)//读超时
            .cache(new Cache(cacheFile, cacheSize))
            .build();
}

然后,在Request.Builder中通过cacheControl来控制是否需要读缓存

//设置超时时间和缓存
private void demo9() {
    OkHttpClient client = getOkHttpClient();
    Request request = new Request.Builder()
            .url("http://192.168.30.217/shopping/test/demo1")
            //.cacheControl(CacheControl.FORCE_CACHE) //强制请求缓存,不会请求网络,没有缓存则回调异常
            //.cacheControl(CacheControl.FORCE_NETWORK) //强制请求网络,如果断网则直接回调异常
            .build();
    Call call = client.newCall(request);
    call.enqueue(new Callback() {
        @Override
        public void onFailure(Call call, IOException e) {
            System.out.println("onFailure:" + e.getMessage());

        }

        @Override
        public void onResponse(Call call, Response response) throws IOException {
            if (null != response.networkResponse()) {
                System.out.println("==来自网络==");
            } else if (null != response.cacheResponse()) {
                System.out.println("==来自缓存==");
            }
            System.out.println(response.body().string());
        }
    });
}

注意: OkHttp的缓存会受接口响应头的cache-control控制,我这里整理了一张缓存策略的表
在这里插入图片描述
特别注意:

FORCE_CACHE的情况,如果响应头是没有设置cache-control或者为no-cache的,即使本地有缓存,也直接回调异常,这里所说的本地有缓存是指在另外2种请求方式下得到的缓存,这里分2种情况,响应头有设置cache-control时的缓存和响应头没有设置cache-control的缓存,如果是没有设置cache-control响应头时缓存的,那么FORCE_CACHE的方式是直接回调异常的.

设置网络优先策略

上面例子可以看出Okhttp只能设置强制网络或者强制缓存策略,如果我们设置先请求网络,网络请求失败再请求缓存,也就是网络优先策略该怎么弄呢?

这个就需要我们来实现了,也很简单,就是在FORCE_NETWORK失败的时候判断一下

private void demo10() {
    final OkHttpClient client = getOkHttpClient();
    final Request request = new Request.Builder()
            .url("http://192.168.30.217/shopping/test/demo1")
            .cacheControl(CacheControl.FORCE_NETWORK)//强制网络
            .build();

    client.newCall(request).enqueue(new Callback() {
        int count = 0; //异常回调次数

        @Override
        public void onFailure(Call call, IOException e) {
            if (count == 0) {
                //网络请求失败,再请求缓存
                count++;
                Request request = new Request.Builder()
                        .url("http://192.168.30.217/shopping/test/demo1")
                        .cacheControl(CacheControl.FORCE_CACHE) //强制缓存
                        .build();
                client.newCall(request).enqueue(this);
            } else {
                System.out.println("onFailure:" + e.getMessage());
            }

        }

        @Override
        public void onResponse(Call call, Response response) throws IOException {
            if (null != response.networkResponse()) {
                System.out.println("==来自网络==");
            } else if (null != response.cacheResponse()) {
                System.out.println("==来自缓存==");
            }
            System.out.println(response.body().string());
        }
    });
}

https自签名证书验证

首先得拿到服务器提供的证书,关于如何创建签名证书可以看这篇文章Tomcat服务器支持https请求设置

拿到服务器的证书后,先将其存放到android项目的asserts目录下
在这里插入图片描述
OkHttpClient可以通OkHttpClient.Builder来设置sslSocketFactory,这样就可以校验自签名的证书了,通过下面这个方法创建SSLSocketFactory

public SSLSocketFactory getSSLSocketFactory(InputStream... certificates) {
    try {
        CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
        // Create a KeyStore containing our trusted CAs
        KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
        keyStore.load(null);
        int index = 0;
        for (InputStream certificate : certificates) {
            String certificateAlias = Integer.toString(index++);
            keyStore.setCertificateEntry(certificateAlias, certificateFactory.generateCertificate(certificate));

            try {
                if (certificate != null)
                    certificate.close();
            } catch (IOException e) {
            }
        }

        // 通过KeyStore去引导生成的TrustManager
        TrustManagerFactory trustManagerFactory =
                TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());

        trustManagerFactory.init(keyStore);

        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(
                null,
                trustManagerFactory.getTrustManagers(),
                new SecureRandom()
        );
        return sslContext.getSocketFactory();
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

另外,OkHttpClient.Builder的sslSocketFactory方法中的第二参数需要接受一个X509TrustManager对象,这个对象可用于验证客户端和服务器的证书的合法性,通常不做处理,直接new出来

X509TrustManager x509TrustManager = new X509TrustManager() {
    @Override
    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
    }
    @Override
    public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
    }
    @Override
    public X509Certificate[] getAcceptedIssuers() {
        return new X509Certificate[0];
    }
};

另外,为了避免访问服务器的域名和证书上配置的域名不符合,导致错误,还需要配置一下hostnameVerifier方法,忽略域名的校验
2种方式, 第一种直接new 一个 HostnameVerifier, 像这样,不做处理

HostnameVerifier hostnameVerifier = new HostnameVerifier() {
    @Override
    public boolean verify(String hostname, SSLSession session) {
      
        return true;
    }
}

然后,传给OkHttpClient.Builder的hostnameVerifier方法即可.

另一种方式直接传org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER给OkHttpClient.Builder的hostnameVerifier方法即可.2种方式任选其一.

接下来就是设置OkHttpClient的sslSocketFactory了

private void demo14() {
    try {
        Request request = new Request.Builder().url("https://www.baidu.com"/*"https://192.168.30.217/shopping/test/demo1"*/).get().build();

        //仅信任自签名证书,2种方式
        //如果访问的不是自签名的网站就会报这个异常:java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
        SSLSocketFactory sslSocketFactory = getSSLSocketFactory(getAssets().open("tomcat.cer")/*new Buffer()
                .writeUtf8(tomcat_cre)
                .inputStream()*/);


        X509TrustManager x509TrustManager = new X509TrustManager() {
            @Override
            public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {

            }

            @Override
            public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {

            }

            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return new X509Certificate[0];
            }
        };
      

        OkHttpClient client = new OkHttpClient.Builder()
                .sslSocketFactory(sslSocketFactory, x509TrustManager).hostnameVerifier(/*new HostnameVerifier() {
                    @Override
                    public boolean verify(String hostname, SSLSession session) {
                        //或者传org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER表示允许所有证书的域名
                        return true;
                    }
                }*/org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER).build();
        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                System.out.println("onFailure:" + e.getMessage());
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                System.out.println("onResponse:" + response.body().string());
            }
        });
    } catch (Exception e) {
        e.printStackTrace();
    }

}

看上面的注释提到SSLSocketFactory 有2种方式输入证书对应的流,上面介绍的是直接读取asserts目录下的tomcat.cer证书,如果你不想将证书拷贝到asserts中,你可以通过下面的方式解决

在证书所在的目录下打开cmd命令,输入下面命令
keytool -printcert -rfc -file tomcat.cer

会生成一段字符串,如下所示:

"-----BEGIN CERTIFICATE-----\n" +
"MIIDUzCCAjugAwIBAgIEbr1RnTANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJj\n" +
"bjELMAkGA1UECBMCZ2QxCzAJBgNVBAcTAmd6MQ8wDQYDVQQKEwZ0b21jYXQxDzAN\n" +
"BgNVBAsTBnRvbWNhdDEPMA0GA1UEAxMGdG9tY2F0MB4XDTE5MDYxNDA4MjYyN1oX\n" +
"DTI5MDQyMjA4MjYyN1owWjELMAkGA1UEBhMCY24xCzAJBgNVBAgTAmdkMQswCQYD\n" +
"VQQHEwJnejEPMA0GA1UEChMGdG9tY2F0MQ8wDQYDVQQLEwZ0b21jYXQxDzANBgNV\n" +
"BAMTBnRvbWNhdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJtXs81j\n" +
"mX11QCGo6npLjqc5DoYpG6QRGw2ArlD6O/izlSx+N6DcS86kPXOAH+RF2DGH8fKv\n" +
"sKgZNAE01ZTYSSiLiVn+6WkmKqObuU2KnSosFiAkcC/n08lPyGkqNtQ4JQqiJy5v\n" +
"249EgJiqPJVXBfT9Sfk1Uv9fs6UGZwNMxDiyULiTDKLW85r6i14D9wW4XZm4gTBI\n" +
"I9+Ib65aB5RroGKI1pOKyqvF5WkByWeBU+vhTmh/lUKmHD9QGoV5EUfXPK2YYT+n\n" +
"EMQZZvpRkbQsRQ881WsTQAPc1/Sw5kqjSB1sGutr1pY9WVkBj3LNjgv08gLb+FVN\n" +
"CpFEeZUAngn2TBMCAwEAAaMhMB8wHQYDVR0OBBYEFCHsaPBTC14SD5rd+0gobwq6\n" +
"8gl/MA0GCSqGSIb3DQEBCwUAA4IBAQAGcY/vl0b/AWTDHNDu/XRXYEoJUiIG8KM9\n" +
"/zeBc+24uftWiiwsVIs+9EFVDAbexngu/fzFLg+KmWgSf2hiLgKDrD3sa+87xtkP\n" +
"5agBjSjOGE1BaC2UlZFLT1AFi1nTEzv/cq+dreqrjkTziSw4AaU8mFrDkhDrxrw2\n" +
"SZH6gb4hqtAF2vCSx3KfpQcPn3ebxUIKOMri7tbCaqW4ennh9D+0hUypwv5CAdiw\n" +
"bSBLv5E+SPGL1viqTeK85eMGUVlQyTA8AxtyYuToDj+Ppr5S1KlDfIgvhBwOdR/j\n" +
"iTcqD0gTORbGohhTCdDiYWGuj7LEoxyS/7un1Cr/UBTHOEn+4Vmp\n" +
"-----END CERTIFICATE-----"

然后调用getSSLSocketFactory方法的时候,通过okio的Buffer对象来将这段字符串生成流对象,即这段代码
InputStream inputStream = new Buffer().writeUtf8(tomcat_cre).inputStream()

忽略所有证书校验

private void demo14() {
    try {
        Request request = new Request.Builder().url("https://www.baidu.com").get().build();

        X509TrustManager x509TrustManager = new X509TrustManager() {
            @Override
            public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {

            }

            @Override
            public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {

            }

            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return new X509Certificate[0];
            }
        };
       
        //信任所有证书
        SSLContext mCtx = SSLContext.getInstance("TLS");
        mCtx.init(null, new TrustManager[]{x509TrustManager},null);
        sslSocketFactory = mCtx.getSocketFactory();

        OkHttpClient client = new OkHttpClient.Builder()
                .sslSocketFactory(sslSocketFactory, x509TrustManager)
                .hostnameVerifier(org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER).build();
        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                System.out.println("onFailure:" + e.getMessage());
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                System.out.println("onResponse:" + response.body().string());
            }
        });
    } catch (Exception e) {
        e.printStackTrace();
    }

}

下载文件

private void demo15() {
     final String fileName = "c.mp4";
     Request request = new Request.Builder()
             .url("http://192.168.30.217/shopping/test/download?fileName=" + fileName)
             .get()
             .build();
     OkHttpClient client = new OkHttpClient.Builder().build();
     client.newCall(request).enqueue(new Callback() {
         @Override
         public void onFailure(Call call, IOException e) {
             System.out.println("onFailure:" + e.getMessage());
         }

         @Override
         public void onResponse(Call call, Response response) throws IOException {
             BufferedInputStream bis = new BufferedInputStream(response.body().byteStream());
             BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(createDownloadFile(fileName)));
             int b;
             while ((b = bis.read()) != -1) {
                 bos.write(b);
             }
             bos.close();
             System.out.println("onResponse:下载完成!");
         }
     });

 }

/**
 * 创建下载File
 *
 * @return
 */
private File createDownloadFile(String fileName) {
    File downloadDir;
    if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
        downloadDir = new File(getExternalCacheDir(), "download");
    } else {
        downloadDir = new File(getCacheDir(), "download");
    }
    if (!downloadDir.exists()) {
        downloadDir.mkdirs();
    }
    final File downloadFile = new File(downloadDir, fileName);
    if (downloadFile.exists()) {
        downloadFile.delete();
    }
    return downloadFile;
}

监听下载进度

通过装饰者模式,对ResponseBody对象进行增强

public class ProgressResponseBody extends ResponseBody {
    public abstract static class Progress {
        public long startIndex() {
            //初始响应的位置
            return 0;
        }

        public boolean returnOnMainThread() {
            //在主线程中返回
            return true;
        }

        /**
         * 持续进度显示
         *
         * @param bytesRead     当前已响应大小
         * @param contentLength 总文件大小
         * @param progress      响应进度0~100
         */
        public abstract void onProgress(long bytesRead, long contentLength, int progress);
    }

    private Progress mProgress;
    private ResponseBody mResponseBody;

    private Handler mHandler = new Handler(Looper.getMainLooper(), new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            if (msg.what == 200) {
                long[] obj = (long[]) msg.obj;
                mProgress.onProgress(obj[0], obj[1], (int) obj[2]);
            }
            return true;
        }
    });

    public ProgressResponseBody(ResponseBody responseBody, Progress progress) {
        this.mProgress = progress;
        this.mResponseBody = responseBody;
    }

    @Override
    public MediaType contentType() {
        return mResponseBody.contentType();
    }

    @Override
    public long contentLength() {
        return mResponseBody.contentLength();
    }

    @Override
    public BufferedSource source() {
        return Okio.buffer(new ForwardingSource(mResponseBody.source()) {
            //总读取字节数
            long totalBytesRead = 0L;

            @Override
            public long read(Buffer sink, long byteCount) throws IOException {
                //当前已读字节数,若已读完则返回-1
                long bytesRead = super.read(sink, byteCount);
                if (mProgress != null) {
                    totalBytesRead += bytesRead == -1 ? 0 : bytesRead;
                    long curr = totalBytesRead + mProgress.startIndex();//如果有开始位置,则加上开始位置
                    int progress = (int) (curr * 100 / contentLength());

                    if (mProgress.returnOnMainThread()) {
                        //将结果发送到UI线程中
                        long[] obj = new long[]{curr, contentLength(), progress};
                        mHandler.sendMessage(mHandler.obtainMessage(200, obj));
                    } else {
                        //结果保持在子线程中回调
                        mProgress.onProgress(curr, contentLength(), progress);
                    }


                }
                return bytesRead;
            }
        });
    }
}

然后通过OkHttpClient.Builder的addNetworkInterceptor方法设置拦截器,处理响应结果

private void demo17() {
	final String fileName = "c.mp4";
	Request request = new Request.Builder()
	        .url("http://192.168.30.217/shopping/test/download?fileName=" + fileName)
	        .get()
	        .build();
	OkHttpClient client = new OkHttpClient.Builder()
	        .addNetworkInterceptor(new Interceptor() {
	            @Override
	            public Response intercept(Chain chain) throws IOException {
	                //获取原始Response
	                Response originalResponse = chain.proceed(chain.request());
	                //返回新的Response
	                return originalResponse.newBuilder()
	                        .body(new ProgressResponseBody(originalResponse.body(), new ProgressResponseBody.Progress() {
	                            @Override
	                            public long startIndex() {
	                                return 0;
	                            }
	
	                            @Override
	                            public void onProgress(long bytesRead, long contentLength, int progress) {
	                                System.out.println("bytesRead:" + bytesRead + " contentLength:" + contentLength +
	                                	 " progress:" + progress);
	
	                            }
	                        }))
	                        .build();
	            }
	        }).build();
	
	client.newCall(request).enqueue(new Callback() {
	    @Override
	    public void onFailure(Call call, IOException e) {
	        System.out.println("onFailure:" + e.getMessage());
	    }
	
	    @Override
	    public void onResponse(Call call, Response response) throws IOException {
	        System.out.println("code:" + response.code());
	        System.out.println("contentLength:" + response.header("Content-Length"));
	        BufferedInputStream bis = new BufferedInputStream(response.body().byteStream());
	        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(createDownloadFile(fileName)));
	        int b;
	        while ((b = bis.read()) != -1) {
	            bos.write(b);
	        }
	        bos.close();
	        System.out.println("onResponse:下载完成!");
	    }
	});
}

批量上传文件

private void demo18() {
     File file1 = createFile("c.mp4");
     File file2 = createFile("b.jpg");
     RequestBody file1Body = RequestBody.create(MediaType.parse("text/x-markdown;charset=utf-8"), file1);
     RequestBody file2Body = RequestBody.create(MediaType.parse("text/x-markdown;charset=utf-8"), file2);

     MultipartBody multiBody = new MultipartBody.Builder()
             .setType(MultipartBody.FORM) //必须设置,multipart/form-data 才支持文件上传
             .addFormDataPart("mulitupload", file1.getName(), file1Body) //name是表单提交的name
             .addFormDataPart("mulitupload", file2.getName(), file2Body) //name是表单提交的name
             .addFormDataPart("username", "胜哥")
             .addFormDataPart("age", "22")
             .addFormDataPart("hobby", "吃")
             .addFormDataPart("hobby", "喝")
             .addFormDataPart("hobby", "玩")
             .build();

     Request request = new Request.Builder()
             .url("http://192.168.30.217/shopping/test/demo1")
             .post(multiBody)
             .build();

     OkHttpClient client = new OkHttpClient();
     Call call = client.newCall(request);
     call.enqueue(new Callback() {
         @Override
         public void onFailure(Call call, IOException e) {
             System.out.println("onFailure:" + e.getMessage());
         }

         @Override
         public void onResponse(Call call, Response response) throws IOException {
             System.out.println("onResponse:" + response.body().string());
         }
     });
 }

监听文件上传进度

通过装饰者模式对RequestBody进行增强,这里我们只需要监听文件的上传进度即可,普通参数的提交监听进度意义不大,基本很快就提交完了

/**
 * Created by mChenys on 2019/6/17.
 */

public class ProgressRequestBody extends RequestBody {

    private RequestBody mRequestBody;
    private Progress mProgress;
    private long totalByte;
    private Handler mHandler = new Handler(Looper.getMainLooper(), new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            if (msg.what == 200) {
                long[] obj = (long[]) msg.obj;
                mProgress.onProgress(obj[0], obj[1], (int) obj[2]);
            }
            return true;
        }
    });

    public abstract static class Progress {
        public long startIndex() {
            //初始响应的位置
            return 0;
        }

        public boolean returnOnMainThread() {
            //在主线程中返回
            return true;
        }

        /**
         * 持续进度显示
         *
         * @param bytesWritten  当前输出大小
         * @param totalByte 总文件大小
         * @param progress      上传进度0~100
         */
        public abstract void onProgress(long bytesWritten, long totalByte, int progress);
    }

    public ProgressRequestBody(RequestBody requestBody, Progress progress) {
        this.mRequestBody = requestBody;
        this.mProgress = progress;
		//判断是否是文件表单提交
        if (mRequestBody instanceof MultipartBody) {
            MultipartBody multipartBody = (MultipartBody) mRequestBody;
            for (MultipartBody.Part part : multipartBody.parts()) {
                if (null != part.body().contentType()) { //普通参数设置是没有contentType的
                    try {
                        totalByte += part.body().contentLength();//累计总提交的文件大小
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }



    @Override
    public MediaType contentType() {
        return mRequestBody.contentType();
    }

    //包装完成的BufferedSink
    private BufferedSink bufferedSink;

    @Override
    public void writeTo(BufferedSink sink) throws IOException {
        if (bufferedSink == null) {
            //包装
            bufferedSink = Okio.buffer(new ForwardingSink(sink) {
                //当前写入字节数
                long bytesWritten = 0L;

                @Override
                public void write(Buffer source, long byteCount) throws IOException {
                    super.write(source, byteCount);
                    if (null != mProgress) {
                        //增加当前写入的字节数
                        bytesWritten += byteCount;
                        /**
                         * 回调,最终bytesWritten肯定是大于totalByte的,查看源码可知输出的内容还包括其他的
                         * 例如请求头数据,请求参数等等
                         */
                        if (bytesWritten > totalByte) {
                            bytesWritten = totalByte;
                        }
                        int progress = (int) (bytesWritten * 100 / totalByte);
                        if (mProgress.returnOnMainThread()) {
                            //将结果发送到UI线程中
                            long[] obj = new long[]{bytesWritten, totalByte, progress};
                            mHandler.sendMessage(mHandler.obtainMessage(200, obj));
                        } else {
                            //结果保持在子线程中回调
                            mProgress.onProgress(bytesWritten, totalByte, progress);
                        }
                    }
                }
            });
        }
        //写入
        mRequestBody.writeTo(bufferedSink);
        //必须调用flush,否则最后一部分数据可能不会被写入
        bufferedSink.flush();
    }
}

单个文件上传进度监听

//监听上传进度
private void demo19() {
     File file = createFile("c.mp4");
     //构建文件body
     RequestBody body = RequestBody.create(MediaType.parse("text/x-markdown;charset=utf-8"), file);
     MultipartBody multiBody = new MultipartBody.Builder()
             .setType(MultipartBody.FORM) //必须设置,multipart/form-data 才支持文件上传
             .addFormDataPart("upload", file.getName(), body) //name是表单提交的name
             .addFormDataPart("username", "胜哥")
             .addFormDataPart("age", "22")
             .addFormDataPart("hobby", "吃")
             .addFormDataPart("hobby", "喝")
             .addFormDataPart("hobby", "玩")
             .build();

     Request request = new Request.Builder()
             .url("http://192.168.30.217/shopping/test/demo1")
             .post(new ProgressRequestBody(multiBody, new ProgressRequestBody.Progress() {
                 @Override
                 public void onProgress(long bytesWritten, long totalByte, int progress) {
                     System.out.println("bytesWritten:"+bytesWritten+" totalByte:"+totalByte+" progress:"+progress);
                 }
             }))
             .build();

     OkHttpClient client = new OkHttpClient();
     Call call = client.newCall(request);
     call.enqueue(new Callback() {
         @Override
         public void onFailure(Call call, IOException e) {
             System.out.println("onFailure:" + e.getMessage());
         }

         @Override
         public void onResponse(Call call, Response response) throws IOException {
             System.out.println("onResponse:" + response.body().string());
         }
     });
 }

多个文件同时提交,监听总进度

private void demo20() {
    File file1 = createFile("c.mp4");
    File file2 = createFile("b.jpg");
    RequestBody file1Body = RequestBody.create(MediaType.parse("text/x-markdown;charset=utf-8"), file1);
    RequestBody file2Body = RequestBody.create(MediaType.parse("text/x-markdown;charset=utf-8"), file2);

    MultipartBody multiBody = new MultipartBody.Builder()
            .setType(MultipartBody.FORM) //必须设置,multipart/form-data 才支持文件上传
            .addFormDataPart("mulitupload", file1.getName(), file1Body) //name是表单提交的name
            .addFormDataPart("mulitupload", file2.getName(), file2Body) //name是表单提交的name
            .addFormDataPart("username", "胜哥")
            .addFormDataPart("age", "22")
            .addFormDataPart("hobby", "吃")
            .addFormDataPart("hobby", "喝")
            .addFormDataPart("hobby", "玩")
            .build();

    Request request = new Request.Builder()
            .url("http://192.168.30.217/shopping/test/demo1")
            .post(new ProgressRequestBody(multiBody, new ProgressRequestBody.Progress() {
                @Override
                public void onProgress(long bytesWritten, long totalByte, int progress) {
                    System.out.println("bytesWritten:"+bytesWritten+" totalByte:"+totalByte+" progress:"+progress);
                }
            }))
            .build();

    OkHttpClient client = new OkHttpClient();
    Call call = client.newCall(request);
    call.enqueue(new Callback() {
        @Override
        public void onFailure(Call call, IOException e) {
            System.out.println("onFailure:" + e.getMessage());
        }

        @Override
        public void onResponse(Call call, Response response) throws IOException {
            System.out.println("onResponse:" + response.body().string());
        }
    });
}

服务端代码实现

上面都是客户端的代码实现,下面介绍下服务端的代码,有需要的可以拿去用,服务器端用的是java实现的 ,使用的是Struts2框架,该框架的使用,大家如果不熟悉的话,可以先看看我博客专栏分类里面的Struts2栏目中的文章。

javabean

public class User {
	private String username;
	private int age;
	private List<String> hobby;
	private String image;
	
	private List<String> images;
	
	//get set 方法
}

Action类

package blog.csdn.net.mchenys.action;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.io.FileUtils;
import org.apache.struts2.ServletActionContext;

import com.googlecode.sslplugin.annotation.Secured;
import com.opensymphony.xwork2.ModelDriven;

import blog.csdn.net.mchenys.domain.User;
import blog.csdn.net.mchenys.utils.UploadUtils;

/**
 * 测试
 */
public class TestAction extends ActionSupport implements ModelDriven<User> {

	private static final long serialVersionUID = 1L;
	//模型驱动方式注入参数
	private User user = new User();

	@Override
	public User getModel() {
		return user;
	}

	//处理响应格式为json的数据
	protected Map<String, Object> dataMap = new HashMap<String, Object>();
	
	//必须提供get方法
	public Map<String, Object> getDataMap() {
		return dataMap;
	}

	/**单文件上传
	 * 通过属性驱动注入3个值 文件上传相关的3个属性 ,struts2会自动注入 
	 * 上传的文件 : 属性名与表单中file的name属性名一致
	 * 上传的文件名:属性名前段是name属性名一致+FileName; 
	 * 上传的文件类型: 属性名前段是name属性名一致 + ContentType;
	 */
	private File upload; // 表单的name是upload
	private String uploadFileName; // 文件名
	private String uploadContentType;// 文件类型

	public void setUpload(File upload) {
		this.upload = upload;
	}

	public void setUploadFileName(String uploadFileName) {
		this.uploadFileName = uploadFileName;
	}

	public void setUploadContentType(String uploadContentType) {
		this.uploadContentType = uploadContentType;
	}
	
	/**
	 * 批量文件上传,和单文件上传类似,只需要将那3个属性变成数组即可
	 */
	private File[] mulitupload; // 表单的name是mulitupload
	private String[] mulituploadFileName; // 文件名
	private String[] mulituploadContentType;// 文件类型
	
	public void setMulitupload(File[] mulitupload) {
		this.mulitupload = mulitupload;
	}

	public void setMulituploadFileName(String[] mulituploadFileName) {
		this.mulituploadFileName = mulituploadFileName;
	}

	public void setMulituploadContentType(String[] mulituploadContentType) {
		this.mulituploadContentType = mulituploadContentType;
	}

	// 文件下载
	private String fileName;// 文件类型
	private String downloadPath;//文件下载路径
	private String contentLength;//文件下载的长度
	
	/**
	 * 返回文件的长度,对应struts.xml中的<param name="contentLength">contentLength</param>
	 * @return
	 */
	public String getContentLength() {
		System.out.println("===getContentLength==="+contentLength);
		return contentLength;
	}
	
	public void setFileName(String fileName) {
		System.out.println("===setFileName==="+fileName);
		this.fileName = fileName;
	}

	/**
	 * 返回文件名,对应struts.mlx中的<param name="contentDisposition">中的filename=${fileName}
	 * @return
	 */
	public String getFileName() {
		System.out.println("===getFileName==="+fileName);
		return fileName;
	}

	/**
	 * 返回InputStream,对应struts.mlx中的<param name="inputName">inputStream</param>
	 *
	 * @return
	 */
	public InputStream getInputStream() {
		System.out.println("====getInputStream====");
		try {
			//转换格式,否则输出的文件名有中文时,浏览器不会显示
			this.fileName=new String(fileName.getBytes(),"iso-8859-1");
			return new FileInputStream(new File(downloadPath));
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	
	/**
	 * 文件下载
	 * 
	 * @return
	 */
	public String download() {
		System.out.println("====download====");
		// 获取绝对路径
		this.downloadPath = ServletActionContext.getServletContext().getRealPath("/download/"+fileName);
		//指定文件长度(可选)
		this.contentLength = String.valueOf(new File(downloadPath).length());
		System.out.println("downloadPath:"+downloadPath);
		/*try {
			// 解决下载的文件名是中文的文件获取文件名时乱码问题,如果已经配置过编码拦截器的可以不需要处理
			this.downloadPath = new String(downloadPath.getBytes("ISO-8859-1"), "UTF-8");
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}*/
		return "downloadOK";
	}

	public String demo1() throws Exception {
		HttpServletRequest request = ServletActionContext.getRequest();
		String cookie = request.getHeader("Cookie");

		if (null != this.uploadFileName) { //说明是单文件上传
			// 说明有文件需要上传
			String filename = UploadUtils.getUUIDName(this.uploadFileName);
			// 保存到tomcat目录下
			String uploadDidr = "E:\\apache-tomcat-7.0.52\\webapps\\upload";
			// 保存上传的文件,通过apache提供的工具类来操作
			FileUtils.copyFile(upload, new File(uploadDidr, filename));

			// 保存文件到User中,只保存相对路径,外部访问的时候可以拼上tomcat服务器的地址
			String image = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort()
					+ "/upload/" + filename;

			user.setImage(image);
		}
		
		if (null != this.mulituploadFileName) { // 批量文件上传
			// 保存到tomcat目录下
			String uploadDidr = "E:\\apache-tomcat-7.0.52\\webapps\\upload";
			
			//保存上传的文件浏览路径
			List<String> images = new ArrayList<>();
			
			//循环读写文件
			for (int i = 0; i < mulituploadFileName.length; i++) {
				//uuid处理后的文件名
				String filename = UploadUtils.getUUIDName(mulituploadFileName[i]);
				
				//需要上传的文件
				File file = mulitupload[i];
				
				// 保存上传的文件,通过apache提供的工具类来操作
				FileUtils.copyFile(file, new File(uploadDidr, filename));

				// 保存文件到User中,只保存相对路径,外部访问的时候可以拼上tomcat服务器的地址
				String image = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort()
						+ "/upload/" + filename;
				
				images.add(image);
				user.setImages(images);

			}
		}

		if (user.getHobby() != null) {
			List<String> hobbys = new ArrayList<>();
			for (String hobby : user.getHobby()) {
				if (hobby.contains(",")) { // 解决按,拼接提交的数据
					String[] split = hobby.split(",");
					hobbys.addAll(Arrays.asList(split));
				}
			}
			if (hobbys.size() > 0) {
				user.setHobby(hobbys);
			}
		}
		
		//输出json数据
		dataMap.put("status", 0);
		dataMap.put("msg", "请求成功");
		dataMap.put("user", user);

		return SUCCESS;
	}

	public String demo2() throws Exception {
		System.out.println("demo2 start");
		String content = null;

		BufferedReader br = new BufferedReader(new InputStreamReader(getRequest().getInputStream()));
		String line = null;
		StringBuilder sb = new StringBuilder();
		while ((line = br.readLine()) != null) {
			sb.append(line);
		}
		content = sb.toString();
		System.out.println("content=" + content);

		dataMap.put("content", content);
		return SUCCESS;
	}

}

EncodingIntereptor

处理get和post请求参数乱码问题

package blog.csdn.net.mchenys.intercept;

import java.io.UnsupportedEncodingException;
import java.util.Iterator;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.struts2.ServletActionContext;
import org.apache.struts2.StrutsStatics;

import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
/**
 * 解决请求乱码问题
 * @author mChenys
 *
 */
public class EncodingIntereptor extends AbstractInterceptor {

	private static final long serialVersionUID = 6826256332417695666L;

	@Override
	public String intercept(ActionInvocation invo) throws Exception {
		HttpServletRequest request = ServletActionContext.getRequest();
		HttpServletResponse response = ServletActionContext.getResponse();
		if (request.getMethod().equalsIgnoreCase("post")) {
			try {
				request.setCharacterEncoding("utf-8");
			} catch (UnsupportedEncodingException e) {
				e.printStackTrace();
			}
		} else {
			Iterator<String[]> iterval = request.getParameterMap().values().iterator();
			while (iterval.hasNext()) {
				String[] parames = iterval.next();
				for (int i = 0; i < parames.length; i++) {
					try {
						parames[i] = new String(parames[i].getBytes("iso8859-1"), "utf-8");
					} catch (UnsupportedEncodingException e) {
						e.printStackTrace();
					}
				}
			}
		}
		response.setContentType("text/html;charset=UTF-8");
		response.setHeader("Cache-Control", "no-cache");
		return invo.invoke();
	}

}

UploadUtils

package blog.csdn.net.mchenys.utils;

import java.util.UUID;

/**
 * 文件上传的工具类
 * @author Administrator
 */
public class UploadUtils {
	
	/**
	 * 传入文件的名称,返回的唯一的名称
	 * 例如:gril.jpg	返回sdjsljfsjdl.jpg
	 * @param filename
	 * @return
	 */
	public static String getUUIDName(String filename){
		// 先查找
		int index = filename.lastIndexOf(".");
		// 截取
		String lastname = filename.substring(index, filename.length());
		// 唯一 字符串  fsd-sfsdf-sfsd-sdfsd
		String uuid = UUID.randomUUID().toString().replace("-", "");
		return uuid+lastname;
	}
	
	public static void main(String[] args) {
		String filename = "girl.jpg";
		String uuid = getUUIDName(filename);
		System.out.println(uuid);
	}
}

struts.xml配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
	"-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
	"http://struts.apache.org/dtds/struts-2.3.dtd">

<struts>

	<!-- 设置文件上传的大小 视频文件上传最大为100M -->
	<constant name="struts.multipart.maxSize" value="104857600"></constant>

	
	<package name="test" extends="ssl-default,json-default"
		namespace="/test">
		<!-- 自定义结果类型 -->
		<result-types>
			<result-type name="mulitStream" class="blog.csdn.net.mchenys.result.MulitStreamResult"></result-type>
		</result-types>
		
		<!-- 自定义拦截器 -->
		<interceptors>
			<!-- 声明自定义的编码处理拦截器 -->
			<interceptor name="encoding"
				class="blog.csdn.net.mchenys.intercept.EncodingIntereptor" />
			<!-- 定义拦截器栈 -->
			<interceptor-stack name="myStack">
				<!-- 引入自定义的拦截器 -->
				<interceptor-ref name="encoding" />
				<!-- 引入默认的拦截器 -->
				<interceptor-ref name="defaultStack" />
			</interceptor-stack>
		</interceptors>

		

		<action name="*" class="blog.csdn.net.mchenys.action.TestAction"
			method="{1}">
			<!-- 引入拦截器栈 -->
			<interceptor-ref name="myStack" />
			
			<!-- json数据返回类型,需要额外引入struts2-json-plugin-2.3.24.jar包 -->
			<result type="json">
				<param name="root">dataMap</param>
				<!-- 是否去掉返回值为NULL的properties -->
				<param name="excludeNullProperties">true</param>
			</result>

			<!-- 普通文件下载 -->
			<result name="downloadOK" type="stream"><!-- result类型是流(stream)类型 -->
				<!-- inputName指向被下载文件的来源,对应Action中返回的InputStream -->
				<param name="inputName">inputStream</param>
				<!-- 指定文件下载的处理方式,内联(inline)和附件(attachment)两种方式,attachment会弹出文件保存对话框 -->
				<param name="contentDisposition">attachment;filename=${fileName}</param>
				<!-- 文件大小 -->
				<param name="contentLength">contentLength</param>
				<!--指定下载文件的缓冲大小 -->
				<param name="bufferSize">4096</param>
			</result>


		</action>
	</package>

</struts>

搞定~~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值