1.OkHttp3简介:
①支持http和https协议,api相同,易用(s:ssl安全套接字,易于做适配)
②http使用线程池,https使用多路复用
③okhttp支持同步和异步调用
④支持普通form和文件上传、下载form
⑤操作请求和响应(日志,请求头,body等)
⑥okhttp可以设置缓存
⑦支持透明的gzip压缩响应体
2.OkHttp3配置
①依赖:
implementation 'com.squareup.okhttp3:okhttp:3.12.0'
②添加网络权限:<uses-permission android:name="android.permission.INTERNET"/>
3.OkHttp3使用思路
get请求思路
①获取okHttpClient对象
②构建Request对象
③构建Call对象
④通过Call.enqueue(callback)方法来提交异步请求,execute()方法实现同步请求
⑴get异步请求
String URL="http://www.qubaobei.com/ios/cf/dish_list.php?stage_id=1&limit=20&page=1";
//获取okHttpClient对象(构建者模式/建造者模式)
OkHttpClient okHttpClient = new OkHttpClient();
//构建Request对象
final Request request = new Request.Builder()
.get()
.url(Constants.URL)
.build();
//构建Call对象
Call call = build.newCall(request);
//通过Call#enqueue(Callback)方法来提交异步请求;
//里面帮我们切换过子线程了
call.enqueue(new Callback() {
//请求失败的回调
@Override
public void onFailure(Call call, IOException e) {
Log.e(TAG, "onFailure: " + e.toString());
}
//请求成功的回调方法
//response 响应对象,里面有我们返回来的数据
//我们想要的东西大部分都在response.body里面
// response.body().string() 已经将流转换成了字符串
@Override
public void onResponse(Call call, Response response) throws IOException {
Log.e(TAG, "onResponse: " + response.body().string());
}
});
⑵get同步请求
......//前三步一样
//通过Call.execute()方法实现同步请求(获取网络数据是耗时操作,在子线程中执行)
new Thread(new Runnable() {
@Override
public void run() {
//同步的方法:阻塞当前线程,所以需要自己去切换线程
//如果在主线程中进行,会报错,NetworkOnMainThreadException
//在主线程中进行网络请求异常
//主线程是ui线程,不允许阻塞
try {
Response execute = call.execute();
Log.e(TAG, "run: " + execute.body().string());
} catch (IOException e) {
e.printStackTrace();
Log.e(TAG, "run: " + e.toString());
}
}
}).start();
post请求思路
①获取okHttpClient对象
②构建RequestBody
③构建Request对象
④构建Call对象
⑤通过Call.enqueue(callback)方法来提交异步请求,execute()方法实现同步请求
⑴post异步请求
String URL_POST="http://www.qubaobei.com/ios/cf/dish_list.php?stage_id=1&limit=20&";
//获取okHttpClient对象
OkHttpClient build = new OkHttpClient.Builder().build();
//媒体的类型
MediaType parse = MediaType.parse("text/x-markdown; charset=utf-8");
//请求的body,请求体
RequestBody body = RequestBody.create(parse, "我是一个宝宝");
Request request = new Request.Builder().post(body).url(post_url).build();
Call call = build.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.e(TAG, "onFailure: " + e.toString());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
Log.e(TAG, "onResponse: " + response.body().string());
}
});
⑵post同步请求
//通过Call.execute()方法实现同步请求
new Thread(new Runnable() {
@Override
public void run() {
try {
Response execute = call.execute();
Log.e(TAG, "run: " + execute.body().string());
//java.lang.IllegalStateException: closed
//response.body().string()只能使用一次 ??
//因为使用1次过后,流就关闭了
//Log.e(TAG, "run: "+string);
} catch (IOException e) {
e.printStackTrace();
Log.e(TAG, "Exception: " + e.toString());
}
}
}).start();
⑶提交form表单
//onFailure: java.net.UnknownServiceException:
// CLEARTEXT communication to yun918.cn not permitted by network security policy
//android 9.0新特性:不支持http请求,只支持https请求
//怎么就让9.0的手机支持http请求?? 在清单文件中配置就可以了
//client.newCall(request).enqueue();
OkHttpClient build = new OkHttpClient.Builder().build();
//FormBody 是RequestBody的子类
RequestBody body = new FormBody.Builder()
.addEncoded("username", "pig")//添加的是form表单的键值对
.addEncoded("password", "123")
.addEncoded("phone", "18834071256")
.addEncoded("verify", "cnmb")
.build();
Request request = new Request.Builder().post(body).url(post_form).build();
build.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.e(TAG, "onFailure: " + e.toString());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
Log.e(TAG, "onResponse: " + response.body().string());
}
});
⑷post分块请求上传图片
//client.newCall(request).enqueue();
OkHttpClient build = new OkHttpClient.Builder().build();
//MultipartBody 是requestbody的子类
//媒体的类型,application/octet-stream 以二进制形式提交文件
MediaType type = MediaType.parse("application/octet-stream");
//sd卡的图片
File file = new File("/storage/emulated/legacy/Pictures/shous.jpg");
//文件存在
if (file.exists()){
RequestBody requestBody = RequestBody.create(type, file);
//可以提交多个请求体
MultipartBody body = new MultipartBody.Builder()
.setType(MultipartBody.FORM)//key 和file这两个键是固定的
.addFormDataPart("key","aaa")//添加第一个请求体,提交上去的图片在哪个目录下存储
.addFormDataPart("file","hehe.jpg",requestBody)//添加第二个请求体,需要上传的文件
.build();
Request request = new Request.Builder().post(body).url(post_img).build();
build.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.e(TAG, "onFailure: "+e.toString());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
Log.e(TAG, "onResponse: "+response.body().string());
}
});
}
⑸post提交流
OkHttpClient build = new OkHttpClient.Builder().build();
//以流的形式提交参数
RequestBody body = new RequestBody() {
@Override
public MediaType contentType() {
//媒体的类型
return MediaType.parse("text/x-markdown; charset=utf-8");
}
/**
* 以流的形式去写
* @param sink 输出流
* @throws IOException
*/
@Override
public void writeTo(BufferedSink sink) throws IOException {
sink.write("This is a difficult problem".getBytes());
}
};
Request request = new Request.Builder().post(body).url(post_url).build();
build.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.e(TAG, "onFailure: "+e.toString());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
Log.e(TAG, "onResponse: "+response.body().string());
}
});
❤总结:
1.让 OkHttpClient 保持单例,用同一个 OkHttpClient 实例来执行所有请求,因为每一个 OkHttpClient 实例都拥有自己的连接池和线程池,重用这些资源可以减少延时和节省资源,如果为每个请求创建一个 OkHttpClient 实例,显然就是一种资源的浪费。
2.每一个Call(其实现是RealCall)只能执行一次,否则会报异常。
3.response.body().string()只调用一次
4.子线程加载数据后,主线程刷新数据
4.下拉刷新上拉加载
①导依赖(
implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0')
②在界面中创建SmartRefreshLayout,包裹 列表组件:RecyclerView或ListView等
③添加OnRefreshListener监听,实现下拉刷新,OnLoadMoreListener监听,实现上拉加载
srl.setOnLoadMoreListener(new OnLoadMoreListener() {
@Override
public void onLoadMore(@NonNull RefreshLayout refreshLayout) {
page++;
initData();
}
});
srl.setOnRefreshListener(new OnRefreshListener() {
@Override
public void onRefresh(@NonNull RefreshLayout refreshLayout) {
list.clear();
page=1;
initData();
}
});
5.请求头处理(Header)以及超时和缓冲处理以及响应处理
private void initData() {
//设置超时
OkHttpClient build = new OkHttpClient.Builder()
.connectTimeout(5, TimeUnit.SECONDS)//设置连接时间
.readTimeout(5, TimeUnit.SECONDS)//设置读取时间
.writeTimeout(5, TimeUnit.SECONDS)//设置写入时间
.cache(new Cache(new File(getCacheDir(),"cache"),10*1024*1024))//指定缓存路径,10m大小
.build();
//请求头设置
final Request request = new Request.Builder()
.addHeader("Content-Type","application/x-www-form-urlencoded;charset=utf-8")
.header("User-Agent","OkHttp Example")
.get().url(Constanst.URL)
.build();
Call call = build.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.e(TAG, "onFailure: "+e.getMessage());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
//响应信息
String message = response.message();
Log.e(TAG, "响应信息: "+message);
//响应头
Headers headers = response.headers();
for (int i = 0; i <headers.size() ; i++) {
Log.e(TAG, "响应头: "+headers.name(i)+"------"+headers.value(i));
}
//响应体
Log.e(TAG, "响应体: "+Thread.currentThread().getName());
String json = response.body().string();
Gson gson = new GsonBuilder().serializeNulls().create();
Bean bean = gson.fromJson(json, Bean.class);
final List<Bean.DataBean> data = bean.getData();
runOnUiThread(new Runnable() {
@Override
public void run() {
list.addAll(data);
adapter.notifyDataSetChanged();
}
});
}
});
❤补充:都是根目录
File file = getCacheDir();//本应用的缓存目录,缓存删除后就没有了
File file1 = Environment.getExternalStorageDirectory();//手机的应用管理,缓存一直都在
6.请求体处理
form表单,String字符串,流,文件
7.HttpURLconnection及OkHttp3的对比分析
①HttpUrlConnection,google官方提供的用来访问网络,但是实现的比较简单,只支持1.0/1.1
②并没有多路复用,如果碰到app大量网络请求的时候,性能比较差,
③HttpUrlConnection底层也是用Socket来实现的
④OkHttp像HttpUrlConnection一样,实现了一个网络连接的过程。
⑤OkHttp和HttpUrlConnection是一级的,用socket实现了网络连接,OkHttp更强大,
⑥HttpUrlConnection在IO方面用到的是InputStream和OutputStream,但OkHttp用的是sink和source,这两个是在Okio这个开源库里的, feredSink(支持缓冲)、GzipSink(支持Gzip压缩)、ForwardingSink和InflaterSink(后面这两者服务于GzipSink)
⑦多个相同host和port的stream可以共同使用一个socket,而RealConnection就是处理连接的,那也就是说一个RealConnection上可能有很多个Stream
⑧OkHttp代码比HttpURLConnection精简的多
8.拦截器

