A line of code to achieve Okhttp,Retrofit,Glide download and upload progress monitoring

8219 Read 11 minutes

The original address: http://www.jianshu.com/p/5832c776621f

introduction

Publish the previous article I do not write a line of code Toolbar! And you're still encapsulating BaseActivity? It was already a month ago, when some people said I was the title party, and some people did not recognize my content, but this did not hinder me, two days won the nuggets when the first week list, and was reproduced by Hong Yang public number, the cumulative reading volume of more than 30,000

The research results of the previous article enable MVPArms to listen to the life cycle of all activities and fragments of the entire App (including three-party libraries), and can insert code into its life cycle. This time I take another recent research results to report to you, of course, the same New features on MVPArms

Github: Your Star is what keeps me going ✊

gif

Listing requirements

Uploading and downloading is a necessary function of most apps, and displaying the progress bar is also an important part of improving user experience. Of course, as the author of the configurable integration framework MVPArms, I want to improve the user experience and development efficiency of developers again, so I must provide a set of solutions

So I opened Github simple search and Retrofit, Okhttp, Glide related progress monitoring library, there are a lot of libraries, but did not meet the needs I want, so I rolled up my sleeves, ready to masturbate, of course, before opening a simple comb their own needs

  1. The library must support multiple platforms,Okhttp, Retrofit, Glide must be supported at the same time
  2. Although these three libraries are supported, the library cannot contain these three libraries, allowing users to introduce them themselves and reduce the volume of the library
  3. Use must be simple!! Better be a line of code
  4. Low intrusion, does not need to change the network request code written before, introduce or not introduce this library, can not have any impact on the previous code
  5. Low coupling, the code that the user makes the network request, must not have too much correlation with the code of the progress receiver
  6. Progress information about a network request can be received anywhere in the App
  7. It is not only necessary to meet the one-to-one relationship between a data source and a progress receiver, but also to meet the one-to-many relationship between a data source and multiple progress receivers, so that the progress bar in multiple different locations can be updated synchronously
  8. By default, it runs on the main thread, allowing users to have less trouble switching threads

Demand analysis and research

Cool all at once, write so many needs, when the product manager is a word cool!

Take a closer look at these 8 needs, instant meng forced, sister this is not pit yourself? In addition to the last item, I know that I can use Handler to achieve it, and other ideas are completely out of my mind. Come on, as a high-quality young man, I have to go ahead despite difficulties. Let's start with the first demand analysis!

Requirement 1 (Multi-platform support)

Read Google before writing found that Okhttp to achieve upload and download progress monitoring, it is not difficult, only rewrite RequestBody and ResponseBody, and cooperate with Interceptor to each request the original RequestBody and ResponseBody replacement, you can do that, it's all template code, you can just copy and paste it, and Retrofit uses Okhttp underneath, so you can do the same thing with progress monitoring

But how does Glide implement progress monitoring? My first reaction was that since Retrofit can be easily implemented using the Okhttp request network, it is also possible to change Glide's underlying request framework to Okhttp. As a library of such great force, there must be a way to extend it, so I immediately turned to Glide Source code, confirmed their own ideas, found that Glide bottom is the use of HttpConenction to request the network, and this class can be replaced, quickly Google under

compile 'com. Making. Bumptech. Glide: okhttp3 - integration: 1.4.0 @ aar'

ok, find a solution, use the classes provided above to replace the underlying request framework with Okhttp, the most core place of this framework has found a way to achieve, mainly through Okhttp, like eating reassurance, instant comfort

Requirement 2 (Volume reduction)

This requirement is Googled and is very simple. The dependency framework is provided, and the framework introduced during packaging is not included

Requirement 3 (one-line implementation)

For this kind of external Api design requirements, we should implement the main function, and then slowly optimize to achieve the goal, so first analyze the following requirements

Requirement 4 (Low intrusion)

As mentioned in requirement 1, the key to implement upload and download progress monitoring is to replace the original RequestBody and ResponseBody of each request with a rewritten one in the Interceptor

How do I identify requests to monitor progress?

Replacement is easy, but not every request needs to monitor the upload and download progress, it is impossible to replace every request. At first, I think of generating a tag for the request that needs to monitor the progress, and then analyzing this tag in the Interceptor will indicate that the request needs to monitor the upload or download progress, and then start to replace it

Therefore, I think the simplest way is to add a custom Header to the request, so that there is no need to define other classes. The Interceptor traverses all headers and finds this custom Header, and can replace it

But that doesn't solve the need 4, because this allows the user to request more operations than usual, if you want to make the previous code with progress monitoring function, you have to change one by one, increasing the amount of labor, and this operation is generated for my library, when the user does not want to use the library, it will involve modifying the previous code, which also increases the intrusion

Url as tag

A thought flashed, but also what mark, Url is unique, can not be used as a mark!!!

Requirement 5 (low coupling), Requirement 6 (reception anywhere), and requirement 7 (one-to-many)

Borrow the EventBus idea

