Android 扩展OkHttp支持请求优先级调度

本文介绍如何为OkHttp添加请求优先级调度功能,通过自定义请求优先级并修改OkHttp源码实现请求按优先级执行。

在当今这个App泛滥的时代,网络请求几乎是每一个App必不可少的一部分,请求几乎遍布App的每一个界面中。我们进入A界面后,App发起了一系列请求,这时候假如还有一部分请求没有被执行,我们就进入B界面开始新的网络请求,这时候原来A界面的网络请求我们有两个选择:

  • 取消A界面的所有未开始执行的网络请求
  • 不取消A界面的所有网络请求,但是B界面的请求要优先于A界面的请求执行,B界面的网络请求执行完毕后再去执行A界面未执行完毕的请求。

对于第一种情况,我们很好做到,在Activity的onDestroy回调中取消该界面中所有请求,这里需要明确一点,本篇文章的网络层是OkHttp,既然选择了OkHttp,如果要在onDestroy中取消未开始执行以及已经开始执行的网络请求,就必须给每一个请求设置一个tag,然后通过该tag来需要网络请求。比较明智的做法是以该Activity的上下文的hash值作为tag。取消请求时将hash值传入,则该界面所有的请求都可以取消。

但是实际情况并非如此,有一部分网络请求我们不想取消它,仍然想要进行请求,因为这部分的请求比较重要,需要拉到客户端进行使用,取消这个请求可能会带来不必要的麻烦,因此,我们需要保留这些请求。但是我们进入了一个新的界面,新界面的网络优先级比较高,应该先被执行,这就是第二种情况。

每种情况有对应的解决方法,第一种情况显得比较简单,我们先来实现它。

