Web requests are an important part of the Android client. The following is an introduction to my own Android network request practice from the beginning. Hope to give just contact Android network part of the friends some help. This article contains:

HTTP request & response

The structure of the Http request package starts at the entry level. A request is a string of text sent to the target server. What kind of text? Text with the following structure. HTTP request package structure




Request packet

Example:

POST/meme. PHP/home/user/login HTTP / 1.1 Host: 114.215.86.90 cache-control: no - Cache Postman - Token: bd243d6b-da03-902f-0a2c-8e9377f6f6ed Content-Type: application/x-www-form-urlencoded tel=13637829200&password=123456Copy the code

If the request is made, the response packet will be received (if an HTTP server exists on the other side)




Response packet

Example:

HTTP/1.1 200 OK Date: Sat, 02 Jan 2016 13:20:55 GMT Server: Apache/2.4.6 (CentOS) PHP/ 5.6.14x-POWERed-By: PHP/5.6.14 Content-Length: 78 keep-alive: timeout=5, Max =100 Connection: keep-alive content-Type: application/json; PHP/5.6.14 Content-Length: 78 keep-alive: timeout=5, Max =100 Connection: keep-alive Content-type: application/json; charset=utf-8 {"status":202,"info":"\u6b64\u7528\u6237\u4e0d\u5b58\u5728\uff01","data":null}Copy the code

There are two Http request modes

methods describe
GET Requests data at the specified URL in an empty body (for example, to open a web page).
POST Requests data for the specified URL, passing parameters (in the request body).
HEAD Similar to a GET request, except that the response body returned is empty and is used to retrieve the response header.
PUT Data transferred from the client to the server replaces the contents of the specified document.
DELETE Asks the server to delete the specified page.
CONNECT Reserved in HTTP/1.1 for proxy servers that can pipe connections.
OPTIONS Allows clients to view server performance.
TRACE The command output displays the requests received by the server for testing or diagnosis.

Only Post and Get are commonly used.

Get&Post

Key-value pairs are often used to transfer parameters in web requests (a few apis use JSON, not mainstream after all). From the above introduction, it can be seen that although the original intention of Post and Get is a form submission and a request page, there is no difference in essence. So let’s talk about where the parameters are in these two.

  • Get Enter parameters in the URL:

    http://xxxx.xx.com/xx.php?params1=value1 ¶ ms2 = value2Copy the code

    Even using routing

      http://xxxx.xx.com/xxx/value1/value2/value3Copy the code

    That’s where the Web server framework comes in.

  • The Post parameters are encoded in the request body. Encoding includes X-www-form-urlencoded and form-data. X-www-form-urlencoded is coded like this:

      tel=13637829200&password=123456Copy the code

    Form-data is encoded like this:

      ----WebKitFormBoundary7MA4YWxkTrZu0gW
      Content-Disposition: form-data; name="tel"
    
      13637829200
      ----WebKitFormBoundary7MA4YWxkTrZu0gW
      Content-Disposition: form-data; name="password"
    
      123456
      ----WebKitFormBoundary7MA4YWxkTrZu0gWCopy the code

    The superiority of X-www-form-urlencoded was obvious. X-www-form-urlencoded can only transmit key-value pairs, but form-data can transmit binary

Because the URL exists in the request line. So the difference between Get and Post is essentially whether the parameters are placed in the request line or in the request body but either way they can be placed in the request header. It is common to put some sender’s constants in the request header.

Someone says:

  • Get is clear text, Post is hidden mobile is not a browser, don’t use HTTPS everything is clear text.
  • Get transfer data upper limit XXX nonsense. The limit is the URL length in the browser, not the Http protocol. Mobile requests are not affected. The Http server section is limited Settings can be set.
  • Get Chinese requires encoding

    Is really… Pay attention to.URLEncoder.encode(params, "gbk");

Post is still recommended for parameter passing. There’s nothing better, it’s just a more harmonious society with everyone doing it.

That’s the request. Now the response. The request is a key-value pair, but the return data is usually Json. For structured data in memory, the object must be serialized into text using data description language (DSLL), passed in Http, and restored from text to structured data at the receiving end. Object (server)<–> Text (Http transport)<–> object (mobile).

Most of the data returned by the server is complex structured data, so Json is best suited. Json parsing library has a lot of Google Gson, Ali FastJson. See how Gson is used here.

HttpClient & HttpURLConnection

HttpClient has long since been deprecated, and the question of who is better is only for inexperienced interviewers. You can see why here.

Here’s how to use HttpURLConnection. That’s what we started with.

