Im having big troubles using a Target inside an adapter. Im confused about the documentation on the code
Objects implementing this class must have a working implementation of
{#link #equals(Object)} and {#link #hashCode()} for proper storage internally. Instances of this
interface will also be compared to determine if view recycling is occurring. It is recommended
that you add this interface directly on to a custom view type when using in an adapter to ensure
correct recycling behavior.
Im trying to use the Target in this way:
class CustomTarget implements Target {
private ImageView imageView;
public CustomTarget(ImageView imageView) {
this.imageView = imageView;
}
#Override
public void onBitmapLoaded(final Bitmap bitmap, Picasso.LoadedFrom from) {
imageView.setImageDrawable(new RoundedAvatarDrawable(bitmap));
}
#Override
public void onBitmapFailed(Drawable errorDrawable) {
imageView.setImageDrawable(errorDrawable);
}
#Override
public void onPrepareLoad(Drawable placeHolderDrawable) {
imageView.setImageDrawable(placeHolderDrawable);
}
#Override
public boolean equals(Object o) {
return imageView.equals(o);
}
#Override
public int hashCode() {
return imageView.hashCode();
}
}
#Override
public View getView(int position, View v, ViewGroup parent) {
....
RoundedAvatarDrawable r = new RoundedAvatarDrawable(BitmapFactory.decodeResource(mContext.getResources(), R.drawable.ic_avatar_seahorse));
ImageCacheController.with(mContext).getPicasso().load(member.getPicture_url()).resize(100, 100).centerCrop().placeholder(r).error(r).into(new CustomTarget(viewHolder.ivAvatar));
....
}
It's doesn't work and the images change between each others randomly
You don't show your whole getView function, so without knowing how you use the viewHandler, here's my take on what's going on:
Your problem is that you're creating a new CustomTarget every time getView gets called. You are going against the point of having a Target object. Let me elaborate.
When a new download request is made, previous requests to the same target get stopped or don't result in a call to the Target's callbacks. (so if the Target gets reused for a different row in a list it doesn't get both rows' images).
You are using a new object for each request, effectively hinting Picasso that each request is for a different row so to speak. The doc says "Instances of this interface will also be compared to determine if view recycling is occurring", so since each request has a newly created CustomTarget object, no two requests will have the same object and a row recycle won't be detected.
You're also using viewHolder. In this case I think the viewHolder should be extending the Target interface (if you only have 1 image per row). This way everytime you request a download you can use the same object and not create a new one.
You're also delegating the implementation of your CustomTarget to the ImageView's implementation. Make sure that ImageView's equals and hashCode functions fullfill the requirements Picasso asks for.
Some info on how to implement equals and hashCode: What issues should be considered when overriding equals and hashCode in Java?
It seems your equals method is broken. You are comparing an imageview to a custom target. This might fix it:
public boolean equals(Object o) {
if(o instanceof CustomTarget) {
return ((CustomTarget) o).imageView.equals(this.imageView);
}
return super.equals(o);
}
Related
I am creating a custom View and I would like to listen for the transformation changes. For example, the ones triggered by View#setScaleX. One way to do it is overriding all the methods:
setTranslationX
setTranslationY
setTranslationZ
setElevation
setRotation
setRotationX
setRotationY
setScaleX
setScaleY
setPivotX
setPivotY
setCameraDistance
setAnimationMatrix
Am I missing anything? I don't care for the top/left/bottom/right properties so they are left out intentionally. However this is cumbersome. It would be better if I can just get a callback and listen for it. Is that possible?
//Make some kind of callback
public interface TransformationCallback{
//String whatWho is an example it can be anything.
void onTransform(String whatWho);
}
public class YourView extends View{
private TransformationCallback callback;
//Pass an interface into the View constructor
public YourView(Context context, TransformationCallback callback){
super(context);
this.callback = callback;
}
}
#Override
public void setTranslationX(float x){
//call onTransform from the callback
callback.onTransform("setTranslationX was called");
super.setTranslationX(x);
}
The only problem with this, is it will not detect internal changes to the underlining values that these functions "set".
For example there is a variable inside View called protected int mLeft; Which is modified multiple times, internally not using functions.
The variable is also protected meaning abstractions of View can also modify it without function calls.
For the most part only external classes that mess with Views will use those functions which may or may not effect you.
I have a list of objects the user can create and delete on runtime and each object is assigned an icon. When I do multiple add/delete operations on these objects at some point I get the following exception.
java.lang.IllegalStateException: Cannot recycle a resource that has already been recycled
at com.bumptech.glide.load.engine.EngineResource.recycle(EngineResource.java:71)
at com.bumptech.glide.load.engine.ResourceRecycler$ResourceRecyclerCallback.handleMessage(ResourceRecycler.java:37)
at android.os.Handler.dispatchMessage(Handler.java:98)
at android.os.Looper.loop(Looper.java:135)
I perform no recycle operations on my own and I do not make any calls to Glide bitmap pool. My custom Glide model is the following class
public class MyGlideInput {
private String packageName, apkFilePath, iconResName;
public MyGlideInput() {
}
#Override
public boolean equals(#Nullable Object obj) {
if(obj == null){
return false;
}
if(!(obj instanceof MyGlideInput)){
return false;
}
MyGlideInput input = (MyGlideInput) obj;
return stringsEqual(packageName, input.packageName ) && stringsEqual(apkFilePath, input.apkFilePath)
&& stringsEqual(iconResName, input.iconResName);
}
#Override
public int hashCode() {
return (apkFilePath+packageName+iconResName+"").hashCode();
}
public boolean stringsEqual(String a, String b){
return a != null && a.equals(b);
}
}
on RecyclerView adapter class in bindViewHolder I do:
GlideInput glideInput = new GlideInput().setDrawableResName(iconResName);
GlideApp.with(img).load(glideInput).dontAnimate().error(R.drawable.warning).into(img);
Any suggestions?
From what I have seen until now, this error appears mostly because of transformations if you have any. I had some recycling in transform method even though it's stated that developers should not do it by themselves in their custom transformations. Also, you should be really careful in RecycleView.
Can you provide us with more of code, e.g. some transformations that you use or whatever you think it can have some bad impact?
I have a simple recyclerview that contains a custom ImageGLSurfaceView in which it shows a bitmap. If i had a simple imageview i would have used (Picasso or glide etc) for this easily. But i have ImageGLSurfaceView so i am trying to do is to mimic ImageLoader lib class. So i have ImageLoader class that has loadImage method which i call from recycler view's adapter and i cache/store ImageGLSurfaceView object in hashmap and handle it like below:
public void loadImage(final FilteredImage filteredImage, final ImageGLSurfaceView imageView) {
if(images.containsKey(filteredImage)) {
images.get(filteredImage).setImageBitmap(filteredImage.getBitmap());
images.get(filteredImage).setFilterWithConfig(filteredImage.getFilter());
} else {
images.put(filteredImage, imageView);
imageView.setSurfaceCreatedCallback(new ImageGLSurfaceView.OnSurfaceCreatedCallback() {
#Override
public void surfaceCreated() {
imageView.setImageBitmap(filteredImage.getBitmap());
imageView.setFilterWithConfig(filteredImage.getFilter());
}
});
}
}
But list is slow or i think it is recreating ImageGLSurfaceView object again on list scroll. Or maybe i should put surfaceCreatedCallback on background thread. So i decided to use RxJava for this purpose. I want to subscribe for ImageGLSurfaceView to be created and once it is created then set Image bitmap and dont create ImageGLSurfaceView again. Please guide me if this is the right approach to achieve this? Or should i completely change the code?
I have just migrated to the Fresco library for loading images in my app.
I need to listen to Image Loading Events, of course I read this article in documentation Listening to download events
This is exactly what I need, but....
There few things that I don't like.
My goal is to hide View if it fails to download it from the net.
I cannot reference SimpleDraweeView from controller, even on callback method. I need to hide View, but it seems that I cannot get reference to it.
Each time I need to load image, I need to create object of controller using Builder, and this can cause performance issues when using this approach with list of a lot of items with images.
holder.simpleDraweeViewImage.setController(Fresco.newDraweeControllerBuilder()
.setControllerListener(controllerListener)
.setUri(currentItem.getImage())
.build());
I need to able to have reference to the SimpleDraweeView from controller, and in MVC pattern approach it seems okay if controller is aware about view.
Please suggest the best way to rich my goal.
Thanks.
Can hide on onFailure method:
ControllerListener listener = new BaseControllerListener<ImageInfo>() {
#Override
public void onFinalImageSet(String id, #Nullable ImageInfo imageInfo, #Nullable Animatable animatable) {
//Action on final image load
}
#Override
public void onFailure(String id, Throwable throwable) {
//Action on failure
}
};
DraweeController controller = Fresco.newDraweeControllerBuilder()
.setUri(uri)
.setControllerListener(listener)
.build();
draweeView.setController(controller);
Regarding 1, perhaps you can do something like this:
class ControllerListenerWithView() extends BaseControllerListener {
private final WeakReference<View> mViewReference;
ControllerListenerWithView(View view) {
mViewReference = new WeakReference<>(view);
}
#Nullable
protected View getView() {
return mViewReference.get();
}
}
Then:
ControllerListener controllerListener = new ControllerListenerWithView(holder.simpleDraweeViewImage) {
#Override
public void onFailure(String id, Throwable throwable) {
View view = getView();
if (view != null) {
view.setVisibility(View.GONE);
}
}
};
If you don't have the view accessible at the listener creation time, instead of passing view via listener constructor, you can add a setter method and do:
controllerListener.setView(holder.simpleDraweeViewImage);
controller = ...
holder.simpleDraweeViewImage.setController(controller);
If this looks ugly to you, well, that's because it is ugly :) Design that involves circular references is just ugly. DraweeController doesn't have a reference to the view (not directly at least). DraweeController references a DraweeHierarchy which references Drawables and the top-level drawable has a WeakReference to the parent view in order to propagate Drawable.Callback events. But that's it. DraweeController doesn't need view and we can't/won't keep reference to the view in it. The reason for that is that DraweeControllers and DraweeHierarchies can be used in contexts other than View and it is unnecessary for controller to have a back reference to the view. DraweeController controls DraweeHierarchy, not the view.
Regarding 2, while building controller, you can specify setOldController(view.getController()). That way the old controller which you are replacing will be reused while building a new one. This saves allocations and helps scroll-perf.
I'm using View Pager to show images which are downloaded from the network in my application. The number of images could be from 5 to 20. I'm using Volley library to do the network operations. The app wasn't taking much memory before but now after adding the view pager, the app takes a lot of memory and every time i open this activity, the memory used in heap increase (checked from the log messages). I also used Eclipse Memory analyzer to check where the leak was and it is definitely the bitmaps and the multiple instances of this activity. There is definitely a leak, as this activity isn't getting GC'ed, some references are keeping this from getting garbage collected. I've added my implementation of the view pager here.
public class ViewPagerAdapter extends PagerAdapter {
Context context;
public ViewPagerAdapter(Context context) {
this.context = context;
}
#Override
public int getCount() {
return photoReferences.size();
}
#Override
public boolean isViewFromObject(View view, Object object) {
return view == ((RelativeLayout) object);
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
final ImageView im;
final ProgressBar pb;
View itemView = inflater.inflate(R.layout.place_photos_item, container, false);
im = (ImageView) itemView.findViewById(R.id.placeImage);
attributes = (TextView) itemView.findViewById(R.id.placeAttributes);
pb = (ProgressBar) itemView.findViewById(R.id.progressBarPhoto);
imageLoader.get(url, new ImageListener() {
public void onErrorResponse(VolleyError arg0) {
im.setImageResource(R.drawable.onErrorImage);
}
public void onResponse(ImageContainer response, boolean arg1) {
if (response.getBitmap() != null) {
im.startAnimation(AnimationUtils.loadAnimation(context, android.R.anim.fade_in));
im.setImageBitmap(response.getBitmap());
pb.setVisibility(View.GONE);
}
}
});
((ViewPager) container).addView(itemView);
return itemView;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
((ViewPager) container).removeView((RelativeLayout) object);
}
}
Also, I'm using the Bitmap Cache of size 3 times the number of screenBytes(screenWidth * screenHeight * 4). I'm testing on Nexus 4 running 4.3 and I never run into a OOM exception cause the heap size is huge on this device but the app can take more than 100 mb of memory(it will crash on most devices) if I open the activity again and again, and before it used to take around 16-20 mbs of memory no matter what. Here's the cache code.
public class BitmapCache extends LruCache<Object, Object> implements ImageCache {
public BitmapCache(int maxSize) {
super(maxSize);
}
#Override
public Bitmap getBitmap(String url) {
return (Bitmap) get(url);
}
#Override
public void putBitmap(String url, Bitmap bitmap) {
put(url, bitmap);
}
}
Could anyone please suggest me what should I do to catch the leak? Is there anything wrong in the View Pager or my Volley usage? I'm not happy with the transition of the Pager as well, lags a bit, is that related?
Update: Here's the screenshot of MAT, possible leak. This is on every activity that uses Volley library. I've been reading a lot but I couldn't solve the problem. Is volley causing leak or am I doing something terribly wrong?
You can find your leak by using MAT. First you run your app and leak a few activity instances. Then you grab a snapshot of the heap and look for those leaked Activity objects... you can use 'Object Query Language' (OQL) to find them by type (e.g. "SELECT * FROM com.foo.FooActivity").
Once you've found a leaked object, right-click on it and ask MAT to trace all its incoming references back to their GC roots. The leaked reference will be one of those.
For a better introduction to the technique you could try this article:
http://android-developers.blogspot.co.uk/2011/03/memory-analysis-for-android.html
I guess you are using using Viewpager and Imageviews
About image views you are using powerful image downloading and caching library like latest Volley Imageloading(really helpful for large size images) to improve the image loading capabilities in a efficient way.
About Viewpager you have to use efficient adapter FragmentStatePagerAdapter:
This version of the pager is more useful when there are a large number of pages, working more like a list view. When pages are not visible to the user, their entire fragment may be destroyed, only keeping the saved state of that fragment. This allows the pager to hold on to much less memory associated with each visited page as compared to FragmentPagerAdapter at the cost of potentially more overhead when switching between pages.
please think before you are using FragmentPagerAdapter becouse it stores the whole fragment in memory, and could increase a memory overhead if a large amount of fragments are used in ViewPager. In contrary its sibling, FragmentStatePagerAdapter only stores the savedInstanceState of fragments, and destroys all the fragments when they lose focus. Therefore FragmentStatePagerAdapter should be used when we have to use dynamic fragments, like fragments with widgets, as their data could be stored in the savedInstanceState. Also it wont affect the performance even if there are large number of fragments. In contrary its sibling FragmentPagerAdapter should be used when we need to store the whole fragment in memory. When I say the whole fragment is kept in memory it means, its instances wont be destroyed and would create a memory overhead. Therefore it is advised to use FragmentPagerAdapter only when there are low number of fragments for ViewPager. It would be even better if the fragments are static, since they would not be having large amount of objects whose instances would be stored. Hope this clears out the difference between Android FragmentPagerAdapter and FragmentStatePagerAdapter.
Try to learn Google android gallary app example, use image view loading animations to make a great user experience.
I hope this will solves your grow heap problems.
Credits:FragmentPagerAdapter vs FragmentStatePagerAdapter
You forget to recycle your downloaded Bitmaps as they become unneeded.
Basically, every Bitmap you handle manually, you have to recyle().
That being said, your destroyItem() method should look something like this:
public void destroyItem(ViewGroup container, int position, Object object) {
RelativeLayout rl = (RelativeLayout) object;
ImageView im = rl.findViewById(R.id.image_view);
bitmapDrawable = (BitmapDrawable) im.getDrawable();
if (bitmapDrawable != null && bitmapDrawable.getBitmap() != null) {
bitmap = bitmapDrawable.getBitmap();
bitmap.recycle();
}
container.removeView(rl);
}
You should check out the new version of Volley , old version did cause the leak problem.
In old version ,Volley has 4 thread do request , And each of them will keep a request , and request keep strong reference of listener , and your response listener do something with the ImageView , ImageView keep the Activity context. so all of your View is leaked.
In MAT use select * from instanceof android.app.Activity you will see your Activity is leaked.
New Version of Volley has fixed this problem . please check out here
And use this will help your find out your leaked Activity , leakcanary