Android——Loading images with HTTP POST

Keywords: Android network Gradle xml

When we develop Android, we usually use image loading framework to display pictures, such as universal limage loader, Glide and so on. Developers only need to pass the URL of the network picture and the picture control to display. Simple and rough. But if you encounter a fantastic need like mine (the picture is a URL for a post request), you're right about this article.
As we all know, the URL that requests to query data or open an image is GET request, of course, it is ok to use post. However, the test found that the two image loading frameworks of universal limageloader and Glide in the project were useless when processing the image URLs of POST requests. Guess that GET requests are used by default in both frameworks, so check the source code, find the corresponding code and then customize or modify it to POST requests.
Here I record my operation for follow-up inspection.
1. Universal limage loader. Source code modification.
Because the universal limageloader framework used in the project uses the source code, so here is to directly find the source code for modification, if you can not directly modify the source code, do not know whether to provide a custom method to modify (Glide is using custom loading, you can refer to, but I did not study).
The com.nostra13.universalimageloader.core.download.BaseImageDownloader class is the framework that uses HttpURLConnection to get InputStream through GET request to display images. HttpURLConnection does not set the request mode, but uses the default GET request. So, when we create the HttpURLConnection, we add the code that sets the request as POST.

    // Source code
    protected HttpURLConnection createConnection(String url, Object extra) throws IOException {
        String encodedUrl = Uri.encode(url, ALLOWED_URI_CHARS);
        HttpURLConnection conn = (HttpURLConnection) new URL(encodedUrl).openConnection();
        conn.setConnectTimeout(connectTimeout);
        conn.setReadTimeout(readTimeout);

        return conn;
    }

    // Modified code
    protected HttpURLConnection createConnection(String url, Object extra) throws IOException {
        String encodedUrl = Uri.encode(url, ALLOWED_URI_CHARS);
        HttpURLConnection conn = (HttpURLConnection) new URL(encodedUrl).openConnection();
        conn.setConnectTimeout(connectTimeout);
        conn.setReadTimeout(readTimeout);

        // Use POST only for our projects, or for some URL s that only use POST to get pictures, while others still use GET.
        if (url.startsWith("XXXXXXX")) {
            conn.setRequestMethod("POST");
        }

        return conn;
    }

After testing, OK.

2, Glide. Custom loading class.
The Glide framework is used in the form of a gradle configuration, so it is impossible to modify the source code. But by looking at the source code, we found that it requested the URL to get the class of InputStream.

com.bumptech.glide.load.data.HttpUrlFetcher

The following are the specific methods of requests:

private InputStream loadDataWithRedirects(URL url, int redirects, URL lastUrl, Map<String, String> headers)
            throws IOException {
        if (redirects >= MAXIMUM_REDIRECTS) {
            throw new IOException("Too many (> " + MAXIMUM_REDIRECTS + ") redirects!");
        } else {
            // Comparing the URLs using .equals performs additional network I/O and is generally broken.
            // See http://michaelscharf.blogspot.com/2006/11/javaneturlequals-and-hashcode-make.html.
            try {
                if (lastUrl != null && url.toURI().equals(lastUrl.toURI())) {
                    throw new IOException("In re-direct loop");
                }
            } catch (URISyntaxException e) {
                // Do nothing, this is best effort.
            }
        }
        urlConnection = connectionFactory.build(url);
        for (Map.Entry<String, String> headerEntry : headers.entrySet()) {
          urlConnection.addRequestProperty(headerEntry.getKey(), headerEntry.getValue());
        }
        urlConnection.setConnectTimeout(2500);
        urlConnection.setReadTimeout(2500);
        urlConnection.setUseCaches(false);
        urlConnection.setDoInput(true);

        // Connect explicitly to avoid errors in decoders if connection fails.
        urlConnection.connect();
        if (isCancelled) {
            return null;
        }
        final int statusCode = urlConnection.getResponseCode();
        if (statusCode / 100 == 2) {
            return getStreamForSuccessfulRequest(urlConnection);
        } else if (statusCode / 100 == 3) {
            String redirectUrlString = urlConnection.getHeaderField("Location");
            if (TextUtils.isEmpty(redirectUrlString)) {
                throw new IOException("Received empty or null redirect url");
            }
            URL redirectUrl = new URL(url, redirectUrlString);
            return loadDataWithRedirects(redirectUrl, redirects + 1, url, headers);
        } else {
            if (statusCode == -1) {
                throw new IOException("Unable to retrieve response code from HttpUrlConnection.");
            }
            throw new IOException("Request failed " + statusCode + ": " + urlConnection.getResponseMessage());
        }
    }

Obviously, it also uses the default request mode GET of HttpURLConnection. If you're using source code, you can make judgments and set POST requests before urlConnection.connect() in the same way as the universal limageloader.
If you can't modify the source code directly like me, you can use the following ways:
1. Copy the HttpUrlFetcher class and change the name to HttpUrlPostFetcher. The modification is to make judgments and set up POST requests before urlConnection.connect(); methods.
2. Create a new subclass of ModelLoader, HttpUrlPostLoader, which indicates the use of the newly customized HttpUrlPostFetcher class. The code is as follows:

public class HttpUrlPostLoader implements ModelLoader<GlideUrl, InputStream> {

    public static class Factory implements ModelLoaderFactory<GlideUrl, InputStream> {

        public Factory() {}

        @Override
        public ModelLoader<GlideUrl, InputStream> build(Context context, GenericLoaderFactory factories) {
            return new HttpUrlPostLoader();
        }

        @Override
        public void teardown() {}
    }

    public HttpUrlPostLoader() {
    }

    @Override
    public DataFetcher<InputStream> getResourceFetcher(GlideUrl model, int width, int height) {
        return new HttpUrlPostFetcher(model);
    }
}

3. Custom GlideModule, a subclass of GlideModule to be used, using ModelLoader created in the second step. The code is as follows:

public class CustomGlideModule implements GlideModule {

    @Override
    public void applyOptions(Context context, GlideBuilder builder) {
        //Configuration through builder.setXXX.
    }

    @Override
    public void registerComponents(Context context, Glide glide) {
        //Configuration through glide.register.
        glide.register(GlideUrl.class, InputStream.class, new HttpUrlPostLoader.Factory());
    }
}

4. Register in Android Manifest. XML with the following code:

<meta-data
   android:name="XXXX.CustomGlideModule"
   android:value="GlideModule" />

After testing, OK.

Posted by ibelimb on Tue, 18 Dec 2018 14:57:04 -0800