您的位置:首页 > 文旅 > 美景 > Android的OkHttp使用和原理

Android的OkHttp使用和原理

2024/12/23 11:00:38 来源:https://blog.csdn.net/qq_44016573/article/details/141222292  浏览:    关键词:Android的OkHttp使用和原理

前言

OkHttp的出现代替了HttpUrlConnection,被谷歌官方收纳为底层的网络框架。特点如下:

  • 支持HTTP/2框架下的socket复用
  • 通过连接池减少连接的延时
  • 使用GZIP进行数据压缩
  • 使用缓存技术避免重复请求

当网络出现问题时,OkHttp会静默重新恢复连接,因为是静默的,所以用户无感知。

构建

首先在模块下的build.gradle文件中导入OkHttp依赖,并开启viewBinding:

android {...buildFeatures {viewBinding = true //开启viewBinding}
}dependencies {...implementation 'com.squareup.okhttp3:okhttp:3.14.+'
}

同时在AndroidManifest.xml文件中开启网络访问权限:

<uses-permission android:name="android.permission.INTERNET" />

viewBinding可以去除findViewById的操作。我们在xml中简单的使用一个button开启网络服务:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><Buttonandroid:id="@+id/btn1"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="测试请求" /><TextViewandroid:id="@+id/textView"android:layout_width="match_parent"android:layout_height="wrap_content" /></LinearLayout>

使用viewBinding获取控件,并新建一个OkHttpClient实例:

public class MainActivity extends AppCompatActivity {ActivityMainBinding binding;OkHttpClient okHttpClient;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);binding = ActivityMainBinding.inflate(getLayoutInflater());setContentView(binding.getRoot());// 也可以使用okHttpClient = new OkHttpClient()来创建okHttpClient = new OkHttpClient.Builder().build();binding.btn1.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {testGet();}});}/*** 获取网络数据*/private void testGet() {// 创建Request实例,它描述了我们需要请求的内容Request request = new Request.Builder()// 此处传入一个url数据请求,需要在Manifest文件开通网络权限.url("https://api.scryfall.com/cards/search?order=name&q=name%3Dthree+visits").build();// 发起网络请求可以是同步也可以是异步的// 同步写法,因为网络请求是耗时操作,需要开辟一个新的子线程new Thread(new Runnable() {@Overridepublic void run() {try {// newCall是发起网络请求的方法,需要传入Request,即我们对request的描述// 该方法的返回值是responseResponse response = okHttpClient.newCall(request).execute();String result = response.body().string();// 更新ui需要在主线程runOnUiThread(new Runnable() {@Overridepublic void run() {binding.textView.setText(result);}});} catch (IOException e) {throw new RuntimeException(e);}}}).start();}
}

在使用OkHttpClient时,尽量保持单例创建,因为其构造方法中存有很多内容。

在获取网络数据时,我们首先新建一个Request实例,它是用来描述我们需要向网络请求的内容的;其次使用OkHttpClient的newCall方法装在我们的请求,并使用execute方法执行。

执行请求后会返回服务端的结果,类型是Response。此时我们就可以使用请求到的结果了。

异步请求写法如下:

okHttpClient.newCall(request).enqueue(new Callback() {@Overridepublic void onFailure(@NonNull Call call, @NonNull IOException e) {}@Overridepublic void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {String result = response.body().string();runOnUiThread(new Runnable() {@Overridepublic void run() {binding.textView.setText(result);}});}});

双任务队列机制

在使用OkHttpClient的newCall方法时,会返回一个Call类型的实例。我们使用的execute等方法都是在执行Call中的方法,因此我们可以理解Call这个类就是封装了执行业务的方法。

在异步执行的方式中,我们使用了enqueue方法。查看上述的源码,可以其中发现调用了client的dispatcher().enqueue方法,并新建了一个AsyncCall实例传入(对应机制图的第一步)。

AsyncCall继承了NamedRunnable类,而这个类继承了Runnable类,因此我们主要关注AsyncCall是如何重写execute方法的。

