注意:在配置完以下依赖,请求时失败请配置标红代码
android:usesCleartextTraffic="true"
配置:
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:usesCleartextTraffic="true"
android:theme="@style/AppTheme">
***
</application>
一,简介
OkHttp 是一个高效的 HTTP 客户端,具有非常多的优势:
- 能够高效的执行 http,数据加载速度更快,更省流量
- 支持 GZIP 压缩,提升速度,节省流量
- 缓存响应数据,避免了重复的网络请求
- 使用简单,支持同步阻塞调用和带回调的异步调用
OkHttp 支持 Android2.3 以上,JDK1.7 以上。
官网地址:Overview - OkHttp
github地址:GitHub - square/okhttp: Square’s meticulous HTTP client for the JVM, Android, and GraalVM.
二,基本用法
添加 OkHttp3
依赖:
implementation 'com.squareup.okhttp3:okhttp:3.10.0'
添加json字符串转换成实体类依赖:
implementation 'com.google.code.gson:gson:2.8.5'
最新的版本号可以在官网和github上找到
创建OkHttpClient实例
//方式一:创建OkHttpClient实例,使用默认构造函数,创建默认配置OkHttpClient(官方建议全局只有一个实例)
OkHttpClient okHttpClient = new OkHttpClient();
//方式二:通过new OkHttpClient.Builder() 一步步配置一个OkHttpClient实例
OkHttpClient httpClient = new OkHttpClient.Builder().connectTimeout(13, TimeUnit.SECONDS).build();
//方式三:如果要求使用现有的实例,可以通过newBuilder().build()方法进行构造
OkHttpClient client = okHttpClient.newBuilder().build();
1. get 请求
使用 OkHttp 进行 get 请求只需要四个步骤
- 新建 OkHttpClient对象
- 构造 Request 对象
- 将 Request 对象封装为 Call
- 通过 Call 来执行同步或异步请求
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url(HttpTools.getAllUserUrl)
.get() //默认为GET请求,可以不写
.build();
Call call = client.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call,IOException e) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(), "请求失败!", Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onResponse(Call call,Response response) throws IOException {
final String str = response.body().string();
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(), str, Toast.LENGTH_SHORT).show();
}
});
}
});
后台代码:
// 查询用户全部数据
@RequestMapping("/showInfo")
@ResponseBody
public String getUsers(ModelMap map){
map.addAttribute("users",serviceImpl.queryAll());
System.out.println(map.get("users"));
return "userInfo";
}
1.1 get 同步请求
通过 call.excute() 方法来提交同步请求,这种方式会阻塞线程,而为了避免 ANR 异常,Android3.0 之后已经不允许在主线程中访问网络了
所以 OkHttp 的同步 get 请求需要开启一个子线程:
new Thread(new Runnable() {
@Override
public void run() {
try {
// 同步请求
Response response = call.execute();
Log.d(TAG, response.body().toString());
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
1.2 get 异步请求
通过 call.enqueue(Callback)方法来提交异步请求
// 异步请求
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.d(TAG, "onFailure: " + e);
}
@Override
public void onResponse(Call call, Response response) throws IOException {
Log.d(TAG, "OnResponse: " + response.body().toString());
}
});
2. post 请求
post同步、异步请求和get的同步异步请求方式一样(execute(同步) enqueue(异步)
2.1 FormBody表单形式请求
方式一:
OkHttpClient client = new OkHttpClient();
//创建表单请求参数
FormBody.Builder builder = new FormBody.Builder();
builder.add("username", userNameET.getText().toString());
builder.add("password", passwordET.getText().toString());
FormBody formBody = builder.build();
Request request = new Request.Builder()
.url(HttpTools.loginUrl)
.post(formBody)
.build();
Call call = client.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call,IOException e) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(), "登录失败!", Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onResponse(Call call, Response response) throws IOException {
final String str = response.body().string();
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(),str,Toast.LENGTH_SHORT).show();
}
});
}
});
后台代码:
/**
* 用户登录 --- 传递参数
* */
@RequestMapping(value="/login", method=RequestMethod.POST)
@ResponseBody// 将java对象转为json格式的数据。
public String loginUser(@RequestParam("username")String username,
@RequestParam("password")String password) {
// 设置值
User user = new User();
user.setUsername(username);
user.setPassword(password);
int flag = serviceImpl.loginVerification(user);
if (flag <= 0) {
System.out.println("失败");
return "error";
}
System.out.println("成功!");
return "success";
}
方式二 :
OkHttpClient client = new OkHttpClient();
//创建表单请求参数
FormBody.Builder builder = new FormBody.Builder();
builder.add("name", regName.getText().toString());
builder.add("sex",regSex.getText().toString());
builder.add("address", regAddress.getText().toString());
builder.add("username", userName.getText().toString());
builder.add("password", password.getText().toString());
FormBody formBody = builder.build();
Request request = new Request.Builder()
.url(HttpTools.registerUrl)
.post(formBody)
.build();
Call call = client.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call,IOException e) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(), "注册失败!", Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onResponse(Call call, Response response) throws IOException {
final String str = response.body().string();
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(),str,Toast.LENGTH_SHORT).show();
finish();
}
});
}
});
后台代码:
/**
* 用户注册 --- 传递对象
* */
@RequestMapping(value="/register", method=RequestMethod.POST)
@ResponseBody
public String registerUser(User user) {
serviceImpl.registerUser(user);
return "success";
}
方式三:
String webUrl = "http://47.112.180.188:8080/renren-fast/app/login";
// 创建OKHttpClient
OkHttpClient client = new OkHttpClient.Builder().build();
// 创建参数
Map m = new HashMap();
m.put("mobile",account);
m.put("password",pwd);
// 参数转换成json字符串
JSONObject jsonObject = new JSONObject(m);
String jsonStr = jsonObject.toString();
RequestBody requestBodyJson = RequestBody.create(MediaType.parse("application/json;charset=utf-8"),
jsonStr);
// 创建Request
Request request = new Request.Builder()
.url(webUrl)
.addHeader("contentType","application/json;charset=utf-8")
.post(requestBodyJson)
.build();
// 创建Call
Call call = client.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call,IOException e) {
Log.e("onFailure",e.getMessage());
}
@Override
public void onResponse(Call call,Response response) throws IOException {
String result = response.body().string();
System.out.println(result);
}
});
2.2 解释一下
Content-Type(MediaType),即是Internet Media Type,互联网媒体类型;也叫做MIME类型,在Http协议消息头中,使用Content-Type来表示具体请求中的媒体类型信息。用于定义网络文件的类型和网页的编码,决定文件接收方将以什么形式、什么编码读取这个文件。常见的媒体格式类型有:
- text/html:HTML格式
- text/pain:纯文本格式
- image/jpeg:jpg图片格式
- application/json:JSON数据格式
- application/octet-stream:二进制流数据(如常见的文件下载)
- application/x-www-form-urlencoded:form表单encType属性的默认格式,表单数据将以key/value的形式发送到服务端
- multipart/form-data:表单上传文件的格式
使用 create 方法可以用来用于上传 String 和 File 对象,具体实现如下:
上传JSON字符串:
OkHttpClient client = new OkHttpClient();
//指定当前请求的 contentType 为 json 数据
MediaType JSON = MediaType.parse("application/json; charset=utf-8");
String jsonStr = "{\"name\":\"zhangsan\",\"age\":\"20\"}";
Request request = new Request.Builder()
.url(url)
.post(RequestBody.create(JSON, jsonStr))
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
}
});
上传文件:
OkHttpClient client = new OkHttpClient();
File file = new File(filePath);
Request request = new Request.Builder()
.url(url)
.post(RequestBody.create(MediaType.parse("application/octet-stream"), file))
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
}
});
2.3 使用MultipartBody同时上传多种类型数据
多文件和键值对同时上传
File file = new File(filePath);
MediaType mediaType = MediaType.parse("application/octet-stream;charset=utf-8");
MultipartBody.Builder multipartBodyBuilder = new MultipartBody.Builder();
multipartBodyBuilder.setType(MultipartBody.FORM);
// 传递参数
multipartBodyBuilder.addFormDataPart("file",file.getName(),RequestBody.create(mediaType,file));
multipartBodyBuilder.addFormDataPart("name","张三");
multipartBodyBuilder.addFormDataPart("age","23");
//构建请求体
RequestBody requestBody = multipartBodyBuilder.build();
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url(HttpTools.upLoadFile)
.post(requestBody)
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(), "上传失败!", Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onResponse(Call call, Response response) throws IOException {
final String str = response.body().string();
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(), str, Toast.LENGTH_SHORT).show();
}
});
}
});
后台代码
@RequestMapping(value="/upload",method=RequestMethod.POST)
@ResponseBody
public String upLoadFile(@RequestParam("file") MultipartFile upFile,
@RequestParam("name") String name,
@RequestParam("age") String age,
HttpServletRequest request,
HttpServletResponse response) {
System.out.println(upFile.getName());
System.out.println(name);
System.out.println(age);
// 判断文件手否有内容
if (!upFile.isEmpty()) {
// 先设定一个放置上传文件的文件夹(该文件夹可以不存在,下面会判断创建)
String deposeFilesDir = "C:\\Users\\admin\\Desktop\\file\\";
// 获取附件原名(有的浏览器如chrome获取到的是最基本的含 后缀的文件名,如myImage.png)
// 获取附件原名(有的浏览器如ie获取到的是含整个路径的含后缀的文件名,如C:\\Users\\images\\myImage.png)
String fileName = upFile.getOriginalFilename();
// 如果是获取的含有路径的文件名,那么截取掉多余的,只剩下文件名和后缀名
int index = fileName.lastIndexOf("\\");
if (index > 0) {
fileName = fileName.substring(index + 1);
}
// 判断单个文件大于1M
long fileSize = upFile.getSize();
if (fileSize > 1024 * 1024) {
System.out.println("文件大小为(单位字节):" + fileSize);
System.out.println("该文件大于1M");
}
// 当文件有后缀名时
if (fileName.indexOf(".") >= 0) {
// split()中放正则表达式; 转义字符"\\."代表 "."
String[] fileNameSplitArray = fileName.split("\\.");
// 加上random戳,防止附件重名覆盖原文件
fileName = fileNameSplitArray[0] + (int) (Math.random() * 100000) + "." + fileNameSplitArray[1];
}
// 当文件无后缀名时(如C盘下的hosts文件就没有后缀名)
if (fileName.indexOf(".") < 0) {
// 加上random戳,防止附件重名覆盖原文件
fileName = fileName + (int) (Math.random() * 100000);
}
System.out.println("fileName:" + fileName);
// 根据文件的全路径名字(含路径、后缀),new一个File对象dest
File dest = new File(deposeFilesDir + fileName);
// 如果该文件的上级文件夹不存在,则创建该文件的上级文件夹及其祖辈级文件夹;
if (!dest.getParentFile().exists()) {
dest.getParentFile().mkdirs();
}
try {
// 将获取到的附件file,transferTo写入到指定的位置(即:创建dest时,指定的路径)
upFile.transferTo(dest);
} catch (IllegalStateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("文件的全路径名字(含路径、后缀)>>>>>>>" + deposeFilesDir + fileName);
return "success";
}
return "error";
}
下载文件
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url(HttpTools.downloadFile+"?fileName=test.jpg")
.get()
.build();
Call call = client.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call,IOException e) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(), "下载失败!", Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onResponse(Call call, Response response) throws IOException {
InputStream ins = response.body().byteStream();
Bitmap bitmap = BitmapFactory.decodeStream(ins);
File galleyFile = new File(getExternalFilesDir("图片下载").getPath());
File temp = new File(galleyFile.getPath());//要保存文件先创建文件夹
if (!temp.exists()) {
temp.mkdir();
}
// 图片名称
String imgName = new Date().getTime() + ".jpg";
//将要保存图片的路径和图片名称
File file=new File(galleyFile.getPath()+File.separator+imgName);
try {
BufferedOutputStream bos= new BufferedOutputStream(new FileOutputStream(file));
// 压缩图片
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, bos);
int len = 0;
byte[] buffer = new byte[1024];
while ((len = ins.read(buffer,0,buffer.length))!=-1){
bos.write(buffer,0,buffer.length);
bos.flush();
}
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
});
后台代码
/**
* 下载文件(下载到客户端)
*/
@RequestMapping(value = "/download",method = RequestMethod.GET)
@ResponseBody
public void getFile(@RequestParam("fileName") String fileName,
HttpServletRequest request,
HttpServletResponse response) {
//String fileName = "test.jpg";
// 文件名转码,避免中文乱码
String codeFileName = null;
try {
codeFileName = URLEncoder.encode(fileName,"UTF-8");
}
catch (UnsupportedEncodingException e1) {
// TODO Auto-generated catch block e1.printStackTrace(); }
}
/*
*
* tomcat_path -- E:/apache-tomcat-8/webapps/SSM/file
*
*String tomcat_path = request.getSession().getServletContext().getRealPath("file");
*
*/
// 文件路径(fileName 传递过来的文件名)
String downloadFilePath = "C:\\Users\\admin\\Desktop\\file\\"+codeFileName;
File imageFile = new File(downloadFilePath);
if (imageFile.exists()) {
FileInputStream fis = null;
OutputStream os = null;
try {
fis = new FileInputStream(imageFile);
os = response.getOutputStream();
int count = 0;
byte[] buffer = new byte[1024 * 8];
while ((count = fis.read(buffer,0,buffer.length)) != -1) {
os.write(buffer, 0, count);
os.flush();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
fis.close();
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
2.4 使用post请求上传json类型数据
在工程中导入:
dependencies {
// 阿里巴巴fastJson
//implementation 'com.alibaba:fastjson:1.2.49'
implementation ‘com.alibaba:fastjson:1.1.54.android’
}
try {
// 创建json对象
JSONObject jsonObject = new JSONObject();
// 创建数组
ArrayList<String> arrayList = new ArrayList();
HashMap<String,String> map = new HashMap<>();
for (int i=0; i < 5; i++ ){
map.put(String.valueOf(i), "数据"+i);
}
arrayList.add(com.alibaba.fastjson.JSONObject.toJSONString(map));
// 数组参数
jsonObject.put("arrList",arrayList);
// 字符串参数
jsonObject.put("appId", "appId");
jsonObject.put("token", "token");
jsonObject.put("clientId", "clientId");
String data = jsonObject.toString();
// 构造请求体
RequestBody body = RequestBody.create(okhttp3.MediaType.parse("application/json;charset=UTF-8"), data);
Request request = new Request.Builder()
.url(url)
.post(body)
.build();
// 向服务器异步请求数据
OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.newCall(request).enqueue(new okhttp3.Callback() {
@Override
public void onFailure(okhttp3.Call call, IOException e) {
Toast.makeText(getApplicationContext(),"失败", Toast.LENGTH_SHORT).show();
//LogUtils.i(TAG, "失败");
}
@Override
public void onResponse(okhttp3.Call call, okhttp3.Response response) throws IOException {
ResponseBody body = response.body();
Toast.makeText(getApplicationContext(),body.toString(), Toast.LENGTH_SHORT).show();
//LogUtils.i(TAG, "返回数据:" + body.string());
}
});
} catch (JSONException e) {
e.printStackTrace();
}
后台代码:
// 参数为json,参数前用注解@RequestBody,返回值为json,在方法上用注解@RequestBody
@RequestMapping(value="/json_method", method=RequestMethod.POST)
public String jsonMethod(@RequestBody String jsonStr){
System.out.println("jsonStr:"+jsonStr);
return "success"// 跳转的页面名称
}
3. Call 请求器
OkHttp 客户端负责接收应用程序发出的请求,并且从服务器获取响应返回给应用程序。理论听起来十分简单,但是在实践中往往会出现很多意想不到的问题。
通过配置 OkHttpClient,可以配置重写请求、重写响应、跟踪请求、重试请求等多种操作,这样一来你发送的一个简单请求可能就会变成需要发送多个请求以及接收多个响应后才能获得想要的响应。OkHttp 将这些多次的中间请求和响应任务建模成了一个 Call 对象,但是通常情况下中间请求及响应工作不会很多,令人欣慰的是,无论发生URL重定向还是因为服务器出现问题而向一个备用IP地址再次发送请求的情况,你的代码都将正常运行。
执行Call有两种方式:
- 同步:请求和处理响应发生在同一线程。并且此线程会在响应返回之前会一直被堵塞。
- 异步:请求和处理响应发生在不同线程。将发送请求操作发生在一个线程,并且通过回调的方式在其他线程进行处理响应。(一般在子线程发送请求,主线程处理响应)。
Calls可以在任何线程被取消。当这个Call尚未执行结束时,执行取消操作将会直接导致此Call失败!当一个Call被取消时,无论是写入请求主体或者读取响应主体的代码操作,都会抛出一个IOException异常。
4.Cookie存储/获取以及使用
cookie存/取工具类:
package com.chy.https;
import android.content.Context;
import android.content.SharedPreferences;
import java.util.List;
import okhttp3.Cookie;
/**
* OKHttp3 cookies存/取类
* */
public class OkHttp3Cookie {
private static final String COOKIE_FLAG = "COOKIE_FLAG";
private static final String COOKIE_VAL = "COOKIE_VAL";
/**
* 存储cookie
* */
public static void saveCookies(Context context,List<Cookie> cookies){
if (cookies.isEmpty())
return;
Cookie cookie = cookies.get(0);
String name = cookie.name();
String value = cookie.value();
SharedPreferences cookie_sp = context.getSharedPreferences(COOKIE_FLAG,Context.MODE_PRIVATE);
SharedPreferences.Editor edit = cookie_sp.edit();
edit.putString(COOKIE_VAL,name+"="+value);
edit.apply();
}
/**
* 获取cookie
* */
public static String getCookies(Context context){
SharedPreferences cookie_sp = context.getSharedPreferences(COOKIE_FLAG,Context.MODE_PRIVATE);
String cookieVal = cookie_sp.getString(COOKIE_VAL, null);
return cookieVal;
}
}
使用示例:
存储
// 获取cookies
Headers headers = response.headers();
List<Cookie> cookies = Cookie.parseAll(request.url(), headers);
// 存储cookies
OkHttp3Cookie.saveCookies(context,cookies);
使用
// 获取cookie
String cookieVal = OkHttp3Cookie.getCookies(context);
// 创建Request
Request request = new Request.Builder()
.url(dataUrl)
.addHeader("Cookie",cookieVal)
.get()
.build();