Android 扩展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值传入,则该界面所有的请求都可以取消。

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

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

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private Button btn1;
    private Button btn2;
    private OkHttpClient mOkHttpClient;
    @Override

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btn1 = (Button) findViewById(R.id.btn1);
        btn2 = (Button) findViewById(R.id.btn2);
        btn1.setOnClickListener(this);
        btn2.setOnClickListener(this);
        mOkHttpClient = new OkHttpClient();
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.e("TAG", "onDestroy");
        cancelByTag(this.hashCode());
    }
    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn1:
                sendRequest();
                break;
            case R.id.btn2:
                startActivity(new Intent(this, SecondActivity.class));
                finish();
                break;
        }
    }

    private void sendRequest() {
        Request.Builder builder = new Request.Builder();

 builder.url("https://www.baidu.com").tag(this.hashCode());

        Request request1 = builder.build();
        Request request2 = builder.build();
        Request request3 = builder.build();
        Request request4 = builder.build();
        Request request5 = builder.build();
        Request request6 = builder.build();
        Request request7 = builder.build();
        Request request8 = builder.build();
        Request request9 = builder.build();
        Request request10 = builder.build();

        final Call call1 = mOkHttpClient.newCall(request1);
        final Call call2 = mOkHttpClient.newCall(request2);
        final Call call3 = mOkHttpClient.newCall(request3);
        final Call call4 = mOkHttpClient.newCall(request4);
        final Call call5 = mOkHttpClient.newCall(request5);
        final Call call6 = mOkHttpClient.newCall(request6);
        final Call call7 = mOkHttpClient.newCall(request7);
        final Call call8 = mOkHttpClient.newCall(request8);
        final Call call9 = mOkHttpClient.newCall(request9);
        final Call call10 = mOkHttpClient.newCall(request10);

        final Callback callback = new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                Log.e("TAG", "failure. isCanceled:" + call.isCanceled() + " isExecuted:" + call.isExecuted());
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                Log.e("TAG", "success. isCanceled:" + call.isCanceled() + " isExecuted:" + call.isExecuted());
            }
        };

        call1.enqueue(callback);
        call2.enqueue(callback);
        call3.enqueue(callback);
        call4.enqueue(callback);
        call5.enqueue(callback);
        call6.enqueue(callback);
        call7.enqueue(callback);
        call8.enqueue(callback);
        call9.enqueue(callback);
        call10.enqueue(callback);

    }

    public void cancelByTag(Object tag) {
        for (Call call : mOkHttpClient.dispatcher().queuedCalls()) {
            if (tag.equals(call.request().tag())) {
                call.cancel();
            }
        }

        for (Call call : mOkHttpClient.dispatcher().runningCalls()) {
            if (tag.equals(call.request().tag())) {
                call.cancel();
            }
        }
    }
}

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

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

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

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

public class Person implements Comparable<Person>{
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    @Override
    public String toString() {
        return "Person{" +
                "name=‘" + name + ‘\‘‘ +
                ", age=" + age +
                ‘}‘;
    }
    @Override
    public int compareTo(Person another) {
        return this.age-another.age;
    }
}

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

Person p1=new Person("张三",23);
Person p2=new Person("李四",12);
Person p3=new Person("王五",21);
Person p4=new Person("赵六",8);
Person p5=new Person("钱七",40);
List<Person> persons = Arrays.asList(p1, p2, p3, p4, p5);
System.out.println(persons);
Collections.sort(persons);
System.out.println(persons);

输出结果如下

[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方法即可

@Override
public int compareTo(Person another) {
    return another.age-this.age;
}

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

public class Person{
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
    @Override
    public String toString() {
        return "Person{" +
                "name=‘" + name + ‘\‘‘ +
                ", age=" + age +
                ‘}‘;
    }
}

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

public class PersonComparator implements Comparator<Person> {
    @Override
    public int compare(Person person1, Person person2) {
        return person1.getAge()-person2.getAge();
    }

}

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


