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;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn1 = (Button) findViewById(R.id.btn 1);
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{
//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上,有興趣的能夠下過來進行參考。
- PriorityOkHttp
Android 擴展OkHttp支持請求優先級調度