Android Open Source - ghwatch Image Loader






From Project

Back to project page ghwatch.

License

The source code is released under:

Apache License

If you think the Android project ghwatch 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

/*
 * Copyright 2014 contributors as indicated by the @authors tag.
 * /* w w  w  . ja  v  a 2 s .  c  o m*/
 * 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 com.daskiworks.ghwatch.image;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.ref.WeakReference;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.ProgressBar;

import com.daskiworks.ghwatch.Utils;

/**
 * Asynchronous internet images loader with caching (in memory and on filesystem).
 * 
 * @author Vlastimil Elias <vlastimil.elias@worldonline.cz>
 * 
 */
public class ImageLoader {

  private static final String TAG = "ImageLoader";

  /**
   * Timeout of images in file cache [millis]
   */
  private static final int IMG_FILE_CACHE_TIMEOUT = 24 * 60 * 60 * 1000;

  private Context context;
  private MemoryCache memoryCache = new MemoryCache();
  private FileCache fileCache;
  private Map<ImageView, String> imageViews = Collections.synchronizedMap(new WeakHashMap<ImageView, String>());
  private ExecutorService executorService;
  private Map<String, ImageToLoad> imageToLoadTasksMap = new WeakHashMap<String, ImageToLoad>();

  private static ImageLoader instance;

  /**
   * Get instance for use.
   * 
   * @param context to be used in loader
   * @return image loader instance for use
   */
  public static ImageLoader getInstance(Context context) {
    if (instance == null)
      instance = new ImageLoader(context);
    return instance;
  }

  private ImageLoader(Context context) {
    this.context = context.getApplicationContext();
    fileCache = new FileCache(context);
    executorService = Executors.newFixedThreadPool(5);
  }

  /**
   * Display image from given URL - cached.
   * 
   * @param url to get image from
   * @param imageView to display image into
   */
  public void displayImage(String url, ImageView imageView) {
    if (url == null || url.isEmpty())
      imageView.setVisibility(View.INVISIBLE);
    imageViews.put(imageView, url);
    Bitmap bitmap = memoryCache.get(url);
    if (bitmap != null) {
      showImageInView(imageView, bitmap, false);
    } else {
      queueImageLoad(url, imageView);
      setProgressBarVisibility(imageView, true);
    }
  }

  /**
   * Load image for given URL. All caches are used as in {@link #displayImage(String, ImageView)}.
   * 
   * @param url to get image from
   * @return image bitmap or null if not available
   */
  public Bitmap loadImageWithFileLevelCache(String url) {
    if (url == null || url.isEmpty())
      return null;
    Bitmap bitmap = memoryCache.get(url);
    if (bitmap != null)
      return bitmap;
    return loadImageWithFileLevelCache(null, url);
  }

  private void setProgressBarVisibility(ImageView imageView, boolean visible) {
    ProgressBar pb = (ProgressBar) ((View) imageView.getParent()).findViewById(android.R.id.progress);
    if (visible) {
      if (pb != null) {
        pb.setVisibility(View.VISIBLE);
        imageView.setVisibility(View.INVISIBLE);
      }
    } else {
      if (pb != null) {
        pb.setVisibility(View.INVISIBLE);
      }
      imageView.setVisibility(View.VISIBLE);
    }
  }

  private void queueImageLoad(String url, ImageView imageView) {
    synchronized (imageToLoadTasksMap) {
      ImageToLoad p = imageToLoadTasksMap.get(url);
      if (p == null) {
        p = new ImageToLoad(url, imageView);
        imageToLoadTasksMap.put(url, p);
        executorService.submit(new ImageLoaderTask(p));
      } else {
        p.addImageView(imageView);
      }
    }

  }

  protected Bitmap loadImageWithFileLevelCache(ImageToLoad imageToLoad, String url) {

    if (imageToLoad != null)
      url = imageToLoad.url;
    File f = fileCache.getFile(url);

    // get from file cache if exists there and not too old
    if (f.exists()) {
      boolean isTimeoutValid = (f.lastModified() > System.currentTimeMillis() - IMG_FILE_CACHE_TIMEOUT);
      if (isTimeoutValid || imageToLoad != null) {
        Bitmap b = decodeFile(f);
        if (b != null) {
          if (isTimeoutValid) {
            return b;
          } else {
            // temporarily show image before new one is loaded from web
            showImage(imageToLoad, b, false);
          }
        }
      }
    }

    // from web
    if (Utils.isInternetConnectionAvailable(Utils.getConnectivityManager(context))) {

      InputStream is = null;
      OutputStream os = null;
      try {
        Bitmap bitmap = null;
        URL imageUrl = new URL(url);
        HttpURLConnection conn = (HttpURLConnection) imageUrl.openConnection();
        conn.setConnectTimeout(30000);
        conn.setReadTimeout(30000);
        conn.setInstanceFollowRedirects(true);
        is = conn.getInputStream();
        if (!f.exists()) {
          Log.d(TAG, "File do not exists: " + f.getAbsolutePath());
          f.createNewFile();
        }
        os = new FileOutputStream(f);
        Utils.copyStream(is, os);
        os.close();
        os = null;
        bitmap = decodeFile(f);
        return bitmap;
      } catch (Throwable ex) {
        Log.w(TAG, "Can't load image from server for URL " + url + " due exception: " + ex.getMessage(), ex);
        if (ex instanceof OutOfMemoryError)
          memoryCache.clear();
      } finally {
        Utils.closeStream(os);
        Utils.closeStream(is);
      }
    }

    if (f.exists()) {
      // not connected to internet or some error, so use from file if available (even timed out) to show at least something
      return decodeFile(f);
    }
    return null;
  }