<code class="hljs avrasm has-numbering">public class MainActivity extends AppCompatActivity implements View<span class="hljs-preprocessor">.OnClickListener</span> {
    private Button btn1<span class="hljs-comment">;</span>
    private Button btn2<span class="hljs-comment">;</span>
    private OkHttpClient mOkHttpClient<span class="hljs-comment">;</span>
    @Override

    protected void onCreate(Bundle savedInstanceState) {
        super<span class="hljs-preprocessor">.onCreate</span>(savedInstanceState)<span class="hljs-comment">;</span>
        setContentView(R<span class="hljs-preprocessor">.layout</span><span class="hljs-preprocessor">.activity</span>_main)<span class="hljs-comment">;</span>
        btn1 = (Button) findViewById(R<span class="hljs-preprocessor">.id</span><span class="hljs-preprocessor">.btn</span>1)<span class="hljs-comment">;</span>
        btn2 = (Button) findViewById(R<span class="hljs-preprocessor">.id</span><span class="hljs-preprocessor">.btn</span>2)<span class="hljs-comment">;</span>
        btn1<span class="hljs-preprocessor">.setOnClickListener</span>(this)<span class="hljs-comment">;</span>
        btn2<span class="hljs-preprocessor">.setOnClickListener</span>(this)<span class="hljs-comment">;</span>
        mOkHttpClient = new OkHttpClient()<span class="hljs-comment">;</span>
    }
    @Override
    protected void onDestroy() {
        super<span class="hljs-preprocessor">.onDestroy</span>()<span class="hljs-comment">;</span>
        Log<span class="hljs-preprocessor">.e</span>(<span class="hljs-string">"TAG"</span>, <span class="hljs-string">"onDestroy"</span>)<span class="hljs-comment">;</span>
        cancelByTag(this<span class="hljs-preprocessor">.hashCode</span>())<span class="hljs-comment">;</span>
    }
    @Override
    public void onClick(View v) {
        switch (v<span class="hljs-preprocessor">.getId</span>()) {
            case R<span class="hljs-preprocessor">.id</span><span class="hljs-preprocessor">.btn</span>1:
                sendRequest()<span class="hljs-comment">;</span>
                <span class="hljs-keyword">break</span><span class="hljs-comment">;</span>
            case R<span class="hljs-preprocessor">.id</span><span class="hljs-preprocessor">.btn</span>2:
                startActivity(new Intent(this, SecondActivity<span class="hljs-preprocessor">.class</span>))<span class="hljs-comment">;</span>
                finish()<span class="hljs-comment">;</span>
                <span class="hljs-keyword">break</span><span class="hljs-comment">;</span>
        }
    }

    private void sendRequest() {
        Request<span class="hljs-preprocessor">.Builder</span> builder = new Request<span class="hljs-preprocessor">.Builder</span>()<span class="hljs-comment">;</span>

 builder<span class="hljs-preprocessor">.url</span>(<span class="hljs-string">"https://www.baidu.com"</span>)<span class="hljs-preprocessor">.tag</span>(this<span class="hljs-preprocessor">.hashCode</span>())<span class="hljs-comment">;</span>



        Request request1 = builder<span class="hljs-preprocessor">.build</span>()<span class="hljs-comment">;</span>
        Request request2 = builder<span class="hljs-preprocessor">.build</span>()<span class="hljs-comment">;</span>
        Request request3 = builder<span class="hljs-preprocessor">.build</span>()<span class="hljs-comment">;</span>
        Request request4 = builder<span class="hljs-preprocessor">.build</span>()<span class="hljs-comment">;</span>
        Request request5 = builder<span class="hljs-preprocessor">.build</span>()<span class="hljs-comment">;</span>
        Request request6 = builder<span class="hljs-preprocessor">.build</span>()<span class="hljs-comment">;</span>
        Request request7 = builder<span class="hljs-preprocessor">.build</span>()<span class="hljs-comment">;</span>
        Request request8 = builder<span class="hljs-preprocessor">.build</span>()<span class="hljs-comment">;</span>
        Request request9 = builder<span class="hljs-preprocessor">.build</span>()<span class="hljs-comment">;</span>
        Request request10 = builder<span class="hljs-preprocessor">.build</span>()<span class="hljs-comment">;</span>


        final <span class="hljs-keyword">Call</span> call1 = mOkHttpClient<span class="hljs-preprocessor">.newCall</span>(request1)<span class="hljs-comment">;</span>
        final <span class="hljs-keyword">Call</span> call2 = mOkHttpClient<span class="hljs-preprocessor">.newCall</span>(request2)<span class="hljs-comment">;</span>
        final <span class="hljs-keyword">Call</span> call3 = mOkHttpClient<span class="hljs-preprocessor">.newCall</span>(request3)<span class="hljs-comment">;</span>
        final <span class="hljs-keyword">Call</span> call4 = mOkHttpClient<span class="hljs-preprocessor">.newCall</span>(request4)<span class="hljs-comment">;</span>
        final <span class="hljs-keyword">Call</span> call5 = mOkHttpClient<span class="hljs-preprocessor">.newCall</span>(request5)<span class="hljs-comment">;</span>
        final <span class="hljs-keyword">Call</span> call6 = mOkHttpClient<span class="hljs-preprocessor">.newCall</span>(request6)<span class="hljs-comment">;</span>
        final <span class="hljs-keyword">Call</span> call7 = mOkHttpClient<span class="hljs-preprocessor">.newCall</span>(request7)<span class="hljs-comment">;</span>
        final <span class="hljs-keyword">Call</span> call8 = mOkHttpClient<span class="hljs-preprocessor">.newCall</span>(request8)<span class="hljs-comment">;</span>
        final <span class="hljs-keyword">Call</span> call9 = mOkHttpClient<span class="hljs-preprocessor">.newCall</span>(request9)<span class="hljs-comment">;</span>
        final <span class="hljs-keyword">Call</span> call10 = mOkHttpClient<span class="hljs-preprocessor">.newCall</span>(request10)<span class="hljs-comment">;</span>

        final Callback callback = new Callback() {
            @Override
            public void onFailure(<span class="hljs-keyword">Call</span> <span class="hljs-keyword">call</span>, IOException e) {
                Log<span class="hljs-preprocessor">.e</span>(<span class="hljs-string">"TAG"</span>, <span class="hljs-string">"failure. isCanceled:"</span> + <span class="hljs-keyword">call</span><span class="hljs-preprocessor">.isCanceled</span>() + <span class="hljs-string">" isExecuted:"</span> + <span class="hljs-keyword">call</span><span class="hljs-preprocessor">.isExecuted</span>())<span class="hljs-comment">;</span>
            }

            @Override
            public void onResponse(<span class="hljs-keyword">Call</span> <span class="hljs-keyword">call</span>, Response response) throws IOException {
                Log<span class="hljs-preprocessor">.e</span>(<span class="hljs-string">"TAG"</span>, <span class="hljs-string">"success. isCanceled:"</span> + <span class="hljs-keyword">call</span><span class="hljs-preprocessor">.isCanceled</span>() + <span class="hljs-string">" isExecuted:"</span> + <span class="hljs-keyword">call</span><span class="hljs-preprocessor">.isExecuted</span>())<span class="hljs-comment">;</span>
            }
        }<span class="hljs-comment">;</span>

        call1<span class="hljs-preprocessor">.enqueue</span>(callback)<span class="hljs-comment">;</span>
        call2<span class="hljs-preprocessor">.enqueue</span>(callback)<span class="hljs-comment">;</span>
        call3<span class="hljs-preprocessor">.enqueue</span>(callback)<span class="hljs-comment">;</span>
        call4<span class="hljs-preprocessor">.enqueue</span>(callback)<span class="hljs-comment">;</span>
        call5<span class="hljs-preprocessor">.enqueue</span>(callback)<span class="hljs-comment">;</span>
        call6<span class="hljs-preprocessor">.enqueue</span>(callback)<span class="hljs-comment">;</span>
        call7<span class="hljs-preprocessor">.enqueue</span>(callback)<span class="hljs-comment">;</span>
        call8<span class="hljs-preprocessor">.enqueue</span>(callback)<span class="hljs-comment">;</span>
        call9<span class="hljs-preprocessor">.enqueue</span>(callback)<span class="hljs-comment">;</span>
        call10<span class="hljs-preprocessor">.enqueue</span>(callback)<span class="hljs-comment">;</span>

    }

    public void cancelByTag(Object tag) {
        for (<span class="hljs-keyword">Call</span> <span class="hljs-keyword">call</span> : mOkHttpClient<span class="hljs-preprocessor">.dispatcher</span>()<span class="hljs-preprocessor">.queuedCalls</span>()) {
            if (tag<span class="hljs-preprocessor">.equals</span>(<span class="hljs-keyword">call</span><span class="hljs-preprocessor">.request</span>()<span class="hljs-preprocessor">.tag</span>())) {
                <span class="hljs-keyword">call</span><span class="hljs-preprocessor">.cancel</span>()<span class="hljs-comment">;</span>
            }
        }

        for (<span class="hljs-keyword">Call</span> <span class="hljs-keyword">call</span> : mOkHttpClient<span class="hljs-preprocessor">.dispatcher</span>()<span class="hljs-preprocessor">.runningCalls</span>()) {
            if (tag<span class="hljs-preprocessor">.equals</span>(<span class="hljs-keyword">call</span><span class="hljs-preprocessor">.request</span>()<span class="hljs-preprocessor">.tag</span>())) {
                <span class="hljs-keyword">call</span><span class="hljs-preprocessor">.cancel</span>()<span class="hljs-comment">;</span>
            }
        }
    }
}</code>

