me.xiaopan.android.spear.download.HttpClientImageDownloader.java Source code

Java tutorial

Introduction

Here is the source code for me.xiaopan.android.spear.download.HttpClientImageDownloader.java

Source

/*
 * Copyright (C) 2013 Peng fei Pan <sky@xiaopan.me>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package me.xiaopan.android.spear.download;

import android.util.Log;

import org.apache.http.Header;
import org.apache.http.HeaderElement;
import org.apache.http.HttpEntity;
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.HttpResponse;
import org.apache.http.HttpResponseInterceptor;
import org.apache.http.HttpVersion;
import org.apache.http.StatusLine;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.conn.params.ConnManagerParams;
import org.apache.http.conn.params.ConnPerRouteBean;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.entity.HttpEntityWrapper;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.protocol.HttpContext;

import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.net.SocketTimeoutException;
import java.util.Collections;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.locks.ReentrantLock;
import java.util.zip.GZIPInputStream;

import me.xiaopan.android.spear.Spear;
import me.xiaopan.android.spear.request.DownloadRequest;

/**
 * HttpClient?
 */
public class HttpClientImageDownloader implements ImageDownloader {
    private static final String NAME = HttpClientImageDownloader.class.getSimpleName();
    private static final int BUFFER_SIZE = 8 * 1024;
    private static final int DEFAULT_WAIT_TIMEOUT = 60 * 1000; // ?
    private static final int DEFAULT_READ_TIMEOUT = 10 * 1000; // ?
    private static final int DEFAULT_CONNECT_TIMEOUT = 10 * 1000; // 
    private static final int DEFAULT_MAX_ROUTE_CONNECTIONS = 400; // ?
    private static final int DEFAULT_MAX_CONNECTIONS = 800; // 
    private static final int DEFAULT_SOCKET_BUFFER_SIZE = 8192; // Socket?
    private static final int DEFAULT_MAX_RETRY_COUNT = 1; // ?
    private static final int DEFAULT_PROGRESS_CALLBACK_NUMBER = 10; // 
    private static final String DEFAULT_USER_AGENT = "Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.16 Safari/534.24";

    private DefaultHttpClient httpClient;
    private Map<String, ReentrantLock> urlLocks;
    private int maxRetryCount = DEFAULT_MAX_RETRY_COUNT;
    private int progressCallbackNumber = DEFAULT_PROGRESS_CALLBACK_NUMBER;