Person p1=new Person("张三",23);
Person p2=new Person("李四",12);
Person p3=new Person("王五",21);
Person p4=new Person("赵六",8);
Person p5=new Person("钱七",40);

List<Person> persons = Arrays.asList(p1, p2, p3, p4, p5);
System.out.println(persons);
Collections.sort(persons,new PersonComparator());
System.out.println(persons);

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

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

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

 final class AsyncCall extends NamedRunnable{
        //other  field
        private int priority;
        private AsyncCall(Callback responseCallback, boolean forWebSocket) {
            super("OkHttp %s", originalRequest.url().toString());
            //other field
            this.priority = originalRequest.priority();
        }

        int priority() {
            return originalRequest.priority();
        }
        //other method
    }

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

public final class Request {
  //other field
  private final int priority;
  private Request(Builder builder) {
    //other field
    this.priority=builder.priority;
  }
  public int priority(){
    return priority;
  }

  //other method
  public static class Builder {
    //ohther field
    private int priority;
    private Builder(Request request) {
      //other field
      this.priority=request.priority;
    }

    public Builder priority(int priority){
      this.priority=priority;
      return this;
    }
    //other method
  }
}

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

public class AsycCallComparator<T> implements Comparator<T> {
    @Override
    public int compare(T object1, T object2) {
        if ((object1 instanceof RealCall.AsyncCall)
                && (object2 instanceof RealCall.AsyncCall)) {
            RealCall.AsyncCall task1 = (RealCall.AsyncCall) object1;
            RealCall.AsyncCall task2 = (RealCall.AsyncCall) object2;
            int result = task2.priority()
                    - task1.priority();
            return result;
        }
        return 0;
    }

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

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

