OkHttp 简介
HTTP 是现代应用程序网络的方式。这就是我们交换数据和媒体的方式。有效地执行 HTTP 可以使您的内容加载更快并节省带宽。
OkHttp 是一个默认高效的 HTTP 客户端:
- HTTP/2 支持允许对同一主机的所有请求共享一个套接字。
- 连接池减少了请求延迟(如果 HTTP/2 不可用)。
- 透明 GZIP 缩小了下载大小。
- 响应缓存完全避免了网络重复请求。
当网络出现问题时,OkHttp 坚持不懈:它会默默地从常见的连接问题中恢复。如果您的服务有多个 IP 地址,如果第一次连接失败,OkHttp 将尝试备用地址。这对于 IPv4+IPv6 和冗余数据中心中托管的服务是必要的。OkHttp 支持现代 TLS 功能(TLS 1.3、ALPN、证书固定)。它可以配置为回退以实现广泛的连接。
使用 OkHttp 很容易。它的请求/响应 API 设计有流畅的构建器和不变性。它支持同步阻塞调用和带有回调的异步调用。
应用场景
- 请求内容数据(目前来说,数据格式主要是 json,xml 比较少用了)
- 加载图片(一般来说,图片地址以内容的形式返回到手机端,然后再通过图片地址进行加载到控件中)
- 上传文件/数据(头像的上传,采集数据的上传,甚至是录像之类的大文件上传)
使用框架
Android 10 Http 访问配置(https 协议略过)
Android27 以上,默认是不支持 Http 访问的了,需要使用 https,如果你要使用 Http 明文访问,那么需要配置一下。
在清单文件,application 节点,添加
1
| android:networkSecurityConfig="@xml/network_security_config"
|
添加 network_security_config.xml 文件,内容如下:
1 2 3 4 5 6 7 8 9 10
| <?xml version="1.0" encoding="utf-8"?> <network-security-config> <domain-config> <domain includeSubdomains="true">sunofbeaches.com</domain> <domain-config cleartextTrafficPermitted="true"> <domain includeSubdomains="true">www.sunofbeach.net</domain> <domain includeSubdomains="true">imgs.sunofbeaches.com</domain> </domain-config> </domain-config> </network-security-config>
|
关于网络安全配置可参考谷歌官方文档:https://developer.android.google.cn/training/articles/security-config#manifest
声明权限
1
| <uses-permission android:name="android.permission.INTERNET" />
|
添加依赖
1
| implementation("com.squareup.okhttp3:okhttp:4.2.2")
|
异步 GET 请求
步骤:
- 创建 OkHttpClient
- 创建请求内容
- 浏览器根据请求内容创建请求任务
- 执行请求任务
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public void asyncGet(View view) { String url = "https://xxxx.com/api/json"; OkHttpClient okHttpClient = new OkHttpClient(); Request request = new Request.Builder() .url(url) .get() .build(); Call call = okHttpClient.newCall(request); call.enqueue(new Callback() { @Override public void onFailure(@NotNull Call call,@NotNull IOException e) { Log.d(TAG,"onFailure -- > " + e.toString()); }
@Override public void onResponse(@NotNull Call call,@NotNull Response response) throws IOException { Log.d(TAG,"response -- > " + response.body().string()); } }); }
|
运行返回结果:
同步 GET 请求
同步请求需自行处理线程的问题,不可以在 UI 主线程中去执行任务。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| public void syncGet(View view) { //获取商城的分类信息 String url = "https://xxxx.com/api/json"; //1、创建client,理解为创建浏览器 OkHttpClient okHttpClient = new OkHttpClient(); //2、创建请求内容 Request request = new Request.Builder() .url(url) .get() .build(); //3、用浏览器创建调用任务 final Call call = okHttpClient.newCall(request); //4、执行任务 new Thread(new Runnable() { @Override public void run() { try { Response response = call.execute(); Log.d(TAG,"response -- > " + response.body().string()); } catch(IOException e) { e.printStackTrace(); Log.d(TAG,"failure -- > " + e.toString()); } } }).start(); }
|
POST 请求
与 GET 请求基本相似,下面以搜索接口为例
接口地址:https://xxxx.com/api/search
参数:keysword -搜索的关键词
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public void doSearch(View view) { String url = "https://xxxx.com/api/search"; RequestBody requestBody = new FormBody.Builder() .add("keyword","电脑") .build(); OkHttpClient client = new OkHttpClient(); Request request = new Request.Builder() .url(url) .post(requestBody) .build(); Call call = client.newCall(request); call.enqueue(new Callback() { @Override public void onFailure(@NotNull Call call,@NotNull IOException e) { Log.d(TAG,"onFailure -- > " + e.toString()); }
@Override public void onResponse(@NotNull Call call,@NotNull Response response) throws IOException { Log.d(TAG,"response json --> " + response.body().string()); } }); }
|
可以从代码中看出,Formbody 是 RequestBody 的子类,RequestBody 下有两个子类:FormBody、MultipartBody,所以除了提交表单还可以提交文件。
POST 上传单文件
单文件上传的使用场景一般有:上传头像或者上传日志等
操作文件需先声明权限,因为读取的数据一般是在拓展卡里面,所以一般都需要此权限。
1
| <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public void postFile(View view) { String url = "https://127.0.0.1:8080/file/upload"; OkHttpClient httpClient = new OkHttpClient.Builder().build(); File file = new File("/storage/emulated/0/Download/1.jpg"); MediaType mediaType = MediaType.parse("jpeg"); RequestBody fileBody = RequestBody.create(file,mediaType); RequestBody requestBody = new MultipartBody.Builder() .addFormDataPart("file",file.getName(),fileBody) .build(); Request request = new Request.Builder().url(url).post(requestBody).build(); Call call = httpClient.newCall(request); call.enqueue(new Callback() { @Override public void onFailure(@NotNull Call call,@NotNull IOException e) { Log.d(TAG,"上传失败--> " + e.toString()); }
@Override public void onResponse(@NotNull Call call,@NotNull Response response) throws IOException { Log.d(TAG,"上传结果:" + response.body().string()); } }); }
|
返回结果:
1
| {"success":true,"code":10000,"message":"上传成功.文件路径为:E:\\service\\Upload\\1.jpg","data":null}
|
上传的文件类型可参考这个表:https://blog.csdn.net/qyt0147/article/details/80610481
POST 上传多文件
多文件上传需要根据接口程序所提供的参数来。比如接口地址为:http://127.0.0.1:8080/api/files/uploads,参数key为:files,所以多文件上传参数要求必须为files,实现代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| public void postMultiFile(View view) { String url = "http://127.0.0.1:8080/api/files/upload"; OkHttpClient httpClient = new OkHttpClient.Builder().build(); File fileOne = new File("/storage/emulated/0/Download/1.jpg"); File fileTwo = new File("/storage/emulated/0/Download/rBsADV3nxtKACoSfAAAPx8jyjF8169.png"); File fileThree = new File("/storage/emulated/0/Download/rBsADV2rEz-AIzSoAABi-6nfiqs456.png"); MediaType mediaType = MediaType.parse("jpeg"); RequestBody fileOneBody = RequestBody.create(fileOne,mediaType); RequestBody fileTwoBody = RequestBody.create(fileTwo,mediaType); RequestBody fileThreeBody = RequestBody.create(fileThree,mediaType); RequestBody requestBody = new MultipartBody.Builder() .addFormDataPart("files",fileOne.getName(),fileOneBody) .addFormDataPart("files",fileTwo.getName(),fileTwoBody) .addFormDataPart("files",fileThree.getName(),fileThreeBody) .build(); Request request = new Request.Builder().url(url).post(requestBody).build(); Call call = httpClient.newCall(request); call.enqueue(new Callback() { @Override public void onFailure(@NotNull Call call,@NotNull IOException e) { Log.d(TAG,"多文件上传失败--> " + e.toString()); }
@Override public void onResponse(@NotNull Call call,@NotNull Response response) throws IOException { Log.d(TAG,"多文件上传结果:" + response.body().string()); } }); }
|
返回结果:
1
| {"success":true,"code":10000,"message":"上传成功3个文件,路径:E:/service/Upload","data":null}
|
文件下载
文件下载也是提供代码实现对网络文件的请求,文件内容在响应头中,只需把它写入文件里面即可,具体代码则不再演示。
关于 Android 权限问题
Android 6 以下的权限
Android6.0 以下的权限为安装时权限,如果一个应用跑在 android6.0 以下的系统,那么应用所声明的权限,会在安装的时候提示用户是否允许。还有一种 case 是升级,升级应用的时候,如果有新的权限,那么也会提示用户是否允许,不过呢,基本上很少人使用 android6.0 以下的系统了,所以,我们关注点还是在 6.0 以上的运行时权限获取。
Android 6 以上的权限
权限的声明
在配置文件中解析声明即可,如下图所示:
权限检查
危险权限如下图所示:
检查权限代码:
1 2 3 4 5
| int readPermission = checkSelfPermission(Manifest.permission.READ_CALENDAR); int writePermission = checkSelfPermission(Manifest.permission.WRITE_CALENDAR); if(readPermission != PackageManager.PERMISSION_GRANTED || writePermission != PackageManager.PERMISSION_GRANTED) { }
|
请求权限
1 2 3 4 5 6
| int readPermission = checkSelfPermission(Manifest.permission.READ_CALENDAR); int writePermission = checkSelfPermission(Manifest.permission.WRITE_CALENDAR); if(readPermission != PackageManager.PERMISSION_GRANTED || writePermission != PackageManager.PERMISSION_GRANTED) { requestPermissions(new String[]{Manifest.permission.READ_CALENDAR,Manifest.permission.WRITE_CALENDAR},PERMISSION_REQUEST_CODE); }
|
权限请求的结果处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @Override public void onRequestPermissionsResult(int requestCode,@NonNull String[] permissions,@NonNull int[] grantResults) { if(requestCode == PERMISSION_REQUEST_CODE) { if(grantResults.length == 2 && grantResults[0] == PackageManager.PERMISSION_GRANTED && grantResults[1] == PackageManager.PERMISSION_GRANTED) { Log.d(TAG,"has permissions.."); } else { Log.d(TAG,"no permissionS..."); finish(); } } }
|
重写方法:onRequestPermissionsResult()
以上代码中的结果,是没有做处理的。这里面有一个特殊的 case,就是用户在权限请求时,拒绝了,再次提示时,会有一个勾选框:不再询问。如果用户勾选了不再询问,那么你再次请求同一个权限时,安卓系统不再给出权限请求的提示,用户只可以到设置里开启权限。
如果,在 onRequestPermissionsResult 方法中,检查到的结果是无权限,那么,还要判断用户是不是勾选了不再询问,如果是的话,那么用户看不到任何提示。那么可以能通过以下这个方法进行检查:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| @Override public void onRequestPermissionsResult(int requestCode,@NonNull String[] permissions,@NonNull int[] grantResults) { if(requestCode == PERMISSION_REQUEST_CODE) { if(grantResults.length == 2 && grantResults[0] == PackageManager.PERMISSION_GRANTED && grantResults[1] == PackageManager.PERMISSION_GRANTED) { Log.d(TAG,"has permissions.."); } else { Log.d(TAG,"no permissionS..."); if(!ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_CALENDAR)&&!ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.READ_CALENDAR)) { Log.d(TAG,"用户之前勾选了不再询问..."); } else { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_CALENDAR,Manifest.permission.READ_CALENDAR}, PERMISSION_REQUEST_CODE); Log.d(TAG,"请求权限..."); } } } }
|
End