Android extended OkHttp supports request Priority Scheduling
In today's era of App flooding, network requests are almost an essential part of every App, and requests are spread across almost every interface of the App. After we enter interface A, the App initiates A series of requests. If some of the requests are not executed, we enter interface B to start A new network request, at this time, we have two options for the original network request on interface:
Canceling all unexecuted network requests on interface A does not cancel all network requests on interface A. However, requests on interface B take precedence over requests on interface, after the network request of interface B is executed, the request that has not been executed on interface A is executed.
In the first case, we do our best to cancel all requests on the interface in the onDestroy callback of the Activity. here we need to make it clear that the network layer of this article is OkHttp. Since OkHttp is selected, if you want to cancel a network request that has not started or started to be executed in onDestroy, you must set a tag for each request and use the tag to request a network request. It is wise to use the hash value of the context of the Activity as the tag. When the hash value is passed in when the request is canceled, all requests on the interface can be canceled.
However, this is not the case. Some network requests do not want to be canceled, but still need to be requested because these requests are important and need to be pulled to the client for use, canceling this request may cause unnecessary trouble. Therefore, we need to keep these requests. However, we entered a new interface. The network in the New Territories has a high priority and should be executed first. This is the second case.
There is a corresponding solution for each case. The first case is relatively simple. Let's implement it first.
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(); } } }}
After clicking the send request button, all requests are sent with a tag. Then, we need to quickly click the jump button to finish the current page, the onDestroy method will be called back later. In the onDestyoy method, we call the method to cancel the request. If there are other requests that have not started execution, the request will be canceled. In this way, the first case is simply implemented.
In the second case, we need to know the concept of how to sort elements in a collection. Generally, there are two methods.
Implement the Comparable interface for the classes to be compared, call the Collections. sort (list) method for sorting, create a class for the Comparator interface, and call the Collections. sort (list, comparator) method for sorting
Assume that we have a class named Person, which has two attributes: name and age. We have a List, which contains all persons. We want to sort the List, in addition, the sorting principle is to sort by age from small to large. According to the method for implementing the Comparable interface, we need to implement the Person interface, just like this.
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; }}
At this time, we will generate a List Of all Person instances. We will call the sort method to sort the List and see how the result works.
Person p1 = new Person ("Zhang San", 23); Person p2 = new Person ("Li Si", 12); Person p3 = new Person ("Wang Wu", 21 ); person p4 = new Person ("Zhao ", 8); Person p5 = new Person ("Qian Qi", 40); List
Persons = Arrays. asList (p1, p2, p3, p4, p5); System. out. println (persons); Collections. sort (persons); System. out. println (persons );
The output result is as follows:
[Person {name = 'zhang san', age = 23}, Person {name = 'Li si', age = 12}, Person {name = 'wang wu', age = 21 }, person {name = 'zhao 6', age = 8}, Person {name = 'Qian 7', age = 40}]
[Person {name = 'zhao liu', age = 8}, Person {name = 'Li si', age = 12}, Person {name = 'wang 5 ', age = 21}, Person {name = 'zhang san', age = 23}, Person {name = 'Qian 7', age = 40}]
You can see that the order is sorted by age, and from small to large, so if you want to sort from large to small, it is very easy to modify the compareTo method.
@Overridepublic int compareTo(Person another) { return another.age-this.age;}
If the Comparator interface is implemented, we do not need to change the Person class. The original Person class is as follows:
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 + '}'; }}
Instead, you can create a new class to implement the Comparator interface.
public class PersonComparator implements Comparator
{ @Override public int compare(Person person1, Person person2) { return person1.getAge()-person2.getAge(); }}
When sorting, pass in the comparator.
Person p1 = new Person ("Zhang San", 23); Person p2 = new Person ("Li Si", 12); Person p3 = new Person ("Wang Wu", 21 ); person p4 = new Person ("Zhao ", 8); Person p5 = new Person ("Qian Qi", 40); List
Persons = Arrays. asList (p1, p2, p3, p4, p5); System. out. println (persons); Collections. sort (persons, new PersonComparator (); System. out. println (persons );
After we know how to compare and sort a class, we start our official content and let okhttp support priority scheduling, that is, the second case at the beginning of the article. The Network request of interface B has A higher priority than that of interface A. Therefore, we should have A variable to represent this priority. Then we need to sort according to the priority.
Unfortunately, by default, Okhttp does not support priority scheduling. We have to modify the underlying source code of OkHttp for extended support, but this is a last resort.
In the RealCall class, there is an internal class AsyncCall, and all the Asynchronous Network requests will be encapsulated into this type. NewCall in OkHttpClient wraps the Request object into RealCall, while enqueue in RealCall converts itself into an AsyncCall object for asynchronous execution. AsyncCall is the indirect subclass of the Runnale object. Therefore, variables representing the priority should be stored in the AsyncCall class, that is, 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 }
Similarly, we need to expose the priority variable in the Request, namely, 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 }}
Then we need to implement a comparator to sort by priority from large to small
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; }
Then there is a Dispatcher in OkHttp, and an ExecutorService In the Dispatcher. ExecutorService can be configured by itself, and then can be scheduled according to the priority, the default distributor uses SynchronousQueue for scheduling. We need to change it to a priority queue, comment out the original new object, and replace it with our priority queue, the creation of the priority queue requires a comparator, that is, the comparator we just created.
The following method is used to set the thread pool in the 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; }
Then, we simulate sending 10 requests with different priorities and the priority is out of order, and the console will output
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/}
Obviously, except the first request, other requests are an ordered priority queue.
This is just a simple implementation reference. The specific implementation scheme depends on your own needs.
In this way, OkHttp supports priority scheduling, but it is implemented by modifying the source code. Although the modified Code is not much, it is also modified. In this case, we recommend that you do not do this.
I put the modified OkHttp source code on Github. If you are interested, refer to it for reference.