  // decodes image and scales it to reduce memory consumption
  private Bitmap decodeFile(File f) {
    if (!f.exists())
      return null;
    FileInputStream stream1 = null;
    FileInputStream stream2 = null;
    try {
      // decode image size
      BitmapFactory.Options o = new BitmapFactory.Options();
      o.inJustDecodeBounds = true;
      stream1 = new FileInputStream(f);
      BitmapFactory.decodeStream(stream1, null, o);
      stream1.close();

      // Find the correct scale value. It should be the power of 2.
      final int REQUIRED_SIZE = 70;
      int width_tmp = o.outWidth, height_tmp = o.outHeight;
      int scale = 1;
      while (true) {
        if (width_tmp / 2 < REQUIRED_SIZE || height_tmp / 2 < REQUIRED_SIZE)
          break;
        width_tmp /= 2;
        height_tmp /= 2;
        scale *= 2;
      }

      // decode with inSampleSize
      BitmapFactory.Options o2 = new BitmapFactory.Options();
      o2.inSampleSize = scale;
      stream2 = new FileInputStream(f);
      Bitmap bitmap = BitmapFactory.decodeStream(stream2, null, o2);
      stream2.close();
      return bitmap;

    } catch (Exception e) {
      Log.w(TAG, "Can't decode cached image file due exception: " + e.getMessage(), e);
    } finally {
      Utils.closeStream(stream1);
      Utils.closeStream(stream2);
    }
    return null;
  }

  private class ImageToLoad {
    public String url;
    public boolean isShownAlready = false;
    Set<WeakReference<ImageView>> imageViewList = new HashSet<WeakReference<ImageView>>();

    public ImageToLoad(String u, ImageView i) {
      url = u;
      imageViewList.add(new WeakReference<ImageView>(i));
    }

    public void addImageView(ImageView i) {
      imageViewList.add(new WeakReference<ImageView>(i));
    }
  }

  class ImageLoaderTask implements Runnable {
    ImageToLoad imageToLoad;

    ImageLoaderTask(ImageToLoad photoToLoad) {
      this.imageToLoad = photoToLoad;
    }

    @Override
    public void run() {
      try {
        synchronized (imageToLoadTasksMap) {
          if (!isAtLeastOneImageViewValid(imageToLoad)) {
            imageToLoadTasksMap.remove(imageToLoad.url);
            return;
          }
        }
        Bitmap bmp = loadImageWithFileLevelCache(imageToLoad, null);
        if (bmp != null)
          memoryCache.put(imageToLoad.url, bmp);
        showImage(imageToLoad, bmp, true);
      } catch (Throwable th) {
        Log.e(TAG, "Image loading error: " + th.getMessage(), th);
      }
    }

  }

  private void showImage(ImageToLoad imageToLoad, Bitmap bmp, boolean removeFromMap) {
    synchronized (imageToLoadTasksMap) {
      if (removeFromMap)
        imageToLoadTasksMap.remove(imageToLoad.url);
      if (isAtLeastOneImageViewValid(imageToLoad)) {
        BitmapDisplayerTask bd = new BitmapDisplayerTask(bmp, imageToLoad);
        Activity a = getActivity(imageToLoad.imageViewList);
        if (a != null)
          a.runOnUiThread(bd);
      }
    }
  }

  private Activity getActivity(Set<WeakReference<ImageView>> s) {
    for (WeakReference<ImageView> wr : s) {
      if (wr != null) {
        ImageView iv = wr.get();
        if (iv != null)
          return (Activity) iv.getContext();
      }
    }
    return null;
  }

  boolean isAtLeastOneImageViewValid(ImageToLoad photoToLoad) {
    for (WeakReference<ImageView> wr : photoToLoad.imageViewList) {
      if (wr != null)
        if (isImageViewValid(photoToLoad, wr.get()))
          return true;
    }
    return false;
  }