当我们点击发送请求的按钮之后,所有请求都被设置了一个tag后发送出去,然后我们需要快速的点击跳转按钮,让当前页面finish掉,之后就会回调onDestroy方法,onDestyoy方法中我们调用了取消请求的方法,如果还有请求没有开始执行,该请求就会被取消掉。这样,第一种情况就简单的实现了下。

在实现第二种情况的时候,我们需要知道一个概念,就是一个集合中如何对元素进行排序,通常,有两种做法。

  • 将待比较的类实现Comparable接口,调用Collections.sort(list)方法进行排序
  • 新建一个类实现Comparator接口,调用Collections.sort(list,comparator)方法进行排序

假如现在我们有一个类叫Person,它有两个属性,name和age,我们有一个List,里面都是Person,我们希望对这个List进行排序,并且排序的原则是根据age从小到大排序。按照实现Comparable接口的方法,我们需要将Person实现该接口,就像这样子。

<code class="hljs cs has-numbering"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> Person implements Comparable<Person>{
    <span class="hljs-keyword">private</span> String name;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">int</span> age;

    <span class="hljs-keyword">public</span> <span class="hljs-title">Person</span>(String name, <span class="hljs-keyword">int</span> age) {
        <span class="hljs-keyword">this</span>.name = name;
        <span class="hljs-keyword">this</span>.age = age;
    }
    <span class="hljs-keyword">public</span> String <span class="hljs-title">getName</span>() {
        <span class="hljs-keyword">return</span> name;
    }
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">setName</span>(String name) {
        <span class="hljs-keyword">this</span>.name = name;
    }
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> <span class="hljs-title">getAge</span>() {
        <span class="hljs-keyword">return</span> age;
    }
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">setAge</span>(<span class="hljs-keyword">int</span> age) {
        <span class="hljs-keyword">this</span>.age = age;
    }
    @Override
    <span class="hljs-keyword">public</span> String <span class="hljs-title">toString</span>() {
        <span class="hljs-keyword">return</span> <span class="hljs-string">"Person{"</span> +
                <span class="hljs-string">"name='"</span> + name + <span class="hljs-string">'\''</span> +
                <span class="hljs-string">", age="</span> + age +
                <span class="hljs-string">'}'</span>;
    }
    @Override
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> <span class="hljs-title">compareTo</span>(Person another) {
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>.age-another.age;
    }
}</code>

