Problems using bitmaps for thumbnails - android

I'm trying to make an app that takes videos and then displays the videos in a gridview. I have gotten to the point where all the video thumbnails are showing up in the gridview like they should. However, my problem is that the method I am using has a lot of lag time. It takes some time to get the gridview loaded and when I try to scroll there is always some sort of lag. This is most likely due to the fact that I'm using bit maps for every video thumbnail.
I was wondering what I could do to make this a lot quicker and smoother. My code is posted below.
class VideoAdapter extends BaseAdapter {
Context context;
ArrayList<String> videopathlist;
Bitmap bmthumb;
VideoAdapter(Context c, ArrayList<String> filepathlist){
context = c;
videopathlist = new ArrayList<String>();
this.videopathlist.addAll(filepathlist);
Log.i(TAG, "" + videopathlist);
}
#Override
public int getCount() {
return videopathlist.size();
}
#Override
public Object getItem(int position) {
return videopathlist.get(position);
}
#Override
public long getItemId(int arg0) {
return 0;
}
#Override
public View getView(int posiiton, View arg1, ViewGroup arg2) {
ImageView imageview = new ImageView(context);
bmthumb = ThumbnailUtils.createVideoThumbnail(videopathlist.get(posiiton), MediaStore.Video.Thumbnails.MINI_KIND);
if (bmthumb != null) {
Log.i(TAG1, "There is a thumbnail");
imageview.setImageBitmap(bmthumb);
} else {
Log.i(TAG1, "There is NOT a thumbnail");
}
imageview.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageview.setLayoutParams(new GridView.LayoutParams(160, 160));
return imageview;
}
Thanks for your help

2 things:
Use a ViewHolder to cache everything, as described here: Making ListView Scrolling Smooth (applies to GridViews too)
Also, use an ImageLoader to load images dynamically. There are many available or you can build your own. Here are some:
Volley
Android-Universal-Image-Loader
ImageLoader
All these loaders make use of Bitmap caches as described here: Caching Bitmaps
Also, you could potentially be actually creating the thumbnail bitmaps when returning the view. This strikes me as very expensive. I would do this as a seperate step, unrelated to displaying them. For example, kick off an AsyncTask when your activity is first created to generate any new thumbnails. Then in your adapter just query the MediaStore to get the generated thumbnails.

Related

Vector drawable loading in RecyclerView

I need to load VectorDrawable resources depending on different cases into imageviews in recycler-view. The current implementation instantiates a new drawable everytime, which i am sure, leads to performance loss with a huge amount of items in the list. I want to change it, so that drawables are reused effectively to reduce the amount of created objects. I am using Picasso and i thought it could be a good idea to delegate this work to it, but it looks like Picasso is not able to load VectorDrawable's. Is there a way to manage this?
UPD.
The resource is loaded using ContextCompat.getDrawable and set using setImageDrawable. ImageView drawable is cleared in onViewRecycled
The only solution i can think of is to move the loading of dynamic drawable images from the UI thread to the background thread. To make the loading smoother, you need to load the images Asynchronously using an AsyncTask in your adapter. This will take the load off the UI thread and make the user experience much better. You are not able to use Picasso, hence i recommend a simple AsyncTask.
I found a good snippet for this, have a look:
public View getView(int position, View convertView,
ViewGroup parent) {
ViewHolder holder;
...
holder.position = position;
new ThumbnailTask(position, holder)
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, null);
return convertView;
}
private static class ThumbnailTask extends AsyncTask {
private int mPosition;
private ViewHolder mHolder;
public ThumbnailTask(int position, ViewHolder holder) {
mPosition = position;
mHolder = holder;
}
#Override
protected Cursor doInBackground(Void... arg0) {
// Download bitmap here
}
#Override
protected void onPostExecute(Bitmap bitmap) {
if (mHolder.position == mPosition) {
mHolder.thumbnail.setImageBitmap(bitmap);
}
}
}
private static class ViewHolder {
public ImageView thumbnail;
public int position;
}
For more info, please have a look at this link.

ListView BaseAdapter is hanging

