Android Open Source - Android-J0Loader Data Loader






From Project

Back to project page Android-J0Loader.

License

The source code is released under:

MIT License

If you think the Android project Android-J0Loader listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

/*
 * The MIT License Copyright (c) 2014 Krayushkin Konstantin (jangokvk@gmail.com)
 */* ww w.  j av a  2s.  com*/
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
 * associated documentation files (the "Software"), to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge, publish, distribute,
 * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or
 * substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
 * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

package ru.jango.j0loader;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.net.URLConnection;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Handler;

import ru.jango.j0loader.queue.DefaultQueue;
import ru.jango.j0loader.queue.Queue;
import ru.jango.j0util.LogUtil;

/**
 * Base abstract loader class. Capabilities:
 * <ul>
 * <li>load data itself (HTTP GET without sending params, file, ftp, jar - see {@link java.net.URLConnection})</li>
 * <li>report into main thread by {@link ru.jango.j0loader.DataLoader.LoadingListener} (protected post...() methods)</li>
 * <li>can call listeners' methods synchronously in loading thread (see {@link #setFullAsyncMode(boolean)})</li>
 * <li>provides one {@link java.lang.Thread} for asynchronous queue execution ({@link #loadInBackground(Request)})</li>
 * <li>control thread execution ({@link #canWork()}, {@link #cancelCurrent()})</li>
 * <li>control queue execution ({@link #createQueue()})</li>
 * </ul>
 * <br>
 *
 * Uses Java listeners model (classical Observer pattern) - many
 * {@link ru.jango.j0loader.DataLoader.LoadingListener}s could listen to one loader; and loader
 * reports to all listeners about all events. <b>Each listener should check itself, whether the data
 * passed into it's methods is valid for it, or should be ignored.</b> It could be done with help of the
 * {@link ru.jango.j0loader.Request} objects, witch are passed into each
 * {@link ru.jango.j0loader.DataLoader.LoadingListener}'s methods.
 *
 * @param <T>   after postprocessing of the loaded data an object of type T will be created and passed into
 *              {@link ru.jango.j0loader.DataLoader.LoadingListener#processFinished(Request, byte[], Object)}
 */
public abstract class DataLoader<T> {
  protected final int PROGRESS_UPDATE_INTERVAL_MS = 200;
  protected final int BUFFER_SIZE_BYTES = 50;
    protected final int CONNECT_TIMEOUT = 15000;
    protected final int READ_TIMEOUT = 10000;

  private Handler mainThreadHandler;
  private Set<LoadingListener<T>> listeners;

  private Thread loaderThread;
  private Queue queue;
  private boolean working;        // TRUE if the queue is executing
    private boolean currCancelled;  // TRUE if processing of current Request should be stopped
  private boolean debug;          // TRUE if debug messages should be logged
    private boolean fullAsyncMode;  // TRUE if listeners should be executed in loading thread

  public DataLoader() {
    mainThreadHandler = new Handler();
    listeners = new HashSet<LoadingListener<T>>();
        queue = createQueue();
  }
  
  protected void logDebug(String message) {
    if (isDebug())
      LogUtil.d(getClass(), message);
  }

    /**
     * Checks network connection.
     */
    public static boolean isOnline(Context context)  {
        final ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        final NetworkInfo nInfo = cm.getActiveNetworkInfo();

        return (nInfo != null && nInfo.isConnected());
    }

    ////////////////////////////////////////////////////////////////////////
    //
    //    Setters and getters
    //
    ////////////////////////////////////////////////////////////////////////
  
  /**
   * Adds new listener. A {@link ru.jango.j0loader.DataLoader.LoadingListener} object should be
     * unique - you can't add a single listener twice.
   * 
   * @param listener  new loading listener
   */
  public void addLoadingListener(LoadingListener<T> listener) {
    listeners.add(listener);
  }
  
  /**
   * Removes a certain {@link ru.jango.j0loader.DataLoader.LoadingListener}.
   * 
   * @param listener  loading listener to remove
   */
  public void removeLoadingListener(LoadingListener<T> listener) {
    listeners.remove(listener);
  }

    /**
     * Attempts to stop a queue execution. It doesn't actually stops the execution - only sets an
     * internal flag to FALSE. Various longrunning methods check this flag and stop themselves.
     * <br><br>
     * Calling this method won't stop queue immediately, but all operations will die soon after it.
     */
    public void stopWorking() {
        working = false;
    }

