Android 擴充OkHttp支援要求優先順序調度

來源:互聯網
上載者:User

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

@Overridepublic 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 {    @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 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 implements Comparator {    @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(), Util.threadFactory("OkHttp Dispatcher", false));            executorService = new ThreadPoolExecutor(4, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,                    new PriorityBlockingQueue(60, new AsycCallComparator()), 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上,有興趣的可以下過來進行參考。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.