I am getting some odd behavior in my scroll-able listview. It is controlled by a BaseAdapter.
As I scroll up or down it will sometimes hang and then bounce back in the opposite direction. I have no idea why and do not know how to trouble shoot it.
My base adapter is below and it loads about 90 fragments with an image and a bunch of text.
public class PosterListAdapter extends BaseAdapter {
private final ArrayList<Poster> listItems;
private final LayoutInflater inflater;
public PosterListAdapter(ArrayList<Poster> listItems, LayoutInflater inflater) {
this.listItems = listItems;
this.categoryList = categoryItems;
this.inflater = inflater;
}
#Override
public int getCount() {
//Log.d("getCount", String.valueOf(this.listItems.size()));
return this.listItems.size();
}
#Override
public Poster getItem(int i) {
return this.listItems.get(i);
}
#Override
public long getItemId(int i) {
return 0;
}
#Override
public View getView(int i, View convertView, ViewGroup viewGroup) {
MyViewHolder mViewHolder;
SparseArray<String> categoryMap = db.getCategoryMap();
if (convertView == null) {
convertView = inflater.inflate(R.layout.catalog_list_fragment, viewGroup, false);
mViewHolder = new MyViewHolder(convertView);
convertView.setTag(mViewHolder);
} else {
mViewHolder = (MyViewHolder) convertView.getTag();
}
//LayoutParams params = (LayoutParams) imageView.getLayoutParams();
Poster item = this.listItems.get(i);
String filename = item.getPosterFilename();
mViewHolder.posterAuthor.setText("Author: "+item.getPresenterFname()+' '+item.getPresenterLname());
mViewHolder.posterAltAuthors.setText("Supporting Authors: "+item.getPosterAuthors());
mViewHolder.posterTitle.setText(item.getPosterTitle());
mViewHolder.posterSynopsis.setText(item.getPosterSynopsis());
mViewHolder.posterNumber.setText("Poster: "+String.valueOf(item.getPosterNumber()));
mViewHolder.posterPresentation.setText("Live Presentation: "+item.getSessionDate()+" at "+item.getSessionTime()+"\nAt Station: "+item.getPosterStation());
String category = categoryMap.get(item.getCatID());
mViewHolder.posterCategory.setText("Category: "+category);
File imgFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).getAbsolutePath(), "/"+filename+"/"+filename+".png");
//Here I thought maybe the resizing of the image or even the image itself
// was causing the hang up so I tried the list without it and it is still
// hanging.
//mViewHolder.imageView.setImageURI(Uri.fromFile(new File(imgFile.toString())));
if (imgFile.exists()){
/*Bitmap bmp = BitmapFactory.decodeFile(imgFile.toString());
int newWidth = 500;
Bitmap sizedBMP = getResizedBitmap(bmp, newWidth);
mViewHolder.imageView.setImageBitmap(sizedBMP);*/
}
else{
//set no image available
}
return convertView;
}
private class MyViewHolder {
TextView posterTitle, posterAuthor, posterSynopsis, posterCategory, posterNumber, posterAltAuthors,posterPresentation;
ImageView imageView;
public MyViewHolder(View item) {
posterTitle = (TextView) item.findViewById(R.id.poster_title);
posterAuthor = (TextView) item.findViewById(R.id.poster_author);
posterAltAuthors = (TextView) item.findViewById(R.id.poster_altAuthors);
posterSynopsis = (TextView) item.findViewById(R.id.poster_synopsis);
posterCategory = (TextView) item.findViewById(R.id.poster_category);
posterNumber = (TextView) item.findViewById(R.id.poster_number);
posterPresentation = (TextView) item.findViewById(R.id.poster_presentation);
imageView = (ImageView) item.findViewById(R.id.poster_thumb);
}
}
}
I know that is a big chunk of code and it may be ugly.. If you could point me in the right direction as to how to trouble shoot it that would be helpful as well. I am using eclipse.
I bet if you comment out that line File imgFile = new File(... (and the code that depends on imgFile) that your list scrolling will improve.
That's still an I/O operation and since getView() runs in the UI thread, it may cause hiccups.
What you should do is: once you have the ImageView in getView(), you start an AsyncTask to open the file, decode it, and assign the bitmap to the ImageView.
Then you have to handle things like: the ImageView gets recycled, but the task isn't completed.
Read this article: Multithreading for Performance | Android Developers Blog. It's dealing with images from a remote server, but all the principles are still the same.
Also: does this line SparseArray<String> categoryMap = db.getCategoryMap(); do a database lookup? That could cause a hiccup as well.
TL;DR getView() needs to be fast; put slow operations in a non-UI thread.
Database related operations should done in separate thread. as it might take time to get data if number of records are greater.
and getView() method will be called more then one time. we can say it will be called in loop till size of your array list (getCount()).
Second thing: File operation is also should be done in separate thread.because IO task is also some times time consuming.
Solution:
SparseArray<String> categoryMap = db.getCategoryMap();
put this line out of getView() method as i cant see any list dependent parameter in this line. this should be done in either constructor or separate thread.
Run your database operations and File IO in separate thread like using AsyncTask
I agree with previous answer, looks like creating new File is the only heavy operation which hangs your app.
Try to use some image loading library, for example Glide. It will simplify your code, also you won't have to deal with background job. Library will do it all for you.
1.Import library, e.g. if you use gradle insert this into your build.gradle file:
repositories {
mavenCentral() // jcenter() works as well because it pulls from Maven Central
}
dependencies {
compile 'com.github.bumptech.glide:glide:3.7.0'
compile 'com.android.support:support-v4:19.1.0'
}
2.In your adapter replace code related to loading picture with this:
Glide
.with(context)
.load(<full path to your local file>)
.into(mViewHolder.imageView);
3.And it is all. Glide will handle recycling and other things internally, also it is optimized for fast loading and smooth scrolling.
Image related processes really consumes so much memory on the UIThread of the app, you should be setting your image using asynchronous(background) approach. Or if its much of a hassle for you, there are tons of libraries available that will do this. I specifically recommend using Picasso for it is very dynamic, easy to use and is regularly updated. Loading an image would require one line of code:
Picasso.with(getApplicationContext()).load(<your image path or url or resource id>).into(YourImageView);
Picasso also have a variety of image options you can choose from.

Android - Gridview images mixes on scroll

I have a gridview which displays two column imageviews. I am loading these images with an async task (see this post Lazy load of images in ListView )
But when i am scrolling gridview , the images in positions are mixing. For example 14th image shows 1th image , i think view is trying to show old image before async task finishes.
My code :
public Content getItem(int position) {
return contents.get(position);
}
public long getItemId(int position) {
return position;
}
#Override
public boolean hasStableIds() { return true; }
public View getView(int position, View convertView, ViewGroup parent) {
Thumbnail contentView;
Content current = contents.get(position);
if (convertView == null) {
contentView = new Thumbnail(mContext);
}
else {
contentView = (Thumbnail) convertView;
}
contentView.title = current.getTitle();
contentView.image = current.getImage();
contentView.link = current.getLink();
contentView.init();
return contentView;
}
Init function
TextView titleText = (TextView)super.findViewById(R.id.titleText);
titleText.setText(title);
ImageView imageControl = (ImageView)super.findViewById(R.id.thumbImage);
DrawableManager loadImage = new DrawableManager(); loadImage.fetchDrawableOnThread(imgUrl, imageControl);
Waiting for your help
Thanks
It happens because of resource reusing. What you should do:
First, just set some kind of default image to your imageView (contentView.image.setImageResource(DEFAULT_RESOURCE)) inside getView method (transitional default picture is better than wrong one).
Set unique tag to your image, for example, position or url of image to load (contentView.image.setTag(url)).
When AsyncTask finishes, you can use some checks like
String url=(String)imageView.getTag();
if (url.equals(mUrl)) { //mUrl can be transmitted to AsyncTask instance separately
mActivity.runOnUiThread(new Runnable() {
#Override
public void run() {
imageView.setImageDrawable(bmp);
});
});
}
It's needed because Adapter does not allocate memory for all N ImageView for N items. It stores just required amount for visible items and some reserved ImageViews. So there is no warranty that ImageView reference that you store will be actual within several seconds cause it can became invisible and be reused by visible ones.
Don't load images yourself. Use Universal Image Loader . It can cache, images and loaded bitmaps. Making your problem go away. It also supports showing stub images and automatic pause on scrolling and so on.
It solves your specic problems by only loading an image for the last instance of an imageView if you reuse it.
imageLoader.displayImage(imageUri, imageView);
Is all that is needed to asynchronously load an image.

