I use Volley NetworkImageView to download images from internet and show in my listview. Now I want to make Volley NetworkImageView show saved images when there is no network available. Volley has already cached images by URL as a key because when I use
Entry entry = SingletonRequestQueue.getInstance(context).getRequestQueue().getCache().get(imageURL);
the entry.data is not null. But my problem is that image resolutions are high and I can not use
Bitmap b = BitmapFactory.decodeByteArray(entry.data, 0, entry.data.length);
because it creates a lot of lag and I have to reinvent the wheel because again I must create asynctask see when listview has scrolled to cancel decoding, recycling the bitmap, creating in memory cache, finding best insample value and ...
so better Idea is just do some tricks that make Volley NetworkImageView use its own DiskLRUCache to show them when there is no network.
Any idea?
My code:
public class SingletonRequestQueue {
private static SingletonRequestQueue mInstance;
private RequestQueue mRequestQueue;
private ImageLoader mImageLoader;
private static Context mCtx;
private LruBitmapCache mLruBitmapCache;
private SingletonRequestQueue(Context context) {
mCtx = context;
mRequestQueue = getRequestQueue();
mLruBitmapCache = new LruBitmapCache(LruBitmapCache.getCacheSize(context));
mImageLoader = new ImageLoader(mRequestQueue,mLruBitmapCache);
}
public static synchronized SingletonRequestQueue getInstance(Context context) {
if (mInstance == null) {
mInstance = new SingletonRequestQueue(context);
}
return mInstance;
}
public RequestQueue getRequestQueue() {
if (mRequestQueue == null) {
// getApplicationContext() is key, it keeps you from leaking the
// Activity or BroadcastReceiver if someone passes one in.
mRequestQueue = Volley.newRequestQueue(mCtx.getApplicationContext(),new OkHttpStack());
// mRequestQueue = Volley.newRequestQueue(mCtx.getApplicationContext());
}
return mRequestQueue;
}
public <T> void addToRequestQueue(Request<T> req) {
getRequestQueue().add(req);
}
public ImageLoader getImageLoader() {
return mImageLoader;
}
public LruBitmapCache getLruBitmapCache() {
return mLruBitmapCache;
}
public void setLruBitmapCache(LruBitmapCache lruBitmapCache) {
mLruBitmapCache = lruBitmapCache;
}
}
and in my adapter:
public IssueListAdapter(Context context, int resource, List<Issue> objects) {
super(context, resource, objects);
this.context = context;
this.mIssueList = objects;
mImageLoader = SingletonRequestQueue.getInstance(context).getImageLoader();
}
public static class ViewHolder{
public NetworkImageView mNetworkImageView;
public TextView mFee;
public TextView mName;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if(convertView == null){
holder = new ViewHolder();
LayoutInflater inflater =
(LayoutInflater) context.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.gridview_issuelist_item, parent, false);
holder.mNetworkImageView = (NetworkImageView)convertView.findViewById(R.id.NetworkImageView_MainActivity_issue_image);
holder.mName = (TextView)convertView.findViewById(R.id.TextView_MainActivity_name);
holder.mFee = (TextView)convertView.findViewById(R.id.TextView_MainActivity_fee);
Utility.settingTypfaceFont(context, holder.mName);
Utility.settingTypfaceFont(context, holder.mFee);
convertView.setTag(holder);
}else{
holder = (ViewHolder)(convertView.getTag());
}
final Issue issue = mIssueList.get(position);
holder.mName.setText(issue.getTitle());
holder.mFee.setText(String.valueOf(issue.getFee()));
String imageURL = issue.getPublicCover();
holder.mNetworkImageView.setImageUrl(imageURL, mImageLoader);
holder.mNetworkImageView.setDefaultImageResId(R.drawable.placeholder2);;
/*
Entry entry = SingletonRequestQueue.getInstance(context).getRequestQueue().getCache().get(imageURL);
if(entry != null && entry.data != null){
byte[] imageByte = entry.data;
loadBitmap(imageByte, holder.mNetworkImageView,imageURL);
}else{
holder.mNetworkImageView.setImageUrl(imageURL, mImageLoader);
}*/
return convertView;
}
#Override
public int getCount() {
if(mIssueList != null){
return mIssueList.size();
}
else{
return 0;
}
}
public List<Issue> getIssueList() {
return mIssueList;
}
}
I prefer to use Volley/retrofit with Android-Universal-Image-Loader
/Picasso, picture loader libs have done a great job in loading and caching images indeed.
They handle everything with a single line of code by default:
Picasso.with(context).load("http://i.imgur.com/DvpvklR.png").into(imageView);
Also you can animate, resize your images and add placeholder while they loading.
Just add this line in BasicNetwork class
if (!ConnectivityUtils.isNetworkEnabled(CardApplication.getContext()) && request instanceof ImageRequest) {
VolleyLog.e("Cached response", "No Network Connectivity for Url=", request.getUrl());
return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED,
request.getCacheEntry().data, responseHeaders, true);
}
and for data request expiry you can change the Cached.Entry using using own HttpHeaderParser
Here is Link which explain thing in detail
When you restart your app in offline, the last thing you can rely on just the Disk Cache(i.e. DiskBasedCache). Volley's local cache consist of network data and the response headers. But in this situation, we just need to focusing on the Cache-Control header. For instance, if the server-side return that header is "Cache-Control: max-age=604800", that's tell Volley to cache the response resource for 604800 seconds( source at HttpHeaderParser.parseCacheHeaders() ). Then next time we retrieving the same url's data would checking if exceeded the cache expire time, finally decide retrieve from network or local.
Follow your describe, I suppose your server-side deliver you a value like Cache-Control:must-revalidate|proxy-revalidate|no-cache|no-store, that's why you can't reuse the last retrieved data when you were in offline.
Right now there is question came : once we can manipulate the cache expire time, we'll be capable of increase that time to a large enough value so we can ensure us use that data in offline.
Unfortunately, Volley does not support us to do this. So if you can make the server-side to delivering a viable max-age for this?
If not, I'd suggest you to change to another library which fulfill this desired. and there actually have one can be your friend, is Netroid. It's based on Volley and offered a few improvements, that won't make you change your current code very much. With it, control the expire time would be far easier, and more features would be come with.
mImageLoader = new SelfImageLoader(mRequestQueue, mLruBitmapCache) {
#Override
public void makeRequest(ImageRequest request) {
// you can manipulate the cache expire time with one line code.
request.setCacheExpireTime(TimeUnit.DAYS, 10);
// you can even according to the different request to
// set up the corresponding expire time.
if (request.getUrl().contains("/for_one_day/")) {
request.setCacheExpireTime(TimeUnit.DAYS, 1);
} else {
request.setCacheExpireTime(TimeUnit.DAYS, 10);
}
}
};
the full code was on the project's sample module, i hope this can be helpful.
Hello #mmlooloo I have created a project which use DiskLRUCache and Volley. Here's the link of my repository DiskLRUCache using Volley. Hope it will helps you to show saved image. Thanks.
Search internet for "android how to check internet connectivity"
implement that and check it in your cache implementation (like LruCache).
if(networkAvailable()){ getFromNetwork()} else { getFromCache()}
logic is ok? then just try.
It seems your cache impl class is LruBitmapCache.
then how about check connectivity in that class?
public Bitmap getBitmap(String url) {
if(networkAvailable()/* this is your impl */){
// dont use cache
return null;
}
return getFromCache(); // or something like that;
}
If I understand you correctly, you would benefit if the memory cache provided to the ImageLoader class that's used by your NetworkImageView will be persisted between app runs, without losing the fact that it's a memory cache.
That memory cache keeps the correctly sized bitmap in normal operation - which you would like available even if the network goes down.
So here's an idea: every time you're app is closed, persist on file the images from the cache. The next time you load your app, when you create the memory cache - check for a persisted version on the disk, and if it's available - populate the memory cache from the disk.
There are several approaches you can take to decide when to persist an image and when to delete it.
Here's one approach: create a hybrid memory / disk cache. It would work exactly the same as your memory cache works now with the following differences:
Every time putBitmap() is called, along with your normal operation, save an encoded version of the bitmap to the disk in a background thread / AsyncTask.
Every time a bitmap is removed from the cache (I'm assuming you have some sort of space constraint on the cache), delete that file from the disk on a background thread / AsyncTask.
Create a "loadFromDisk" task, to be performed in the background every time a memory cache is created (once per app run) to populate your memory cache with the available images from the disk.
You can't avoid decoding the bitmaps, however you can cut the size and having to deal with resizing large bitmaps.
Does this help you?
Related
I have implemented the BrowseSupportFragment from the Leanback library. It has a left navigation bar with rowItems and headerIcons. Each time I move from one row in the navigation drawer the fragments are reloaded. This is not good since it keeps making server calls and therefore my image caching doest work. This is how I call each fragment in my BrowseSupportFragment,
using same idea as here:
https://www.javatips.net/api/platform_frameworks_support-master/samples/SupportLeanbackDemos/src/com/example/android/leanback/BrowseSupportFragment.java
private static class PageRowFragmentFactory extends BrowseSupportFragment.FragmentFactory {
private final BackgroundManager mBackgroundManager;
PageRowFragmentFactory(BackgroundManager backgroundManager) {
this.mBackgroundManager = backgroundManager;
}
// new fragment is registered and called from here
#Override
public Fragment createFragment(Object rowObj) {
Row row = (Row) rowObj;
mBackgroundManager.setDrawable(null);
if (row.getHeaderItem().getId() == HEADER_ID_1) {
return new MovieGalleryFragment();
} else if (row.getHeaderItem().getId() == HEADER_ID_2) {
return new SeriesGalleryFragment();
} else if (row.getHeaderItem().
getId() == HEADER_ID_3) {
return new SortByGenreFragment();
} else if (row.getHeaderItem().
getId() == HEADER_ID_4) {
return new SortByCountryFragment();
} else if (row.getHeaderItem().
getId() == HEADER_ID_5) {
return new WebViewFragment();
}
throw new
IllegalArgumentException(String.format("Invalid row %s", rowObj));
}
}
public static class PageFragmentAdapterImpl extends MainFragmentAdapter<MovieGalleryFragment> {
public PageFragmentAdapterImpl(MovieGalleryFragment fragment) {
super(fragment);
}
}
and in my fragments i extend
public class MovieGalleryFragment extends VerticalGridSupportFragment implements BrowseSupportFragment.MainFragmentAdapterProvider{
final MainActivityFragment.PageFragmentAdapterImpl mMainFragmentAdapter = new MainActivityFragment.PageFragmentAdapterImpl(this);
..........
}
so when i move from MovieGalleryFragment() to SeriesGalleryFragment and back to MovieGalleryFragment(), it is reloaded and images recalled from server. How do I prevent this ??
thank you.
Your image cache shouldn't be live in one of these fragments, so the fragment being destroyed and recreated should not cause another fetch. For example, MovieGalleryFragment should request an image from an app-wide cache before attempting to fetch from the server. Since you want to maintain an in-memory cache, a disk cache, and cleanup rules, it can be very complicated to get this right. I'd recommend relying on something like Picasso, Coil, Glide, etc. In general, you want to heavily rely on cache this way anyway since the user might close the app and reopen it shortly after.
I have a RecyclerView.
<android.support.v7.widget.RecyclerView android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
I have CardView as a listItem of my RecyclerView
<android.support.v7.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:orientation="vertical"
android:background="#android:color/white"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.android.volley.toolbox.NetworkImageView
android:id="#+id/networkImageView"
android:adjustViewBounds="true"
android:scaleType="fitXY"
android:layout_width="match_parent"
android:layout_height="130dp"/>
</android.support.v7.widget.CardView>
and in onBindViewHolder of recyclerAdapter I am loading the NetworkImageView with the an image on the network
ImageLoader imageLoader = RequestUtil.getInstance(_context).getImageLoader();
String imageUrl = "http://someinterneturl/asdfaas/dsawes.png"; // I get these URL for images from a service.
networkImageView.setImageUrl(imageUrl,imageLoader);
I have RequestUtil class from where I get my ImageLoader
public class RequestUtil {
private static RequestUtil _instance;
private RequestQueue _requestQueue;
private static Context _ctx;
private ImageLoader _ImageLoader;
private static final String DEFAULT_CACHE_DIR = "volley";
private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 8;
private RequestUtil(Context context) {
_ctx = context;
}
public static synchronized RequestUtil getInstance(Context context) {
if(_instance == null) {
_instance = new RequestUtil(context);
}
return _instance;
}
public RequestQueue getRequestQueue() {
if(_requestQueue == null) {
//_requestQueue = Volley.newRequestQueue(_ctx.getApplicationContext());
_requestQueue = getNewRequestQueue();
}
return _requestQueue;
}
public <T> void addToRequestQueue(Request<T> request) {
getRequestQueue().add(request);
}
public ImageLoader getImageLoader() {
if (_ImageLoader == null) {
_ImageLoader = new ImageLoader(this.getRequestQueue(),
new LruBitmapCacheUtil());
}
return this._ImageLoader;
}
private RequestQueue getNewRequestQueue(){
Context context = _ctx.getApplicationContext();
File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
HttpStack stack= new HurlStack();
Network network = new BasicNetwork(stack);
RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network,DEFAULT_NETWORK_THREAD_POOL_SIZE);
queue.start();
return queue;
}
}
This works fine but when I scroll the RecyclerViewList fast then sometimes in one NetworkImageView on the screen the image does not load. I see several below logs in android studio
W/art: Long monitor contention with owner Thread-6 (8925) at com.android.volley.Response com.android.volley.toolbox.ImageRequest.parseNetworkResponse(com.android.volley.NetworkResponse)(ImageRequest.java:124) waiters=5 in com.android.volley.Response com.android.volley.toolbox.ImageRequest.parseNetworkResponse(com.android.volley.NetworkResponse) for 279ms
most of the times I also see the below logs even for this images that were displayed.
D/Volley: [416] BasicNetwork.logSlowRequests: HTTP response for request=<[ ] "http://someinterneturl/asdfaas/dsawes.png" 0xe48e98af LOW 249> [lifetime=10978], [size=867672], [rc=200], [retryCount=2]
Please help me in fixing this problem by loading all the images when the scrolling stops and removing the above two warnings from the log. Let me know if you need more information.
I faced the same issue in my previous project and replaced the volley library with Picasso.
It worked like a charm and it have excellent mapping downloaded images to corresponding views and also optimised caching technique.
Refer http://square.github.io/picasso/ for usage and complete tutorial.
Hope it helps.
Don't use NetworkImageView. NetworkImageView is a nice shortcut, but its inflexible. By doing the loading yourself, you can take control over the loading of the image via ImageRequest or ImageLoader. For example, if you detect that the recycler view is scrolling rapidly you can stop making requests until it stops. But NetworkImageView was written for the simplest case- it assumes you really want the image and loads it immediately without a cancel. Use it for things where you aren't recycling.
You should prefetch the images in the recyclerview, so that when you scroll fast, the images will appear immediately. There are many ways to do it, but none of them is easy. Glide has example on how to prefetch images in recyclerview: https://github.com/bumptech/glide/tree/master/integration/recyclerview
I'm trying to get images for list view using Volley Library. I created simple HTTP helper with the following method.
/**
* Processing Image request and gets the image with given URL
*/
public Bitmap makeImageRequest(String url) {
ImageLoader il = new ImageLoader(queue, new BitmapLruCache());
il.get(url, new ImageLoader.ImageListener() {
#Override
public void onResponse(ImageLoader.ImageContainer response, boolean isImmediate) {
mBitmap = processImageResponse(response);
}
#Override
public void onErrorResponse(VolleyError error) {
VolleyLog.d(Constants.Global.ERROR, "Error: " + error.getMessage());
mBitmap = null;
}
});
return mBitmap;
}
But problem is that:
new BitmapLruCache()
Method is not recognized.
So i tried to create ImageLoader using the following code which i found on the URL:
http://www.androidhive.info/2014/05/android-working-with-volley-library-1/
ImageLoader imageLoader = AppController.getInstance().getImageLoader();
But in thos code i cannot find out where to get
AppController
Because the method code is triggered from the custom
public class HttpHelperClass
And called from the activity using the:
// Try to load remote image from URL
Bitmap bm = http.makeImageRequest("http://camranger.com/wp-content/uploads/2014/10/Android-Icon.png");
ImageView iv = (ImageView) findViewById(R.id.imageView);
iv.setImageBitmap(bm);
Is it the right approach how to load images and how can i repair my code to make succcessfull request?
Many thanks for any advice.
I think your makeImageRequest method will always return null because it takes some time for the onResponse or onErrorResponse listeners to get called but you return the mBitmap immidately!
If you want to use Volley's ImageLoader you'd better get image inside your activity or ... not from another class like your HttpHelperClass.
Also AppController is a class that extends Application and you should create it yourself. (It's in your AndroidHive link. Section 3. Creating Volley Singleton class)
And also for caching images you should not create a new ImageLoader everytime because in this way the caching gets meaningless. You should get that from your AppController class.
Furthermore I suggest you using Picasso because it's way better that Volley in image loading and a lot easier!
With Picasso you only need to call the following line to load an image from web into an ImageView:
Picasso.with(context).load(urlString).to(imageView);
I have a simple ListActivity that shows images and I inizialize my OkHttpClient for Picasso Builder in the constructor of the ImageAdapter class:
picassoClient = new OkHttpClient();
picassoClient.interceptors().add(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request newRequest = chain
.request()
.newBuilder()
.addHeader("Cookie","xyz")
.build();
return chain.proceed(newRequest);
}
});
new Picasso.Builder(context).downloader(new OkHttpDownloader(picassoClient)).build();
then in getView() I use Picasso to load images in ImageView:
Picasso.with(context).load(xyzUrl).fit().centerCrop().into(vImage);
It works well, but on device's rotation i see that heap size sometimes slowly grows, sometimes quickly and sometimes remains stable. Only rarely it drops. Am i leaking memory or is there something wrong in code?
EDIT:
I inserted this code after Picasso's call in the getView()
if (BuildConfig.DEBUG) {
Log.i("HEAP SIZE",
String.valueOf((Runtime.getRuntime().totalMemory() / 1024)
- (Runtime.getRuntime().freeMemory() / 1024)));
}
and I found that the heap size's growth happens in the getView() after loading bitmap into ImageView.
What is wrong?
EDIT 2:
tried to set static ImageAdapter, nothing changes
EDIT 3:
tried with RecyclerView instead of ListView, same behavior: heap size grows continuously while scrolling image list stepping by 30-40 bytes at every onBindViewHolder(). After device's rotation heap size grows sometimes stepping by even 2-3 Mbytes. Rarely it drops.
Why heap size slowly but continuously grows and why am I leaking some cache or some cached bitmaps after device's rotation?
UPDATE:
tried adapter without the code in the constructor (that is without new OkHttpClient and new Picasso.Builder), it works and the heap size now drops well remaining stable. Then, what is the correct way to initialize the client with cookies headers management?
UPSHOT:
finally I created my PicassoInstance class, which creates a unique static Picasso singleton and set it as the Picasso Library's singleton. Then I set it in my adapter constructor
PicassoInstance.setPicassoSingleton(context);
It works well, and it is a correct way I hope.
public class PicassoInstance {
private static Picasso myPicassoInstance = null;
public static void setPicassoSingleton(Context context) {
if (myPicassoInstance == null) {
myPicassoInstance = createMyPicassoInstance(context);
Picasso.setSingletonInstance(myPicassoInstance);
if (BuildConfig.DEBUG) {
Log.i("PICASSO INSTANCE", "CREATED");
}
}
}
private static Picasso createMyPicassoInstance(Context context) {
OkHttpClient myOkHttpClient = new OkHttpClient();
myOkHttpClient.interceptors().add(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request newRequest = chain.request().newBuilder()
.addHeader("Cookie", "xyz").build();
if (BuildConfig.DEBUG) {
Log.i("ON INTERCEPT", "COOKIE ADDED");
}
return chain.proceed(newRequest);
}
});
return new Picasso.Builder(context).downloader(
new OkHttpDownloader(myOkHttpClient)).build();
}
}
The picasso instance being returned from PicassoBuilder.build() should be a singleton, and when you need to use picasso throughout the app you should be accessing that singleton, instead of Picasso.with... you should be accessing
YourClass.getMyPicassoSingleton().with...
Otherwise you're keeping separate caches, etc for those picasso instances
edit: as I noted below, you can also call
picasso.setSingletonInstance(myPicasso);
right where you invoke the build method above, which would also solve your problem without holding onto the singleton yourself. that is probably a cleaner solution
I cannot close it as too broad, but I'd recommend you took a memory dump and gave it a long hard look in Eclipse Memory Analizer Tool to find which references are still being kept alive and by who.
This is also a great write up on it.
Adapters as fields are leaky. Views contain context which contains views. And fragments are even worse offenders. ListActivities were an API1 tool that should have been deprecated long ago. All very leak prone, but that's the Android way.
I am trying to implement something that is very similar to UrlImageViewHelper (https://github.com/koush/UrlImageViewHelper) where you can easily using a simple one line of code, load images from a url, and if the image was already downloaded it is loaded from the cache instead. The major difference is that I want the same effect but instead of downloading it from a url, I want to receive the images from my own server using my own client-server communication. Every image on my server can be uniquely identified by a string, and I use this as the id for the image.
My main idea was this: Use an LRU Cache to hold the images, but instead of holding the Bitmaps (that are very large) I want to hold the raw image data binary, so I can use the same image to build bitmaps of different sizes and qualities on demand depending on the specific situation.
This is my implementation so far:
public class ImageHandler {
private static class BitmapCache extends LruCache<String, byte[]>
{
public WigoBitmapCache(int maxSize) {
super(maxSize);
}
#Override
protected int sizeOf(String key, byte[] value) {
return value.length;
}
}
private static class ImageHandlerThread extends Thread
{
/* THIS THREAD WILL DECODE THE IMAGE AND SET THE BITMAP TO THE IMAGEVIEW IN THE BACKGROUND */
Activity activity;
ImageView imageView;
byte[] imageBytes;
public ImageHandlerThread(Activity activity, ImageView imageView, byte[] imageBytes)
{
this.activity=activity;
this.imageView=imageView;
this.imageBytes=imageBytes;
}
public void run() {
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length, o);
int factor1=o.outHeight/height;
int factor2=o.outWidth/width;
/* height and width are for now constant */
o = null;
o = new BitmapFactory.Options();
if (factor1>factor2)
o.inSampleSize=factor1;
else
o.inSampleSize=factor2;
Bitmap bit = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length,o);
setBitmap(bit);
bit = null;
}
private void setBitmap(final Bitmap bit) {
activity.runOnUiThread(new Runnable() {
#Override
public void run() {
imageView.setImageBitmap(bit);
}
});
}
}
private static class QueueItem
{ /*USED TO HOLD INFO ABOUT THE IMAGE REQUEST UNTIL THE IMAGE GETS FROM THE SERVER */
String imageName;
Activity activity;
ImageView imageView;
public QueueItem(String imageName, Activity activity, ImageView imageView)
{
this.imageName=imageName;
this.activity = activity;
this.imageView = imageView;
}
}
private BitmapCache cache; // this cache holds the image binaries
private ArrayList<QueueItem> queue; // this queue holds the info about the request, until the server sends the image
public ImageHandler(int maxSize)
{
cache=new BitmapCache(maxSize);
queue = new ArrayList<QueueItem>();
}
public synchronized void setBitmap(Activity activity, ImageView imageView, String imageName)
{
byte[] imageBytes = cache.get(imageName);
if (imageBytes==null)
{
QueueItem item = new QueueItem(imageName, activity, imageView);
queue.add(item);
/* HERE IS THE CODE TO RETRIEVE THE IMAGE BINARY FROM MY SERVER, THIS CODE WORKS FINE, SO THERE IS NO REASON TO BOHER YOU WITH IT */
}
else
{
ImageHandlerThread thread = new ImageHandlerThread(activity, imageView, imageBytes);
thread.start();
}
}
public synchronized void insert (String imageName, byte[] imageBytes)
{
/* THIS METHOD IS THE CALLBACK THAT IS CALLED WHEN THE IMAGE BINARY IS RECEIVED FROM THE SERVER */
cache.put(imageName, imageBytes);
for (QueueItem item: queue)
{
if (item.imageName.equals(imageName))
{
ImageHandlerThread thread = new ImageHandlerThread(item.activity, item.imageView, imageBytes);
thread.start();
queue.remove(item);
}
}
}
}
Basically, the main method here is setBitmap(), it gets the activity, the imageView that needs the bitmap, and the name of the image name. If the image is already in the cache, a new thread is started to decode the bytes into the proper size bitmap and set the bitmap to the imageView. If the image is not present in the cache, the request is put in a queue until the image is received, the image is retrieved from the server and then the same thread as before is started.
All this works absolutely fine, the problem is that when the imageView is set another bitmap for the image or even when the activity is destroyed, the bitmap is still resident in memory and is not collected by the GC.
At first I though that it is because I was keeping a reference to the activity, and that reference keeps the activity alive, but it seems not to be the case, my reference to an activity is very short lived, and once the image arrives from the server this reference is cleared.
I am running out of memory fast with this implementation, and I have no idea why or what to do to fix it. The bitmaps I create are not collected although I keep no references to them. Could this be an artifact of the way I decode the images? or do the threads keep references that are not collected properly? Anyone has any ideas?
Ok, so I misunderstood the problem, what was happening is that the GC was not collecting the finished thread objects because I was running this in debug mode, and not in run mode. When I ran this in run mode, the memory usage of my app did not go above 8MB at all, while when it was in debug mode I got to the 25MB+ area.
Conclusion: Do not trust the GC and memory usage info in debug mode, especially if you have many short term threads running.