Good day.I have an google map with cluster manager.Simple one,where i use the cluster to draw markers grouped or not.Anyway i got an method callback from cluster manager which is the Cluster item render one.Inside that callback i am applying custom image to the marker:The user image inside marker.I found Picasso to be the best to handle bitmap loading and at the same time got me lots of headache.I am using Target class from Picasso to initiate the bitmap callbacks:OnPreLoad,OnFail,OnBitmapLoaded.The issue is that on first cluster item render the onBitmapLoaded not called and generally it is never gets called unless it has been touched second time.On first time nothing happens,no callback is triggered except OnPreLoad and by googling i found that the great Picasso holds weak reference to the class.I tried all the examples of the google:Making Target reference strong(getting the initialazation of class out of method and init the class inside my class like the follows)
#Override
protected void onClusterItemRendered(MarkerItem clusterItem, Marker marker) {
mMarker = marker;
mMarkerItem = clusterItem;
Picasso.with(mContext).load(clusterItem.getImageUrl()).transform(new CircleTransformation()).into(target);
}
private Target target = new Target() {
#Override
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
Log.d(TAG, "onBitmapLoaded: ");
}
#Override
public void onBitmapFailed(Drawable errorDrawable) {
Log.d(TAG, "onBitmapFailed: ");
}
#Override
public void onPrepareLoad(Drawable placeHolderDrawable) {
Log.d(TAG, "onPrepareLoad: ");
}
};
#Override
protected void onBeforeClusterItemRendered(MarkerItem item, MarkerOptions markerOptions) {
markerOptions.title(item.getTitle());
markerOptions.icon(item.getIcon());
}
At this point i get the same result....Sometimes the bitmap loaded and sometimes not.Mostly not...
Anyway i have tried to implement the interface class to my own class as follows:
public class PicassoMarkerView implements com.squareup.picasso.Target {
private static final String TAG = "MarkerRender";
private Bitmap mMarkerBitmap;
private ClusterManager<MarkerItem> mClusterManager;
private MarkerItem mMarkerItem;
private Marker mMarker;
public PicassoMarkerView() {
}
#Override
public int hashCode() {
return mMarker.hashCode();
}
#Override
public boolean equals(Object o) {
if (o instanceof PicassoMarkerView) {
Marker marker = ((PicassoMarkerView) o).mMarker;
return mMarker.equals(marker);
} else {
return false;
}
}
#Override
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap,
mMarkerBitmap.getWidth() - 15, (int) (mMarkerBitmap.getHeight() / 1.5 - 15),
false);
mMarker.setIcon(BitmapDescriptorFactory.fromBitmap(overlay(mMarkerBitmap, scaledBitmap, 8, 7)));
Log.d(TAG, "onBitmapLoaded: ");
}
#Override
public void onBitmapFailed(Drawable errorDrawable) {
Log.d(TAG, "onBitmapFailed: ");
}
#Override
public void onPrepareLoad(Drawable placeHolderDrawable) {
Log.d(TAG, "onPrepareLoad: ");
}
private Bitmap overlay(Bitmap bitmap1, Bitmap bitmap2, int left, int top) {
Bitmap res = Bitmap.createBitmap(bitmap1.getWidth(), bitmap1.getHeight(),
bitmap1.getConfig());
Canvas canvas = new Canvas(res);
canvas.drawBitmap(bitmap1, new Matrix(), null);
canvas.drawBitmap(bitmap2, left, top, null);
return res;
}
public void setMarkerBitmap(Bitmap markerBitmap) {
this.mMarkerBitmap = markerBitmap;
}
public void setClusterManager(ClusterManager<MarkerItem> clusterManager) {
this.mClusterManager = clusterManager;
}
public void setMarkerItem(MarkerItem markerItem) {
this.mMarkerItem = markerItem;
}
public void setMarker(Marker marker) {
this.mMarker = marker;
}
}
Unfortunatally this is not working either...Same result...So please dear friends can you give me an working example of this?As far as i could google,the issue mostly happens to the user which try to do this inside loop and my onClusterItemRender some sort of loop lets say as it is triggered every time marker is visible to user,so yeah it is triggered several times and as fast as loop so give me some idea please and help me out...
Important to mention that i do not need to use methods from picasso like fetch(),get() as they are not necessary and not fitting the purpose of the app.
I encountered similar issue and holding reference to the target didn't help at all.
The purpose of my project was to use 2 different image downloading api's to show an images gallery and to give the user the ability to choose which api to use.
Beside Picasso I used Glide, and I was amazed by the results, Glide's api worked flawlessly in every aspect wile Picasso gave me hell (that was my first time using Glide, I usually used Picasso so far, seems like today it's gonna change ^^ ).
So my suggestion to you is:
Use glide over Picasso (no such weak reference on their target).
Since I had to use both libraries I ended up using get() in an handler, not sure if it will help you but it solved my problem:
handlerThread = new HandlerThread(HANDLER_THREAD_NAME);
handlerThread.start();
Handler handler = new Handler(handlerThread.getLooper());
handler.post(new Runnable() {
#Override
public void run() {
Bitmap bitmap = null;
try {
bitmap = picasso.with(appContext).load(url).get();
} catch (IOException e) {
e.printStackTrace();
}finally {
if (bitmap != null) {
//do whatever you wanna do with the picture.
//for me it was using my own cache
imageCaching.cacheImage(imageId, bitmap);
}
}
}
});
Related
I'm trying to use an IntentService for processing and uploading images that's running in a different process to have more memory. I'm Using also Picasso to load the Image. When the Image is small the bitmap is loaded successfully and uploaded, however if the image is big the IntentService is terminated before Picasso is done loading It.
Picasso have to run on UIThread
Here is the code.
private void downloadImage(File file) {
final Uri uri = Uri.fromFile(file);
Handler uiHandler = new Handler(Looper.getMainLooper());
uiHandler.post(new Runnable() {
#Override
public void run() {
Picasso.with(NewImageProcessingService.this).load(uri).transform(new ImageLoadingUtil.DecreaseQualityTransformation(imageQuality)).into(NewImageProcessingService.this);
}
});
}
#Override
protected void onHandleIntent(Intent intent) {
File file = (File) intent.getSerializableExtra(KEY_IMAGE_FILE);
imageQuality = ImagesUtils.IMAGE_QUALITY
.values()[intent.getIntExtra(IMAGE_QUALITY, ImagesUtils.IMAGE_QUALITY.DEFAULT.ordinal())];
downloadImage(file);
}
This question is quite old, but if anyone steps by. The Target is getting garbage collected before it can show the bitmap.
Use it like this
public class BitmapLoader {
public static Target getViewTarget(final OnImageLoadingCompleted onCompleted) {
return new Target() {
#Override
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
onCompleted.imageLoadingCompleted(bitmap);
}
#Override
public void onBitmapFailed(Drawable errorDrawable) {
}
#Override
public void onPrepareLoad(Drawable placeHolderDrawable) {
}
};
}
}
You need to have a strong reference to the Target so have a field in your IntentService holding it e.g.
private Target viewTarget;
viewTarget = BitmapLoader.getViewTarget(bitmap -> {
// do stuff with the bitmap
});
new Handler(Looper.getMainLooper()).post(() -> Picasso.with(getApplicationContext()).load(object.getImageUrl()).into(viewTarget));
I've got an issue with onBitmapLoaded. The method is not called when it should be (it is called the second time i enter my view). Nevertheless i keep a reference to my target since i add it to an arraylist.
I don't understand why it's not working.
Does someone have an idea ?
public void loadBitmap() {
if(loadtarget == null) {
loadtarget = new Target(){
#Override
public void onPrepareLoad(Drawable arg0) {
Log.d("Bitmap","On prepare load");
targetList.remove(this);
return;
}
#Override
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
Log.d("Bitmap","OKAY for :" + filename);
targetList.remove(this);
handleLoadedBitmap(bitmap);
}
#Override
public void onBitmapFailed(Drawable errorDrawable) {
Log.d("Bitmap","Error for :" + filename);
}
};
}
targetList.add(loadtarget);
Picasso.with(context).load(imageUrl).into(loadtarget);
}
If targetList and loadtarget are both local variables then they will be marked for GC collecting as soon as the method finishes.
Make sure targetList is a class variable so that its outlives the method.
I've find some kind of trick to solve my problem.
By replacing :
Picasso.with(context).load(imageUrl).into(targetList.get(i));
With :
Picasso.with(context).load(imageUrl).transform(new Transformation() {
#Override
public Bitmap transform(Bitmap source) {
handleLoadedBitmap(source);
return source;
}
#Override
public String key() {
return "";
}
}).into(imageView); // imageView is a fictive imageView allocated only for this operation
my code is working. I'm not sure that it's the best solution but it fixed my problem.
Thanks for all the effort #Budius has made.
Most part of image work of my app could be handled by Picasso/Glide, however, some images are displayed in a TextView by Html.fromHtml. And the images in TextView are also used frequently.
However, I don't know how to implement getDrawable() method with Picasso/Glide for the ImageGetter passed to Html.fromHtml. Is it possible to share the same cache of Picasso/Glide for these pictures in TextView and other bitmaps?
Or should I use an custom LruCache instead to cache these pictures form ImageGetter separately? Will this way increase the risk of an OOM error? And I think it creates unnecessary workload to use 2 different systems for processing images.
Update: I tried to use .get() of Picasso, but the doc says
/**
* The result of this operation is not cached in memory because the underlying
* {#link Cache} implementation is not guaranteed to be thread-safe.
*/
So the cache is not used in this case.
Update:
The answer of #Budius is right, but code of setting bounds for the Drawable is missing, which leaves the Drawable not displayed in the TextView. So I modified the code in the DrawableWrapper class into:
public void setWrappedDrawable(Drawable drawable) {
if (mDrawable != null) {
mDrawable.setCallback(null);
}
mDrawable = drawable;
if (drawable != null) {
mDrawable.setBounds(0,0,mDrawable.getIntrinsicWidth(),mDrawable.getIntrinsicHeight());
drawable.setCallback(this);
}
}
Update:, the problem is still unsolved. If you implement the solution forementioned, there are some strange behaviors for the image in TextView. Sometimes the image could not be refreshed unless you switch to another app and switch back, and the position of image is severely incorrect.
Update: I have post all the code for test below. There're still some bugs. Without a placeholder, it still throws an NPE. With a placeholder, the behavior is very strange. The first time I enter TestActivity, it shows the placeholder but it won't change into the downloaded pic. But after I switch to another app or press a back button and enter TestActivity again, the pic is displayed(maybe because it's in the cache?).
And also the size of pic is right but the place is still not left for the image. And if I call mDrawable.setBounds(getBounds()); instead of mDrawable.setBounds(0,0,getIntrinsicWidth(),getIntrinsicHeight());, it will not be displayed.
DrawableWrapper
public class DrawableWrapper extends Drawable implements Drawable.Callback {
private Drawable mDrawable;
public DrawableWrapper(Drawable drawable) {
setWrappedDrawable(drawable);
}
#Override
public void draw(Canvas canvas) {
mDrawable.draw(canvas);
}
#Override
public int getIntrinsicWidth() {
return 384;
}
#Override
public int getIntrinsicHeight() {
return 216;
}
//... other delegation methods are omitted
public void setWrappedDrawable(Drawable drawable) {
if (mDrawable != null) {
mDrawable.setCallback(null);
}
mDrawable = drawable;
if (drawable != null) {
mDrawable.setBounds(0,0,getIntrinsicWidth(),getIntrinsicHeight());
drawable.setCallback(this);
}
}
}
PicassoTargetDrawable
public class PicassoTargetDrawable extends DrawableWrapper
implements Target {
private Context context;
public PicassoTargetDrawable(Context context) {
super(new ColorDrawable(0));
// use application context to not leak activity
this.context = context.getApplicationContext();
}
public void onBitmapFailed(Drawable errorDrawable) {
setWrappedDrawable(errorDrawable);
invalidateSelf();
}
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
setWrappedDrawable(new BitmapDrawable(context.getResources(), bitmap));
context = null;
invalidateSelf();
}
public void onPrepareLoad(Drawable placeHolderDrawable) {
setWrappedDrawable(placeHolderDrawable);
invalidateSelf();
}
}
TestActivity
public class TestActivity extends FragmentActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TextView textView = new TextView(this);
textView.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT));
setContentView(textView);
String html = "<div>test<br/>" +
"<img src=\"http://i2.cdn.turner.com/money/dam/assets/150910165544-elon-evo-open-still-384x216.png\"></img>" +
"<br/>/test</div>";
textView.setText(Html.fromHtml(html, new Html.ImageGetter() {
#Override
public Drawable getDrawable(String source) {
PicassoTargetDrawable d = new PicassoTargetDrawable(TestActivity.this);
Picasso.with(TestActivity.this)
.load(source)
//add placeholder here
.into(d);
return d;
}
}, null));
}
}
My Suggestion is to return a wrap drawable. And keep using Picasso to download the image.
On the following link you can find an DrawableWrapper, it's from Googles support library, but it's not part of the public docs, so I would just copy the whole code into your project https://android.googlesource.com/platform/frameworks/support/+/master/v7/appcompat/src/android/support/v7/graphics/drawable/DrawableWrapper.java
And then you create a PicassoTargetDrawable from it.
public class PicassoTargetDrawable extends DrawableWrapper
implements Picasso.Target {
private Context context;
public PicassoTargetDrawable(Context context) {
super(new ColorDrawable(0));
// use application context to not leak activity
this.context = context.getApplicationContext();
}
public void onBitmapFailed(Drawable errorDrawable) {
setWrappedDrawable(errorDrawable);
invalidateSelf();
}
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
setWrappedDrawable(new BitmapDrawable(context.getResources(), bitmap));
context = null;
invalidateSelf();
}
public void onPrepareLoad(Drawable placeHolderDrawable) {
setWrappedDrawable(placeHolderDrawable);
invalidateSelf();
}
}
Then it's just a matter of loading it up
public void Drawable getDrawable(String source) {
PicassoTargetDrawable d = new PicassoTargetDrawable(context);
Picasso.with(context)
.load(source)
..... add here onError and placeholder drawables
.into(d);
return d;
}
PS.:
I wrote all this without looking up too much, there will probably be a few typos and a few issues to sort it out, but it's certainly enough for you to understand the concept.
update:
Just correcting your code.
The TextView already told the WrapDrawable the Bounds it should use. If you're telling the new mDrawable that it can use whatever size it wants, it will use whatever size it wants. So instead of passing its own intrinsic width/height, you should pass the size that was give to the WrapDrawable
public void setWrappedDrawable(Drawable drawable) {
if (mDrawable != null) {
mDrawable.setCallback(null);
}
mDrawable = drawable;
if (drawable != null) {
mDrawable.setBounds(getBounds());
drawable.setCallback(this);
}
}
I'm trying to clear the cache memory of Picasso via Android coding.
Can anyone please help me in this issue..?
I have tried using the following code, but this was not useful in my case:
Picasso.with(getActivity()).load(data.get(pos).getFeed_thumb_image()).skipMemoryCache().into(image);
Use this instead :
Picasso.with(getContext()).load(data.get(pos).getFeed_thumb_image()).memoryPolicy(MemoryPolicy.NO_CACHE).into(image);
Remove cache of Picasso like this.
public class Clear {
public static void clearCache (Picasso p) {
p.cache.clear();
}
}
This util class can clear the cache for you. You just have to call it:
Clear.clearCache(Picasso.with(context));
EDIT:
The class Clear must be in the package :
package com.squareup.picasso;
Because cache is not accessible from outside that package.
Like in this answer: https://stackoverflow.com/a/23544650/4585226
if you are trying to load an image through Json(from db) try clearing the networkCache for a better result.
Picasso.with(context).load(uri).networkPolicy(NetworkPolicy.NO_CACHE)
.memoryPolicy(MemoryPolicy.NO_CACHE)
.placeholder(R.drawable.bv_logo_default).stableKey(id)
.into(viewImage_imageView);
Instead of clearing the complete cache if one wants to refresh the image with the given Uri. try this Picasso.with(context).invalidate(uri); it internally removes the key from the cache maintained by Picasso.
Excerpt from Picasso.java
/**
* Invalidate all memory cached images for the specified {#code uri}.
*
* #see #invalidate(String)
* #see #invalidate(File)
*/
public void invalidate(Uri uri) {
if (uri == null) {
throw new IllegalArgumentException("uri == null");
}
cache.clearKeyUri(uri.toString());
}
When activity destroy, unfortunately bitmap was not recycled if we're using Picasso. I try to programmatically recycle bitmap, what's loaded in to image view. There is a way to reference to loaded bitmap by using Target.
Target mBackgroundTarget = new Target() {
Bitmap mBitmap;
#Override
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
if (bitmap == null || bitmap.isRecycled())
return;
mBitmap = bitmap;
mBgImage.setImageBitmap(bitmap);
mHandler.post(new Runnable() {
#Override
public void run() {
// Do some animation
}
});
}
#Override
public void onBitmapFailed(Drawable errorDrawable) {
recycle();
}
#Override
public void onPrepareLoad(Drawable placeHolderDrawable) {
}
/**
* Recycle bitmap to free memory
*/
private void recycle() {
if (mBitmap != null && !mBitmap.isRecycled()) {
mBitmap.recycle();
mBitmap = null;
System.gc();
}
}
};
And when Activity destroy, I call onBitmapFailed(null) to recycle loaded bitmap.
#Override
protected void onDestroy() {
super.onDestroy();
try {
if (mBackgroundTarget != null) {
mBackgroundTarget.onBitmapFailed(null);
Picasso.with(context).cancelRequest(mBackgroundTarget);
}
} catch (Exception e) {
e.printStackTrace();
}
}
But remember, DON'T CACHE IMAGE IN MEMORY by this case, It will cause Use recycled bitmap exception.
Picasso.with(context)
.load(imageUrl)
.resize(width, height)
.memoryPolicy(MemoryPolicy.NO_CACHE)
.into(mBackgroundTarget);
Hope this help.
If you keep reference of your custom Downloader implementation you can clear cache.
public class PicassoUtil {
private static Picasso sInstance;
private static OkHttp22Downloader sDownloader;
public static Picasso getPicasso(Context context){
if(sInstance == null) {
sDownloader = new OkHttp22Downloader(context)
Picasso.Builder builder = new Picasso.Builder(context);
builder.downloader(sDownloader);
sInstance = builder.build(sDownloader);
}
return sInstance;
}
public static void clearCache(){
if(sDownloader != null){
sDownloader.clearCache();
}
}
}
It is important to have access to your http client and its Cache. In my implementation there is access to the cache, hence clearing cache with clearCache() method.
i had the same problem.
It worked for me.
I used Picasso in RecycleView inside a dialog. When i closed dialog, picasso doesnt clear cache. But while you are using the dialog it clears image cache. However there is some cache that is not cleared. Maybe the cache that was not cleared is the last you seen in dialog before dialog.dismiss().
use this
memoryPolicy(MemoryPolicy.NO_CACHE,MemoryPolicy.NO_STORE)
Picasso.with(activity).load(file).resize(100,100).centerCrop().memoryPolicy(MemoryPolicy.NO_CACHE,MemoryPolicy.NO_STORE).into(contactImage, new com.squareup.picasso.Callback() {
#Override
public void onSuccess() {
}
#Override
public void onError() {
}
});
Picasso.with(this.getContext()).load(gamePlayer.getPlayerProfileUrl()).skipMemoryCache().into(iv);
This also works
I'm using Google Maps Android API Utility Library and I'm downloading certain images from internet that I want to use as markers.
The way I'm doing it is like in the following snippet:
class MarkerItemClusterRenderer extends DefaultClusterRenderer<MarkerItem> {
...
#Override
protected void onBeforeClusterItemRendered(MarkerItem item,
final MarkerOptions markerOptions) {
super.onBeforeClusterItemRendered(item, markerOptions);
mImageLoader.get(item.getImageUrl(), new ImageListener() {
#Override
public void onErrorResponse(VolleyError error) {
Log.i("XXX", error.toString());
}
#Override
public void onResponse(ImageContainer response, boolean isImmediate) {
if (response != null && response.getBitmap() != null) {
mImageIcon.setImageBitmap(response.getBitmap());
Bitmap icon = mIconGenerator.makeIcon();
Bitmap bhalfsize = Bitmap.createScaledBitmap(icon, 150,
150, false);
markerOptions.icon(BitmapDescriptorFactory
.fromBitmap(bhalfsize));
}
}
});
}
The problem is, that when the image is downloaded, the map (and thus the marker) doesn't refresh, so most of the times (but not always) I still see the red default markers.
I tried to do mImageIcon.invalidate(); mImageIcon.requestLayout(); but there's still no luck.
Is there anyway to achieve this?
Thanks a lot in advance.
You just need to make all this stuff in
protected void onClusterItemRendered(T clusterItem, Marker marker) {
...
}
In onBeforeClusterItemRendered you set icon on MarkerOptions in async callback. At this time it could be added to map and become real Marker. So you icon will be set to already useless object.
That's why you need to do it in onClusterItemRendered
Let's say you have GoogleMap object declared as:
private GoogleMap mMap;
In onResponse() method before applying any change to marker, try writing following statement to clear previous markers:
mMap.clear();
Now set your new marker.
I might be a bit late but i write it down so it can be useful for somebody looking for a solution like i was.
Basically what you have to do is refresh the marker and not the ClusterItem, but i used my own ClusterItem implementation to store some important data.
So your code inside onBeforeClusterItemRendered becomes like this:
LatLngBounds bounds = map.getProjection().getVisibleRegion().latLngBounds; //take visible region on map
if(bounds.contains(item.getPosition()) && !item.hasImage()) { //if item is not inside that region or it has an image already don't load his image
mImageLoader.get(item.getImageUrl(), new ImageListener() {
#Override
public void onErrorResponse(VolleyError error) {
Log.i("XXX", error.toString());
}
#Override
public void onResponse(ImageContainer response, boolean isImmediate) {
if (response != null && response.getBitmap() != null) {
mImageIcon.setImageBitmap(response.getBitmap());
Bitmap icon = mIconGenerator.makeIcon();
Bitmap bhalfsize = Bitmap.createScaledBitmap(icon, 150,
150, false);
//Set has image flag
item.setHasImage(true);
//Find the right marker
MarkerManager.Collection markerCollection = mClusterManager.getMarkerCollection();
Collection<Marker> markers = markerCollection.getMarkers();
for (Marker m : markers) {
if (id.equals(m.getTitle())) {
//set the icon
m.setIcon(BitmapDescriptorFactory.fromBitmap(image));
break;
}
}
}
}
});
}
And your MyItem class must have some parameters which are useful for remember our stuff:
public class MyItem implements ClusterItem {
private String itemId;
private LatLng mPosition;
private WMWall wall;
private boolean hasImage = false;
public MyItem(double latitude, double longitude) {
mPosition = new LatLng(latitude, longitude);
}
#Override
public LatLng getPosition() {
return mPosition;
}
public WMWall getWall() {
return wall;
}
public void setWall(WMWall wall) {
this.wall = wall;
}
public String getItemId() {
return itemId;
}
public void setItemId(String itemId) {
this.itemId = itemId;
}
public boolean hasImage() {
return hasImage;
}
public void setHasImage(boolean hasImage) {
this.hasImage = hasImage;
}
}
It is really important to load only the images of markers contained into bounds, otherwise you'll run into OOM.
And if the hasImage() method returns true we don't need to load the image again since it is already stored into the marker object.