Obtaining image from android gallery and put in gridview

I'm looking for some advice on how to do this.
I want to have an activity where the user select from android gallery, then the image will be added into a grid view on the activity. I have successfully implemented both separately, but when I have to combine them I'm at a loss. Grid View tutorial is here. Problem is that grid view tutorial uses images from res/drawable, so the uri i obtain from gallery doesn't exactly work.
How should I set the image inside the ImageAdapter class? I've been trying to do imageView.setImageBitmap(bitmap) with the uri address of one of the images in my phone, but it didn't work.
I'm thinking of creating an ArrayList of String that contains uri for the images obtained from the gallery. This way i can add, delete, and store the images with ease.
Other questions along with this is that if i get the images displayed, will it refresh if i simply call setAdapter again? would delete work automatically if i delete from the source ArrayList?
Thank you
The following is the code from grid view tut that i edited:
public class ImageAdapter extends BaseAdapter {
private Context mContext;
public ImageAdapter(Context c) {
mContext = c;
}
public int getCount() {
return imageId.size();
}
public Object getItem(int position) {
return null;
}
public long getItemId(int position) {
return 0;
}
// create a new ImageView for each item referenced by the Adapter
public View getView(int position, View convertView, ViewGroup parent) {
ImageView imageView;
if (convertView == null) { // if it's not recycled, initialize some attributes
imageView = new ImageView(mContext);
imageView.setLayoutParams(new GridView.LayoutParams(85, 85));
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageView.setPadding(8, 8, 8, 8);
} else {
imageView = (ImageView) convertView;
}
Uri targetUri = Uri.parse(tests.get(0));
//tests contains the uri of the photo i'm trying to import from my phone gallery in string form
Bitmap bitmap;
bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(targetUri));
imageView.setImageBitmap(bitmap);
return imageView;
}
}
Answer to the first part : If your tests contains the Uri of the image that you have selected, simply use imageView.setImageURI(targetUri).
Answer to second part : To refresh a GridView, just call mGridView.invalidateViews() and your whole GridView will be redrawn and thus any changes that have taken place in your source would be reflected here. No need to call setAdapter() again. setAdapter() will be called only once initially, when you are drawing the grid for the first time. After that, just invalidateViews() to refresh it.