  /**
     * Sets internal flag to TRUE, that indicates that queue can be executed. Method doesn't actually
     * starts the execution ({@link #start()} does), it only allows execution.
   */
  public void allowWorking() {
    working = true;
  }
  
  /**
   * Checks if queue execution is allowed.
     *
     * @see #stopWorking()
     * @see #allowWorking()
   */
  public boolean canWork() {
        return working;
  }

  /**
     * Attempts to stop downloading the current queue element. It doesn't actually stops the
     * downloading - only sets an internal flag. The downloading loop than this flag and stops
     * itself.
     * <br><br>
     * Calling this method won't stop downloading, but the operation will die soon after it.
   */
  public void cancelCurrent() {
    currCancelled = true;
  }

    /**
     * Checks if processing of current queue element was cancelled.
     *
     * @see #cancelCurrent()
     */
    public boolean isCurrentCancelled() {
        return currCancelled;
    }

    /**
     * By default full asynchronous mode is OFF and all
     * {@link ru.jango.j0loader.DataLoader.LoadingListener}'s methods are called in main thread
     * (inter-thread communication is already embed in loaders). It is very useful for example
     * for updating interface. <br>
     * But if you don't need to switch into main thread, you can turn FullAsync mode ON and all
     * your postprocessing logic (listener's methods) will be executed in loading thread, but
     * that may stretch delays between executing requests (that is, loading itself). That could
     * be useful when working for example with {@link android.content.ContentProvider}.
     * <br><br>
     *
     * Basically this mode was needed for tests:)
     */
    public void setFullAsyncMode(boolean fullAsyncMode) {
        this.fullAsyncMode = fullAsyncMode;
    }

    /**
     * Checks if in full asynchronous mode.
     */
    public boolean isFullAsyncMode() {
        return fullAsyncMode;
    }

    /**
     * Check if debug logging is on.
     */
  public boolean isDebug() {
    return debug;
  }

    /**
     * Switches debug logging on/off. Default - OFF.
     */
  public void setDebug(boolean debug) {
    this.debug = debug;
  }

    /**
     * Returns a {@link java.lang.Thread} where the queue is executed. In subclasses this
     * method could be overwritten to provide another thread.
     */
    protected Thread getLoaderThread() {
        if (!(loaderThread!=null && loaderThread.isAlive()))
            return loaderThread = new Thread(queueRunnable);

        return loaderThread;
    }

    ////////////////////////////////////////////////////////////////////////
    //
    //    Queue controlling methods
    //
    ////////////////////////////////////////////////////////////////////////

    /**
     * Adds a {@link ru.jango.j0loader.Request} into the end of the loading queue.
     *
     * @param request   a {@link Request} to add
     */
    public void addToQueue(Request request) {
        queue.add(request);
    }

    /**
     * Adds all {@link ru.jango.j0loader.Request}s into the end of the loading queue.
     *
     * @param requests   a pack of {@link Request}s to add
     */
    public void addToQueue(Collection<Request> requests) {
        queue.addAll(requests);
    }

    /**
     * Removes a {@link Request} from the loading queue. If this {@link ru.jango.j0loader.Request} is
     * already being processed, it could be retrieved by {@link #getCurrentQueueElement()} and the
     * procession could be stopped by {@link #cancelCurrent()} (stops only the current, not all queue).
     *
     * @param request   a {@link Request} to remove
     */
    public void removeFromQueue(Request request) {
        queue.remove(request);
    }

    /**
     * Returns current element (witch is processed now) or null.
     */
    public Request getCurrentQueueElement() {
        return queue.current();
    }

    /**
     * Returns number of elements in queue.
     */
    public int getQueueSize() {
        return queue.size();
    }

    /**
     * Clears the loading queue.
     */
    public void clearQueue() {
        queue.clear();
    }

    /**
     * Checks if the queue is empty.
     */
    public boolean isQueueEmpty() {
        return queue.isEmpty();
    }

    /**
     * Actually starts the execution of the queue in a separate {@link java.lang.Thread}.
     */
    public void start()  {
        allowWorking();

        final Thread thread = getLoaderThread();
        if (!thread.isAlive()) thread.start();
    }

