ViewHolder Class and OOM Management - java

Does using the ViewHolder class may prevent leaks and large heap retains?
class holder
{
WeakReference<ImageView> img;
}
holder holdr = new holder();
holder.img = ...
displayImage(holder.img);
Should I just pass it as a parameter, Instead of declaring it as global?

The ViewHolder-Pattern got developed mainly due to performance-issues when you are using a ListAdapter that displays some View-elements with the same structure over and over for each list-item.
Your code might call findViewById() frequently during the scrolling of ListView, which can slow down performance. Even when the Adapter returns an inflated view for recycling, you still need to look up the elements and update them. A way around repeated use of findViewById() is to use the "view holder" design pattern.
You should also make your ViewHolder-class static
You can read more about it on Googles Dev Page.
I also recommend watching this Google IO video

Related

is WeakReference of Android Views safe?

Hello i am writing a app which uses many customView(parent as FrameLayout holding activity's context).
In the app there is a recyclerView it's holder is having N(significantly large number) of view's(Custom).
As the number of View's are dynamically added (i am testing for 10k+ view's).
in recycler View i am not doing anything special but using coroutine's so that rendering of 10k view will not block the Main Thread
But due to large number's of View's in each RecyclerView Holder it's Filling up the heap memory of the app very fast as i scroll and give's OOM(Out of Memory) Crash.
To solve this issue i read and found I can use weak reference's to the
View.But i have not found any example or reference to this approach.So
i am not sure if using WeakReference on the Android View is a good
solution or not.
Code and Demo Project Reference Below.
RecyclerView Adapter :
override fun onBindViewHolder(holder: ViewPageHolder, position: Int) {
val slideViewMultipleCanvas = SlideViewMultipleCanvas(context)
holder.slideViewItemHolder.addView(slideViewMultipleCanvas)
holder.positionHolder = position
val childJob = launch {
//this is where 10k+ view's are created
slideViewMultipleCanvas.setNumOfObjects(numOfItemsInViewPage)
slideViewMultipleCanvas.startJob()
}
map[position] = childJob
}
CustomView :
...
...
suspend fun setNumOfObjects(numberOfObjects: Int) {
withContext(Dispatchers.Default) {
for (i in 0 until numberOfObjects) {
// To solve OOM as number of view here in heap are >10k
listOfObject.add(WeakReference(ShapeView(context)))
}
}
}
...
...
private fun addViewInScope(): Boolean {
for (shapeView in listOfObject) {
shapeView.get()?.let {
addView(it)
}
}
return true
}
....
....
After Using WeakReference from my testing i have not faced any issue(NO OOM's) till now but still i get Lag's whenever GC(Garbage Collector) kick's in and collect all the weak reference.
My Main concerns are
Is There any other way to handle creation of huge number of View without using WeakReference?
Is there any issue's if i use weak reference for Views?
how to decrease GC lag?(even using WeakReference i am not getting smooth scroll)
P.S. : I am already handling:
null cases if GC collect's the weak referenced View's
cancelling coroutine job's when view is recycled in RecyclerView.(can i release the weak Referred view also of this ViewHolder also???)
Look into RecyclerViewPools, you should be able to allocate a specific view (if the dynamically created ones are the same view) using the itemType and have the recyclerView reuse those views also
The main lag isn’t from Weakreferences, but purely the need to generate so many views so quickly, if you could keep a cached list of views to reuse (kind of like the recyclerViewPool) then that would reduce the inflating lag
Only issue if you’ve got so many views it causes OOM, is it possible to combine the views into a generic view with multiple functionality (less physical views, but more logic and complexity in each to cater for being used under multiple situations
This would allow you to reuse the same view more times, meaning less caching/inflating, you’ll just need to make sure the code is performing efficiently

Lazy load recyclerview images from mediastore

I want to load images I have in my recyclerview after 350ms and I think I'm using wrong method for that. This is my code:
#Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
holder.songView.setText(objects_.get(position).getAlbum());
holder.artistView.setText(objects_.get(position).getArtist());
holder.cover.setImageDrawable(context.getResources().getDrawable(R.drawable.song));
if(holder.r!=null){
handler.removeCallbacks(holder.r);
}
holder.r = new Runnable() {
#Override
public void run() {
Drawable img = Drawable.createFromPath(objects_.get(position).getCover());
if (img != null) {
holder.cover.setImageDrawable(img);
Bitmap bitmap = ((BitmapDrawable) img).getBitmap();
Palette palette = Palette.from(bitmap).generate();
Palette.Swatch p = palette.getVibrantSwatch();
if (p != null) {
holder.albumholder.setBackgroundColor(p.getRgb());
}
}
}
};
handler.postDelayed(holder.r,300);
}
But I have a problem with this. when I fast scroll recyclerview images of previous items loads at first then changes to new items picture. You can see result in GIF from this link:
http://8pic.ir/images/nkaaeqdvigqy4c6g2h5n.gif
what can I do to fix it?
I don't understand why do you need this 350ms delay but if you want to do it try some other approach:
Your problem is linked to the fact that RecyclerView recycles (suprise...) item views instead of creating new. That means that you will see previously load image, and if you have posted delayed task (handler.postDelayed(...)) it will be executed event if view was recycled, so wrong image can be loaded for particular list item.
General problem is that you're doing to much work in your onBindViewHolder. You should try to reduce computations here, or at least try to move them to some separate thread (handler is using this same thread it was created - in this case the UI thread).
Create handler inside view holder instead of inside your adapter.
Set some placeholder as an image
clear tasks (messages) currently waiting to be executed:
holder.handler.removeCallbacksAndMessages(null);
post load task (handler.postDelayed(...))
It's also possible that all you need is some nice image loading library like Picasso.
As we know recycler view reuse same view during scroll so it is displaying older images while you lazy load for some moments and after that it will update your imageview.
Solution is simply reset your imageview to default( ie white background or default image) state before lazyload .
You should avoid setimageresource() ,instead use setimagedrawable()
setImageResource Vs setDrawable
Instead of using the runnable for loading images, use AsyncTask. You'll need to execute a separate AsyncTask for each image. This AsyncTask will be saved as a WeakReference inside the drawable object which will be set in the respective ImageView.
WeakReferences are used for mapping purposes. The advantage of using WeakReferences is that the entries can be removed from the memory as soon as they are not required by your app. They will be removed by the Garbage Collector. We need to use the WeakReferences because there can be a large number of AsyncTasks getting executed (equal to the number of items present in the RecyclerView) at the same time and Android system or your app will not be able to identify which AsyncTask belongs to which ImageView without these references.
Now, as the drawable is set in the ImageView, it will contain the WeakReference to its respective AsyncTask. This AsyncTask will process the respective bitmap or drawable to be set on the ImageView and all this will be done off the UI thread.
In order to set the AsyncTask in the drawable object, you'll need to create a custom drawable class which will work as a Drawable object but will have the benefit of attaching an AsyncTask to it.
This Drawable object and AsyncTask will take care of loading the images.
The complete explanation and code for this concept has been provided on Android Developers website. Visit the link: http://developer.android.com/training/displaying-bitmaps/process-bitmap.html

Java android baseAdapter set list item one time

Hello i have a fully working code for my list adapter:
public View getView(int position, View convertView, ViewGroup parent) {
View vi=convertView;
if(convertView==null)
vi = inflater.inflate(R.layout.overview_item, null);
//getting id's
TextView name =(TextView)vi.findViewById(R.id.userUsername);
TextView date =(TextView)vi.findViewById(R.id.imageDate);
ImageView image=(ImageView)vi.findViewById(R.id.userImage);
ImageView avatar=(ImageView)vi.findViewById(R.id.userAvatar);
//setting text
name.setText(dataNames.get(position));
date.setText(dataDates.get(position));
//set image
Log.d("test: ", "Adapter wants to get picture");
imageLoader.DisplayImage(dataImage.get(position), image);
imageLoader.DisplayImage(dataAvatars.get(position), avatar);
return vi;
}
This code works perfect but the problem is this function runs everytime when you scroll throught the listview so whenever the lis item is getting in sight. And that's not what i want. i want it to do this function just once for every list item. This is because when your scrolling fast trought the list it has to load all images again so the loading image is showing and it keeps jumping because the loading image is another size then the image wich is getting loaded. I hope thay tou understand my question and can help me. Already thanks and if i'm not clear please ask my anything in the comments.
So short:
How do i run this code just once for every list-item and not everytime when it's getting in sight?
Your code might call findViewById() frequently during the scrolling of ListView, which can slow down performance. Even when the Adapter returns an inflated view for recycling, you still need to look up the elements and update them. A way around repeated use of findViewById() is to use the "view holder" design pattern.
Check this links:
1 - http://www.javacodegeeks.com/2013/09/android-viewholder-pattern-example.html
No, you should not. This is the way ListView works. Beside, you should use ViewHolder pattern for better performance.
If you still want to do this, you could remove check NULL with convertView. It will solve your problem, but lead to performance, I think.

Should "this" be always used as the Context in creating a new object?

When a new object is made, it often goes:
TextView textView = new TextView(this)
Here, should we always use "this" pointer (an instance of an Activity, normally) as the Context of TextView? For me it seems that any Context would work in many circumstances, such as the following method.
TextView textView = new TextView(this);
textView.setTextSize(textSize);
textView.setTextColor(textColor);
textView.setText(text);
tableRow.addView(textView);
And my assertion is, as I am not adopting any resources, any Context could replace "this." (I assume it is wrong.) Why should we use "this" instead of any other Contexts?
Here, should we always use "this" pointer (an instance of an Activity, normally) as the Context of TextView?
Occasionally, there is a better choice, but that's usually in obvious circumstances:
You are using Presentation to route stuff to an external display, and so you use the Context associated with that Display
You are writing an InputMethodService and need to return the View in onCreateInputView()
You are writing a DreamService and need to call setContentView() and choose to create your widgets in Java code rather than use a layout
Etc.
I am not adopting any resources
Your code is not. Your app is, in the form of framework classes accessing style/theme resources for your activity (or app).
The blog post by Dave Smith in laalto's comment is an excellent starting point for choosing the right Context for the right situation.

ListView: Prevent a view from recycling

I have a ListView wich use recycled views. I'm trying to prevent a view from recycle.
So I use setHasTransientState:
android.support.v4.view.ViewCompatJB.setHasTransientState(View view, boolean hasTransientState)
It works very well on Jellybean version but it doesn't do anything on Api < 16.
Is there a way to make it work or there is different approach for pre Jellybean ?
I found out how to set a RecyclerListener like #Daniel Chow suggested.
listView.setRecyclerListener(new RecyclerListener() {
#Override
public void onMovedToScrapHeap(View view) {
// Stop animation on this view
}
});
For pre Jellybean, I think you can just use setRecyclerListener on ListView and when RecyclerListener#onMovedToScrapHeap(View view) is called, clear the animation on the view who has been recycled and directly do the final job which was supposed to be done when animation ends.
The code inside onMovedToScrapHeap(View view) depends on how you implement the animation, e.g. you can call View#clearAnimation() if you previously used View#startAnimation to start animation.
Use android.support.v4.view.ViewCompat.setHasTransientState(View view, boolean hasTransientState) instead of android.support.v4.view.ViewCompatJB.setHasTransientState(View view, boolean hasTransientState)
Besides the animation problem Daniel talked about, another issue where knowing when your view is recycled has to do with memory management. If you are putting large, memory intensive bitmaps in your list items, it may be possible that you don't want your view recycled if its not likely to be re-used by other items. This hook gives you a chance to clear the bitmap you may have assigned to an ImageView. Hopefully, this is a rare problem.

Categories