I have a ListView that display Bitmap pictures. These Bitmaps get to be quite large. When i scroll on the ListView it seems to be very heavy. What techniques can i use to optimize the ListView ? This may cover compressing the Bitmap in memory, or ways to enhance the List View memory management ?
First, read this,
http://developer.android.com/training/improving-layouts/smooth-scrolling.html
It talks about the view holder pattern, and loading images in a threads. also, read this,
http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html
This talks about how to build an efficient memory cache for bitmaps.
If that's not enough, another technique you can employ is to avoid loading images until scrolling stops. This prevents the listview from loading all of the images if the user say flings to the bottom of the list. Basically, something like this,
pagerList.setOnScrollListener(new OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (scrollState != OnScrollListener.SCROLL_STATE_IDLE) {
return;
}
// load images for adapter views between first and first+count.
// depending on your memory requirements, you can pre-load additional
// images before first and after first+count to give a better
// user exp
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
first = firstVisibleItem;
count = visibleItemCount;
}
});
This requires that you keep a handle to the ImageView for each item in your adapter, so you can find it later and set the appropriate bitmap into it. This could be as simple as keeping an array of image views in your adapter, where the index == the position in the list view.
I mostly use LruCache to optimize list and load images from cache LruCache
add this in getView in BaseAdapter class
#Override
public View getView(.....
..... . .
Bitmap image = getBitmapFromMemCache(name);
if (image == null)
{
image = decodeSampledBitmapFromUri(image_path.get(position), 64,64);
}
else
{
Log.i("loding. ", "from cache "+name);
}
// Bitmap ThumbImage = ThumbnailUtils.extractThumbnail(BitmapFactory.decodeFile(details), 64, 64);
holder.img.setImageBitmap(image);
addBitmapToMemoryCache(name, image);
Related
I have to show a list of posts, some of them with images (think in something like twitter) and load more when I do scroll on the ListView but images take some seconds to appear. How can I do the load without delay? Currently I'm doing it with Picasso
Here my code:
ListView lv = findViewById(R.id.list_feed);
arrayPosts = fillArray(data);
adapter= new PostAdapter(this, arrayPosts);
lv.setOnScrollListener(new AbsListView.OnScrollListener() {
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
int totalItemCount) {
int lastIndexInScreen = visibleItemCount + firstVisibleItem;
if (lastIndexInScreen >= totalItemCount && totalItemCount > 0 && !isLoading) {
isLoading = true;
loadMore();
}
}
});
lv.setAdapter(adapter);
My Adapter's getView() method:
public View getView(int i, View view, ViewGroup viewGroup) {
Post post = (Post) getItem(i);
view = LayoutInflater.from(context).inflate(R.layout.row_post_item, null);
(...)
Picasso.with(context).load(post.getImgProfile()).into(imgProfile);
Picasso.with(context).load(post.getImagePost()).into(imgPost);
(...)
return view;
}
You can't do it with no delay at all. The download speed depends on Internet connection & the device, so some delay has to exist, unless you pre-load images one page up front in advance (that's how most apps do it). But even then fast scrolling may cause some delay.
I would suggest Glide.
It is much faster than Picasso. It handles the download to memory and cache utilization better than Picasso. It acts better with GIFs, and prevents nasty OutOfMemoryError. Of course you can load placeholders until your image loads etc.
It even comes recommended by Google.
Cheers
Picasso is a very good way to load images because of its caching abilities , still I would recommend you read this answer and then decide which option you want to use according to your specific needs.
I'm making an app and I'm using grid view to show all of my images, the problem is I have lots of images, about 12,000, and I don't want to load all of them at the start, because lets face it, it will take forever, so I was wondering what is the best way of accessing the server to fetch more items once the gridview has reached the end.
Thanks.
To achieve load more items on scrolled to end of the gridview
gridView.setOnScrollListener(new AbsListView.OnScrollListener(){
#Override
public void onScroll(AbsListView view,
int firstVisibleItem, int visibleItemCount,
int totalItemCount) {
//Algorithm to check if the last item is visible or not
final int lastItem = firstVisibleItem + visibleItemCount;
if(lastItem == totalItemCount){
// here you have reached end of list, load more data
fetchMoreItems();
}
}
#Override
public void onScrollStateChanged(AbsListView view,int scrollState) {
//blank, not required in your case
}
});
One way is to start the server request for an image in the adapter getView(). When the request completes, you set the retrieved bitmap on the ImageView. There is a very in-depth description of this technique on the Android Developer Blog: Multithreading For Performance | Android Developers Blog. Most servers are fast enough that the lag time to display the image is not objectionable.
However, if you want to load images before the ImageViews are displayed, here's another way you can do it with an OnScrollListener, an AsyncTask, and a list adapter for the `GridView.
Say you have a constant final int THRESHOLD = 50; and a variable lastItem that starts at zero.
When user displays your GridView the AsyncTask kicks off and retrieves the first 100 images. In the onPostExecute(), it adds the images to your adapter and calls notifyDataSetChanged(). It also adds 100 to lastItem.
So now your adapter has 100 images. The user is scrolling through images and the OnScrollListener gets called.
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount)
if (firstVisibleItem >= lastItem - THRESHOLD) {
new FetchImageAsyncTask().execute();
}
}
Your AsyncTask executes, lastItem is updated to 200, and your adapter gets 100 more images.
Then the user scrolls some more and it starts all over again.
By using the threshold, the user can scroll another 50 images before the end of the list, and that may be enough time to load the next 100 images.
Recently, I started using Google ImageWorker class for loading bitmaps on a background thread. This class handles everything, including putting the bitmaps in the ImageView. Google talks about this class (and it's helper classes for Image manipulation and caching) here: http://developer.android.com/training/displaying-bitmaps/process-bitmap.html
The ImageView(s) in my case are parts of list items in a ListView. Here's the interesting part of getView on my ArrayAdapter:
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
View view = convertView == null ? inflater.inflate(R.layout.nodelist_item, null) : convertView;
NodeHolder holder = getHolder(view);
Node node = mList.get(position);
holder.txtTitle.setText(node.Title);
//Setting the rest of the different fields ...
//And finally loading the image
if(node.hasArt())
worker.loadImage(node.ImageID, holder.imgIcon);
}
The entire getView method can be seen here: http://pastebin.com/CJbtVfij
The worker object is a very simple implementation of the ImageWorker:
public class NodeArtWorker extends ImageWorker {
protected int width;
protected int height;
public NodeArtWorker(Context context) {
super(context);
addImageCache(context);
//Code for getting the approximate width and height of the ImageView, in order to scale to save memory.
}
#Override
protected Bitmap processBitmap(Object data) {
Bitmap b = getBitmap(data);
if(b == null) return null;
return Utils.resizeBitmap(b, width, height);
}
protected Bitmap getBitmap(Object data) {
//Downloads the bitmap from a server, and returns it.
}
}
This works very well, and the performance is much better now than before. However, if I change the ImageID on some of the items in the list, and call adapter.notifyDataSetChanged() to rebuild the view (and thereby start loading new bitmaps), I get a RuntimeException:
0 java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap#41adf448
1 at android.graphics.Canvas.throwIfRecycled(Canvas.java:1058)
2 at android.graphics.Canvas.drawBitmap(Canvas.java:1159)
3 at android.graphics.drawable.BitmapDrawable.draw(BitmapDrawable.java:440)
4 at android.widget.ImageView.onDraw(ImageView.java:1025)
5 at android.view.View.draw(View.java:14126)
The full stacktrace can be seen in the following pastebin entry, but is probably uninteresting, as it is the typical Android view hierarchy redraw stacktrace: http://pastebin.com/DsWcidqw
I get that the Bitmaps are being recycled, but I don't understand exactly where and what can be done about it. As my code comes directly from Google, and is, as far as I understand, the recommended way of doing this, I am very confused.
On Android, Bitmaps can be recycled to be later re-used (much faster than re-creating a Bitmap).
The Bitmap#recycle()method will flag the Bitmap as recycled.
So if you try to set such a recycled bitmap to an ImageView or draw it on a Canvas, you will end up with this exception:
java.lang.RuntimeException: Canvas: trying to use a recycled bitmap
The official demo that you linked on your question is dealing with recycled Bitmaps.
It uses a dedicated method hasValidBitmap() and checks the Bitmap#isRecycled() value:
private synchronized boolean hasValidBitmap() {
Bitmap bitmap = getBitmap();
return bitmap != null && !bitmap.isRecycled();
}
So you need to do the same. When you're searching on your cache for a Bitmap, before applying it to an ImageView, check if it's recycled or not. If it's not you can directly set it, otherwise, you need to update the Bitmap.
To create a new Bitmap from a recycled Bitmap, you can use the Bitmap::createBitmap(Bitmap) method.
You can find more details on this page.
i think this is not the ImageWorker class bug, this is
com.mobeta.android.dslv.DragSortListView.dispatchDraw(DragSortListView.java:797)
issue. so in order to see my answer is correct or not just remove that library and see if it works or not. after that i think the best solutions are:
1- not use that library.
2- report issue on github.
3- solve it by your own and debugge it.
4- or you can set null for image holder bitmap
holder.imgIcon.setBitmapDrawble(null);
and then call worker.loadImage.
I have an iPhone and an Android app. On iPhone I use NSFetchedResultsController to manage the data I have for my UITableView's. Currently I have a lot of data (several thousands of rows).
On my Android app i'm using ORMLite and i'm storing the data into a table, but when I fetch, i'm pulling out all data into an array and using ArrayAdapters. Is there a more efficient way to do this such as an NSFetchedResultsController type object? I want to only load the objects needed, rather than all of them for performance reasons.
Is there a way to do this within ORMLite? I tried to find in the documentation but found nothing so far... Is there anything else I'm missing that would be of help here?
You could use CursorAdapter.
See http://developer.android.com/reference/android/widget/CursorAdapter.html
and http://www.mysamplecode.com/2012/07/android-listview-cursoradapter-sqlite.html
or you could implement an Endless Adapter
Lazy Loading might help here.
Lets presume you have a data of 10000 entries. You could display a finite subset of this say 90 entries at a time.You could initially load a few entries say 30(1-30). On scrolling to the end you load 30 more entries.As soon as the no.of entries loaded reaches 90 (on the third load) you could truncate the current dataset as 31-120 instead of the previous 1-90.
The same goes the other way too i.e on scrolling upwards.
int offset=0;
listView.setOnScrollListener(new AbsListView.OnScrollListener() {
final int MAX_SUBSET=90;
final int INTERVAL=30;
#Override
public void onScrollStateChanged(AbsListView absListView, int i) {
}
#Override
public void onScroll(AbsListView absListView, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
final int lastItem = firstVisibleItem + visibleItemCount;
if(lastItem == totalItemCount) {
//Load new data and update The adapter.
if(totalItemCount<=MAX_SUBSET){
loadData(0,totalItemCount+INTERVAL);
}
else{
offset+=INTERVAL;
loadData(offset,MAX_SUBSET);
}
}
else if(0==firstVisibleItem){
//Load old data
if(totalItemCount<=MAX_SUBSET){
loadData(0,totalItemCount+INTERVAL);
}
else{
offset-=INTERVAL;
loadData(offset,MAX_SUBSET);
}
}
});
Ps: I have missed out a few boundary checks but hope you get the general idea.
I've got the below screen that contains some images (6 per visible page). Scrolling up and down seems quite laggy to me. It's like it's rendering the images again. Scrolling back up seems worse than scrolling down.
Anyone know how to increase performance in such an area to create a nice smooth scroll?
Update: The images and text is all retrieved from my SQLite database. The list is created using SimpleCursorAdapter.
private class HistoryViewBinder implements SimpleCursorAdapter.ViewBinder
{
//private int wallpaperNumberIndex;
private int timeIndex;
private int categoryIndex;
private int imageIndex;
private java.text.DateFormat dateFormat;
private java.text.DateFormat timeFormat;
private Date d = new Date();
public HistoryViewBinder(Context context, Cursor cursor)
{
dateFormat = android.text.format.DateFormat.getDateFormat(context);
timeFormat = android.text.format.DateFormat.getTimeFormat(context);
//wallpaperNumberIndex = cursor.getColumnIndexOrThrow(HistoryDatabase.KEY_WALLPAPER_NUMBER);
timeIndex = cursor.getColumnIndexOrThrow(HistoryDatabase.KEY_TIME);
categoryIndex = cursor.getColumnIndexOrThrow(HistoryDatabase.KEY_CATEGORY);
imageIndex = cursor.getColumnIndexOrThrow(HistoryDatabase.KEY_IMAGE);
}
#Override
public boolean setViewValue(View view, Cursor cursor, int columnIndex)
{
Log.d(TAG, "setViewValue");
if (view instanceof TextView)
{
Log.d(TAG, "TextView");
TextView tv = (TextView) view;
if (columnIndex == timeIndex)
{
Log.d(TAG, "timeIndex");
d.setTime(cursor.getLong(columnIndex));
tv.setText(timeFormat.format(d) + " " + dateFormat.format(d));
return true;
}
else if (columnIndex == categoryIndex)
{
Log.d(TAG, "categoryIndex");
tv.setText(cursor.getString(columnIndex));
return true;
}
}
else if (view instanceof ImageView)
{
Log.d(TAG, "ImageView");
ImageView iv = (ImageView) view;
if (columnIndex == imageIndex)
{
Log.d(TAG, "imageIndex");
byte[] image = cursor.getBlob(columnIndex);
Bitmap bitmapImage = BitmapFactory.decodeByteArray(image, 0, image.length);
iv.setImageBitmap(bitmapImage);
return true;
}
}
return false;
}
}
The problem is that each image is decoded at the moment the view is prepared for showing. The ListView will recycle your views that means that at the moment a view leaves the screen it will be reused and therefore the image will be overwritten and garbage collected. If the item reenters the screen the image has to be decoded from the database again.
The decoding is reasonable fast but if the user changes the position in the list very quickly all the decoding calls will make your list very laggy.
I would implment something like an ImageCache. A class that holds a Map with WeakReferences to images. Everytime you want to display an image you take a look if the image is allready in the map and if the WeakReference still points to an object, if this is not the case you need to decode the image and then store it in the map.
Take a look at the lazy loading questions, these will show you how to put the decoding in a background task and then update the list the moment the images are loaded. It is a bit more effort but it can make the list much much more faster. If you are going to use the code example from the lazy loading questions try to use AsyncTasks instead of Threads to do the decoding in.
Here is sth, what you should read, and it should help you to solve problem (whole Displaying Bitmaps Efficiently part):
http://developer.android.com/training/displaying-bitmaps/process-bitmap.html
If your images are not the proper size, they are being scaled on the fly, which is not a cheap operation, particularly for large images. Be sure that you are loading thumbnails, not the wallpaper, out of your database.
Also, you can use Traceview to find out where your time is being spent.