    /**
     * Resets the loader to it's initial clear state: <br>
     * <ul>
     * <li>stop operations by {@link #stopWorking()}</li>
     * <li>clear queue by {@link #clearQueue()}</li>
     * </ul>
     */
    public void reset() {
        stopWorking();
        clearQueue();
    }

    /**
     * Special method for queue configuration. By default {@link ru.jango.j0loader.DataLoader}
     * creates an instance of {@link ru.jango.j0loader.queue.DefaultQueue}, but if a queue with
     * different logic is required, it could be substituted here.
     * <br><br>
     * This method with conjunction of {@link ru.jango.j0loader.queue.Queue} hierarchy defines a
     * usual Iterator pattern.
     *
     * @return  loading queue instance
     */
    protected Queue createQueue() {
        return new DefaultQueue();
    }

    ////////////////////////////////////////////////////////////////////////
    //
    //    Loading methods
    //
    ////////////////////////////////////////////////////////////////////////
  
  /**
     * Does the subclass-specific loading operations - loading itself and postprocessing. It is
     * called from a separate thread.
   *
   * @param request  {@link Request} from the loading queue
   */
  protected abstract void loadInBackground(Request request) throws Exception;

  /**
     * Helper method for subclasses - opens an {@link java.io.InputStream} and does the loading.
   *
   * @return  raw just loaded data
   */
  protected byte[] load(Request request) throws IOException, URISyntaxException {
    InputStream in = null;
    try {
      in = openInputStream(request);
      return doLoad(request,in);
    } finally {
            try {
                assert in != null; // don't like yellow warnings in Android Studio!
                in.close();
            } catch(Exception ignored) {}
        }
  }
  
    /**
   * Helper method for subclasses - actually does the loading. Also automatically calls
     * {@link #onDownloadingUpdateProgress(Request, long, long)} during the work; handles
     * {@link #canWork()} and {@link #isCurrentCancelled()} flags.
   */
  protected byte[] doLoad(Request request, InputStream in) throws IOException {
    long progressLastUpdated = System.currentTimeMillis();
    int nRead, totalRead = 0;
    byte[] data = new byte[BUFFER_SIZE_BYTES];

    final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
        final BufferedInputStream input = new BufferedInputStream(in);
    while ((nRead = input.read(data, 0, data.length))!=-1 && canWork() && !isCurrentCancelled())  {
      buffer.write(data, 0, nRead);
      totalRead += nRead;
      
      boolean updateProgress = System.currentTimeMillis() > progressLastUpdated + PROGRESS_UPDATE_INTERVAL_MS;
      if (request.getResponseContentLength()!=-1 && updateProgress) {
        progressLastUpdated = System.currentTimeMillis();
        onDownloadingUpdateProgress(request, totalRead, request.getResponseContentLength());
      }
    }
    buffer.flush();

    final byte[] ret = buffer.toByteArray();
    buffer.close();
        logDebug("doLoad: " + request.getURI() + " : " + (new String(ret, "UTF-8"))); 
        return ret;
    }

  /**
     * Helper method for subclasses - actually opens an {@link java.io.InputStream} and sets
     * content length inside the passed {@link ru.jango.j0loader.Request} object -
     * {@link ru.jango.j0loader.Request#setResponseContentLength(long)}.
   */
  protected InputStream openInputStream(Request request) throws IOException, URISyntaxException {
    final URLConnection urlConnection = request.getURL().openConnection();
        configURLConnection(urlConnection);

    request.setResponseContentLength(urlConnection.getContentLength());
    return urlConnection.getInputStream();
  }

    /**
     * Applies default configurations to specified {@link java.net.URLConnection}.
     */
    protected void configURLConnection(URLConnection urlConnection) {
        urlConnection.setUseCaches(false);
        urlConnection.setDoInput(true);
        urlConnection.setDoOutput(false);
        urlConnection.setConnectTimeout(CONNECT_TIMEOUT);
        urlConnection.setReadTimeout(READ_TIMEOUT);
    }

    ////////////////////////////////////////////////////////////////////////
    //
    //    Listeners methods callers (crossthread communication)
    //
    ////////////////////////////////////////////////////////////////////////

    /**
     * Checks various stop flags (i.e. {@link #canWork()}, {@link #isCurrentCancelled()})
     */
    protected boolean canPingListeners() {
        return canWork() && !isCurrentCancelled();
    }

