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"/>
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();
}
}
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();
}
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;
}
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());
}
});
}
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());
}
});
}
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());
}
});
}
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());
}
});
}
首先得拿到服务器提供的证书,关于如何创建签名证书可以看这篇文章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栏目中的文章。
public class User {
private String username;
private int age;
private List<String> hobby;
private String image;
private List<String> images;
//get set 方法
}
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中的contentLength
* @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中的中的filename=${fileName}
* @return
*/
public String getFileName() {
System.out.println("===getFileName==="+fileName);
return fileName;
}
/**
* 返回InputStream,对应struts.mlx中的inputStream
*
* @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;
}
}
处理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();
}
}
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>
<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" />
<result type="json">
<param name="root">dataMapparam>
<param name="excludeNullProperties">trueparam>
result>
<result name="downloadOK" type="stream">
<param name="inputName">inputStreamparam>
<param name="contentDisposition">attachment;filename=${fileName}param>
<param name="contentLength">contentLengthparam>
<param name="bufferSize">4096param>
result>
action>
package>
struts>
搞定~~