Android 擴展OkHttp支持請求優先級調度

在當今這個App氾濫的時代,網絡請求差點兒是每一個App不可缺乏的一部分。請求差點兒遍及App的每一個界面中。咱們進入A界面後。App發起了一系列請求,這時候假如另外一部分請求沒有被運行,咱們就進入B界面開始新的網絡請求。這時候原來A界面的網絡請求咱們有兩個選擇:php

  • 取消A界面的所有未開始運行的網絡請求
  • 不取消A界面的所有網絡請求,但是B界面的請求要優先於A界面的請求運行,B界面的網絡請求運行完成後再去運行A界面未運行完成的請求。

對於第一種狀況,咱們很是好作到。在Activity的onDestroy回調中取消該界面中所有請求,這裏需要明白一點,本篇文章的網絡層是OkHttp,既然選擇了OkHttp。假設要在onDestroy中取消未開始運行以及已經開始運行的網絡請求,就必須給每一個請求設置一個tag。而後經過該tag來需要網絡請求。java

比較明智的作法是以該Activity的上下文的hash值做爲tag。git

取消請求時將hash值傳入。則該界面所有的請求都可以取消。github

但是實際狀況並非如此,有一部分網絡請求咱們不想取消它,仍然想要進行請求,因爲這部分的請求比較重要。需要拉到client進行使用,取消這個請求可能會帶來沒必要要的麻煩,所以,咱們需要保留這些請求。但是咱們進入了一個新的界面,新界面的網絡優先級比較高。應該先被運行,這就是另一種狀況。markdown

每種狀況有相應的解決方法。第一種狀況顯得比較簡單,咱們先來實現它。網絡

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();
            }
        }
    }
}

當咱們點擊發送請求的button以後,所有請求都被設置了一個tag後發送出去,而後咱們需要高速的點擊跳轉button,讓當前頁面finish掉,以後就會回調onDestroy方法,onDestyoy方法中咱們調用了取消請求的方法。假設還有請求沒有開始運行,該請求就會被取消掉。這樣,第一種狀況就簡單的實現了下。異步

在實現另一種狀況的時候。咱們需要知道一個概念,就是一個集合中怎樣對元素進行排序,一般,有兩種作法。ide

  • 將待比較的類實現Comparable接口,調用Collections.sort(list)方法進行排序
  • 新建一個類實現Comparator接口。調用Collections.sort(list,comparator)方法進行排序

假如現在咱們有一個類叫Person。它有兩個屬性,name和age。咱們有一個List,裏面都是Person。咱們但願對這個List進行排序,並且排序的原則是依據age從小到大排序。post

依照實現Comparable接口的方法,咱們需要將Person實現該接口,就像這樣子。ui

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{
        //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上,有興趣的可以下過來進行參考。

相關文章
相關標籤/搜索