okhttp3现在基本都在用的一个底层网络框架。这篇博客主要的目的就是将OKHttp3这个框架在开发中能用到的地方都记录下来,也当一个工具文档为日后使用时查找方便。
implementation 'com.squareup.okhttp3:okhttp:3.8.0' implementation 'com.squareup.okio:okio:1.12.0' 复制代码
<uses-permission android:name="android.permission.INTERNET"/> 复制代码
完事了,接下来介绍使用
OkHttpClient mClient = new OkHttpClient.Builder() // 构建者模式,创建实例 .connectTimeout(20, TimeUnit.SECONDS) // 设置连接超时时间 .build(); Request mRequest = new Request.Builder() // 构建者模式,创建请求信息 .get() .url("https://www.baidu.com") .build(); Call call = mClient.newCall(mRequest); // 将request转换成call call.enqueue(new Callback() { // 执行call @Override public void onFailure(Call call, IOException e) { } @Override public void onResponse(Call call, Response response) throws IOException { final String strByNet = response.body().string(); // 切换到主线程 runOnUiThread(new Runnable() { @Override public void run() { tv_msg.setText(strByNet); } }); } }); 复制代码
好了,这就是get的简单使用,不过说了是工具,自然要列出扩展 OkHttpClient.Builder的扩展属性
* final Dispatcher dispatcher; //重要:分发器,分发执行和关闭由request构成的Call * final Proxy proxy; //代理 * final List<Protocol> protocols; //协议 * final List<ConnectionSpec> connectionSpecs; //传输层版本和连接协议 * final List<Interceptor> interceptors; //重要:拦截器 * final List<Interceptor> networkInterceptors; //网络拦截器 * final ProxySelector proxySelector; //代理选择 * final CookieJar cookieJar; //cookie * final Cache cache; //缓存 * final InternalCache internalCache; //内部缓存 * final SocketFactory socketFactory; //socket 工厂 * final SSLSocketFactory sslSocketFactory; //安全套接层socket 工厂,用于HTTPS * final CertificateChainCleaner certificateChainCleaner; // 验证确认响应证书 适用 HTTPS 请求连接的主机名。 * final HostnameVerifier hostnameVerifier; // 主机名字确认 * final CertificatePinner certificatePinner; // 证书链 * final Authenticator proxyAuthenticator; //代理身份验证 * final Authenticator authenticator; // 本地身份验证 * final ConnectionPool connectionPool; //连接池,复用连接 * final Dns dns; //域名 * final boolean followSslRedirects; //安全套接层重定向 * final boolean followRedirects; //本地重定向 * final boolean retryOnConnectionFailure; //重试连接失败 * final int connectTimeout; //连接超时 * final int readTimeout; //read 超时 * final int writeTimeout; //write 超时 复制代码
为了测试接下来的功能,我们自己搭建一个服务器。 先搭建环境,配置tomcat 作者的是mac所以就介绍下mac下配置tomcat的方式,Windows的小伙伴们可以参考这个( www.cnblogs.com/beginner-bo… )
打开终端输入命令,简单办法,终端输入cd,然后直接将bin文件夹直接拖拽到终端
cd /Library/Tomcat/bin
将目标文件授权,终端输入命令
chmod +x *.sh
启动tomcat
./startup.sh
浏览器中输入: http://localhost:8080/
到这里,恭喜你tomcat配置成功
服务端作者使用的是IDEA,怎么在这里面配置Tomcat,以及后面要使用的Servlet可看这个文章,会用的小伙伴可以跳过( www.cnblogs.com/wfhking/p/9… )
好了,到这里,服务端已经搭建完毕,接下来,我们书写服务端程序吧!
@WebServlet(name = "TestPost") public class TestPost extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setHeader("Access-Control-Allow-Origin", "*"); // 跨域 response.setContentType("text/html"); // 设置响应内容类型 response.setCharacterEncoding("UTF-8"); // 指定编码 // 获取前端传入的数据 BufferedReader br = request.getReader(); String line; StringBuffer mStringBuff = new StringBuffer(); while ((line = br.readLine()) != null){ mStringBuff.append(line); } //设置逻辑实现 PrintWriter out = response.getWriter(); String jsonStr = "服务器收到信息并返回:/n" + mStringBuff.toString(); out.println(jsonStr); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } } 复制代码
好了,这样就行了,接下来继续实现我们的客户端的post
private void doPost(String username, String pass, String hobby) { // 编码集 final MediaType FORM_CONTENT_TYPE = MediaType.parse("application/json; charset=utf-8"); // 接口地址 final String uri = "http://192.168.0.102:8081/TestOkhttp3_war_exploded/TestPost"; // 创建实例 OkHttpClient okhttp = new OkHttpClient.Builder() .build(); // 创建表单及数据 HashMap<String, String> map = new HashMap<>(); map.put("username", username); map.put("password", pass); map.put("hobby", hobby); String jsonStr = new Gson().toJson(map); RequestBody formBody = RequestBody.create(FORM_CONTENT_TYPE, jsonStr); // 创建请求实例 Request request = new Request.Builder() .url(uri) .post(formBody) .build(); Call call = okhttp.newCall(request); call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { Log.e("lybj", "接口调用失败"); } @Override public void onResponse(Call call, Response response) throws IOException { final String strByNet = response.body().string(); // 切换到主线程 runOnUiThread(new Runnable() { @Override public void run() { tv_msg.setText(strByNet); } }); } }); } 复制代码
当然这么写了之后,会出现一个异常
CLEARTEXT communication ** not permitted by network security policy 这是因为,Android高版本后限制了HTTP访问权限。
解决方案有2个,要么采用https,要么采用下面的方法 在res里面新建xml文件夹,创建文件network_security_config.xml
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true" />
</network-security-config>
复制代码
在application中引用它
<application ... android:networkSecurityConfig="@xml/network_security_config"> 复制代码
ok了,post可以正常使用了
在project settings -> Artifacts -> 选择自己的工程 -> 右边OutPut directory 中看到自己的输出路径,然后找到该路径,可查看到自己提交的文件及控制台输出的参数信息。
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 设置编码 request.setCharacterEncoding("UTF-8"); response.setContentType("text/html;charset=UTF-8"); PrintWriter pw = response.getWriter(); try { // 设置系统环境 DiskFileItemFactory factory = new DiskFileItemFactory(); // 文件存储的路径 String storePath = getServletContext().getRealPath("/WEB-INF/files"); if(!new File(storePath).exists()){ new File(storePath).mkdirs(); } ServletFileUpload upload = new ServletFileUpload(factory); upload.setFileSizeMax(4 * 1024 * 1024); // 设置单个文件大小不能超过4M upload.setSizeMax(4 * 1024 * 1024); // 设置总文件上传大小不能超过6M // 解析 List<FileItem> items = upload.parseRequest(request); for (FileItem item : items) { // 普通字段,表单提交过来的 if (item.isFormField()){ String name = item.getFieldName(); String value = item.getString("UTF-8"); System.out.println(name + "==" + value); } else { // 解析上传的文件 // String mimeType = item.getContentType(); 获取上传文件类型 // if(mimeType.startsWith("image")){ InputStream in = item.getInputStream(); String fileName = item.getName(); if (fileName == null || "".equals(fileName.trim())) { continue; } fileName = fileName.substring(fileName.lastIndexOf("//") + 1); fileName = UUID.randomUUID() + "_" + fileName; // 按日期来建文件夹 String storeFile = storePath + "/" + fileName; OutputStream out = new FileOutputStream(storeFile); byte[] b = new byte[1024]; int len = -1; while ((len = in.read(b)) != -1) { out.write(b, 0, len); } in.close(); out.close(); item.delete(); // 删除临时文件 } } PrintWriter out = response.getWriter(); out.println("上传成功"); } catch (org.apache.commons.fileupload.FileUploadBase.FileSizeLimitExceededException e) { pw.write("单个文件不能超过4M"); } catch (org.apache.commons.fileupload.FileUploadBase.SizeLimitExceededException e) { pw.write("总文件不能超过6M"); } catch (FileUploadException e) { e.printStackTrace(); } } 复制代码
/** * 上传文件 * */ private void doUpload(File file, String userId, String msg){ // 接口地址 OkHttpClient client = new OkHttpClient.Builder() .connectTimeout(30, TimeUnit.SECONDS) .build(); RequestBody fileRequestBody1 = RequestBody.create(MediaTypeUtils.UPLOAD_FILE.value, file); // 可传多个 MultipartBody body = new MultipartBody.Builder() .setType(MultipartBody.FORM) .addFormDataPart("userId", userId) .addFormDataPart("msg", msg) .addFormDataPart("file", "myFileName", fileRequestBody1) .build(); Request rb = new Request.Builder() .header("Authorization", "Client-ID " + UUID.randomUUID()) .url(ApiUtils.TestPostUpload) .post(body) .build(); Call call = client.newCall(rb); call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { Log.e("lybj", "接口调用失败"); } @Override public void onResponse(Call call, Response response) throws IOException { final String string = response.body().string(); runOnUiThread(new Runnable() { @Override public void run() { tv_msg.setText(string); } }); } }); } 复制代码
接口封装了下
public interface ApiUtils { String BaseUri = "http://192.168.0.102:8081/TestOkhttp3_war_exploded/"; // post提交json String TestPost = BaseUri + "TestPost"; // 上传文件 String TestPostUpload = BaseUri + "TestPostUpload"; } 复制代码
定义一个枚举类型,用于存储上传使用的信息
public enum MediaTypeUtils { JSON_UTF_8(MediaType.parse("application/json; charset=utf-8")), // 设置Json数据传输并指定Utf-8为编码集 UPLOAD_FILE(MediaType.parse("multipart/form-data")); // 上传文件 public MediaType value; MediaTypeUtils(MediaType value) { this.value = value; } } 复制代码
好了,这就可以了
简单起见,我们直接下载个网上的APK
/** * 下载文件 * */ private void doDownload(){ // 接口地址 OkHttpClient client = new OkHttpClient.Builder() .connectTimeout(60, TimeUnit.SECONDS) .build(); Request rb = new Request.Builder() .get() .url(ApiUtils.TestPostDownload) .build(); Call call = client.newCall(rb); call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { Log.e("lybj", "接口调用失败"); } @Override public void onResponse(Call call, Response response) throws IOException { writeFile(response); } }); } 复制代码
/** * 下载文件 * */ private void writeFile(Response response) { InputStream is = null; FileOutputStream fos = null; is = response.body().byteStream(); String path = Environment.getExternalStorageDirectory().getAbsolutePath(); File file = new File(path, "hehe.apk"); try { fos = new FileOutputStream(file); byte[] bytes = new byte[1024]; int len = 0; // 获取下载的文件的大小 long fileSize = response.body().contentLength(); long sum = 0; int porSize = 0; while ((len = is.read(bytes)) != -1) { fos.write(bytes); sum += len; porSize = (int) ((sum * 1.0f / fileSize) * 100); Message message = handler.obtainMessage(1); message.arg1 = porSize; handler.sendMessage(message); } } catch (Exception e) { e.printStackTrace(); } finally { try { if (is != null) { is.close(); } if (fos != null) { fos.close(); } } catch (IOException e) { e.printStackTrace(); } } Log.i("myTag", "下载成功"); } 复制代码
Handler handler = new Handler() { @Override public void handleMessage(Message msg) { if (msg.what == 1) { pro.setProgress(msg.arg1); } } }; 复制代码
好了,到了这里,下载也OK了,上传和下载之前,别忘记动态申请Android权限哈。
为了优化(扯淡的故事)接口要求压缩上传的数据,好了直接贴代码
build.gradle
implementation 'com.zhouyou:rxeasyhttp:2.1.2' 复制代码
OkHttpClient client = new OkHttpClient.Builder() .connectTimeout(60, TimeUnit.SECONDS) .addInterceptor(new GzipRequestInterceptor()) .build(); 复制代码
当然,需要服务器支持,OK,完事了