Images in ListView search results - android

I have a ListActivity that launches a task to hit a web service and display the results in a ListView. Each one of the results has an image ID attached to it.
I wrote a method that will get the image IDs of the rows displayed on screen (firstVisiblePosition() to lastVisiblePosition()) and launch a task to query another web service to get the images to display for those items. I call this method when the list's scroll state becomes SCROLL_STATE_IDLE. This makes it so the user can scroll and the task to get the images for the visible rows does not execute until the scrolling stops, preventing it from looking up images for off-screen rows.
My issue is that when the results initially show in the ListView, I can't find a good way to call my method to look up which image IDs to query for. Apparently, calling this method right after calling setAdapter does not work (I'm guessing because some of the ListView's work happens asynchronously). I am using multiple Adapter's (for reasons not pertinent to this post), so I need a good way of waiting for the list items to show before I call my method to get the IDs.
Any ideas?

After you've set the adapter or called notifyDatasetChanged() on the adapter, add your "load images" code to the list's post queue as a Runnable:
list.post( new Runnable() {
#Override
public void run() {
//do stuff
}
});

If I'm understanding your question right, you're having trouble loading images over the net and performance issues; if so,
I would create a simple image cache in my Adapter such as a local but global HashMap:
private HashMap&ltString, Drawable&gt imgCache = new HashMap&ltString, Drawable&gt();
then in my getView() method, I would asynchronously (using a Thread and a Handler) load the images and save loaded images in my imgCache by assigning position as the key and loaded images as Drawables.
final Handler h = new Handler() {
public void handleMessage(Message msg) {
if(msg.obj != null) {
Drawable drawable = (Drawable)msg.obj;
image.setImageDrawable(drawable);
image.postInvalidate();
image.requestLayout();
imgCache.put(cacheKey, drawable);
}
}
};
loadImage(myImageView, imageURL, h); // threaded method which loads the images from net
also, in my getView() method I would first ask imgCache to see if the image already exist before loadImage is called.
This should optimize your list and rescue you from using multiple Adapter etc.
Hope this helps,
-serkan

Related

Custom SimpleCursor adapter BindView calls

I wanted to know if there is a way to make the BindView work only once per item?
Are items that we scroll away from the screen getting destroyed? and when they come back we must run bindview again?
The reason is I got my adapter to Download an image and set it on the item.
when i scroll the image is getting downloaded again, even if i didnt quit the application.
the bindview is called each time an item "returns" into display so even tho i already downloaded it, it will preform the asyntask again ( the asyntask is called in the adapter)
I even tried saving BLOB on my database after each download, to know when to call the asyntast and when just to make a bitmap out of the array
if(photoBArray != null){
Bitmap bitmap = BitmapFactory.decodeByteArray(photoBArray, 0, photoBArray.length);
holder.icon.setImageBitmap(bitmap);
}else{
holder.icon.setImageResource(R.drawable.loader);
String urlString = "https://maps.googleapis.com/maps/api/place/photo?maxwidth=100&photoreference="
+ c.getString(c.getColumnIndex(PlacesDBHelper.PHOTO_REFERENCE_COL))+"&key=API_KEY";
Log.e("Photo REFERENCE", c.getString(c.getColumnIndex(PlacesDBHelper.PHOTO_REFERENCE_COL)));
try {
URL url = new URL(urlString); // create new URL object from url string
new ImageDownloader(holder.icon, this).execute(url);
}catch (Exception e){
Log.e("Error in url", e.getMessage());
}
}
I am using chrisbanes pulltorefresh listview as well, im not sure if thats the casue for the problem
Bottom line question: how to make an item look steady and ready when he gets back into the screen and not beeign created again? ( cause even if i dont dowloand an image and only setting a bitmap, the action of setimagebitmap might be visible on a slow phone)
Thanks in advance!
The entire point of ListView is to recycle row views. They aren't destroyed, they are passed back to the adapter to be bound with new data for another row. It is expected that getView (or bindView in the case of cursor adapters) will be called again if you scroll an item off screen far enough and then scroll it back on screen.
What you need is an image caching layer between the download task and the adapter. When bindView happens, first check the cache to see if the image is there. If it is, use it. If it's not, download it and add it to the cache, then use it.

Android: GridView Adapter needs to download and display images with async tasks - too slow