public class NetUtils { public static String post(String url, String content) { HttpURLConnection conn = null; // create a URL object URL mURL = new URL(URL); Conn = (HttpURLConnection) murl.openConnection (); conn = (HttpURLConnection) murl.openConnection (); conn.setRequestMethod("POST"); // Set the request method to Post conn.setReadTimeout(5000); // Set the read timeout to 5 seconds conn.setConnectTimeout(10000); // Set the connection timeout to 10 seconds conn.setDoOutput(true); // Set this method to allow output to the server // String data = content; OutputStream out = conn.getoutputstream (); // get an OutputStream and write data to the server. By default, the system does not allow output to the server. // Get an output stream and write data to the server out.write(data.getbytes ()); out.flush(); out.close(); int responseCode = conn.getResponseCode(); If (responseCode == 200) {InputStream is = conn.getinputStream (); if (responseCode == 200) {InputStream is = conn.getinputStream (); String response = getStringFromInputStream(is); return response; } else { throw new NetworkErrorException("response status is "+responseCode); } } catch (Exception e) { e.printStackTrace(); } finally { if (conn ! = null) { conn.disconnect(); // close the connection}} return null; } public static String get(String url) { HttpURLConnection conn = null; MURL = new url (url); conn = (HttpURLConnection) mURL.openConnection(); conn.setRequestMethod("GET"); conn.setReadTimeout(5000); conn.setConnectTimeout(10000); int responseCode = conn.getResponseCode(); if (responseCode == 200) { InputStream is = conn.getInputStream(); String response = getStringFromInputStream(is); return response; } else { throw new NetworkErrorException("response status is "+responseCode); } } catch (Exception e) { e.printStackTrace(); } finally { if (conn ! = null) { conn.disconnect(); } } return null; } private static String getStringFromInputStream(InputStream is) throws IOException { ByteArrayOutputStream os = new ByteArrayOutputStream(); Byte [] buffer = new byte[1024]; int len = -1; while ((len = is.read(buffer)) ! = -1) { os.write(buffer, 0, len); } is.close(); String state = os.toString(); // Convert the stream data to a string using utF-8 (emulator default encoding) os.close(); return state; }}Copy the code

Pay attention to network permissions! How many times I’ve been screwed.

Copy the code

Synchronous & asynchronous

These two concepts exist only in multithreaded programming. By default, Android has only one main thread, also known as the UI thread. Because View drawing can only be done in this thread. So if you block the thread (something made it run here for N seconds), the View will not draw during that time, and the UI will freeze. It is important to avoid time-consuming operations on the UI thread. A network request is typically a time consuming operation. There is only one line of code to make a network request through the Utils class above.

NetUtils.get("http://www.baidu.com"); // This line of code will execute for hundreds of milliseconds.Copy the code

If you write it like this

@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); String response = Utils. Get (" http://www.baidu.com "); }Copy the code

Will die. That’s how you synchronize. A direct time-consuming operation blocks the thread until the data is received and then returns. Android doesn’t allow it. Asynchronous mode:

// The Handler on the main thread new will do subsequent processing on the main thread. private Handler handler = new Handler(); private TextView textView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView = (TextView) findViewById(R.id.text); New Thread(new Runnable() {@override public void run() {// Obtaining data from the network Final String Response = NetUtils.get("http://www.baidu.com"); Handler.post (new Runnable() {@override public void run() {// Update UI textView.settext (response); }}); } }).start(); }Copy the code

The child thread performs time-consuming operations and sends UI updates to the main thread through Handler. This is called asynchrony. Handler is an important part of the Android threading model, not to mention something that has nothing to do with networking. If you don’t know anything about Handler, Google it first. The Handler principle is a good article

But that’s ugly. Asynchronous is usually accompanied by callbacks from his good gay friends. This is the Utils class encapsulated by a callback.

public class AsynNetUtils { public interface Callback{ void onResponse(String response); } public static void get(final String url, final Callback callback){ final Handler handler = new Handler(); new Thread(new Runnable() { @Override public void run() { final String response = NetUtils.get(url); handler.post(new Runnable() { @Override public void run() { callback.onResponse(response); }}); }}); } public static void post(final String url, final String content, final Callback callback){ final Handler handler = new Handler(); new Thread(new Runnable() { @Override public void run() { final String response = NetUtils.post(url,content); handler.post(new Runnable() { @Override public void run() { callback.onResponse(response); }}); }}); }}Copy the code

Then use methods.