OkHttp的拦截器可谓是其整个框架的精髓,用户可传入的 interceptor 分为两类:Application Intercetor和NetworkInterceptor
1.Application interceptors 应用拦截器
okClient.addInterceptor(new LoggingInterceptor())
Application Interceptor 是第一个 Interceptor 因此它会被第一个执行,因此这里的 request 还是最原始的。而对于 response 而言呢,因为整个过程是递归的调用过程,因此它会在 CallServerInterceptor 执行完毕之后才会将 Response 进行返回,因此在 Application Interceptor 这里得到的 response 就是最终的响应,虽然中间有重定向,但是这里只关心最终的 response
①不需要去关心中发生的重定向和重试操作。因为它处于第一个拦截器,会获取到最终的响应
②只会被调用一次,即使这个响应是从缓存中获取的。
③只关注最原始的请求,不去关系请求的资源是否发生了改变,我只关注最后的 response 结果而已。
④因为是第一个被执行的拦截器,因此它有权决定了是否要调用其他拦截,也就是 Chain.proceed() 方法是否要被执行。
⑤因为是第一个被执行的拦截器,因此它有可以多次调用 Chain.proceed() 方法,其实也就是相当与重新请求的作用了。
2.Network Interceptors 网络拦截器
OKClient.addNetworkInterceptor(new LoggingInterceptor())
NetwrokInterceptor 处于第 6 个拦截器中,它会经过 RetryAndFollowIntercptor 进行重定向并且也会通过 BridgeInterceptor 进行 request 请求头和 响应 resposne 的处理,因此这里可以得到的是更多的信息。在打印结果可以看到它内部是发生了一次重定向操作,所以NetworkInterceptor 可以比 Application Interceptor 得到更多的信息了
①因为 NetworkInterceptor 是排在第 6 个拦截器中,因此可以操作经过 RetryAndFollowup 进行失败重试或者重定向之后得到的resposne。
②为响应直接从 CacheInterceptor 返回了。
③观察数据在网络中的传输。
④可以获得装载请求的连接。
9.拦截器的应用
1.日志拦截器
①获取请求
②打印请求信息
③网络请求
④响应
OkHttpClient build = new OkHttpClient.Builder()
.addInterceptor(new LogingInterceptor())//应用拦截器
.addNetworkInterceptor(new LogingInterceptor())//网络拦截器
.connectTimeout(5, TimeUnit.SECONDS)//设置连接时间
.readTimeout(5, TimeUnit.SECONDS)//设置读取时间
.writeTimeout(5, TimeUnit.SECONDS)//设置写入时间
.cache(new Cache(new File(getCacheDir(),"cache"),10*1024*1024))
.build();
class LogingInterceptor implements Interceptor{
@Override
public Response intercept(Chain chain) throws IOException {
//获取请求
Request request = chain.request();
//打印请求信息
long start = System.nanoTime();
Log.d(TAG, String.format("Sending request %s on %s%n%s",request.url(),chain.connection(),request.headers()));
//网络请求
Response response = chain.proceed(request);
//响应
long end = System.nanoTime();
Log.d(TAG, String.format("Received response for %s in %.1fms%n%s",response.request().url(),(end-start)/1e6d,response.headers()));
return response;
}
}
2.缓存拦截器(通过adb命令查看data数据)
在中主要做(post方式无法缓存)
①设置缓存位置
②无网时:设置缓存协议,加载缓存数据
③有网时:加载网络数据
//获取okHttpClient对象(构建者模式/建造者模式)
OkHttpClient build = new OkHttpClient.Builder()
.addInterceptor(new CacheInterceptor())
.addNetworkInterceptor(new CacheInterceptor())
.build();
class CacheInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
//请求数据
Request request = chain.request();
//判断若果无网时,设置缓存协议
if (!isNetworkAvailable(MainActivity.this)) {
request.newBuilder().cacheControl(CacheControl.FORCE_CACHE).build();
}
//开始请求网络,获取响应数据
Response response = chain.proceed(request);
//判断是否有网
if (!isNetworkAvailable(MainActivity.this)) {
int max = 0;
return response.newBuilder()
.removeHeader("Pragma")
.header("CaChe-Control", "public,max-age=" + max)
.build();
}else {
int min=15*60;
return response.newBuilder()
.removeHeader("Pragma")
.header("CaChe-Control", "public,only-if-cached,max-age=" + min)
.build();
}
}
}
//检测是否有网
public static boolean isNetworkAvailable(Context context) {
if (context != null) {
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo info = cm.getActiveNetworkInfo();
if (info != null) {
return info.isAvailable();
}
}
return false;
}
10.OkHttp源码分析