I'm building an app that should make it possible to browse the images of an internet site with lots of galleries and photos. Here are the steps that the app is going through:
Extract URLs to albums and their images from rss feed
Build a ListView with every Album and one thumbnail
The ListView's adapter getView() method sets every item's ImageView to a placeholder, which will be replaced with the real thumbnail by the corresponding AsyncTask
The Adapter's getView() then executes an AsyncTask which fetches the image
The AsyncTask checks the cache first and if the picture is not there, it downloads
Using WeakReferences, the AsyncTasks update (or not) the desired View with the aquired image, thus replacing the placeholder.
When an Album is opened, I go through the same procedure as in step 2 but use a GridView instead of the ListView to display the album contents.
/* GridViewAdapter.getView(...) follows */
public View getView(int position, View convertView, ViewGroup parent)
ImageView thumbnail;
// Recycle or not...
if (convertView == null) {
// Create new View
thumbnail = new ImageView(mContext);
} else {
// Recycle View
thumbnail = (ImageView) convertView;
}
// Set the placeholder Drawable
thumbnail.setImageResource(R.drawable.placeholder);
if (position < amountOfPhotos) {
if (album.getLinks().size() >= 1) {
// imageFetcher creates an AsyncTask for every call of loadThumbnail(...)
imageFetcher.loadThumbnail(thumbnail, album.getURL(position));
}
// Formating the ImageView a little...
...
}
return thumbnail;
The problem is the performance of the AsyncTasks. The ListView displays on my phone around 7 items resulting in roughly a dozen simultaneous AsyncTasks when the user scrolls through the list. This is fine, the list still builds quickly.
Now the album's GridView displays 15 items at once and scrolling through the List creates many many AsyncTasks. Since some AsyncTasks have to download from the web, they stay alive for a couple of seconds. This completely slows out the AsyncTasks, which would only have to reload Bitmaps from cache.
The result is, that as long as many AsyncTasks are running, the GridView does not display images when scrolling back up, even if it just displayed them a few seconds ago. Simply because there are too many AsyncTasks.
Any suggestions on how to solve my problem? I was thinking of something like an AsyncTask factory, where I can queue jobs and set priorities. That way I could control which job is executed next and I could also control the maximum amount of AsyncTasks running at once.
It would already help me if someone could tell me if my approach sounds right in general, or if I'm completely on the wrong track with AsyncTasks here...
I suggest you to use a library like Picasso that's make all of your placeholder/async image downloading very, very easy !
Starting from Android 3.0 AsyncTasks are executed sequentially. So it's totally possible that some long-running AsyncTasks will block others. In order to execute the concurrently you can use asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params);
But generally a good solution will be to create a ThreadPoolExecutor and execute your Runnables on it. This way you can control the number of tasks running and decide which order to use.

Android Listview Filtering versus Repopulation

after some advice really. My app fills a list view on load using a mediastore cursor. This is pulling music linked to user defined folder, which in most cases will be all of their stored music. I have one beta tester that is using an Archos Tablet with approximately 10000 songs on it, running android 2.2. While performance for most users is pretty slick, I wanted to improve the experience for users such as this.
The current process:
User loads app.
App finds default folder
App populates list view with music within and below that folder
User moves to a folder further down the tree, list view is repopulated based on the selected folder
User moves again....list is repopulated based on the selected folder...
So what I'm wondering is this - is it faster/more efficient to use the following process:
User loads app
App finds default folder
app populates list view with music within and below that folder
user moves to a folder within the tree, THE LIST IS FILTERED TO THAT FOLDER
if the user moves higher up the tree than the default data (i.e. potential for new files), the list view is repopulated, but only in this circumstance.
So basically,my questions is "how does filtering compare to repopulation?"
A very good question. Let me try to answer this.
Filtering is actually repopulation the ListView, whereas you create/get a new collection and tell the Adapter it's content has changed by calling notifyDataSetChanged.
The 'heavy' work for a listView is that getView call in it's adapter. I've tested this myself, and if you inflate a new View every time getView is called, the performance drops. Heavenly.
The ListView's adapter is built so that already inflated views can be re-used, which tackles above named problem. Besides, only visible views are loaded, so it's not like the Adapter is going to create 10000 views if you tell it's collection is 10000 items big.
notifyDataSetChanged will tell the adapter to rebuild the listviews content, but it still contains previously inflated views. So here is a big performance win.
So my advice for you is, when you are using the same 'row layout' to just repopulate the ListView using notifyDataSetChanged. I've implemented this multiple times myself without noticing any UI performance issues. Just make sure to do the filtering of your collection an a background thread. (AsyncTask comes in handy here).
One last tip: Do you have any phone thats quite old? Or someone you know does? Find the slowest phone you can and test your application on it for performance. I have a HTC Legend myself, which is outdated and slow if f*ck, but perfect for performance testing. If it runs on my (old) phone, it runs on any phone.
Pseudo code sample if your applications flow:
public class FolderListActivity extends Activity implements OnItemSelected {
// NOTE: THIS IS PSEUDO CODE
private ListView listView
private Adapter adapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstaceState);
// setContentView here
listView = (ListView)findViewById(R.id.your_listview_here);
listView.setOnItemSelectedListener(this);
}
public class AsyncLoadMusicLocationTask extends AsyncTask<Void, Void, List<String>> {
public List<String> doInBackground(Void... params) {
// Load the information here, this happens in the background
// using that cursor, i'm not sure what kind of things you are using
// So I assumed a List of Strings
}
#Override
public void onPostExecute(List<String> result) {
// Here we have our collection that was retrieved in a background thread
// This is on the UI thread
// Create the listviews adapter here
adapter = new Adapter(result, and other parameters);
listView.setAdapter(adapter);
}
}
#Override
public void onItemSelect(Some params, not sure which) {
// THIS SHOULD BE DONE ON THE BACKGROUND THE PREVENT UI PERFORMANCE ISSUES
List<String> collection = adapter.getObjects();
for (int i = 0; i < collection.size(); i++) {
// Filter here
}
// this method will most probably not exist, so you will need to implement your own Adapter class
adapter.setObjects(collections);
adapter.notifyDataSetChanged();
}
}