在AsyncCall被传入的enqueue方法中,包含了我们的双队列机制的实现,。

promoteAndExcecute方法中出现了双队列。

被判断过滤后的可执行的AsyncCall会被投入一个临时的队列中用于遍历。在遍历时会执行AsyncCalls的excuteOn方法,传入一个线程池。

在这个方法中会执行线程池的execute方法,也就是AsyncCall的run方法。实际上这里做的就是把任务交给了线程池执行。

此处的线程池可以看到没有设置核心线程(corePoolSize = 0),所有的操作都是由临时线程完成。当有新任务进来时,就会创建一个执行周期为60s的临时任务;同时因为没有核心线程,被传入线程池的任务会立即执行。

而在AsyncCall的excute方法中,最后会执行finished方法。

在执行到finished方法时,会有会开始新一轮的promoteAndExecuted方法。这样做我们就达到了从等待队列中循环获取AsyncCall。

如果我们使用的是一个while循环,那么cpu就会一直要为了这个循环释放资源,而不是等待队列中有内容才去执行循环。 

责任链模式和拦截器

拦截器就好比请假审批,你的请假会被人事、主管、部门领导等层层审批,最终回到你的手里。

我们可以实现一个自己的拦截器。

public abstract class Handler {// 对于每个handler,它会持有下一个handler(next变量)protected Handler next;public Handler getNext() {return next;}public void setNext(Handler next) {this.next = next;}// 处理请求的接口方法public abstract void handleRequest(String request);
}

 拦截器的具体实现方法如下。

public class MainHandler1 extends Handler {@Overridepublic void handleRequest(String request) {if (request.equals("one")) {Log.i("TAG", "具体处理者1处理该请求");} else {if (getNext() != null) {next.handleRequest(request);} else {Log.i("TAG", "没有人处理请求");}}}
}

可以生成多个MainHandler模拟多个拦截器。

Handler handler1 = new MainHandler1();
Handler handler2 = new MainHandler2();
Handler handler3 = new MainHandler3();
handler1.setNext(handler2);
handler2.setNext(handler3);
handler1.handleRequest("one");

真实的拦截器接口实现如下。

public class LogIntercept implements Interceptor {@Overridepublic Response intercept(Chain chain) throws IOException {// 该层级下的责任链请求requestRequest request = chain.request();long curTime = System.currentTimeMillis();Log.i("TAG", "intercept: REQUEST = " + request.toString());// 该层级下的责任链传递responseResponse response = chain.proceed(request);Log.i("TAG", "intercept: RESPONSE = " + response.toString());Log.i("TAG", "intercept: 耗时 = " + (System.currentTimeMillis() - curTime) + "ms");return response;}
}

我们自己定义的拦截器会先于系统的拦截器执行。

所以如果我们的拦截器中没有很好的做到chain.request责任链请求和chain.proceed责任链传递、而导致链路首先在我们自己的拦截器断了的话,整个责任链就会断开,系统的拦截器自然也不会执行。 

连接池的复用机制

TCP的三次握手和四次挥手

在挥手(即断开连接)时,服务器在接收到来自客户端的断连申请(即第一次挥手)时,会反馈两次,一次是表明接收到断连申请,但不会马上断开,因为很有可能服务器中还有客户端的数据在处理;待数据处理完毕后,会再挥手一次,表明自身已处理完毕,可以断开。

Socket连接池复用

考虑到每次连接都要三次握手、每次断开都要四次挥手显然会造成效率低下,Http协议中有一种KeepAlive机制,它可以在数据完成传输(即原本应断连的情况)后仍然保留连接状态。

在这个机制下,会有一个链路的存活时间。当存活时间到达后,该连接才真正断开。

OkHttp默认支持5个并发KeepAlive,链路默认存活时间为5分钟。

真实的连接信息保存在RealConnection中,包含socket等内容。

RealConnectionPool用于存储RealConnection的队列,同时还有对socket的清理机制。

 

若判断条件成立(需要被清理),会执行cleanupRunnable。 

参考

10-OkHttp小结_哔哩哔哩_bilibili

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com