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">

    <Button
        android:id="@+id/btn1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="测试请求" />

    <TextView
        android: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;

    @Override
    protected 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() {
            @Override
            public 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() {
            @Override
            public void run() {
                try {
                    // newCall是发起网络请求的方法,需要传入Request,即我们对request的描述
                    // 该方法的返回值是response
                    Response response = okHttpClient.newCall(request).execute();
                    String result = response.body().string();
                    // 更新ui需要在主线程
                    runOnUiThread(new Runnable() {
                        @Override
                        public 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() {
            @Override
            public void onFailure(@NonNull Call call, @NonNull IOException e) {

            }

            @Override
            public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
                String result = response.body().string();
                runOnUiThread(new Runnable() {
                    @Override
                    public 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 {
    @Override
    public 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 {
    @Override
    public Response intercept(Chain chain) throws IOException {
        // 该层级下的责任链请求request
        Request request = chain.request();
        long curTime = System.currentTimeMillis();
        Log.i("TAG", "intercept: REQUEST = " + request.toString());
        // 该层级下的责任链传递response
        Response 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

  • 17
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
OkHttp是一个处理网络请求的开源项目,是Android使用最广泛的网络框架。它适用于Android 5.0(API级别21)和Java 8。使用OkHttp发送HTTP请求的过程如下: 1. 域名解析(DNS):将域名解析为IP地址。 2. 建立TCP连接:通过三次握手建立TCP连接。 3. 发起HTTP请求:建立TCP连接后,使用Socket输出流将HTTP报文写出。 4. 等待响应:等待服务器响应。 5. 解析响应:解析服务器返回的HTTP响应。 6. 处理响应数据:根据需要处理响应数据,比如解析JSON或下载文件。 在使用OkHttp时,首先需要创建一个Request对象,可以使用Request.Builder()来构建。然后通过Request对象获得一个Call对象,可以使用client.newCall(request)来创建。接下来,可以使用call.execute()进行同步调用或者call.enqueue()进行异步调用。最后,可以通过Response对象获取响应数据。 总之,使用OkHttp很简单,它的请求/响应API使用了流畅的构建器设计和不可变性设计,支持同步阻塞调用和带有回调的异步调用。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [androidOkHttp使用](https://blog.csdn.net/hanjiexuan/article/details/115894233)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [android okhttp的基础使用【入门推荐】](https://download.csdn.net/download/weixin_38516386/12788438)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值