Why put these three requirements together, because these three requirements remind me of EventBus, where multiple observers register themselves into a container using the same tag, and the observer uses that tag to Post An event, and then from this container to take out all the registered observers with this tag, one by one notification, so that both decouple, and as long as the tag is known, in the App anywhere can listen, also support one-to-many

Coupled with the use of Url as a marker mentioned in requirement 4, I can do not need to change one of the previously requested code, just write the receiver code to achieve the above requirements

Idea Api

Since we are talking about EventBus, I will use the EventBus Api design, the user only one line of code, passing in a tag and an event to achieve upload and download progress monitoring, yes, the tag is the Url, The event is a listener for getting progress information, which satisfies the one-line implementation of requirement 3

Like this

ProgressManager.post(tag, event);

After the user calls this line of code, I put the Url as the Key and the listener as the value into a globally unique Map

Wait a minute? What about one to many? So the value must be List< listener >, which satisfies the one-to-many condition

How to notify listeners internally?

We've registered all the listeners for the urls that need to be listened to in this container, so when should we notify the listeners of the progress, in RequestBody and ResponseBody of course When I start writing or reading binary streams, because only they know the read and write times the first time, now I just need to put all the listeners of the corresponding Url into his Body

As mentioned in Requirement 4, we do not know which requests need to monitor the upload or download progress and which are not, but now we can distinguish by the Url, because we can get the Url of the Request in the Interceptor

We have registered the Url as the Key into the container before. If the container contains this Url, it means that the request needs to monitor the upload or download progress, then we will replace it with a rewritten Body and pass in the listener. After the rewritten Body, binary stream reading will occur Or write continuously through all the listeners of this Url, call the listener's listening method, and pass the progress information, you can perform the user's update logic, which is done

Requirement 8 (Mainline execution)

This is simple, using Handler.post(Runnable) to call the listener's method in Runnable

Framework detail optimization

No manual logout required

We all know that after EventBus registered observers, when the event does not need to be accepted, it needs to be manually logged out, but applied to my library, the reception of the event may not need to be so rigorous, so in order to save users extra steps, I use WeakHashMap instead of the previous Map container, this WeakHashMap finds unused keys during memory reclamation by the Java virtual machine and removes the entire entry, so manual removal () is not required.

lock

As mentioned above, the user only needs one line of code to add the Url and listener to the container, but this line of code may be called in different threads, and some logic in this line of code is not safe in multiple threads, so I need to add thread locks, which is very important for the three-way library, because you can't predict some user operations

Throw clear errors to the user

As I mentioned in requirement 2, Okhttp is not included in the aar package because the library only provides Okhttp, so users who do not include Okhttp in their projects are raised as NoClassDefFoundError This error, however, will make the user not know the real cause of the error, so I will use the library initialization, Class.forName("okhttp3.OkHttpClient"); If I can't find the Okhttp class, the user didn't introduce Okhttp, and I throw a very clear error

Improve performance

Because I mentioned above that when the Body starts reading or writing binary streams, I'm constantly going through all the listeners and calling its listening methods to get a one-to-many update

However, when the number of listeners reaches a certain level, there will be performance problems, and during the pass, and maybe the user will continue to add new listeners, changing the length of the container during the pass is prone to errors

So when I pass the List into the Body, I will put this list.toarray (), the array is allocated to a continuous memory area and the length is fixed, so the index efficiency is advantageous, then use the array to traverse, because the array length is fixed, so there will be no problem of traversal length change

Distinguish between multiple progressions for the same Url

Because the App user may continue to use the same Url to start a new request before the previous progress has been uploaded or downloaded, if the framework user does not do the operation of removing repeated clicks at the upper level, the same Url There will be more than one progress update in progress at the same time, which requires an identifier to distinguish which progress information (all progress updates for this Url will call the listener registered with this Url), so I will create the Body System.currentTimeMillis() is stored as a unique ID, passing progress information along with the Id to the user each time

Sum up

In fact, this library is relatively simple, the core way to achieve in many places can be copied and pasted to, but after I such a package is still better than the previous way, simple and elegant, and the purpose of writing this article is also to share, how to analyze requirements, and how to package and optimize a small library, of course, usually also want to read more source code, and constantly accumulate and learn from excellent Ideas will be inspired when creating, for example, my library is borrowed from the idea of EventBus, when writing code should dare to think and try new ideas that are different from the previous, will continue to progress

Github: The specific implementation also depends on the source code is not? Remember to give Star ✊ thanks!

Public account

Scan the code and follow my public account JessYan to learn and progress together. If there is an update to the framework, I will also notify you at the first time on the public account


Hello My name is JessYan, if you like my articles, you can follow me on the following platforms

  • Personal homepage: jessyan.me
  • GitHub: github.com/JessYanCodi…
  • Nuggets: juejin.cn/user/976022...
  • Jane: www.jianshu.com/u/1d0c0bc63...
  • Weibo: weibo.com/u/178626251...

-- The end