is WeakReference of Android Views safe? - java

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

Related

setOnMouseEntered not Working for ImageView in ListView

i have a problem with setOnMouseEntered on JavaFx for ImageViews.
I'm trying to change the brightness of ImageViews placed in a Listview with ColorAdjust. The effect itself works for ImageViews that are not in the ListView.
I guess that only the ListView triggers the setOnMouseEntered, but not the ImageViews, which is my goal.
The same problem is caused by the hover effects of the ImageViews, which are not triggered as soon as they are in a ListView.
fxml:
<ListView fx:id="cardsView" />
java-Code:
#FXML private ListView<ImageView> cardsView;
private ObservableMap<ImageView, Card> hCards;
#FXML
public void initialize() {
hCards= FXCollections.observableHashMap();
cardsView.getItems().setAll(hCards.keySet());
hCards.addListener(
(MapChangeListener<ImageView, Card>)
change -> {
cardsView.getItems().removeAll(change.getKey());
if (change.wasAdded()) {
cardsView.getItems().add(change.getKey());
}
});
}
Later, for each of these ImageViews will be added:
private void addLightEffectOnMouseEntered(ImageView imageView) {
imageView.setOnMouseEntered(
t -> {
ColorAdjust colorAdjust = new ColorAdjust();
colorAdjust.setBrightness(0.4);
imageView.setEffect(colorAdjust);
});
}
While debugging I figured out that things like css and the setOnMouseEntered are added correctly. So it seems to be somehow blocked by the ListView that the ChildNodes get the setOnMouseEntered or Hover effect instead of the ListView
Your problem is basically the same as this one: Adding EventHandler To ImageView contained in a Label. All Cell specializations, including ListCell, inherit from Labeled and all their default skins inherit from LabeledSkinBase, which is the source of your problem. As a fix to a bug (see other Q&A) when an ImageView is used as the graphic of a Labeled it is set to be mouse-transparent. Since the the ImageView is mouse-transparent your MOUSE_ENTERED handler can never be invoked for obvious reasons.
If you're not aware, the default cell factory of ListView returns a ListCell implementation that, when the item is an instance of Node, sets the graphic of the cell to the item. An easy fix is to use your own ListCell implementation that wraps the ImageView in another node, such as Pane. Here's an example:
listView.setCellFactory(lv -> new ListCell<>() {
private final Pane imageViewContainer = new Pane();
#Override
protected void updateItem(ImageView item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
imageViewContainer.getChildren().clear();
setGraphic(null);
} else {
imageViewContainer.getChildren().setAll(item);
setGraphic(imageViewContainer);
}
}
});
This will prevent the ImageView from becoming mouse-transparent.
As a side note, it's typically not a good idea to use GUI objects (e.g. ImageView) as the model item of a ListView (or any other virtualized control). In this case, it may be an even worse idea since this set-up encourages holding every Image related to your application in memory simultaneously. Depending on how many images there are, as well as how large those images are, this can easily lead to an OutOfMemoryError or at least consume an unnecessary amount of your users' RAM.
You may want to consider using Card as the model item combined with a memory-constrained cache of Image objects (see WeakReference / SoftReference, though you could also look for a third-party caching library). The Card class could hold the location of its associated image or the cache could derive the location based on the state of the Card.
You would still use an ImageView as the graphic of your ListCell, however, so you would still need to use the workaround mentioned above. What using a memory-constrained cache helps with is that, if a Card isn't being displayed in a ListCell then its associated Image possibly becomes eligible for garbage collection, thus reducing the memory demands of your application.
The cache also allows you to use the same Image everywhere in your application (the same Image can be shared between multiple ImageViews) and means you don't always load a new Image when a particular one is needed (as it could still be in memory when requested). In other words, the typical functionality provided by any cache.

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

ViewHolder Class and OOM Management

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

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.

Android - Disposing of Listeners in an Adapter

I have a few ListViews driven by custom Adapters (ArrayAdapter and CursorAdapter). In the getView() and bindView() functions of the adapters, I'm creating a row in the ListView and setting a Listener to a particular UI element in the row.
Currently, as the ListView rows are sent to the Recycler the listener is still hooked up so the memory is never released. I don't see a callback for when the row is being disposed of that would give me an opportunity to detach the listener before the row is recycled.
How do I manage the listener in the ListView rows? I only have the getView()/bindView() to add the listener to the row, but nothing to remove them.
Thanks
Android OS does the ListView row view's recycling for you. The views get generated on the fly when user scrolls the list and get destroyed when they go out of view. Android OS destructs the views whenever other application require more memory and some of your objects are not being used, Sort of garbage collection.
And as to your question regarding removing listeners, they get removed automatically since the object they were point to doesn't exist anymore and they get marked as garbage objects too.
After some playing around I decided to use a ViewHolder pattern, and within that I have a variable that tracks the listener. When the recycled view is provided (where applicable) I pull out the listener (it does still exist) and make sure to unregister it as a listener, before creating a new listener and registering that in its place. This probably doesn't catch EVERY listener, but I'm OK with a few ListView rows being stuck in memory.

Categories