11.OkHttp中的设计模式
12.OkHttp中的线程池
每次开启和销毁线程都特别消耗系统资源,所以使用线程池
•int corePoolSize,核心线程的数量,这些线程始终存活
•int maximumPoolSize,最大线程数量
•long keepAliveTime,非核心线程闲下来存活的时间
•TimeUnit unit,存活的时间单位
•BlockingQueue<Runnable> workQueue,排队策略
•ThreadFactory threadFactory 线程工厂,线程怎么创建出来的
线程池
•1个APP上下文的数量:Service个数+activity个数+1个application对象
•1个APP有且只有一个Application对象,如果开发者不创建,系统会帮我们创建,
•自己创建的Application,需要在清单文件中配置
•APP启动的时候,先走Application的onCreate()
•如果在Application的onCreate中执行耗时操作,直接影响程序的启动时间
13.RecyclerView布局刷新
⑴一般使用:notifyDataSetChanged();
⑵如果只修改一个子条目的数据,全部刷新浪费性能,所以就有局部刷新
①页面结构不变,直接使用api即可
notifyItemInserted(position);//通知插入了一条数据
notifyItemMoved(fromPosition,toPosition);//通知两条数据换位置
notifyItemRemoved(position);//通知移除了一条数据
notifyItemChanged(position);//通知某个数据发生了改变
notifyItemRangeInserted(positionStart,itemCount);//通知从某个位置开始插入数据,共插入了几条
notifyItemRangeChanged(positionStart,itemCount);//通知某个位置开始数据发生了改变,共有几个改变了
notifyItemRangeRemoved(positionStart,itemCount);//通知从某个位置开始移除itemCount个数据
②对应页面结构会发生改变的刷新,页面刷新后会出现索引越界和混乱的问题,所以需要进行如下处理:
mRlvAdapter.mData.add(mPosition,"新数据:"+mPosition);//插入一条数据
mRlvAdapter.notifyItemInserted(mPosition);//通知局部刷新
mRlvAdapter.notifyItemRangeChanged(mPosition,mRlvAdapter.mData.size()-mPosition);//告诉适配器,某个范围内的数据发送了改变是从我们插入位置开始,以下全部数据都要通知,系统会更新新的索引,这样可以避免索引混乱