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 build = new OkHttpClient.Builder().build();
//构建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.getMessage());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
// Log.e(TAG, "当前线程: "+Thread.currentThread().getName());//E/MainActivity: 当前线程: OkHttp http://www.qubaobei.com/...(子线程,主线程打印main)
String json = response.body().string();
Gson gson = new GsonBuilder().serializeNulls().create();
Bean bean = gson.fromJson(json, Bean.class);
final String title = bean.getData().get(0).getTitle();
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(MainActivity.this, title, Toast.LENGTH_SHORT).show();
}
});
}
});
⑵get同步请求
......//前三步一样
//通过Call.execute()方法实现同步请求(获取网络数据是耗时操作,在子线程中执行)
new Thread(new Runnable() {
@Override
public void run() {
try {
Response execute = call.execute();
Log.e(TAG, "当前线程: " + Thread.currentThread().getName());
String json = execute.body().string();
Gson gson = new GsonBuilder().serializeNulls().create();
Bean bean = gson.fromJson(json, Bean.class);
final String title = bean.getData().get(0).getTitle();
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(MainActivity.this, title, Toast.LENGTH_SHORT).show();
}
});
} catch (IOException e) {
e.printStackTrace();
}
}
}).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();
//构建RequestBody
FormBody body = new FormBody.Builder()
.add("page", "1")
.build();
//构建Request对象
Request request = new Request.Builder()
.post(body)
.url(Constants.URL_POST)
.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.getMessage());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
String json = response.body().string();
Gson gson = new GsonBuilder().serializeNulls().create();
Bean bean = gson.fromJson(json, Bean.class);
final String title = bean.getData().get(0).getTitle();
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(MainActivity.this, title, Toast.LENGTH_SHORT).show();
}
});
}
});
⑵post同步请求
//通过Call.execute()方法实现同步请求
new Thread(new Runnable() {
@Override
public void run() {
try {
Response execute = call.execute();
String json = execute.body().string();
Gson gson = new GsonBuilder().serializeNulls().create();
Bean bean = gson.fromJson(json, Bean.class);
final String title = bean.getData().get(1).getTitle();
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(MainActivity.this, title, Toast.LENGTH_SHORT).show();
}
});
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
❤总结:
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.拦截器
拦截器.pngOkHttp的拦截器可谓是其整个框架的精髓,用户可传入的 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源码分析
okHttp源码.png11.OkHttp中的设计模式
12.OkHttp中的线程池
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);//告诉适配器,某个范围内的数据发送了改变是从我们插入位置开始,以下全部数据都要通知,系统会更新新的索引,这样可以避免索引混乱
网友评论