    public HttpClientImageDownloader() {
        this.urlLocks = Collections.synchronizedMap(new WeakHashMap<String, ReentrantLock>());
        BasicHttpParams httpParams = new BasicHttpParams();
        ConnManagerParams.setTimeout(httpParams, DEFAULT_WAIT_TIMEOUT);
        ConnManagerParams.setMaxConnectionsPerRoute(httpParams,
                new ConnPerRouteBean(DEFAULT_MAX_ROUTE_CONNECTIONS));
        ConnManagerParams.setMaxTotalConnections(httpParams, DEFAULT_MAX_CONNECTIONS);
        HttpConnectionParams.setTcpNoDelay(httpParams, true);
        HttpConnectionParams.setSoTimeout(httpParams, DEFAULT_READ_TIMEOUT);
        HttpConnectionParams.setConnectionTimeout(httpParams, DEFAULT_CONNECT_TIMEOUT);
        HttpConnectionParams.setSocketBufferSize(httpParams, DEFAULT_SOCKET_BUFFER_SIZE);
        HttpProtocolParams.setVersion(httpParams, HttpVersion.HTTP_1_1);
        HttpProtocolParams.setUserAgent(httpParams, DEFAULT_USER_AGENT);
        SchemeRegistry schemeRegistry = new SchemeRegistry();
        schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
        schemeRegistry.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443));
        httpClient = new DefaultHttpClient(new ThreadSafeClientConnManager(httpParams, schemeRegistry), httpParams);
        httpClient.addRequestInterceptor(new GzipProcessRequestInterceptor());
        httpClient.addResponseInterceptor(new GzipProcessResponseInterceptor());
    }

    @Override
    public void setMaxRetryCount(int maxRetryCount) {
        this.maxRetryCount = maxRetryCount;
    }

    @Override
    public void setConnectTimeout(int connectTimeout) {
        HttpParams httpParams = httpClient.getParams();
        HttpConnectionParams.setConnectionTimeout(httpParams, connectTimeout);
    }

    @Override
    public void setProgressCallbackNumber(int progressCallbackNumber) {
        this.progressCallbackNumber = progressCallbackNumber;
    }

    /**
     * ?URL?????
     * @param url ?
     * @return URL?
     */
    public synchronized ReentrantLock getUrlLock(String url) {
        ReentrantLock urlLock = urlLocks.get(url);
        if (urlLock == null) {
            urlLock = new ReentrantLock();
            urlLocks.put(url, urlLock);
        }
        return urlLock;
    }

    @Override
    public DownloadResult download(DownloadRequest request) {
        // ?????
        ReentrantLock urlLock = getUrlLock(request.getUri());
        urlLock.lock();

        DownloadResult result = null;
        int number = 0;
        while (true) {
            // ???
            if (request.isCanceled()) {
                if (Spear.isDebugMode())
                    Log.w(Spear.LOG_TAG,
                            NAME + "" + "? - ??" + "" + request.getName());
                break;
            }

            // ?
            File cacheFile = request.getCacheFile();
            if (cacheFile != null && cacheFile.exists()) {
                result = DownloadResult.createByFile(cacheFile, false);
                break;
            }

            try {
                result = realDownload(request);
                break;
            } catch (Throwable e) {
                boolean retry = (e instanceof SocketTimeoutException || e instanceof InterruptedIOException)
                        && number < maxRetryCount;
                if (retry) {
                    number++;
                    if (Spear.isDebugMode())
                        Log.w(Spear.LOG_TAG,
                                NAME + "" + " - ??" + "" + request.getName());
                } else {
                    if (Spear.isDebugMode())
                        Log.e(Spear.LOG_TAG,
                                NAME + "" + " - ???" + "" + request.getName());
                }
                e.printStackTrace();
                if (!retry) {
                    break;
                }
            }
        }

        // ?
        urlLock.unlock();
        return result;
    }

    private DownloadResult realDownload(DownloadRequest request) throws IOException {
        HttpResponse httpResponse;
        try {
            httpResponse = httpClient.execute(new HttpGet(request.getUri()));
        } catch (IOException e) {
            if (Spear.isDebugMode())
                Log.w(Spear.LOG_TAG,
                        NAME + "" + "" + e.getMessage() + "" + request.getName());
            throw e;
        }
        if (request.isCanceled()) {
            releaseConnection(httpResponse);
            if (Spear.isDebugMode())
                Log.w(Spear.LOG_TAG,
                        NAME + "" + "? - ?Response?" + "" + request.getName());
            return null;
        }

        // ??
        StatusLine statusLine = httpResponse.getStatusLine();
        if (statusLine == null) {
            releaseConnection(httpResponse);
            if (Spear.isDebugMode())
                Log.w(Spear.LOG_TAG, NAME + "" + "??" + "" + request.getName());
            return null;
        }
        int responseCode = statusLine.getStatusCode();
        if (responseCode != 200) {
            releaseConnection(httpResponse);
            if (Spear.isDebugMode())
                Log.w(Spear.LOG_TAG, NAME + "" + "??" + responseCode + " "
                        + httpResponse.getStatusLine().getReasonPhrase() + "" + request.getName());
            return null;
        }

        // 
        int contentLength = 0;
        Header[] headers = httpResponse.getHeaders("Content-Length");
        if (headers != null && headers.length > 0) {
            contentLength = Integer.valueOf(headers[0].getValue());
        }
        if (contentLength <= 0) {
            releaseConnection(httpResponse);
            if (Spear.isDebugMode())
                Log.w(Spear.LOG_TAG,
                        NAME + "" + "" + contentLength + "" + request.getName());
            return null;
        }

        return readData(request, httpResponse, contentLength);
    }

    private DownloadResult readData(DownloadRequest request, HttpResponse httpResponse, int contentLength)
            throws IOException {
        File tempFile = null;
        if (request.getCacheFile() != null
                && request.getSpear().getConfiguration().getDiskCache().applyForSpace(contentLength)) {
            tempFile = new File(request.getCacheFile().getPath() + ".temp");
            if (!HttpUrlConnectionImageDownloader.createFile(request.getCacheFile())) {
                tempFile = null;
            }
        }

        // ?????
        InputStream inputStream;
        try {
            inputStream = httpResponse.getEntity().getContent();
        } catch (IOException e) {
            if (Spear.isDebugMode())
                Log.w(Spear.LOG_TAG, NAME + "" + "???" + e.getMessage() + ""
                        + request.getName());
            if (tempFile != null && tempFile.exists() && !tempFile.delete())
                Log.w(Spear.LOG_TAG, NAME + ""
                        + "????"
                        + tempFile.getPath() + "" + request.getName());
            throw e;
        }
        if (request.isCanceled()) {
            HttpUrlConnectionImageDownloader.close(inputStream);
            if (Spear.isDebugMode())
                Log.w(Spear.LOG_TAG,
                        NAME + "" + "? - ???" + "" + request.getName());
            if (tempFile != null && tempFile.exists() && !tempFile.delete())
                Log.w(Spear.LOG_TAG, NAME + ""
                        + "??????"
                        + tempFile.getPath() + "" + request.getName());
            return null;
        }

        // ???ByteArrayOutputStream??
        OutputStream outputStream;
        if (tempFile != null) {
            try {
                outputStream = new BufferedOutputStream(new FileOutputStream(tempFile, false), BUFFER_SIZE);
            } catch (FileNotFoundException e) {
                HttpUrlConnectionImageDownloader.close(inputStream);
                Log.w(Spear.LOG_TAG, NAME + "" + "??" + tempFile.getPath()
                        + "" + request.getName());
                throw e;
            }
        } else {
            outputStream = new ByteArrayOutputStream();
        }

        // ??
        int completedLength = 0;
        boolean exception = false;
        try {
            completedLength = HttpUrlConnectionImageDownloader.readData(inputStream, outputStream, request,
                    contentLength, progressCallbackNumber);
        } catch (IOException e) {
            exception = true;
            if (Spear.isDebugMode())
                Log.w(Spear.LOG_TAG, NAME + "" + "???" + e.getMessage() + ""
                        + request.getName());
            throw e;
        } finally {
            HttpUrlConnectionImageDownloader.close(outputStream);
            HttpUrlConnectionImageDownloader.close(inputStream);
            if (exception && tempFile != null && tempFile.exists() && !tempFile.delete())
                Log.w(Spear.LOG_TAG, NAME + ""
                        + "????"
                        + tempFile.getPath() + "" + request.getName());
        }
        if (request.isCanceled()) {
            if (Spear.isDebugMode())
                Log.w(Spear.LOG_TAG,
                        NAME + "" + "? - ???" + "" + request.getName());
            if (tempFile != null && tempFile.exists() && !tempFile.delete())
                Log.w(Spear.LOG_TAG, NAME + ""
                        + "??????"
                        + tempFile.getPath() + "" + request.getName());
            return null;
        }

        if (Spear.isDebugMode())
            Log.i(Spear.LOG_TAG, NAME + "" + "?" + "" + "" + completedLength + "/"
                    + contentLength + "" + request.getName());

        // ?
        if (tempFile != null && tempFile.exists()) {
            if (tempFile.renameTo(request.getCacheFile())) {
                return DownloadResult.createByFile(request.getCacheFile(), true);
            } else {
                if (!tempFile.delete())
                    Log.w(Spear.LOG_TAG,
                            NAME + "" + "????"
                                    + tempFile.getPath() + "" + request.getName());
                return null;
            }
        } else if (outputStream instanceof ByteArrayOutputStream) {
            return DownloadResult.createByByteArray(((ByteArrayOutputStream) outputStream).toByteArray(), true);
        } else {
            return null;
        }
    }

    public static void releaseConnection(HttpResponse httpResponse) {
        if (httpResponse == null) {
            return;
        }

        HttpEntity httpEntity = httpResponse.getEntity();
        if (httpEntity == null) {
            return;
        }

        InputStream inputStream = null;
        try {
            inputStream = httpEntity.getContent();
        } catch (IOException e) {
            e.printStackTrace();
        }
        if (inputStream == null) {
            return;
        }

        try {
            inputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static class GzipProcessRequestInterceptor implements HttpRequestInterceptor {
        /**
         *  - ??
         */
        public static final String HEADER_ACCEPT_ENCODING = "Accept-Encoding";

        /**
         * ? - gzip
         */
        public static final String ENCODING_GZIP = "gzip";

        @Override
        public void process(HttpRequest request, HttpContext context) throws HttpException, IOException {
            //HEADER_ACCEPT_ENCODING
            if (!request.containsHeader(HEADER_ACCEPT_ENCODING)) {
                request.addHeader(HEADER_ACCEPT_ENCODING, ENCODING_GZIP);
            }
        }
    }

    private static class GzipProcessResponseInterceptor implements HttpResponseInterceptor {

        @Override
        public void process(HttpResponse response, HttpContext context) {
            final HttpEntity entity = response.getEntity();
            if (entity != null) {
                final Header encoding = entity.getContentEncoding();
                if (encoding != null) {
                    for (HeaderElement element : encoding.getElements()) {
                        if (element.getName().equalsIgnoreCase(GzipProcessRequestInterceptor.ENCODING_GZIP)) {
                            response.setEntity(new InflatingEntity(entity));
                            break;
                        }
                    }
                }
            }
        }

        private static class InflatingEntity extends HttpEntityWrapper {
            public InflatingEntity(HttpEntity wrapped) {
                super(wrapped);
            }

            @Override
            public InputStream getContent() throws IOException {
                return new GZIPInputStream(wrappedEntity.getContent());
            }

            @Override
            public long getContentLength() {
                return -1;
            }
        }
    }
}