这时候我们生成一个都是Person实例的List,调用sort方法进行排序看下结果如何

<code class="hljs cs has-numbering">Person p1=<span class="hljs-keyword">new</span> Person(<span class="hljs-string">"张三"</span>,<span class="hljs-number">23</span>);
Person p2=<span class="hljs-keyword">new</span> Person(<span class="hljs-string">"李四"</span>,<span class="hljs-number">12</span>);
Person p3=<span class="hljs-keyword">new</span> Person(<span class="hljs-string">"王五"</span>,<span class="hljs-number">21</span>);
Person p4=<span class="hljs-keyword">new</span> Person(<span class="hljs-string">"赵六"</span>,<span class="hljs-number">8</span>);
Person p5=<span class="hljs-keyword">new</span> Person(<span class="hljs-string">"钱七"</span>,<span class="hljs-number">40</span>);
List<Person> persons = Arrays.asList(p1, p2, p3, p4, p5);
System.<span class="hljs-keyword">out</span>.println(persons);
Collections.sort(persons);
System.<span class="hljs-keyword">out</span>.println(persons);</code>

输出结果如下

[Person{name=’张三’, age=23}, Person{name=’李四’, age=12}, Person{name=’王五’, age=21}, Person{name=’赵六’, age=8}, Person{name=’钱七’, age=40}]
[Person{name=’赵六’, age=8}, Person{name=’李四’, age=12}, Person{name=’王五’, age=21}, Person{name=’张三’, age=23}, Person{name=’钱七’, age=40}]

可以看到按age进行排序,并且从小到大的排了顺序,那么如果要从大到小排序呢,很简单,修改compareTo方法即可

<code class="hljs java has-numbering"><span class="hljs-annotation"></span></code><pre name="code" class="prettyprint"><code class="hljs java has-numbering"><span class="hljs-annotation">@Override</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> <span class="hljs-title">compareTo</span>(Person another) {
    <span class="hljs-keyword">return</span> another.age-<span class="hljs-keyword">this</span>.age;
}</code>

如果实现Comparator接口,那么我们无需改动Person类,最原始的Person类如下

<code class="hljs cs has-numbering"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> Person{
    <span class="hljs-keyword">private</span> String name;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">int</span> age;

    <span class="hljs-keyword">public</span> <span class="hljs-title">Person</span>(String name, <span class="hljs-keyword">int</span> age) {
        <span class="hljs-keyword">this</span>.name = name;
        <span class="hljs-keyword">this</span>.age = age;
    }

    <span class="hljs-keyword">public</span> String <span class="hljs-title">getName</span>() {
        <span class="hljs-keyword">return</span> name;
    }

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">setName</span>(String name) {
        <span class="hljs-keyword">this</span>.name = name;
    }

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> <span class="hljs-title">getAge</span>() {
        <span class="hljs-keyword">return</span> age;
    }

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">setAge</span>(<span class="hljs-keyword">int</span> age) {
        <span class="hljs-keyword">this</span>.age = age;
    }
    @Override
    <span class="hljs-keyword">public</span> String <span class="hljs-title">toString</span>() {
        <span class="hljs-keyword">return</span> <span class="hljs-string">"Person{"</span> +
                <span class="hljs-string">"name='"</span> + name + <span class="hljs-string">'\''</span> +
                <span class="hljs-string">", age="</span> + age +
                <span class="hljs-string">'}'</span>;
    }
}</code>

取而代之的方法便是新建一个类实现Comparator接口

<code class="hljs axapta has-numbering"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PersonComparator</span> <span class="hljs-inheritance"><span class="hljs-keyword">implements</span></span> <span class="hljs-title">Comparator</span><<span class="hljs-title">Person</span>> {</span>
    @Override
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> compare(Person person1, Person person2) {
        <span class="hljs-keyword">return</span> person1.getAge()-person2.getAge();
    }

}</code>

在进行排序的时候将比较器传入即可。