  /**
   * Reports to all listeners that executing of the specified {@link ru.jango.j0loader.Request}
     * has just began.
     *
     * @param request   {@link ru.jango.j0loader.Request} processed now

     * @see #isFullAsyncMode()
   */
  protected void onProcessStarted(final Request request) {
    if (!canPingListeners()) return;
    logDebug("onProcessStarted: " + request.getURI());

        if (isFullAsyncMode()) doPostProcessStarted(request);
        else mainThreadHandler.post(new Runnable() {
      @Override
      public void run() { doPostProcessStarted(request); }
        });
  }

    private void doPostProcessStarted(Request request) {
        for (LoadingListener<T> listener : listeners)
            listener.processStarted(request);
    }

    /**
     * Reports to all listeners that a new chunk of data has been uploaded during processing the
     * specified {@link ru.jango.j0loader.Request}. It could be called with HTTP POST and GET
     * requests while sending params.
     *
     * @param request       {@link ru.jango.j0loader.Request} processed now
     * @param uploadedBytes total bytes that have already been uploaded
     * @param totalBytes    total bytes that should be uploaded (sum of all params sizes)
     *
     * @see #isFullAsyncMode()
     */
  protected void onUploadingUpdateProgress(final Request request, final long uploadedBytes, final long totalBytes) {
    if (!canPingListeners()) return;
    logDebug("onUploadingUpdateProgress: " + request.getURI() + " : "
          + "uploaded " + uploadedBytes + "bytes; "
          + "total " + totalBytes + "bytes");

        if (isFullAsyncMode()) doPostUploadingUpdateProgress(request, uploadedBytes, totalBytes);
    else mainThreadHandler.post(new Runnable()  {
      @Override
      public void run()  { doPostUploadingUpdateProgress(request, uploadedBytes, totalBytes); }
    });
  }

    private void doPostUploadingUpdateProgress(Request request, long uploadedBytes, long totalBytes) {
        for (LoadingListener<T> listener : listeners)
            listener.uploadingUpdateProgress(request, uploadedBytes, totalBytes);
    }

    /**
     * Reports to all listeners that a new chunk of data has been downloaded for the specified
     * {@link ru.jango.j0loader.Request}.
     *
     * @param request       {@link ru.jango.j0loader.Request} processed now
     * @param loadedBytes   total bytes that have already been downloaded
     * @param totalBytes    total bytes that should be downloaded (content length)
     *
     * @see #isFullAsyncMode()
     */
    protected void onDownloadingUpdateProgress(final Request request, final long loadedBytes, final long totalBytes) {
        if (!canPingListeners()) return;
        logDebug("onDownloadingUpdateProgress: " + request.getURI() + " : "
                + "downloaded " + loadedBytes + "bytes; "
                + "total " + totalBytes + "bytes");

        if (isFullAsyncMode()) doPostDownloadingUpdateProgress(request, loadedBytes, totalBytes);
        else mainThreadHandler.post(new Runnable()  {
            @Override
            public void run()  { doPostDownloadingUpdateProgress(request, loadedBytes, totalBytes); }
        });
    }

    private void doPostDownloadingUpdateProgress(Request request, long loadedBytes, long totalBytes) {
        for (LoadingListener<T> listener : listeners)
            listener.downloadingUpdateProgress(request, loadedBytes, totalBytes);
    }

    /**
   * Reports to all listeners that executing of the specified {@link ru.jango.j0loader.Request}
     * has just successfully finished.
     *
     * @param request   {@link ru.jango.j0loader.Request} that had just been processed
     * @param rawData   raw bytes of the downloaded data
     * @param data      postprocessed loader-specific data
     *
     * @see #isFullAsyncMode()
     */
  protected void onProcessFinished(final Request request, final byte[] rawData, final T data) {
    if (!canPingListeners()) return;
    logDebug("onProcessFinished: " + request.getURI() + " : "
          + rawData.length + "bytes");

        if (isFullAsyncMode()) doPostProcessFinished(request, rawData, data);
        else mainThreadHandler.post(new Runnable()  {
      @Override
      public void run()  { doPostProcessFinished(request, rawData, data); }
    });
  }

    private void doPostProcessFinished(Request request, byte[] rawData, T data) {
        for (LoadingListener<T> listener : listeners)
            listener.processFinished(request, rawData, data);
    }