listview gets out of memory exception, but with no memory leaks?

After Honeycomb, Google said that bitmaps are managed by the heap (talked about here), so if a bitmap is no longer accessible, we can assume that GC takes care of it and frees it.
I wanted to create a demo that shows the efficiency of the ideas shown for the listView lecture (from here), so I made a small app. The app lets the user press a button, and then the listview scrolls all the way to the bottom, while it has 10000 items, which their content is the android.R.drawable items (name and image).
For some reason, I get out of memory even though I don't save any of the images, so my question is: How could it be? What is it that I'm missing?
I've tested the app on a Galaxy S III, and yet I keep getting out of memory exceptions if I use the native version of the adapter. I don't understand why it occurs, since I don't store anything.
Here's the code:
public class MainActivity extends Activity
{
private static final int LISTVIEW_ITEMS =10000;
long _startTime;
boolean _isMeasuring =false;
#Override
public void onCreate(final Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final ListView listView=(ListView)findViewById(R.id.listView);
final Field[] fields=android.R.drawable.class.getFields();
final LayoutInflater inflater=(LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
// listen to scroll events , so that we publish the time only when scrolled to the bottom:
listView.setOnScrollListener(new OnScrollListener()
{
#Override
public void onScrollStateChanged(final AbsListView view,final int scrollState)
{
if(!_isMeasuring||view.getLastVisiblePosition()!=view.getCount()-1||scrollState!=OnScrollListener.SCROLL_STATE_IDLE)
return;
final long stopTime=System.currentTimeMillis();
final long scrollingTime=stopTime-_startTime;
Toast.makeText(MainActivity.this,"time taken to scroll to bottom:"+scrollingTime,Toast.LENGTH_SHORT).show();
_isMeasuring=false;
}
#Override
public void onScroll(final AbsListView view,final int firstVisibleItem,final int visibleItemCount,final int totalItemCount)
{}
});
// button click handling (start measuring) :
findViewById(R.id.button).setOnClickListener(new OnClickListener()
{
#Override
public void onClick(final View v)
{
if(_isMeasuring)
return;
final int itemsCount=listView.getAdapter().getCount();
listView.smoothScrollToPositionFromTop(itemsCount-1,0,1000);
_startTime=System.currentTimeMillis();
_isMeasuring=true;
}
});
// creating the adapter of the listView
listView.setAdapter(new BaseAdapter()
{
#Override
public View getView(final int position,final View convertView,final ViewGroup parent)
{
final Field field=fields[position%fields.length];
// final View inflatedView=convertView!=null ? convertView : inflater.inflate(R.layout.list_item,null);
final View inflatedView=inflater.inflate(R.layout.list_item,null);
final ImageView imageView=(ImageView)inflatedView.findViewById(R.id.imageView);
final TextView textView=(TextView)inflatedView.findViewById(R.id.textView);
textView.setText(field.getName());
try
{
final int imageResId=field.getInt(null);
imageView.setImageResource(imageResId);
}
catch(final Exception e)
{}
return inflatedView;
}
#Override
public long getItemId(final int position)
{
return 0;
}
#Override
public Object getItem(final int position)
{
return null;
}
#Override
public int getCount()
{
return LISTVIEW_ITEMS;
}
});
}
}
#all: I know that there are optimizations for this code (using the convertView and the viewHolder design pattern) as I've mentioned the video of the listView made by Google. Believe me, I know what's better; this is the whole point of the code.
The code above is supposed to show that it's better to use what you (and the video) shows. But first I need to show the naive way; even the naive way should still work, since I don't store the bitmaps or the views, and since Google has done the same test (hence they got a graph of performance comparison).
The comment from Tim is spot on. The fact that your code does not utilize convertView in its BaseAdapter.getView() method and keep inflating new views every time is the major cause of why it will eventually run out of memory.
Last time I checked, ListView will keep all the views that are ever returned by the getView() method in its internal "recycle bin" container that will only be cleared if the ListView is detached from its window. This "recycle bin" is how it can produce all those convertView and supply it back to getView() when appropriate.
As a test, you can even comment out the code portion where you assign an image to your view:
// final int imageResId = field.getInt(null);
// imageView.setImageResource(imageResId);
And you will still get the memory allocation failure at one point :)
There are two points your code:
As mentioned by previous answers, you are trying create so many new objects, well, that's the main reason of OutOfMemory problem.
Your code is not efficient enough to load all objects continuously (like swipe up/down for scrolling), well, it's lagging.
Here a hint to fix those two common problems:
Field field = fields[position % fields.length];
View v = convertView;
ViewHolder holder = null;
if (v == null) {
v = inflater.inflate(R.layout.list_item,null);
holder = new ViewHolder();
holder.Image = (ImageView) inflatedView.findViewById(R.id.imageView);
holder.Text = (TextView)inflatedView.findViewById(R.id.textView);
v.setTag(holder);
} else {
holder = (ViewHolder) v.getTag();
}
return v;
This is the simple ViewHolder for efficient ListView.
static class ViewHolder {
ImageView Image;
TextView Text;
}
Pretty much simple but very effective coding.
I have been getting oom error for a long time, when I inflate my listview with too many images (even though the images were already compressed)
Using this in your manifest might solve your issue:
android:largeHeap="true"
this will give your app a large memory to work on.
Use this only when there are no alternate ways for your desired output!
To Know about the drawbacks of using largeHeap check this answer
Your catching Exception e, but OutOfMemoryError is Error, not an Exception. So if your want to catch OutOfMemory your can write something like
catch(Throwable e){}
or
catch(OutOfMemoryError e){}

Categories

Resources