<code class="hljs cs has-numbering">
Person p1=<span class="hljs-keyword">new</span> Person(<span class="hljs-string">"张三"</span>,<span class="hljs-number">23</span>);
Person p2=<span class="hljs-keyword">new</span> Person(<span class="hljs-string">"李四"</span>,<span class="hljs-number">12</span>);
Person p3=<span class="hljs-keyword">new</span> Person(<span class="hljs-string">"王五"</span>,<span class="hljs-number">21</span>);
Person p4=<span class="hljs-keyword">new</span> Person(<span class="hljs-string">"赵六"</span>,<span class="hljs-number">8</span>);
Person p5=<span class="hljs-keyword">new</span> Person(<span class="hljs-string">"钱七"</span>,<span class="hljs-number">40</span>);

List<Person> persons = Arrays.asList(p1, p2, p3, p4, p5);
System.<span class="hljs-keyword">out</span>.println(persons);
Collections.sort(persons,<span class="hljs-keyword">new</span> PersonComparator());
System.<span class="hljs-keyword">out</span>.println(persons);</code>

知道了如何比较一个类并进行排序后,我们开始我们的正式内容,让okhttp支持优先级调度,也就是文章开头的第二种情况。B界面的网络请求比A界面的网络请求优先级要高,因此我们应该有一个变量来代表这种优先级。然后我们需要根据该优先级进行排序。

很遗憾的是Okhttp默认是不支持优先级调度的,我们不得不修改OkHttp底层的源码进行扩展支持,但这又是万不得已的。

在RealCall这个类里面,有一个内部类AsyncCall,所有异步执行的网络请求最终都会被包装成这一个类型。OkHttpClient中的newCall将Request对象包装成RealCall,而RealCall中的enqueue则将自己转换成一个AsyncCall对象进行异步执行,AsyncCall是Runnale对象的间接子类。因此,我们代表优先级的变量应该存储在AsyncCall这个类中,也就是priority。

<code class="hljs axapta has-numbering"> <span class="hljs-keyword">final</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AsyncCall</span> <span class="hljs-inheritance"><span class="hljs-keyword">extends</span></span> <span class="hljs-title">NamedRunnable</span>{</span>
        <span class="hljs-comment">//other  field</span>
        <span class="hljs-keyword">private</span> <span class="hljs-keyword">int</span> priority;
        <span class="hljs-keyword">private</span> AsyncCall(Callback responseCallback, <span class="hljs-keyword">boolean</span> forWebSocket) {
            <span class="hljs-keyword">super</span>(<span class="hljs-string">"OkHttp %s"</span>, originalRequest.url().toString());
            <span class="hljs-comment">//other field</span>
            <span class="hljs-keyword">this</span>.priority = originalRequest.priority();
        }

        <span class="hljs-keyword">int</span> priority() {
            <span class="hljs-keyword">return</span> originalRequest.priority();
        }
        <span class="hljs-comment">//other method</span>
    }</code>

同样的,我们需要在Request中暴露这个优先级的变量,即priority

<code class="hljs cs has-numbering"><span class="hljs-keyword">public</span> final <span class="hljs-keyword">class</span> Request {
  <span class="hljs-comment">//other field</span>
  <span class="hljs-keyword">private</span> final <span class="hljs-keyword">int</span> priority;
  <span class="hljs-keyword">private</span> <span class="hljs-title">Request</span>(Builder builder) {
    <span class="hljs-comment">//other field</span>
    <span class="hljs-keyword">this</span>.priority=builder.priority;
  }
  <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> <span class="hljs-title">priority</span>(){
    <span class="hljs-keyword">return</span> priority;
  }

  <span class="hljs-comment">//other method</span>
  <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> Builder {
    <span class="hljs-comment">//ohther field</span>
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">int</span> priority;
    <span class="hljs-keyword">private</span> <span class="hljs-title">Builder</span>(Request request) {
      <span class="hljs-comment">//other field</span>
      <span class="hljs-keyword">this</span>.priority=request.priority;
    }

    <span class="hljs-keyword">public</span> Builder <span class="hljs-title">priority</span>(<span class="hljs-keyword">int</span> priority){
      <span class="hljs-keyword">this</span>.priority=priority;
      <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>;
    }
    <span class="hljs-comment">//other method</span>
  }
}</code>

之后我们需要实现一个比较器,根据优先级由大到小进行排序