    /**
     * Reports to all listeners that executing of the specified {@link ru.jango.j0loader.Request}
     * has just failed.
     *
     * @param request   {@link ru.jango.j0loader.Request} that had just failed
     * @param e         raised {@link Exception}
     *
     * @see #isFullAsyncMode()
     */
  protected void onProcessFailed(final Request request, final Exception e) {
    if (!canPingListeners()) return;
    if (isDebug()) e.printStackTrace();
    logDebug("onProcessFailed: " + request.getURI() + " : " + e);

        if (isFullAsyncMode()) doPostProcessFailed(request, e);
        else mainThreadHandler.post(new Runnable() {
      @Override
      public void run() { doPostProcessFailed(request, e); }
        });
  }

    private void doPostProcessFailed(Request request, Exception e) {
        for (LoadingListener<T> listener : listeners)
            listener.processFailed(request,e);
    }

    ////////////////////////////////////////////////////////////////////////
    //
    //    Crossthread communication staff
    //
    ////////////////////////////////////////////////////////////////////////
  
  /**
   * Listener interface for loading (uploading and downloading) process.
     *
     * @param <T>   after postprocessing of the loaded data an object of type T will be created and passed into
     *              {@link #processFinished(Request, byte[], Object)}
   */
  public interface LoadingListener<T> {
    /**
         * Called before the loading process started. Process can be divided into uploading and
         * downloading (HTTP POST and GET), or just downloading (other protocols).
     *
         * @param request   {@link ru.jango.j0loader.Request} that is processed now
     */
    public void processStarted(Request request);

    /**
     * Called while the params are uploaded.
     *
         * @param request       {@link ru.jango.j0loader.Request} that is processed now
     * @param uploadedBytes  total bytes that have already been uploaded
     * @param totalBytes  total bytes that should be uploaded (sum of all params sizes)
     */
    public void uploadingUpdateProgress(Request request, long uploadedBytes, long totalBytes);
    
    /**
     * Called while the data is downloaded.
     *
         * @param request       {@link ru.jango.j0loader.Request} that is processed now
     * @param loadedBytes  total bytes that have already been downloaded
     * @param totalBytes  total bytes that should be downloaded (content length)
     */
    public void downloadingUpdateProgress(Request request, long loadedBytes, long totalBytes);

    /**
     * Called when the loading had been successfully finished.
     *
         * @param request   {@link ru.jango.j0loader.Request} that had just successfully finished
     * @param rawData  raw downloaded data
     * @param data    postprocessed loader-specific data
     */
    public void processFinished(Request request, byte[] rawData, T data);
    
    /**
     * Called when the loading has failed (both while uploading and downloading).
     *
         * @param request   {@link ru.jango.j0loader.Request} that had just failed
     * @param e      raised {@link java.lang.Exception}
     */
    public void processFailed(Request request, Exception e);
  }

    /**
     * Main runnable witch executes asynchronously.
     */
  private Runnable queueRunnable = new Runnable() {
    @Override
    public void run()  {
      while (!isQueueEmpty() && canWork()) {
        final Request request = queue.next();
                currCancelled = false;

        try {
          onProcessStarted(request);
          loadInBackground(request);
        } catch (Exception e) { onProcessFailed(request, e); }

        LogUtil.logMemoryUsage();
      }
    }
  };
}




Java Source Code List

ru.jango.j0loader.DataLoader.java
ru.jango.j0loader.JSONLoader.java
ru.jango.j0loader.LoadingAdapter.java
ru.jango.j0loader.ParamedLoader.java
ru.jango.j0loader.Request.java
ru.jango.j0loader.image.AsyncImageView.java
ru.jango.j0loader.image.ImageLoader.java
ru.jango.j0loader.image.cache.Cache.java
ru.jango.j0loader.image.cache.DefaultCache.java
ru.jango.j0loader.image.cache.LRUCache.java
ru.jango.j0loader.image.cache.NullCache.java
ru.jango.j0loader.param.BitmapParam.java
ru.jango.j0loader.param.DataParam.java
ru.jango.j0loader.param.Param.java
ru.jango.j0loader.param.StringParam.java
ru.jango.j0loader.queue.DefaultQueue.java
ru.jango.j0loader.queue.Queue.java
ru.jango.j0loader.queue.SingleURIQueue.java