  private boolean isImageViewValid(ImageToLoad photoToLoad, ImageView iv) {
    if (iv == null)
      return false;
    String tag = imageViews.get(iv);
    if (tag != null && tag.equals(photoToLoad.url)) {
      return true;
    }
    return false;
  }

  class BitmapDisplayerTask implements Runnable {
    Bitmap bitmap;
    ImageToLoad imageToLoad;

    public BitmapDisplayerTask(Bitmap b, ImageToLoad p) {
      bitmap = b;
      imageToLoad = p;
    }

    public void run() {
      for (WeakReference<ImageView> wr : imageToLoad.imageViewList) {
        if (wr != null) {
          ImageView iv = wr.get();
          if (isImageViewValid(imageToLoad, iv)) {
            if (bitmap != null) {
              showImageInView(iv, bitmap, !imageToLoad.isShownAlready);
            }
          }
        }
      }
      imageToLoad.isShownAlready = true;
    }
  }

  protected void showImageInView(ImageView iv, Bitmap bitmap, boolean animate) {
    iv.clearAnimation();
    if (animate)
      iv.setAlpha(0f);
    iv.setImageBitmap(bitmap);
    setProgressBarVisibility(iv, false);
    if (animate)
      iv.animate().alpha(1f).setDuration(200).start();
  }

  /**
   * Clear memory cache.
   */
  public void clearCache() {
    memoryCache.clear();
  }

}




Java Source Code List

com.daskiworks.ghwatch.AboutDialogFragment.java
com.daskiworks.ghwatch.ActivityBase.java
com.daskiworks.ghwatch.ActivityTracker.java
com.daskiworks.ghwatch.FullHeightListView.java
com.daskiworks.ghwatch.IabHelper.java
com.daskiworks.ghwatch.LoginDialogFragment.java
com.daskiworks.ghwatch.MainActivity.java
com.daskiworks.ghwatch.MyBackupAgent.java
com.daskiworks.ghwatch.NotificationListAdapter.java
com.daskiworks.ghwatch.NotificationRepositoriesListAdapter.java
com.daskiworks.ghwatch.SettingsActivity.java
com.daskiworks.ghwatch.StartActivity.java
com.daskiworks.ghwatch.SupportAppDevelopmentDialogFragment.java
com.daskiworks.ghwatch.SwipeDismissListViewTouchListener.java
com.daskiworks.ghwatch.UnreadAppWidgetProvider.java
com.daskiworks.ghwatch.Utils.java
com.daskiworks.ghwatch.WatchedRepositoriesActivity.java
com.daskiworks.ghwatch.WatchedRepositoryListAdapter.java
com.daskiworks.ghwatch.alarm.AlarmBroadcastReceiver.java
com.daskiworks.ghwatch.alarm.MarkNotifiationAsReadReceiver.java
com.daskiworks.ghwatch.backend.AuthenticationManager.java
com.daskiworks.ghwatch.backend.GHConstants.java
com.daskiworks.ghwatch.backend.NotificationStreamParser.java
com.daskiworks.ghwatch.backend.OTPAuthenticationException.java
com.daskiworks.ghwatch.backend.PreferencesUtils.java
com.daskiworks.ghwatch.backend.RemoteSystemClient.java
com.daskiworks.ghwatch.backend.UnreadNotificationsService.java
com.daskiworks.ghwatch.backend.ViewDataReloadStrategy.java
com.daskiworks.ghwatch.backend.WatchedRepositoriesParser.java
com.daskiworks.ghwatch.backend.WatchedRepositoriesService.java
com.daskiworks.ghwatch.image.FileCache.java
com.daskiworks.ghwatch.image.ImageLoader.java
com.daskiworks.ghwatch.image.MemoryCache.java
com.daskiworks.ghwatch.model.AccountType.java
com.daskiworks.ghwatch.model.BaseViewData.java
com.daskiworks.ghwatch.model.BooleanViewData.java
com.daskiworks.ghwatch.model.GHCredentials.java
com.daskiworks.ghwatch.model.GHUserInfo.java
com.daskiworks.ghwatch.model.GHUserLoginInfo.java
com.daskiworks.ghwatch.model.LoadingStatus.java
com.daskiworks.ghwatch.model.NotifCount.java
com.daskiworks.ghwatch.model.NotificationStreamViewData.java
com.daskiworks.ghwatch.model.NotificationStream.java
com.daskiworks.ghwatch.model.Notification.java
com.daskiworks.ghwatch.model.Repository.java
com.daskiworks.ghwatch.model.StringViewData.java
com.daskiworks.ghwatch.model.WatchedRepositoriesViewData.java
com.daskiworks.ghwatch.model.WatchedRepositories.java
com.daskiworks.ghwatch.view.preference.ShowTextPreference.java
com.daskiworks.ghwatch.view.preference.ShowTimestampPreference.java