private TextView textView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView = (TextView) findViewById(R.id.webview); AsynNetUtils.get("http://www.baidu.com", new AsynNetUtils.Callback() { @Override public void onResponse(String response) { textView.setText(response); }});Copy the code

Isn’t it much more elegant? Well, a silly Internet request scheme has taken shape. There’s a lot of stupidity:

  • So every time I have a new Thread, I have a new Handler that’s consuming too much
  • There is no exception handling mechanism
  • No caching mechanism
  • No complete API(headers, parameters, encodings, interceptors, etc.) and debug mode
  • There is no Https

HTTP caching mechanism

Caching is very important on mobile.

  • Reduce the number of requests and reduce server stress.
  • Local data is read faster, keeping pages from being blank for hundreds of milliseconds.
  • Provide data without a network.

The cache is generally controlled by the server (you can control the cache locally in some ways, such as adding cache control information to the filter). By adding the following endpoints to the request header:

Request

Request header field meaning
If-Modified-Since: Sun, 03 Jan 2016 03:47:16 GMT The time when the cached file was last modified.
If-None-Match: “3415g77s19tc3:0” Etag(Hash) value of the cache file
Cache-Control: no-cache Not using caching
Pragma: no-cache Not using caching

Response

Response header field meaning
Cache-Control: public The response is cached in common and is useless on the mobile end
Cache-Control: private The response is privately cached and useless on the mobile side
Cache-Control:no-cache Don’t cache
Cache-Control:no-store Don’t cache
Cache-Control: max-age=60 Cache expiration after 60 seconds (relative time)
Date: Sun, 03 Jan 2016 04:07:01 GMT The time when the current response is sent
Expires: Sun, 03 Jan 2016 07:07:01 GMT Cache expiration time (absolute time)
Last-Modified: Sun, 03 Jan 2016 04:07:01 GMT The time when the server side file was last modified
ETag: “3415g77s19tc3:0” Etag[Hash] value of the server file

Formal use may include only some of these fields as required. The client stores the request information based on this information. The cache is then checked when the client initiates the request. Follow these steps:




Browser caching mechanism

Note that the server returned 304 meaning the data has not changed to read the cache information. As a young man, I was happy to add caching to the web request framework I wrote, until ONE day I saw two things. (Д T/T) /

Volley&OkHttp

Volley&OkHttp should be the most commonly used network request library nowadays. The usage is also very similar. Both manage network requests by constructing requests to be queued.

First, Volley: Volley can be relied on through this library. Volley uses HttpURLConnection on Android 2.3 or later, and HttpClient on Android 2.2 or later. Volley has a cache thread, a network request thread pool (default 4 threads). Volley is less efficient to use directly, so I packaged the various skills I use Volley into a library RequestVolly. In this library I encapsulate the way requests are constructed as functional calls. Maintain a global request queue and extend some convenient apis.

However, Volley can never match OkHttp in terms of scalability. Volley stopped updating, while OkHttp was officially accepted and constantly refined. So I ended up replacing it with OkHttp

See OkHttp usage here for the friendly API and detailed documentation. This article is also very detailed. OkHttp uses Okio for data transfer. They belong to the Square family. But not directly using OkHttp. Square has also released a Retrofit library that doubles OkHttp’s capabilities.

Retrofit&RestAPI

Retrofit greatly simplifies web requests. It’s a Rest API management library that allows you to make web requests directly using OKHttp without having to configure OKHttp. Square, after all. RestAPI is a software design style. Servers serve as repositories for resources. The client requests GET,PUT, POST, and DELETE resources. And it’s stateless, there’s no session involved. The API design is the most important aspect of the interaction between mobile and server. Let’s say this is a standard login interface.




Paste_Image.png

You should be able to see what the request and response packages for this interface look like. Request mode, request parameters, response data, all very clear. Using Retrofit, these apis can be visually reflected in the code.




Paste_Image.png

You can then use Retrofit to provide you with an implementation class for this interface to make a direct network request for structural data.

Note that Retrofit2.0 has a large number of incompatible updates compared to 1.9. Most tutorials on Google are based on 1.9. Here is a 2.0 tutorial.

The tutorial uses Call for asynchronous requests. Retrofit’s greatest strength is its support for RxJava. The one I return in the image above is an Observable. RxJava is difficult to get started, but once you use it, you can’t live without it. Retrofit+OkHttp+RxJava works with the framework to produce tons of output, which I won’t go into here.

I think I’ve reached the top here.

Network image loading optimization

For image transmission, just like the avatar field of the login interface above, the image is not written directly in the returned content, but is given the address of an image. Load as needed.

