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 and Writing Bitmaps to Memory cache is now taken care by the ImageDownloader AsyncTaskLoader.
  • Loading branch information
kaushiknsanji committed Oct 22, 2019
1 parent 46ef6ab commit 30f8dcc
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 77 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
import android.support.v7.app.AppCompatActivity;
import android.text.Layout;
import android.text.TextUtils;
import android.util.Log;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
Expand All @@ -46,8 +45,6 @@
import com.example.kaushiknsanji.bookslibrary.utils.TextAppearanceUtility;
import com.example.kaushiknsanji.bookslibrary.workers.ImageDownloaderFragment;

import java.text.ParseException;

/**
* Activity class that displays the {@link BookInfo} Item data selected in the List/Grid View.
* The Item data is received via an Intent and the corresponding view components in the
Expand Down Expand Up @@ -417,17 +414,12 @@ private void updatePagesInfo(int pageCount, String bookType) {
* retrieved from the Item's {@link BookInfo} Object
*/
private void updateBookImage(String imageLinkForDetailInfo) {
if (!TextUtils.isEmpty(imageLinkForDetailInfo)) {
//Loading the Image when link is present
ImageDownloaderFragment
.newInstance(this.getSupportFragmentManager(), mBookImageView.getId())
.executeAndUpdate(mBookImageView,
imageLinkForDetailInfo,
mBookImageView.getId());
} else {
//Resetting to the default Book image when link is absent
mBookImageView.setImageResource(R.drawable.ic_book);
}
//Loading the Image when link is present
ImageDownloaderFragment
.newInstance(this.getSupportFragmentManager(), mBookImageView.getId())
.executeAndUpdate(mBookImageView,
imageLinkForDetailInfo,
mBookImageView.getId());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
import android.support.annotation.Nullable;
import android.support.v4.app.NavUtils;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.view.MenuItem;
import android.widget.ImageView;

Expand Down Expand Up @@ -56,19 +55,13 @@ protected void onCreate(@Nullable Bundle savedInstanceState) {

//Loading the Image to be shown from the Intent received
Intent bookImageIntent = getIntent();
String imageLinkForBookImageInfo = bookImageIntent.getStringExtra(BOOK_INFO_ITEM_IMAGE_STR_KEY);
if (!TextUtils.isEmpty(imageLinkForBookImageInfo)) {
//Loading the Image when link is present
ImageDownloaderFragment
.newInstance(getSupportFragmentManager(), bookImageView.getId())
.executeAndUpdate(bookImageView,
imageLinkForBookImageInfo,
bookImageView.getId());
} else {
//Resetting to the default Book image when link is absent
//(This case can only occur if there is a problem with network connectivity)
bookImageView.setImageResource(R.drawable.ic_book);
}

//Loading the Image when link is present
ImageDownloaderFragment
.newInstance(getSupportFragmentManager(), bookImageView.getId())
.executeAndUpdate(bookImageView,
bookImageIntent.getStringExtra(BOOK_INFO_ITEM_IMAGE_STR_KEY),
bookImageView.getId());

}

Expand Down
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.bookslibrary.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
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,12 @@

import android.content.Context;
import android.graphics.Bitmap;
import android.support.annotation.Nullable;
import android.support.v4.content.AsyncTaskLoader;
import android.text.TextUtils;
import android.util.Log;

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

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

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

//Stores the Image URL String from which the Image needs to be downloaded
private String mImageURLStr;

Expand All @@ -42,9 +49,9 @@ public class ImageDownloader extends AsyncTaskLoader<Bitmap> {
*
* @param context used to retrieve the application context.
* @param imageURLStr String containing the Image URL from
* which the image needs to be downloaded
* which the image needs to be downloaded. Can be {@code null}.
*/
public ImageDownloader(Context context, String imageURLStr) {
public ImageDownloader(Context context, @Nullable String imageURLStr) {
super(context);
mImageURLStr = imageURLStr;
}
Expand All @@ -58,15 +65,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 @@ -171,16 +199,16 @@ public void onCanceled(Bitmap data) {
*/
private void releaseResources() {
//Invalidating the Loader data
if (mDownloadedBitmap != null) {
mDownloadedBitmap = null;
}
mDownloadedBitmap = null;
mImageURLStr = null;
}

/**
* Method that returns the Image URL String from which this loader downloads the image
*
* @return The Image URL String from which this loader downloads the image
*/
@Nullable
public String getImageURLStr() {
return mImageURLStr;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,18 @@
package com.example.kaushiknsanji.bookslibrary.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.bookslibrary.R;
Expand Down Expand Up @@ -83,44 +87,122 @@ 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;

//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);
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.");
}

//Retrieving the loader at the loaderId if any
ImageDownloader imageDownloader = getImageDownloader(loaderId, loaderManager);

//Resetting the ImageView to the default Book Image for lazy loading
mImageView.setImageResource(R.drawable.ic_book);

//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 Book Image for lazy loading
mImageView.setImageResource(R.drawable.ic_book);
//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 30f8dcc

Please sign in to comment.