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

弓背霞明剑照霜,秋风走马出咸阳。这篇文章主要讲述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值传入。则该界面全部的请求都能够取消。
可是实际情况并不是如此,有一部分网络请求我们不想取消它,仍然想要进行请求,由于这部分的请求比較重要。须要拉到client进行使用,取消这个请求可能会带来不必要的麻烦,因此,我们须要保留这些请求。可是我们进入了一个新的界面,新界面的网络优先级比較高。应该先被运行,这就是另外一种情况。

每种情况有相应的解决方法。第一种情况显得比較简单,我们先来实现它。
public class MainActivity extends AppCompatActivity implements View.OnClickListener { private Button btn1; private Button btn2; private OkHttpClient mOkHttpClient; @Overrideprotected 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(); } } } }

当我们点击发送请求的button之后,全部请求都被设置了一个tag后发送出去,然后我们须要高速的点击跳转button,让当前页面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进行排序,而且从小到大的排了顺序。那么假设要从大到小排序呢,非常easy,改动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{ //otherfield 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上,有兴趣的能够下过来进行參考。
【Android 扩展OkHttp支持请求优先级调度】
  • PriorityOkHttp


    推荐阅读