    public synchronized ExecutorService executorService() {
        if (executorService == null) {
//          executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
//                  new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
            executorService = new ThreadPoolExecutor(4, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
                    new PriorityBlockingQueue<Runnable>(60, new AsycCallComparator<Runnable>()), Util.threadFactory("OkHttp Dispatcher", false));
        }
        return executorService;
    }

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

14===Response{protocol=http/1.1, code=200, message=OK, url=https://www.baidu.com/}

500===Response{protocol=http/1.1, code=200, message=OK, url=https://www.baidu.com/}

100===Response{protocol=http/1.1, code=200, message=OK, url=https://www.baidu.com/}

40===Response{protocol=http/1.1, code=200, message=OK, url=https://www.baidu.com/}

34===Response{protocol=http/1.1, code=200, message=OK, url=https://www.baidu.com/}

30===Response{protocol=http/1.1, code=200, message=OK, url=https://www.baidu.com/}

20===Response{protocol=http/1.1, code=200, message=OK, url=https://www.baidu.com/}

10===Response{protocol=http/1.1, code=200, message=OK, url=https://www.baidu.com/}

5===Response{protocol=http/1.1, code=200, message=OK, url=https://www.baidu.com/}

2===Response{protocol=http/1.1, code=200, message=OK, url=https://www.baidu.com/}

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

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

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

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

时间: 2024-12-10 12:43:20

Android 扩展OkHttp支持请求优先级调度的相关文章

Android 中OKHttp请求数据get和post

1:在Android Studio 的 build.gradle下  添加 然后再同步一下 compile 'com.squareup.okhttp:okhttp:2.4.0'compile 'com.squareup.okio:okio:1.5.0'compile 'junit:junit:4.12' 如:黄色部分 dependencies { compile fileTree(include: ['*.jar'], dir: 'libs') testCompile 'junit:junit:

关于OkHttp–支持SPDY协议的高效HTTP库 com.squareup.okhttp

转载:http://liuzhichao.com/p/1707.html OkHttp–支持SPDY协议的高效HTTP库 柳志超博客 » Program » Andriod » OkHttp–支持SPDY协议的高效HTTP库 posted in Andriod, Life on 2013/07/16 by liuzc Android为我们提供了两种HTTP交互的方式: HttpURLConnection 和 Apache HTTP Client,虽然两者都支持HTTPS,流的上传和下载,配置超时

Libgdx: android单机斗地主支持局域网wifi联网的网络模块核心代码

这个作品是我最近写的,结合我的毕业设计的通信模块和之前的单机版斗地主.我已经上架到豌豆荚了,贴了点广告,看看能不能赚点茶钱.可是一点也不乐观.因此我想分享给大家源码.只要不用于商业. 下面先贴网络模块的核心代码,第一次写这种逻辑用的udp, 经验不够,没有写的那么好看. 这里是我上架的apk,大家下载来试试也无妨: 地址 http://www.wandoujia.com/apps/com.hj.joker package com.hj.net; import java.io.ByteArrayI

android support Percent支持库开发

Android的布局支持百分比的设置进行开发,来学习如何去实现它,不过看起来会像网页的设置,比如宽度的设置属性是`layout_widthPercent`.在此之前,我们一般都会设置Linearlayout的weight权重来实现布局间的比例大小. Percent support Library提供了两个新的类: 1.PercentRelativeLayout 2.PercentFrameLayout 创建新项目 创建一个新的项目来测试,修改`build.gradle`,需要引入以下库 `app

Android中okhttp的使用

框架下载地址:https://github.com/square/okhttp 今天给大家讲解下网络框架okhttp的使用,这个框架非常强大,很多框架都用它来加载网络资源,目前很多开发者还在用AsyncHttp,这个库现在已经不推荐使用了,Google已经去掉了httpclient,下面我们就看一下okhttp的使用. 首先在build.gradle文件中,添加库并同步,如果是eclipse用户就要下载jar包导入libs即可 compile 'com.squareup.okhttp3:okht

(转载)Android之三种网络请求解析数据(最佳案例)

[置顶] Android之三种网络请求解析数据(最佳案例) 2016-07-25 18:02 4725人阅读 评论(0) 收藏 举报  分类: Gson.Gson解析(1)  版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[+] 小武:相信大家都用过网络请求解析数据,只是方法不一样而已,但是,逻辑都是差不多的: 一:AsyncTask解析数据 AsyncTask主要用来更新UI线程,比较耗时的操作可以在AsyncTask中使用. AsyncTask是个抽象类,使用时需要继承这个

使用Kotlin开发Android应用(IV):自定义视图和Android扩展

使用Kotlin开发Android应用(IV):自定义视图和Android扩展 @author ASCE1885的 Github 简书 微博 CSDN 原文链接 在读完扩展函数和默认值这篇文章之后,那么接下来要介绍什么呢?在本系列第一篇文章中我们说过,Kotlin使得Android开发更加简单,本文我们将进一步作介绍. 自定义视图 你应该还记得,在说到Kotlin的局限性时,我们提到了在Kotlin早期版本(M10之前)是不支持自定义视图的,因为当时只能为每个类创建一个构造函数.这通常是足够的,

基于Android Volley的网络请求工具

基于Android Volley的网络请求工具. 一.说明 AndroidVolley,Android Volley核心库及扩展工程.AndroidVolleySample,网络请求工具示例工程.Release,jar包.直接下载 二.Volley基本处理流程: 1.应用初始化Volley.2.Volley创建一个RequestQueue.NetworkDispatcher组及Network.3.RequestQueue即一个Request队列,RequestQueue会创建一个Executor

设计模式(一):命令模式(4)——命令模式扩展之日志请求

前言 命令模式的讲解分为四篇: 设计模式(一):命令模式(1)——基本的命令模式 设计模式(一):命令模式(2)——命令模式扩展之宏命令 设计模式(一):命令模式(3)——命令模式扩展之队列请求 设计模式(一):命令模式(4)——命令模式扩展之日志请求 一.命令模式扩展之日志请求 1.日志请求的工作方式 上一节介绍了命令模式之队列请求,本节介绍一下另一个扩展——日志请求.某些用用需要将我们所有的动作都记录在日志中,并能在系统死机之后,重新调用这些动作恢复到之前的状态.命令模式能够支持这一点.这些