<code class="hljs php has-numbering"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AsycCallComparator</span><<span class="hljs-title">T</span>> <span class="hljs-keyword">implements</span> <span class="hljs-title">Comparator</span><<span class="hljs-title">T</span>> {</span>
    @Override
    <span class="hljs-keyword">public</span> int compare(T object1, T object2) {
        <span class="hljs-keyword">if</span> ((object1 <span class="hljs-keyword">instanceof</span> RealCall.AsyncCall)
                && (object2 <span class="hljs-keyword">instanceof</span> RealCall.AsyncCall)) {
            RealCall.AsyncCall task1 = (RealCall.AsyncCall) object1;
            RealCall.AsyncCall task2 = (RealCall.AsyncCall) object2;
            int result = task2.priority()
                    - task1.priority();
            <span class="hljs-keyword">return</span> result;
        }
        <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;
    }</code>

然后,OkHttp内部有一个Dispatcher分发器,分发器内部有一个ExecutorService,ExecutorService是可以自己进行配置,然后变成可以根据优先级调度的,默认的分发器是使用SynchronousQueue进行调度,我们需要将它改成优先队列,将原来的新建对象注释掉,替换成我们的优先队列,优先队列的创建需要传入一个比较器,也就是刚才我们创建的那个比较器。

下面这个方法就是Dispatcher中设置线程池的方法

<code class="hljs java has-numbering">    <span class="hljs-keyword">public</span> <span class="hljs-keyword">synchronized</span> ExecutorService <span class="hljs-title">executorService</span>() {
        <span class="hljs-keyword">if</span> (executorService == <span class="hljs-keyword">null</span>) {
<span class="hljs-comment">//          executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,</span>
<span class="hljs-comment">//                  new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));</span>
            executorService = <span class="hljs-keyword">new</span> ThreadPoolExecutor(<span class="hljs-number">4</span>, Integer.MAX_VALUE, <span class="hljs-number">60</span>, TimeUnit.SECONDS,
                    <span class="hljs-keyword">new</span> PriorityBlockingQueue<Runnable>(<span class="hljs-number">60</span>, <span class="hljs-keyword">new</span> AsycCallComparator<Runnable>()), Util.threadFactory(<span class="hljs-string">"OkHttp Dispatcher"</span>, <span class="hljs-keyword">false</span>));
        }
        <span class="hljs-keyword">return</span> executorService;
    }
</code>

之后我们模拟发送10个不同优先级的请求,并且优先级是乱序的,控制台则会输出

<code class="hljs livecodeserver has-numbering"><span class="hljs-number">14</span>===Response{protocol=<span class="hljs-keyword">http</span>/<span class="hljs-number">1.1</span>, code=<span class="hljs-number">200</span>, message=OK, url=<span class="hljs-keyword">https</span>://www.baidu.com/}

<span class="hljs-number">500</span>===Response{protocol=<span class="hljs-keyword">http</span>/<span class="hljs-number">1.1</span>, code=<span class="hljs-number">200</span>, message=OK, url=<span class="hljs-keyword">https</span>://www.baidu.com/}

<span class="hljs-number">100</span>===Response{protocol=<span class="hljs-keyword">http</span>/<span class="hljs-number">1.1</span>, code=<span class="hljs-number">200</span>, message=OK, url=<span class="hljs-keyword">https</span>://www.baidu.com/}

<span class="hljs-number">40</span>===Response{protocol=<span class="hljs-keyword">http</span>/<span class="hljs-number">1.1</span>, code=<span class="hljs-number">200</span>, message=OK, url=<span class="hljs-keyword">https</span>://www.baidu.com/}

<span class="hljs-number">34</span>===Response{protocol=<span class="hljs-keyword">http</span>/<span class="hljs-number">1.1</span>, code=<span class="hljs-number">200</span>, message=OK, url=<span class="hljs-keyword">https</span>://www.baidu.com/}

<span class="hljs-number">30</span>===Response{protocol=<span class="hljs-keyword">http</span>/<span class="hljs-number">1.1</span>, code=<span class="hljs-number">200</span>, message=OK, url=<span class="hljs-keyword">https</span>://www.baidu.com/}

<span class="hljs-number">20</span>===Response{protocol=<span class="hljs-keyword">http</span>/<span class="hljs-number">1.1</span>, code=<span class="hljs-number">200</span>, message=OK, url=<span class="hljs-keyword">https</span>://www.baidu.com/}

<span class="hljs-number">10</span>===Response{protocol=<span class="hljs-keyword">http</span>/<span class="hljs-number">1.1</span>, code=<span class="hljs-number">200</span>, message=OK, url=<span class="hljs-keyword">https</span>://www.baidu.com/}

<span class="hljs-number">5</span>===Response{protocol=<span class="hljs-keyword">http</span>/<span class="hljs-number">1.1</span>, code=<span class="hljs-number">200</span>, message=OK, url=<span class="hljs-keyword">https</span>://www.baidu.com/}

<span class="hljs-number">2</span>===Response{protocol=<span class="hljs-keyword">http</span>/<span class="hljs-number">1.1</span>, code=<span class="hljs-number">200</span>, message=OK, url=<span class="hljs-keyword">https</span>://www.baidu.com/}</code>

很明显的看到除了第一个请求外,其他请求是一个有序的优先队列。

这只是一个简单的实现参考,具体实现方案还得看你自己的需求。

这样是扩展了OkHttp支持优先级调度,但是最终还是通过修改底源码实现,虽然修改的代码不多,但也是修改,在不到万不得已的情况下,还是建议不要这么干。

我将修改后的OkHttp源码放到了Github上,有兴趣的可以下过来进行参考。

https://github.com/lizhangqu/PriorityOkHttp


<think>嗯,用户现在想了解Android中进行网络请求的方法或库。之前他们问过关于Compose的问题,现在转向网络请求,可能是在开发一个需要网络交互的App。用户可能已经知道一些基础,但需要更详细的指导。 首先,我需要整理Android网络请求的发展历程。从最初的HttpURLConnection到Apache HttpClient,再到现在的OkHttp和Retrofit。用户提到的引用里提到了这些库,特别是OkHttp和Retrofit,所以需要重点介绍。 用户可能想知道不同库的优缺点,以及如何选择。比如,OkHttp作为底层库,高效且功能全面,而Retrofit则更适用于RESTful API,简化代码。需要对比它们的特性,帮助用户根据项目需求做选择。 还要考虑用户的实际应用场景。比如,如果项目需要处理大量并发请求或缓存,OkHttp的连接池和缓存机制很重要。如果是快速开发API接口,Retrofit的注解和转换器会更方便。此外,用户可能对协程和RxJava集成感兴趣,因为引用里提到了RxJava的案例。 用户提供的引用中有Retrofit和OkHttp的代码示例,特别是引用4中的RxJava结合Retrofit的使用。这可能意味着用户对异步处理和响应式编程有兴趣。需要解释这些库如何简化异步操作,避免主线程阻塞。 另外,需要提到性能优化,比如连接复用、缓存、请求合并等,这些都是用户之前引用中提到的OkHttp的优点。同时,错误处理和重试机制也是实际开发中常见的需求,OkHttp的拦截器可以在这里派上用场。 还要注意依赖引入的问题,引用5中提到了Retrofit的Gradle依赖,可能用户需要知道如何正确添加这些库到项目中。同时,是否需要考虑兼容性,比如最低支持Android版本,OkHttp支持Android 2.3以上,这对老旧项目可能很重要。 用户可能还想了解最新的库,比如Ktor或Volley,虽然引用中没有提到,但作为补充信息可以简要提及,帮助用户全面了解选项。不过根据引用内容,重点应放在OkHttp和Retrofit上。 最后,用户可能需要学习路径建议,比如从基础到高级的使用,如何结合MVVM架构,或者与LiveData、ViewModel配合使用。此外,网络请求的安全性问题,如HTTPS证书处理,也可能是一个关注点,但引用中没有提到,可能需要简要说明。 总结下来,回答需要结构清晰,分层次介绍不同方法,对比优缺点,给出使用场景和示例代码,并涉及性能优化和常见问题处理。确保覆盖用户提到的引用内容,同时补充必要的信息,帮助用户全面理解Android网络请求的库和方法。</think>### Android 网络请求方法及主流库详解 --- #### 一、核心网络请求方式演进 | 发展阶段 | 核心方案 | 特点与局限 | |-----------------|------------------------------|------------------------------------| | **基础阶段** | `HttpURLConnection` | 原生 API,需手动处理线程和流操作 | | **扩展阶段** | `Apache HttpClient` | 多线程优化(已弃用) | | **现代阶段** | `OkHttp` + `Retrofit` | 高性能、声明式 API、协程/Rx 支持 | --- #### 二、主流网络请求库对比 ##### 1. **OkHttp**(底层通信库) - **核心优势** - **连接池复用**:减少 TCP 握手开销,提升并发性能[^1][^2] - **透明压缩**:自动处理 GZIP 压缩 - **拦截器链**:支持自定义日志、缓存、重试策略 ```kotlin val client = OkHttpClient.Builder() .addInterceptor(HttpLoggingInterceptor()) // 日志拦截器 .cache(Cache(directory, 10 * 1024 * 1024)) // 10MB 缓存 .build() val request = Request.Builder() .url("https://api.example.com/data") .build() client.newCall(request).enqueue(object : Callback { override fun onResponse(call: Call, response: Response) { // 处理响应(注意:在后台线程) } }) ``` ##### 2. **Retrofit**(REST API 封装层) - **核心优势** - **声明式接口**:通过注解定义 API 端点[^5] - **数据转换**:内置支持 JSON/XML 序列化(需 `GsonConverter`) - **多范式兼容**:同步/异步、协程、RxJava 统一支持[^4] ```kotlin interface ApiService { @GET("users/{id}") suspend fun getUser(@Path("id") userId: String): User // 协程版本 @POST("login") fun login(@Body request: LoginRequest): Observable<Response> // RxJava 版本 } // 构建 Retrofit 实例 val retrofit = Retrofit.Builder() .baseUrl("https://api.example.com/") .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .client(okHttpClient) // 复用 OkHttp 客户端 .build() ``` --- #### 三、性能优化关键策略 1. **连接复用** - OkHttp 默认启用连接池(最大空闲连接数 5,存活时间 5 分钟)[^2] - 减少重复建立 TCP 连接的开销 2. **缓存机制** - 通过 `CacheControl` 控制响应缓存策略 ```kotlin val request = Request.Builder() .cacheControl(CacheControl.Builder().maxAge(1, TimeUnit.HOURS).build()) .url(url) .build() ``` 3. **请求合并与压缩** - 使用 HTTP/2 的多路复用特性(OkHttp 默认支持) - 启用 GZIP 压缩减少传输数据量 4. **超时与重试** ```kotlin OkHttpClient.Builder() .connectTimeout(15, TimeUnit.SECONDS) .readTimeout(20, TimeUnit.SECONDS) .retryOnConnectionFailure(true) // 自动重试 ``` --- #### 四、典型代码实践 ##### 协程 + Retrofit 实现异步请求 ```kotlin // ViewModel 中发起请求 viewModelScope.launch { try { val user = retrofit.create(ApiService::class.java).getUser("123") _uiState.value = UserLoadedState(user) } catch (e: IOException) { _uiState.value = ErrorState("网络连接失败") } } ``` ##### 使用拦截器实现统一认证 ```kotlin class AuthInterceptor(private val token: String) : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { val request = chain.request().newBuilder() .header("Authorization", "Bearer $token") .build() return chain.proceed(request) } } // 添加到 OkHttpClient OkHttpClient.Builder() .addInterceptor(AuthInterceptor(userToken)) ``` --- #### 五、扩展方案选型 | 特殊场景 | 推荐方案 | |---------------------|------------------------------| | **文件上传/下载** | OkHttp 的 `RequestBody`/`MultipartBody` | | **WebSocket** | OkHttp 原生支持 | | **低级别控制** | 直接使用 OkHttp 的 Raw API | | **极简需求** | `Volley`(轻量级库) | --- #### 六、常见问题解决方案 1. **HTTPS 证书校验** - 自定义 `X509TrustManager` 处理自签名证书 - 使用 `CertificatePinner` 防止中间人攻击 2. **Cookie 持久化** - 实现 `CookieJar` 接口并与 SharedPreferences 结合 3. **大文件下载中断续传** - 通过 `Range` 请求头实现断点续传 ```kotlin request.header("Range", "bytes=$downloadedLength-") ``` --- ### 扩展问题 1. **如何实现网络请求优先级调度?** - 通过 OkHttp 的 `Dispatcher` 控制最大并发数和优先级队列[^2] 2. **Retrofit 如何支持动态修改 BaseUrl?** - 使用 `@Url` 注解或在拦截器中动态替换 Host 3. **网络层如何与 Jetpack Compose 集成?** - 通过 `ViewModel` + `StateFlow` 驱动 UI 更新(结合声明式 UI 特性)[^1] --- ### 参考文献 [^1]: OkHttp 官方文档 - 连接池与缓存机制 [^2]: 《深入理解 OkHttp 设计原理》 [^4]: Retrofit 与协程/RxJava 整合指南 [^5]: Android 官方网络最佳实践
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值