Skip to content

Commit

Permalink
refactor: ImageDownloader improvement
Browse files Browse the repository at this point in the history
* Handling of Null Image links is now taken care by the ImageDownloader AsyncTaskLoader.
* Reading from and Writing Bitmaps to Memory cache is now taken care by the ImageDownloader AsyncTaskLoader.
  • Loading branch information
kaushiknsanji committed Oct 26, 2019
1 parent d2557a6 commit 3687cdc
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 49 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@
import android.text.TextUtils;
import android.util.Log;

import com.example.kaushiknsanji.novalines.cache.BitmapImageCache;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
Expand Down Expand Up @@ -97,11 +95,6 @@ public static Bitmap downloadFromURL(String imageURLStr) {
Log.e(LOG_TAG, "Error occurred while closing the Image Input Stream\n", e);
}
}

//Adding the Bitmap to Memory Cache if generated
if (bitmap != null) {
BitmapImageCache.addBitmapToCache(imageURLStr, bitmap);
}
}

//Returning the Bitmap Image downloaded
Expand Down Expand Up @@ -219,10 +212,10 @@ private static byte[] getImageByteArrayForProcessing(InputStream imageInputStrea

//Declaring Byte Array with a buffer size to buffer the content read
byte[] byteBuff = new byte[4096];
int bytesRead = 0; //Initializing and defaulting the Bytes read to 0
int bytesRead; //Saves the Bytes read

//Declaring the Byte Array to be returned for processing
byte[] imageByteArray = null;
byte[] imageByteArray;

//Writing the Bytes read to the ByteArrayOutputStream
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@
import android.content.Context;
import android.graphics.Bitmap;
import android.support.v4.content.AsyncTaskLoader;
import android.text.TextUtils;
import android.util.Log;

import com.example.kaushiknsanji.novalines.cache.BitmapImageCache;
import com.example.kaushiknsanji.novalines.utils.ImageUtility;
import com.example.kaushiknsanji.novalines.utils.NetworkUtility;

Expand All @@ -31,6 +34,9 @@
*/
public class ImageDownloader extends AsyncTaskLoader<Bitmap> {

//Constant used for logs
private static final String LOG_TAG = ImageDownloader.class.getSimpleName();

//Integer Constant of the Loader
public final static int IMAGE_LOADER = 5;

Expand Down Expand Up @@ -61,15 +67,36 @@ public ImageDownloader(Context context, String imageURLStr) {
*/
@Override
public Bitmap loadInBackground() {
//Proceeding to download when the Internet Connectivity is established
if (NetworkUtility.isNetworkConnected(getContext())) {
//Downloading the Image from URL and returning the Bitmap
Bitmap downloadedBitmap = ImageUtility.downloadFromURL(mImageURLStr);
if (downloadedBitmap != null) {
//Uploading the Bitmap to GPU for caching in background thread (for faster loads)
downloadedBitmap.prepareToDraw();
if (!TextUtils.isEmpty(mImageURLStr)) {
//When we have the Image URL
try {
//Proceeding to download when the Internet Connectivity is established
if (NetworkUtility.isNetworkConnected(getContext())) {
//Looking up for the Image in Memory Cache
Bitmap cachedBitmap = BitmapImageCache.getBitmapFromCache(mImageURLStr);
if (cachedBitmap != null) {
//When Bitmap image was present in Memory Cache, return the Bitmap retrieved
return cachedBitmap;
} else {
//When Bitmap image was NOT present in Memory Cache, download the Image from URL
Bitmap downloadedBitmap = ImageUtility.downloadFromURL(mImageURLStr);
if (downloadedBitmap != null) {
//On Successful download

//Uploading the Bitmap to GPU for caching in background thread (for faster loads)
downloadedBitmap.prepareToDraw();

//Adding the downloaded Bitmap to cache
BitmapImageCache.addBitmapToCache(mImageURLStr, downloadedBitmap);

return downloadedBitmap; //Returning the Bitmap downloaded
}
}
}
} catch (Exception e) {
Log.e(LOG_TAG, "loadInBackground: Failed while downloading the bitmap for the URL "
+ mImageURLStr, e);
}
return downloadedBitmap; //Returning the Bitmap downloaded
}

//For all else, returning null
Expand Down Expand Up @@ -174,9 +201,8 @@ public void onCanceled(Bitmap data) {
*/
private void releaseResources() {
//Invalidating the Loader data
if (mDownloadedBitmap != null) {
mDownloadedBitmap = null;
}
mDownloadedBitmap = null;
mImageURLStr = null;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,18 @@

package com.example.kaushiknsanji.novalines.workers;

import android.content.Context;
import android.content.ContextWrapper;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.text.TextUtils;
import android.widget.ImageView;

import com.example.kaushiknsanji.novalines.R;
Expand Down Expand Up @@ -82,47 +86,125 @@ public static ImageDownloaderFragment newInstance(FragmentManager fragmentManage
* if necessary
*
* @param imageView The ImageView Component on which the Image needs to be updated
* @param imageURLStr String containing the Image URL whose Image needs to be downloaded and updated
* @param imageURLStr String containing the Image URL whose Image needs to be downloaded and updated. Can be {@code null}.
* @param loaderId Integer identifier used while creating this Fragment
*/
public void executeAndUpdate(ImageView imageView, String imageURLStr, int loaderId) {
public void executeAndUpdate(ImageView imageView, @Nullable String imageURLStr, int loaderId) {
//Delegating to other overloaded method with the derived instance for LoaderManager
executeAndUpdate(imageView, imageURLStr, loaderId, obtainLoaderManager(imageView));
}

/**
* Method that loads the Image from Memory Cache or downloads the Image from the URL passed
* if necessary
*
* @param imageView The ImageView Component on which the Image needs to be updated
* @param imageURLStr String containing the Image URL whose Image needs to be downloaded and updated. Can be {@code null}.
* @param loaderId Integer identifier used while creating this Fragment
* @param loaderManager Instance of {@link LoaderManager} to use for downloading the image
*/
public void executeAndUpdate(ImageView imageView, @Nullable String imageURLStr, int loaderId, LoaderManager loaderManager) {
//Saving the parameters passed
mImageView = imageView;
mImageURLStr = imageURLStr;

if (loaderManager == null) {
//When we do not have the LoaderManager instance for downloading the Image
//throw a Runtime Exception
throw new IllegalStateException("LoaderManager is not attached.");
}

//Normalizing the loaderId to start from the ImageDownloader's base ID
loaderId += ImageDownloader.IMAGE_LOADER;

//Looking up for the Image in Memory Cache for the given URL
Bitmap bitmap = BitmapImageCache.getBitmapFromCache(mImageURLStr);
if (bitmap != null) {
//When Bitmap image was present in Memory Cache, update the ImageView
mImageView.setImageBitmap(bitmap);
//Retrieving the loader at the loaderId if any
ImageDownloader imageDownloader = getImageDownloader(loaderId, loaderManager);

//Resetting the ImageView to the default News Thumbnail Image for lazy loading
mImageView.setImageResource(R.drawable.ic_news_thumbnail);

//Boolean to check if we need to restart the loader
boolean isNewImageURLStr = false;
if (imageDownloader != null) {
//When we have a previously registered loader

//Setting the Loader to be restarted when the Image URL passed is
//not the same as that of the loader
isNewImageURLStr = !TextUtils.equals(mImageURLStr, imageDownloader.getImageURLStr());
}

if (isNewImageURLStr) {
//Restarting the Loader when the ImageURL is new
loaderManager.restartLoader(loaderId, null, this);
} else {
//Resetting the ImageView to the default News Thumbnail Image for lazy loading
mImageView.setImageResource(R.drawable.ic_news_thumbnail);
//Invoking the Loader AS-IS if the ImageURL is the same
//or if the Loader is not yet registered with the loaderId passed
loaderManager.initLoader(loaderId, null, this);
}

//Starting the download when Bitmap image is not available from Memory Cache: START
LoaderManager loaderManager = ((FragmentActivity) mImageView.getContext()).getSupportLoaderManager();
boolean isNewImageURLStr = false; //Boolean to check if we need to restart the loader
Loader<Bitmap> loader = loaderManager.getLoader(loaderId); //Getting the loader at the loaderId
if (loader instanceof ImageDownloader) {
//Validating the loader and casting to ImageDownloader
ImageDownloader imageDownloader = (ImageDownloader) loader;
//Checking for inequality of the Image URL with the one from the loader
isNewImageURLStr = !mImageURLStr.equals(imageDownloader.getImageURLStr());
}

if (isNewImageURLStr) {
//Restarting the Loader when the ImageURL is new
loaderManager.restartLoader(loaderId, null, this);
} else {
//Invoking the Loader AS-IS if the ImageURL is the same
//or if the Loader is not yet registered with the loaderId passed
loaderManager.initLoader(loaderId, null, this);
}
//Starting the download when Bitmap image is not available from Memory Cache: END
}

/**
* Method that returns the instance of the {@link ImageDownloader} for the
* Loader Id {@code loaderId} passed.
*
* @param loaderId The Id of the Loader whose Loader instance needs to be looked up
* @param loaderManager Instance of {@link LoaderManager}
* @return Instance of {@link ImageDownloader} if found; else {@code null}
*/
@Nullable
private ImageDownloader getImageDownloader(int loaderId, LoaderManager loaderManager) {
//Getting the loader at the loaderId
Loader<Bitmap> loader = loaderManager.getLoader(loaderId);
if (loader instanceof ImageDownloader) {
//Returning the ImageDownloader instance
return (ImageDownloader) loader;
} else {
//Returning NULL when not found
return null;
}
}

/**
* Method that retrieves the {@link FragmentActivity} instance required
* for obtaining the {@link LoaderManager} instance.
*
* @param context The {@link Context} to retrieve the Activity from.
* @return Instance of the {@link FragmentActivity} is any; else {@code null}
*/
@Nullable
private FragmentActivity obtainActivity(@Nullable Context context) {
if (context == null) {
//Return Null when Null
return null;
} else if (context instanceof FragmentActivity) {
//Return the FragmentActivity instance when Context is of type FragmentActivity
return (FragmentActivity) context;
} else if (context instanceof ContextWrapper) {
//Recall with the Base Context when Context is of type ContextWrapper
return obtainActivity(((ContextWrapper) context).getBaseContext());
}
//Returning Null when we could not derive the Activity instance from the given Context
return null;
}

/**
* Method that retrieves the {@link LoaderManager} instance for the given view {@code imageView}
*
* @param imageView The {@link ImageView} to retrieve the {@link LoaderManager} from.
* @return Instance of {@link LoaderManager} when the {@link FragmentActivity} was derivable
* from {@code imageView}; else {@code null}
*/
@Nullable
private LoaderManager obtainLoaderManager(ImageView imageView) {
//Obtaining the Activity from the ImageView
FragmentActivity activity = obtainActivity(imageView.getContext());
if (activity != null) {
//When we have the Activity, return the LoaderManager instance
return activity.getSupportLoaderManager();
}
//Returning Null when Activity could not be derived from ImageView
return null;
}

/**
Expand Down

0 comments on commit 3687cdc

Please sign in to comment.