If you just use HttpURLConnection to get an image, you can do it, but without optimization it’s just a BUG. It should never be used formally. Note that network images have some characteristics:

  1. It never changes the image associated with a link generally never changes, so when an image is first loaded, it should be cached permanently and no further network requests should be made.
  2. It takes up a lot of memory a picture small dozens of k more than a few M HD code. The sizes are also 64*64 to 2K graphics. You can’t just display it in the UI, or even put it in memory.
  3. It takes a long time to load an image and it takes a few hundred ms to several megabytes to load it. UI placeholder functionality must also be considered during this period.

Tell me about the image processing I did in RequestVolley mentioned above (yes I did, this part of the code can go to Github to see the source code).

Three levels of cache

It’s often said on the Internet that there are three levels of cache – server, file, and memory. But I don’t think a server is level 1 cache, it’s a data source.

  • Memory cache First memory cache uses LruCache. LRU is the Least Recently Used algorithm. A size is determined here. When the total size of objects in the Map is greater than this size, the Least frequently Used objects are released. I limit the memory size to 1/8 of the available memory of the process. The data read from the memory cache is returned directly, and the data read from the hard disk cache is requested.

  • Disk cache Disk cache uses DiskLruCache. This class is not in the API. You have to copy it. See the LRU. I set the hard disk cache size to 100M.

    @Override public void putBitmap(String url, Bitmap bitmap) { put(url, bitmap); Try {Editor Editor = mDiskLruCache. Edit (hashKeyForDisk(url)); bitmap.compress(Bitmap.CompressFormat.JPEG, 100, editor.newOutputStream(0)); editor.commit(); } catch (IOException e) { e.printStackTrace(); }} // Create is called when there is no required data in the memory Lru cache. @override protected Bitmap create(String URL) {// Obtain key String key = hashKeyForDisk(URL); // Read data from hard disk Bitmap Bitmap = null; try { DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key); if(snapShot! =null){ bitmap = BitmapFactory.decodeStream(snapShot.getInputStream(0)); } } catch (IOException e) { e.printStackTrace(); } return bitmap; }Copy the code

    The principle of DiskLruCache is no longer explained (I also fixed a BUG where when adding or deleting records to the Log, the last entry was not output, causing the last entry to remain invalid).

  • The hard disk cache also returns empty with no data, and then requests data from the server.

That’s the whole process. But there are limitations to my approach.

  • Images are stored directly for use without compression
  • File operations are in the main thread
  • There is no complete image processing API

I thought it was good enough until I met the next two.

Fresco&Glide

It’s a no-brainer that they’re all perfectly optimized, and it’s silly to repeat the wheel. Fresco is Facebook’s dark tech. Just looking at the feature description shows that it is very powerful. The official blog is full of details. True level 3 cache, BItmap after transformation (memory), original image before transformation (memory), hard disk cache. Extreme memory management. Apps that use heavy images should be very good. SimpleDraweeView replaces ImageView with SimpleDraweeView, which is more intrusive, depending on the size of its APK package. It’s a huge amount of code.

So I prefer Glide, written by BumpTech. The library has been used extensively in Google’s open source projects, including the official app released at Google I/O in 2014. Here are the details. Use ImageView directly, no initialization, minimal API, rich extension, chain call is what I like. Rich extension refers to this. I’ve also used Picasso. The API is almost identical to Glide, with slightly fewer features and six months of unfixed bugs.

Picture Management scheme

Let’s talk about image storage. Do not exist on their own server, increase traffic pressure, there is no picture processing function. Recommend qiniu and Ali cloud storage (have not used other π__π). They all have a very important image processing. Add parameters to the image Url to do some processing and transfer of the image. So (seven cows processing code)

public static String getSmallImage(String image){ if (image==null)return null; if (isQiniuAddress(image)) image+="? imageView2/0/w/"+IMAGE_SIZE_SMALL; return image; } public static String getLargeImage(String image){ if (image==null)return null; if (isQiniuAddress(image)) image+="? imageView2/0/w/"+IMAGE_SIZE_LARGE; return image; } public static String getSizeImage(String image,int width){ if (image==null)return null; if (isQiniuAddress(image)) image+="? imageView2/0/w/"+width; return image; }Copy the code

It can both speed up requests and reduce traffic. Match with Fresco or Glide. Perfect image loading solution. But that requires you to store all your images on Qiniu or Aliyun, which is fine.

Image/file uploads are also stored using a third party using them, and they all have an SDK with official documentation to teach you. However, the image must be compressed and uploaded. There is no point uploading 1-2m large hd photos.