Lazy-loading images in ListView on Android

I implemented the lazy-loading images in my ListView.
I use a AsyncTask to download the image from the internet and bind it to the ImageView in the UIThread.
It's working except that when I scroll the ListView vary fast, the downloaded images sometimes are binded into the wrong items in the list.
I guess the problem is from the reuse of convertView in the BaseAdapter.
Any ideas to solve it?
Many thanks.
EDIT:
I post the answer as following:
public void setBitmap(int position, Bitmap image) {
View itemView = mListView.getChildAt(position - mListView.getFirstVisiblePosition());
if (itemView != null) {
ImageView itemImageView = (ImageView) itemView.findViewById(R.id.item_imageview);
itemImageView.setImageBitmap(image);
}
}
There are two problems that will arise during lazy loading of images in a ListView.
The old images are still shown until the new ones are loaded. This is easy just set the ImageView to an image is loading view or set it to invisible before starting the image download.
The second problem is harder to solve. Imagine you are scrolling very fast through your list. Now your views may be recycled before the old AsyncTask has finished loading the image. You now have two tasks running that in the onPostExecute method will set an image to the imageview. Now for a short time the wrong image will be shown until the second Task finishes, or even worse for some network related reason they don't finish in the order they started and you have the wrong image overwrite the correct image. To solve this you have to check what image should be displayed after the task finished. In the View class are two methods for things exact like this one:
setTag and getTag You can bind any object to the imageview that comes into your mind. In most of the cases I use setTag to bind the URL of the image as a String to the imageview before I start a task. Now I can cast getTag to a String after the task finished and compare the URL that should be displayed with the URL that I downloaded and only set the image if necessary.
Create a function called void setBitmap(Bitmap bitmap, int position) or similar in your adapter. Let your AsyncTask call this method when a new bitmap is available. This method may then call notifyDataSetChanged() on the UI-Thread itself to ensure the views get refreshed. Holding references to views in an adapter (even by holding them in an AsyncTask) is dangerous!

complex listeviewitem image load hang application problem

I am suing Listview and customize listview to display image inside a list item.
I want display image with every search result.
to display complext listeim i am following the following example
http://developer.android.com/guide/samples/ApiDemos/src/com/example/android/apis/view/List4.html
inside wrapper class i am initiating new thread for every new list item's image so that i won't dealy the displaying image.
my code is below
new Handler().post(new Runnable(){
#Override
public void run() {
Drawable dImage = Util.getImageFromURL(imageURL);
getImageIcon().setImageDrawable(dImage);
}
});
mean 10 images initiates 10 different image loading threads other static data is not inside thread.
problem arises when during image loading page application getting hang it should not hang.... any idea what to do ?
alt text http://img509.imageshack.us/img509/7519/thumbnailx.jpg
Use a background operation to retrieve your images, such as an AsyncTask. All your new Handler().post() stuff does is delay the work by a nanosecond, not have it be performed in the background.
Also, if you are going to use Handlers, just create one.

Categories

Resources