I've spent few days to solve this problem but still can't find a solution. I'm new to Android so my code might be pretty messy!
I have a RecyclerView(Grid layout) that displays thumbnails for images and videos. It loads media files in a specific folder. But when I launch this activity, it takes up so much memory!
To load thumbnails, I created two threads.
Thread 1) MediaLoadThread that queries media files in SDCard. It loops through the cursor and queue thumbnail decode tasks to the different thread.
Thread 2) ThumbnailLoaderThread that decode each individual thumbnail. It receives the content resolver, media type(image or video), and media id. It uses basic .getThumbnail() method. After it's done with getting thumbnail, it triggers the response callback to it's caller thread(MediaLoadThread).
3) When MediaLoadThread(Thread 1) receives the callback, it triggers another callback that lets the activity update the adapter item of the given position. The adapter updates the UI and finally the thumbnail ImageView changes from placeholder to actual thumbnail.
:::Here's my code:::
1) MediaLoadThread.java
#Override
public void run() {
mMediaDataArr.clear();
mLoaderThread.start(); // Prepping the thread 2
mLoaderThread.prepareHandler();
// .... SD Card query stuff .....
if (mediaCursor != null && mediaCursor.moveToFirst()) {
do {
mMediaDataArr.add(new MediaData(videoCursor.getInt(columnIndexId),
mediaCursor.getLong(columnIndexDate), //ID
mediaCursor.getInt(columnIndexType), //MEDIA TYPE
null); //THUMBNAIL BITMAP (NULL UNTIL THE ACTUAL THUMBNAIL IS DECODED)
} while (mediaCursor.moveToNext());
mediaCursor.close();
mResponseHandler.post(new Runnable() {
#Override
public void run() {
// This callback lets the activity activate the adapter and recyclerview so that the user can interact with recyclerview before the app finishes decoding thumbnails.
mCallback.onVideoLoaded(mMediaDataArr);
}
});
//Passing tasks to thread 2
for (int i = 0; i < mMediaDataArr.size(); i++) {
mLoaderThread.queueTask(
mMediaDataArr.get(i).getmMediaType(),
i, mMediaDataArr.get(i).getmMediaId());
}
}
}
}
// This is triggered by thread 2 when it finishes decoding
#Override
public void onThumbnailLoaded(final int position, final Bitmap thumbnail) {
mResponseHandler.post(new Runnable() {
#Override
public void run() {
mCallback.onThumbnailPrepared(position, thumbnail);
}
});
}
2) ThumbnailLoaderThread.java
public void queueTask(int mediaType, int position, int videoId) {
mWorkerHandler.obtainMessage(mediaType, position, videoId).sendToTarget();
}
public void prepareHandler() {
mWorkerHandler = new Handler(getLooper(), new Handler.Callback() {
#Override
public boolean handleMessage(Message msg) {
int type = msg.what;
final int position = msg.arg1;
int videoId = msg.arg2;
try {
if (type == MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE) {
Bitmap newThumb = MediaStore.Images.Thumbnails
.getThumbnail(mCr, videoId,
MediaStore.Images.Thumbnails.MINI_KIND, null);
postResult(position, newThumb);
} else if (type == MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO) {
Bitmap newThumb = MediaStore.Video.Thumbnails
.getThumbnail(mCr, videoId,
MediaStore.Video.Thumbnails.MINI_KIND, null);
postResult(position, newThumb);
}
} catch (Exception e) {
e.printStackTrace();
}
return true;
}
});
}
private void postResult(final int position, final Bitmap newThumb) {
mResponseHandler.post(new Runnable() {
#Override
public void run() {
mCallback.onThumbnailLoaded(position, newThumb);
}
});
}
3) LibraryActivity.java
#Override
public void onThumbnailPrepared(int position, Bitmap thumbnail) {
if (thumbnail != null && position < mData.size()) {
MediaData updatedData = mData.get(position);
updatedData.setmThumbnail(thumbnail);
mData.set(position, updatedData);
mVideoAdapter.notifyItemChanged(position);
}
}
The flow is like this.
1) The activity starts the thread 1.
2) Thread 1 starts querying files and starts thread 2. It passes the media id looping through the cursor.
3) Thread 2 decodes thumbnails with the given media id.
4) When decoding is done, thread 2 triggers the callback to Thread 1 with the result bitmap.
5) Thread 1 receives the bitmap and delivers the bitmap to activity through callback.
6) Activity receives the thumbnail and updates the RecyclerView data with the given bitmap.
It works fine, but when the system allocates almost 50MB of memory for this task... Considering it was only loading 100 thumbnails, I think it's pretty heavy.
:::What I've tried:::
1) I extracted the URI of each individual thumbnail and let the recyclerview adapter to load the image with the given URI when it binds. It works fine and did not consume that much memory, but because it loads images when the item is bound, it reloads the thumbnail whenever I scroll the screen with a little bit of delay.
2) I let the adapter to load thumbnails with the direct thumbnail path. But it won't work when the user cleans up the /.thumbnails folder.
3) I set the BitmapFactory.Options samplsize into 4 when the thread decodes thumbnails. But when it was still heavy and even slower sometimes...
4) In MediaData object, it holds the thumbnail bitmap as a member variable. So I made it null right after the adapter loaded it into the ImageView. Still heavy, and because I made the object's thumbnail into null, it just shows the placeholder when I scroll back.
I really have no clue. Any help would be appreciated!!
You can used nostra universal image loader library to load images. This library is very good for image loading and also some other library like Picasso, glide etc available which you can used instead of making manual coding.
Related
I'm trying to create a RecyclerView that is populated by ImageViews in each cell and each image corresponds to an image in Firebase Storage.
I have a list of Strings that is passed into my RecyclerView adapter and each one represents a URL to an image in Firebase Storage. I load each image inside the onBindViewHolder().
What i get in return is a very VERY slow loading of a few images (around 5-see picture) and then it takes around 4 minutes to load another 5 and it never seems to load any other images after these.
I've read multiple posts on StackOverflow but most of them just tell you to use fitCenter() or centerCrop() but that doesn't change anything in my case. I also read in Glide's documentation that Glide will automatically downsample your images so i shouldn't need to do it manually, right? Any ideas what i could be doing wrong here? The Url Strings are successfully retrieved from Firebase and the queries are resolved almost instantly so i don't think there is any issue there.
UPDATE:
I've made some modifications in the onBindViewHolder() method in order to explicitly request caching of the images from Glide and i also used the thumbnail API to download lower resolutions of the images. Now more images are loading but each one still takes around 7 seconds to load which obviously is too long. If you have any suggestions let me know please.
Here's how the RecyclerView is set up in my main activity:
iconsRCV = findViewById(R.id.cardIconsRCV)
iconsRCV.layoutManager = GridLayoutManager(this,5) // set the layout manager for the rcv
val iconUrls : ArrayList<String> = ArrayList() // initialize the data with an empty array list
val adapter = CardIconAdapter(this,iconUrls) // initialize the adapter for the recyclerview
iconsRCV.adapter = adapter // set the adapter
Note that i get new data when certain queries are done and then i call adapter.notifyDataSetChanged() to pass new data to the RecyclerView.
CardIconAdapter.java:
public class CardIconAdapter extends RecyclerView.Adapter<CardIconAdapter.ViewHolder> {
private RequestOptions requestOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL).centerCrop().error(R.drawable.applogotmp);
private List<String> urlsList;
private Context context;
class ViewHolder extends RecyclerView.ViewHolder {
ImageView iconImg;
ViewHolder(#NonNull View view) {
super(view);
iconImg = view.findViewById(R.id.cardIcon);
}
}
public CardIconAdapter(Context cntxt, List<String> data) {
context = cntxt;
urlsList = data;
}
#NonNull
#Override
public CardIconAdapter.ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.card_icons_rcv_item,parent,false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull CardIconAdapter.ViewHolder holder, int position) {
GlideApp.with(context).load(urlsList.get(position)).apply(requestOptions).into(holder.iconImg);
}
#Override
public int getItemCount() {
return urlsList.size();
}
}
P.S. The image sizes in Firebase are mostly udner 200KB but with a small few reaching 4MB. Also, the ImageView in the R.layout.card_icons_rcv_item layout is 75x75 in size.
Hope you have used latest version of glide.
There are few ways for better image loading and caching,
credit goes to this nice article .
1. Enable Disk Cache
val requestOptions = RequestOptions().diskCacheStrategy(DiskCacheStrategy.ALL)
Glide.with(context).load(url).apply(requestOptions).into(imageView)
2. List item
val requestOptions = RequestOptions()
.diskCacheStrategy(DiskCacheStrategy.ALL)
.signature(ObjectKey(signature))
Glide.with(context).load(url).apply(requestOptions).into(imageView)
3. Override Image Size (Optional)
val requestOptions = RequestOptions()
.diskCacheStrategy(DiskCacheStrategy.ALL)
.signature(ObjectKey(signature))
.override(100, 100) // resize does not respect aspect ratio
Glide.with(context).load(url).apply(requestOptions).into(imageView)
4. Add Thumbnail Url
// With thumbnail url
Glide.with(context).load(url)
.thumbnail(Glide.with(context).load(thumbUrl))
.apply(requestOptions).into(imageView)
// Without thumbnail url
// If you know thumbnail size
Glide.with(context).load(url)
.thumbnail(Glide.with(context).load(url).apply(RequestOptions().override(thumbSize)))
.apply(requestOptions).into(imageView)
// With size multiplier
Glide.with(context).load(url)
.thumbnail(0.25f)
.apply(requestOptions).into(imageView)
5. Setup Monthly Schedule for Cleaning
// This method must be called on the main thread.
Glide.get(context).clearMemory()
Thread(Runnable {
// This method must be called on a background thread.
Glide.get(context).clearDiskCache()
}).start()
6. To Transform bitmap
// TODO remove after transformation is done
.diskCacheStrategy(SOURCE) // override default RESULT cache and apply transform always
.skipMemoryCache(true) // do not reuse the transformed result while running
.diskCacheStrategy(DiskCacheStrategy.ALL) // It will cache your image after loaded for first time
.format(DecodeFormat.PREFER_ARGB_8888) //for better image quality
.dontTransform() // to load image faster just skip transform
.placeholder(R.drawable.placeholder) // use place holder while image is being load
So I recently wanted to try out the caching feature of the Picasso library, & I got into this confusing situation:
I retrieve the images' file names & paths from my web server (using Retrofit2), & I store them into ImageComponent objects (model):
public class ImageComponent {
private int id; // 'id' in database
private String filename; // image name
private String path; // image path in server storage
private Bitmap bitmap;
// Overloaded constructor
// Getters & setters
}
So now that the loading is successful, I populate a RecyclerView with these images using Picasso. The loading and inflation process is successful, but it gets a little tricky when caching the images.
Case1: using android.util.LruCache
(For convenience, I will post the entire code of the Recyclerview's adapter. I will try to be concise)
// imports
import android.util.LruCache;
public class ImageAdapter extends RecyclerView.Adapter<ImageAdapter.ViewHolder> {
private Context mContext; // Activity's context
private List<ImageComponent> mImages; // The imageComponents to display
// The contreversial, infamous cache
private LruCache<Integer, Bitmap> mImageCache;
public ImageAdapter(Context context, List<ImageComponent> images) {
mContext = context;
mImages = images;
// Provide 1/8 of available memory to the cache
final int maxMemory = (int)(Runtime.getRuntime().maxMemory() /1024);
final int cacheSize = maxMemory / 8;
mImageCache = new LruCache<>(cacheSize);
}
#Override
public ImageAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// Nothing special
}
#Override
public void onBindViewHolder(final ImageAdapter.ViewHolder holder, final int position) {
// Current ImageComponent
ImageComponent imageComponent = mImages.get(position);
// Full image path in server storage
String imagePath = Constants.SERVER_IP_ADDRESS + Constants.UPLOADS_DIRECTORY
+ imageComponent.getPath();
// Display the file's name
holder.text.setText(imageComponent.getFilename());
final ImageView imageView = holder.image;
// Get bitmap from cache, check if it exists or not
Bitmap bitmap = mImageCache.get(imageComponent.getId());
if (bitmap != null) {
Log.i("ADAPTER", "BITMAP IS NOT NULL - ID = " + imageComponent.getId());
// Image does exist in cache
holder.image.setImageBitmap(imageComponent.getBitmap());
}
else {
Log.i("ADAPTER", "BITMAP IS NULL");
// Callback to retrieve image, cache it & display it
final Target target = new Target() {
#Override
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
ImageComponent img = mImages.get(position);
// Display image
holder.image.setImageBitmap(bitmap);
// Cache the image
img.setBitmap(bitmap);
mImages.set(position, img);
mImageCache.put(img.getId(), bitmap);
}
#Override
public void onBitmapFailed(Drawable errorDrawable) {
}
#Override
public void onPrepareLoad(Drawable placeHolderDrawable) {
}
};
// Tag the target to the view, to keep a strong reference to it
imageView.setTag(target);
// Magic
Picasso.with(mContext)
.load(imagePath)
.into(target);
}
}
#Override
public int getItemCount() {
return mImages.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
ImageView image;
TextView text;
// Constructor & view binding, not that special
}
}
RESULT1
(Notice those 2 last images, & how they show other previous images before displaying the correct one)
A few notes:
I ran across a problem, where the images weren't displayed at all. After some research, I found this answer which suggested binding the target to the ImageView. (worked)
I didn't quite understand how Picasso caches the images. Is it an automatic or manual process ? This answer states that Picasso handles this task for you. But when I actually tried it out (without the android Lrucache), no caching seemed to be done : The images were getting reloaded every time I scroll back & forth.
Actually I was going to post a second use case where things went even more wrong, using the Picasso's Lrucache (images were being shown randomly , & change with every scroll), but I think this post is already long enough.
My questions are:
Why do I get that weird behavior ? (as shown in the attached GIF)
How does this whole caching process work ? Should I (or could I) use a Lrucache when making use of Picasso ?
What's the difference between the Lrucache that comes with the SDK & Picasso's ? (Performance, best use case scenarios, etc...)
I think using both LRU cache and Picasso is causing the weird behaviour. I have used Picasso to cache Image to an Adapter, which works completely fine. you can check in here
Picasso cache Image automatically when used with adapter, it will cache like this, if the child item of list/Recycler view is not visible it will stop caching the image for the respective child.So it's better to use Picasso alone with Adapter.
The main usage of Picasso over LRU cache is that, Picasso is easy to use.
ex : specifying Memory cache Size in Picasso.
Picasso picasso = new Picasso.Builder(context)
.memoryCache(new LruCache(250))
.build();
Picasso also allow you to notify user with an Image when there is an error in downloading, a default holder for Imageview before loading the complete image.
Hope it helps.
I have a NetworkImageView which loads its content from a URL, but in a specific case I also want to be able to load an image from the user's Gallery (or even capture one with the camera).
I'm using Image Chooser Library to load the image from the gallery, and after choosing, it allows me to get the file path of the image. This path is something like /mnt/sdcard/bimagechooser/IMG_20140811_155007906.jpg
If I try to load the image directly from this path, Volley will raise an exception stating:
NetworkDispatcher.run: Unhandled exception java.lang.RuntimeException: Bad URL /mnt/sdcard/bimagechooser/IMG_20140811_155007906.jpg
I also tried setting the drawable:
Drawable newImage = Drawable.createFromPath(imagePath);
mNetworkImageView.setImageDrawable(newImage);
Nothing happens when these lines run, the view remains empty/unchanged.
What is the correct way to set the NetworkImageView content without a URL?
The reason that setImageDrawable does not work is because in onLayout and setImageUrl call the private method loadImageIfNecessary, which will call another private method setDefaultImageOrNull if there was no image url provided. This wipes out whatever you set via setImageDrawable.
Volley's NetworkImageView code...
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
loadImageIfNecessary(true);
}
void loadImageIfNecessary(final boolean isInLayoutPass) {
...
// if the URL to be loaded in this view is empty, cancel any old requests and clear the
// currently loaded image.
if (TextUtils.isEmpty(mUrl)) {
if (mImageContainer != null) {
mImageContainer.cancelRequest();
mImageContainer = null;
}
setDefaultImageOrNull();
return;
}
...
}
private void setDefaultImageOrNull() {
if(mDefaultImageId != 0) {
setImageResource(mDefaultImageId);
}
else {
setImageBitmap(null);
}
}
You can do as #mmlooloo suggests and manually add your image to the image cache OR you create your own "NetworkImageView" based off of Volley's and have onLayout NOT call loadImageIfNecessary when no url is provided. You can then override the ImageView setters setImageDrawable and setImageURI to set the url to null. You can then use setImageDrawable and setImageURI as you normally would with ImageView.
NOTE: You may run into issues if you override setImageBitmap or setImageResource methods as these are called within loadImageIfNecessary. I have not run into issues yet, however I have not done much testing.
Your class...
public class MyNetworkImageView extends ImageView {
//Copy the code from Volley's NetworkImageView and change onLayout as below.
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (!TextUtils.isEmpty(mUrl)) {
loadImageIfNecessary(true);
}else{
if (mImageContainer != null) {
mImageContainer.cancelRequest();
mImageContainer = null;
}
}
}
//Change the other ImageView image setters, for example...
#Override
public void setImageDrawable(Drawable drawable) {
mUrl = null;
super.setImageDrawable(drawable);
}
#Override
public void setImageURI(Uri uri) {
mUrl = null;
super.setImageURI(uri);
}
}
I am not sure but it worth trying:
when you create memory cache for your mImageLoader keep it somewhere, now create cache key for your
image by this code:
private static String getCacheKey(String url, int maxWidth, int maxHeight) {
return new StringBuilder(url.length() + 12).append("#W").append(maxWidth)
.append("#H").append(maxHeight).append(url).toString();
}
after that put your image into the cache by calling :
mCache.putBitmap(cacheKey, response);
then again send your url to NetworkImageView, Volley will find your url in his cache and return it to you.
I hope it helps!!
I have this ImageAdapter for android's listView:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = null;
RelativeLayout relativeLayout = null;
Offer currentOffer = mOffersList.get(position);
if (convertView == null) { // create a new view if no recycling
// available
// Make up a new view
LayoutInflater inflater = (LayoutInflater) mContext
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = inflater.inflate(R.layout.offer_list_item, null);
relativeLayout = (RelativeLayout) view
.findViewById(R.id.offerImage);
} else {
view = (View) convertView;
relativeLayout = (RelativeLayout) view
.findViewById(R.id.offerImage);
setBackgroundDrawable(relativeLayout, null);
}
String imageUrl = "";
imageUrl = currentOffer.getImageUrl().toString();
Bitmap bitmap = imageCache.get(imageUrl);
if (bitmap != null) {
Drawable dr = new BitmapDrawable(mContext.getResources(), bitmap);
setBackgroundDrawable(relativeLayout, dr);
} else {
if (!downloadingImageUrls.contains(imageUrl)) {
downloadingImageUrls.add(imageUrl);
new DownloadImageAsyncTask().execute(imageUrl);
}
}
return view;
}
and this:
class DownloadImageAsyncTask extends AsyncTask<String, Void, Void> {
#Override
protected Void doInBackground(String... params) {
String imageUrl = params[0];
try {
Bitmap bitmap = BitmapFactory
.decodeStream((InputStream) new URL(imageUrl)
.getContent());
imageCache.put(imageUrl, bitmap);
} catch (IOException e) {
Log.e("DownloadImageAsyncTask", "Error reading bitmap" + e);
}
downloadingImageUrls.remove(imageUrl);
return null;
}
#Override
protected void onPostExecute(Void result) {
notifyDataSetChanged();
}
}
why do all of the list items loaded together? it's don' asynchronously but yet not one by one. All together.
how can i load it lazily?
and why is this code more efficient?
// better
public class DownloadImageAsyncTask2 extends
AsyncTask<String, Void, Bitmap> {
private final ImageView imageView;
public DownloadImageAsyncTask2(ImageView imageView) {
this.imageView = imageView;
}
#Override
protected void onPreExecute() {
Log.i("DownloadImageAsyncTask", "Starting image download task...");
}
#Override
protected Bitmap doInBackground(String... params) {
try {
return BitmapFactory.decodeStream((InputStream) new URL(
params[0]).getContent());
} catch (IOException e) {
Log.e("DownloadImageAsyncTask", "Error reading bitmap" + e);
}
return null;
}
#Override
protected void onPostExecute(Bitmap bitmap) {
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
}
}
}
To answer your initial question, any adapter you write is exactly as it's called; it's an adapter. It lets you translate your data into Views displayed in an AdapterView. It would not make sense to force all views to be "loaded lazily" and, quite frankly, should not do that for a few reasons.
Maybe you have prefetched all your images and don't need lazy loading. Maybe they even just come from resources and don't need to be handled in a special way at all.
How would it know from where to lazily load your images (database, memory map, file, network resource, other android service, etc.)? That's your job when writing the adapter.
You might argue it would be nice for Adapter subclasses to have a loadLazily(Uri image, ImageView view) function. If so why not subclass AbsAdapter yourself and add it or submit a patch to the AOSP. I, however, doubt you will find agreement that even a function like that should be included as part of an Adapter. It's really your job to manage your data in an efficient manner, despite how normal it is to lean on the system for data management in Android.
To answer your other questions about the two methods of lazy loading you proposed, the difference is that the first method causes the AdapterView to reload all of its Views whereas in the second method you are simply invalidating the view into which you are loading the image.
I don't actually agree the second method is "better" on a whole because every time your configuration is changed or process dies and Views need to be reloaded you need to make new calls over the network or to the file system to load your images. If you cache them as you do in the first method then you can at least avoid needing to reload all the images through configuration changes. If you wrote a separate image loading service that ran in its own process (not something I'm recommending) you could also avoid the second case (of your :default process getting killed).
Point is, you're responsible for loading your images, not Android. If you want it done for you take a look at these great image loading or otherwise general resource acquisition libraries for android: Local image caching solution for Android: Square Picasso vs Universal Image Loader . I've personally used Universal-Image-Loader before and admit it works as advertised. However, it caches all your images on external storage which might not be an option for you.
The listview can call getView for all items in its onMeasure (cf. measureHeightOfChildren in listView.java).
So, with this getView, it loads all images.
To load lazily, you need to load images elsewhere: let the views ask the image when needed (onDraw) to a manager (which will load them asynchronously).
notifyDataSetChanged() with update all the listview (meaning recycling views, calling getView ...), imageView.setImageBitmap will update only the ImageView so it's a better idea.
use this imageloader it works IMAGELOADER
You can use this library https://github.com/thest1/LazyList. It's very easy to use.
I am getting json data. In that json I have a url for an image. Now I want to display that Image in ImageView. How can I do acheive this? Here is my code
class LoadInbox extends AsyncTask<String, String, String> {
/**
* Before starting background thread Show Progress Dialog
* */
#Override
protected void onPreExecute() {
super.onPreExecute();
pDialog = new ProgressDialog(Home.this);
pDialog.setMessage("Loading Inbox ...");
pDialog.setIndeterminate(false);
pDialog.setCancelable(false);
pDialog.show();
}
/**
* getting Inbox JSON
* */
protected String doInBackground(String... arg0) {
// Building Parameters
List<NameValuePair> params = new ArrayList<NameValuePair>();
JSONObject json = userFunctions.homeData();
Log.e("Data", json.toString());
// Check your log cat for JSON reponse
Log.d("Inbox JSON: ", json.toString());
try {
data = json.getJSONArray(TAG_DATA);
Log.d("inbox array: ", data.toString());
// looping through All messages
for (int i = 0; i < data.length(); i++) {
JSONObject c = data.getJSONObject(i);
// Storing each json item in variable
String profile_img = c.getString(TAG_PROFILE_IMG);
// creating new HashMap
HashMap<String, String> map = new HashMap<String, String>();
// adding each child node to HashMap key => value
map.put(TAG_PROFILE_IMG, profile_img);
// adding HashList to ArrayList
inboxList.add(map);
}
} catch (JSONException e) {
e.printStackTrace();
}
return null;
}
protected void onPostExecute(String file_url) {
// dismiss the dialog after getting all products
pDialog.dismiss();
// updating UI from Background Thread
runOnUiThread(new Runnable() {
public void run() {
/**
* Updating parsed JSON data into ListView
* */
ListAdapter adapter = new SimpleAdapter(
Home.this, inboxList,
R.layout.home_list_item, new String[] { TAG_PROFILE_IMG },
new int[] { R.id.profile_img2 });
// updating listview
setListAdapter(adapter);
}
});
}
here is my layout
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<ImageView
android:id="#+id/profile_img2"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingTop="8dip"
android:paddingLeft="8dip"
android:paddingBottom="4dip" />
</RelativeLayout>
So you'll want to create another AsyncTask that given a URL will load the image, and populate some control. I typically do something like this:
ImageView imageView = (ImageView)findById(R.id.blah);
new ImageLoader( person.getImageUri(), imageView, 128, 128 ).execute();
The ImageLoader would be another AsyncTask like this:
public class ImageLoader extends AsyncTask<URI,Integer,BitmapDrawable> {
private Uri imageUri;
private ImageView imageView;
private int preferredWidth = 80;
private int preferredHeight = 80;
public ImageLoader( URI uri, ImageView imageView, int scaleWidth, int scaleHeight ) {
this.imageUri = uri;
this.imageView = imageView;
this.preferredWidth = scaleWidth;
this.preferredHeight = scaleHeight;
}
public BitmapDrawable doInBackground(URI... params) {
if( imageUri == null ) return null;
String url = imageUri.toString();
if( url.length() == 0 ) return null;
HttpGet httpGet = new HttpGet(url);
DefaultHttpClient client = new DefaultHttpClient();
HttpResponse response = client.execute( httpGet );
InputStream is = new BufferedInputStream( response.getEntity().getContent() );
try {
Bitmap bitmap = BitmapFactory.decodeStream(is);
if( preferredWidth > 0 && preferredHeight > 0 && bitmap.getWidth() > preferredWidth && bitmap.getHeight() > preferredHeight ) {
return Bitmap.createScaledBitmap(bitmap, preferredWidth, preferredHeight, false);
} else {
return bitmap;
}
} finally {
is.close();
}
}
}
public void onPostExecute( BitmapDrawable drawable ) {
imageView.setImageBitmap( drawable );
}
}
Then you can kick this AsyncTask off when the image is being bound in your ListView by creating your own subclass ListAdapter. So you'll have to ditch using SimpleAdapter because things aren't simple anymore. This has a lot of advantages so you only load the images being displayed. That means a very small number is loaded out of the total. Also your user can see the data before the image loads so quicker access to the data. If you did this in your existing AsyncTask you'd load every image, and the user would have to wait for every single one to finish before the data is shown to the user. There are somethings that can be improved by this. One AsyncTask uses its own thread so you'll be running a lot of threads potentially (10 or more) all at once. That can kill your server with lots of clients. You can centralize these using an ExecutorService (ie thread pool) but you'll have to ditch using AsyncTask and implement your own facility to run the job off the UI thread and post the results back on the UI thread. Second, your images will load every time the user scrolls. For this I implemented my own caching scheme based on the URI of the image so I only load the image once and return it from the cache. It's a little too much code to post here, but these are exercises for the reader.
Also notice I'm not posting back to the UI thread in onPostExecute(). That's because AsyncTask does that for me I don't have to do it again as your code above shows. You should just remove that extra runnable and inline the code in onPostExecute().
you can try picasso is really easy to use and works really well.
Picasso.with(this.getActivity()).load(person.getImageUri()).into(imageView); // if person.getImageUri() has the url image loaded from json
And that's it.
As it looks you are getting the more than one url (as in array)
1- Keep all the url in an hastable with key as url and value as image View.
2- Show your UI and with loading image.
3- create the other task download image one by one and update in the image view.
as example lazy imageloader......
http://iamvijayakumar.blogspot.in/2011/06/android-lazy-image-loader-example.html
http://codehenge.net/blog/2011/06/android-development-tutorial-asynchronous-lazy-loading-and-caching-of-listview-images/
Android Out of Memory error with Lazy Load images
Android lazy loading images class eats up all my memory
Lazy Load images on Listview in android(Beginner Level)?
Lazy load of images in ListView