In my Android application I would like to download a certain number of photos which are linked to points on a map. For example, I have ten points on a map and I have 5 or 6 photos for each point.
At the begining I get 5 or 6 URL (http://theimageurls.com/image.jpg) per point, and for each photo,
I want to download them
I want to save them in the device.
Of course, everything's done in background.
The problem is that sometimes my images are not persisted... but the problem is very irregular and can be encountered on whatever image...
here is my code :
For each point, loadPictures() is called :
private void loadPictures(final KickstartrGeolocPoint point) {
//Photos : get directory path
final File pointDir = FileUtils.getPointPhotoDir(getActivity(), point);
//set boolean to true if the point has photos not downloaded.
ArrayList<String> photosNotDownloaded = getPhotosNotDownloaded(point, pointDir);
if(photosNotDownloaded.size()!=0){
point.setPhoto(true);
kickstartrGeolocService.updatePoint(currentPoint);
}else{
fillPointData(point);
return;
}
//for each urls, download and save photo
for(final String url : photosNotDownloaded){
if (!Utils.isValidUrl(url)) continue;
BitmapUtils.getBitmapFromUrl(url, new OnBitmapRetrievedListener() {
#Override
public void bitmapRetrieved(Bitmap bitmap) {
LogWrapper.debug(getClass(), "Ready to be saved");
persistImageforPoint(point, pointDir, bitmap, FileUtils.getFileNameFromUrl(url));
}
#Override
public void onLoadingStarted(String imageUri, View view) {
LogWrapper.debug(getClass(), "Image downloading... " + imageUri);
//YOU CAN NOTIFY THE VIEW HERE
}
#Override
public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
LogWrapper.debug(getClass(), "Loading failed : " + imageUri);
}
});
}
}
getBitmapFromUrl() is a static method which use an external library called https://github.com/nostra13/Android-Universal-Image-Loader.
getBitmapFromUrl() :
public static void getBitmapFromUrl(String url, final OnBitmapRetrievedListener listener) {
//final int maxSize = context.getResources().getDimensionPixelSize(R.dimen.icon_max_size);
ImageLoader.getInstance().loadImage(url, new ImageLoadingListener() {
#Override
public void onLoadingStarted(String imageUri, View view) {
listener.onLoadingStarted(imageUri,view);
}
#Override
public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
listener.onLoadingFailed(imageUri,view,failReason);
}
#Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
listener.bitmapRetrieved(loadedImage);
}
#Override
public void onLoadingCancelled(String imageUri, View view) {
//do nothing for the moment...
// TODO notify the UI
}
});
}
persistImageforPoint() is called to save the photo :
private void persistImageforPoint(KickstartrGeolocPoint point, File pointDir, Bitmap bitmap, String imageName) {
// Create a subfolder for each point with its id
if (!pointDir.exists()) {
if (!pointDir.mkdirs()) {
LogWrapper.debug(FileUtils.class, "Failed to create directory");
return;
}
}
final String fullPath = pointDir.getPath()+File.separator+FileUtils.getPointPhotoPrefix(point)+FileUtils.getFileNameFromUrl(imageName);
//save the file. Asynchronous task --> do not block the UI
new BitmapPersist(bitmap,fullPath, new OnBitmapPersistedListener() {
#Override
public void persistedSuccessfully() {
addPhotoView(fullPath);
}
#Override
public void errorInPersistance() {
LogWrapper.error(getClass(),"Error persisting image with path "+fullPath);
}
}).execute();
}
Here is my BitmapPersist class :
public class BitmapPersist extends AsyncTask<String, Void, Boolean> {
private String path;
private Bitmap bitmap;
private OnBitmapPersistedListener onBitmapPersistedListener;
public BitmapPersist(Bitmap bitmap, String path, OnBitmapPersistedListener onBitmapPersistedListener) {
this.bitmap=bitmap;
this.path=path;
this.onBitmapPersistedListener=onBitmapPersistedListener;
}
#Override
protected Boolean doInBackground(String... params) {
FileOutputStream out = null;
try {
out = new FileOutputStream(path);
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, out);
return true;
} catch (Exception e) {
e.printStackTrace();
} finally {
try{
out.close();
} catch(Throwable ignore) {}
}
return false;
}
#Override
protected void onPostExecute(Boolean success) {
if (success)
onBitmapPersistedListener.persistedSuccessfully();
onBitmapPersistedListener.errorInPersistance();
}
}
Problem : Sometimes my pictures are not saved, sometimes the application crashes... although everything's done in background threads...
Thanks for your help.
EDIT : Maybe I can add precisions on the library I'm using to download images : It's Android-Universal-Image-Loader (link above). This library works with memory cache and... disk cache. Maybe I can look at this cache. Maybe I can change the path of disk cache and save my pictures with that. I'm also looking for someone who knows this library (developped by NOSTRA)... The documentation is quite succinct...
SOLUTION : Don't need to create 2 Asynctasks : Download the picture synchronously and then persist it in the same asynctask.
Related
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);
}
}
}
});
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 have this method, everything is worked perfectly but images always got from server and not load from cache! what happened ?
public static void makeImageRequest(String Unique_ID, final View parentView, final int id) {
String url = FILE_UPLOAD_FOLDER + Unique_ID + ".png";
final int defaultImageResId = R.drawable.user;
// Retrieves an image specified by the URL, displays it in the UI.
ImageCacheManager.getInstance().getImage(url, new ImageListener() {
#Override
public void onErrorResponse(VolleyError error) {
ImageView imageView = (ImageView) parentView.findViewById(id);
imageView.setImageResource(defaultImageResId);
}
#Override
public void onResponse(ImageContainer response, boolean isImmediate) {
if (response.getBitmap() != null) {
ImageView imageView = (ImageView) parentView.findViewById(id);
imageView.setImageBitmap(response.getBitmap());
} else if (defaultImageResId != 0) {
ImageView imageView = (ImageView) parentView.findViewById(id);
imageView.setImageResource(defaultImageResId);
}
}
});
}
Just use Picasso instead ImageCacheManager. Picasso is a powerful image downloading and caching library for Android. Images add much-needed context and visual flair to Android applications. Picasso allows for hassle-free image loading in your application—often in one line of code!
Picasso.with(context).load("http://i.imgur.com/DvpvklR.png").into(imageView);
Here also can manage whether the image is successfully downloaded or it fails:
Picasso.with(context)
.load("http://i.imgur.com/DvpvklR.png")
.into(imageView, new Callback() {
#Override
public void onSuccess() {
// your code
}
#Override
public void onError() {
// your code
}
});
You should only add this line in your gradle:
compile 'com.squareup.picasso:picasso:2.5.2'
Hope it helps!
I'm creating an application where the user selects one item from a recycler view and this starts the download of a group of images.
I'm using universal image loader's loadImage method to fetch images for me which is working very well.
The problem is,if the user wants to cancel downloading this group of images (can be very network consuming) I cannot find a way to make ui stop only some of the images.
I'm aware of the method ImageLoader.cancelDisplayTask(), but as I'm using loadImage I have no ImageView or ImageAware available to send as a parameter to this task.
Also ImageLoader.stop() or ImageLoader.pause() stops all current downloads and I want to stop only the ones selected by the user.
How can I achieve this?
PS: A suggestion to some future development would be returning a pointer of the download task when using loadImage method. Maybe I'll look it myself when I have some time to contribute to this awesome library.
Here's my code for downloading an image.
private void downloadFile(final URL downloadURL,
final URL destinationURL,
final boolean isThumbnail,
final MyDownloadListener listener) {
final NewsData thisClass = this;
final ImageLoader il = ImageLoader.getInstance();
File imageFile = il.getDiskCache().get(destinationURL.toString());
if (imageFile.exists()) {
fallDownloadCompleted(this, isThumbnail);
return;
}
il.stop();
il.loadImage(
downloadURL.toString(),
null,
Application.getAppDisplayOptions(),
new SimpleImageLoadingListener() {
#Override
public void onLoadingStarted(String imageUri, View view) {
if (listener != null) {
listener.onLoadingStarted(thisClass);
}
}
#Override
public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
fallDownloadFail(thisClass, failReason, isThumbnail);
}
#Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
try {
il.getDiskCache().save(destinationURL.toString(), loadedImage);
if (isToday()) deleteImageFile(imageUri);
} catch (IOException e) {
deleteImageFile(imageUri);
fallDownloadFail(
thisClass,
new FailReason(FailReason.FailType.IO_ERROR, new Throwable()),
isThumbnail);
return;
}
fallDownloadCompleted(thisClass, isThumbnail);
}
},
new ImageLoadingProgressListener() {
#Override
public void onProgressUpdate(String imageUri, View view, int current, int total) {
float progress = 0;
if (total > 0 && numberOfTotalPages > 0) {
if (!isThumbnail) {
progress = numberOfAvailablePages / (float) numberOfTotalPages;
progress += (current / (float) total) / numberOfTotalPages;
} else {
progress = current / (float) total;
}
}
if (listener != null) {
listener.onProgressUpdate(thisClass, progress);
}
}
});
}
private ArrayList<ImageLoader> images = new ArrayList<ImageLoader>();
images.remove():
? Not familiar with ImageLoader class but I hope this helps
These might help
android: Universal Image Loader get drawable from string array
How to change array list to string array for using Universal Image Loader?
https://github.com/nostra13/Android-Universal-Image-Loader/issues/109
I managed to solve this issue with a small workaround.
I couldn't find a way to cancel a particular ImageLoader.loadImage() so what I had to do was use the normal ImageLoader.displayImage() by passing a NonViewAware class in place of the image view required as a parameter.
I then added this NonViewAware into an ArrayList<NonViewAware> to keep track of which images were being downloaded.
Finally when I wanted to cancel a display task I just called ImageLoader.cancelDisplayTask(NonViewAware) with one of the stored items from the list.
I'm working on an app that should connect to a URL, retrieve the image as Bitmap and then show it in a viewer based on PanoramaGL library.
The problem is that even if Log says that the image is catched, the display remains blank.
Here is the code I'm using to retrieve the bitmap also following the very detailed answer I found there How to load an ImageView by URL in Android?. -- in fact i'd like to read the image directly without cache or so on.
As you will see most parts of codes are taken from online tutorials as I'm just starting developping.
PanoramaActivity.java (in order to test it can be used as the MainActivity)
import java.io.InputStream;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;
import com.panoramagl.PLImage;
import com.panoramagl.PLSpherical2Panorama;
import com.panoramagl.PLView;
public class PanoramaActivity extends PLView {
Private static final String TAG = "+++++++++++++++";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.panorama_activity_main);
new DownloadImageTask(this).execute("http://www.pianetacellulare.it/UserFiles/image/Android/Jelly%20Bean/photo_sphere_esempio.jpg");
}
private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
PanoramaActivity pan;
public DownloadImageTask(PanoramaActivity act) {
this.pan = act;
}
protected Bitmap doInBackground(String... urls) {
String urldisplay = urls[0];
Bitmap mIcon11 = null;
try {
InputStream in = new java.net.URL(urldisplay).openStream();
mIcon11 = BitmapFactory.decodeStream(in);
} catch (Exception e) {
Log.e("Error", e.getMessage());
e.printStackTrace();
}
return mIcon11;
}
protected void onPostExecute(Bitmap result) {
Log.d(TAG,"onPostExecute");
PLSpherical2Panorama panorama = new PLSpherical2Panorama();
panorama.setImage(new PLImage(result));
pan.setPanorama(panorama);
}
}
}
I've created the constructor passing PanoramaActivity as I suppose that the app flow until onPostExecute method and there I need to find a way to display the image.
If I'm wrong or there are any better ways please let me know.
I'm posting also panorama_activity_main.xml layout for tests:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
tools:context=".PanoramaActivity" >
</RelativeLayout>
Any hints appreciated, thanks in advance :)
UPDATE. I tried the same AsyncTask class using ImageView instead of PLView, and the image came out, so the download of the image is correctly done.. There is something in the use of PanoramaGL which is incorrect
remove
setContentView(R.layout.panorama_activity_main);
it is overwriting image. --> even if I do not undestand why this happens
And above all.. be sure that the image you are linking to follows requirement:
PanoramaGL only supports images with sizes at power of two e.g. 2048x1024, 1024x1024, 1024x512, 512x512, 512x256, 256x256, 256x128.
Use Image loader
private void loadPanorama()
{
try
{
final Context context = this.getApplicationContext();
PLIPanorama panorama = null;
//Lock panoramic view
this.setLocked(true);
Toast.makeText(context, "image loader start", Toast.LENGTH_LONG).show();
imageLoader.loadImage("http://104.245.38.221/www/200/000/005/2000000057042/f/z_0/00.jpg", new SimpleImageLoadingListener()
{
#Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage)
{
cubicPanorama.setImage(new PLImage(loadedImage, false), PLCubeFaceOrientation.PLCubeFaceOrientationFront);
}
});
imageLoader.loadImage("http://104.245.38.221/www/200/000/005/2000000057042/b/z_0/00.jpg", new SimpleImageLoadingListener()
{
#Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage)
{
cubicPanorama.setImage(new PLImage(loadedImage, false), PLCubeFaceOrientation.PLCubeFaceOrientationBack);
}
});
imageLoader.loadImage("http://104.245.38.221/www/200/000/005/2000000057042/l/z_0/00.jpg", new SimpleImageLoadingListener()
{
#Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage)
{
cubicPanorama.setImage(new PLImage(loadedImage, false), PLCubeFaceOrientation.PLCubeFaceOrientationLeft);
}
});
imageLoader.loadImage("http://104.245.38.221/www/200/000/005/2000000057042/r/z_0/00.jpg", new SimpleImageLoadingListener()
{
#Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage)
{
cubicPanorama.setImage(new PLImage(loadedImage, false), PLCubeFaceOrientation.PLCubeFaceOrientationRight);
}
});
imageLoader.loadImage("http://104.245.38.221/www/200/000/005/2000000057042/u/z_0/00.jpg", new SimpleImageLoadingListener()
{
#Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage)
{
cubicPanorama.setImage(new PLImage(loadedImage, false), PLCubeFaceOrientation.PLCubeFaceOrientationUp);
}
});
imageLoader.loadImage("http://104.245.38.221/www/200/000/005/2000000057042/d/z_0/00.jpg", new SimpleImageLoadingListener()
{
#Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage)
{
cubicPanorama.setImage(new PLImage(loadedImage, false), PLCubeFaceOrientation.PLCubeFaceOrientationDown);
}
});
panorama = cubicPanorama;
Toast.makeText(context, "image loader end", Toast.LENGTH_LONG).show();
if(panorama != null)
{
//Set camera rotation
panorama.getCamera().lookAt(0.0f, 170.0f);
//Add a hotspot
//panorama.addHotspot(new PLHotspot(1, new PLImage(PLUtils.getBitmap(context, R.raw.hotspot), false), 0.0f, 170.0f, 0.05f, 0.05f));
//Reset view
this.reset();
//Load panorama
this.startTransition(new PLTransitionBlend(2.0f), panorama); //or use this.setPanorama(panorama);
}
//Unlock panoramic view
this.setLocked(false);
}
catch(Throwable e)
{
Toast.makeText(this.getApplicationContext(), "Error: " + e, Toast.LENGTH_SHORT).show();
}
}
If black screen appears you can override
#Override
public void onLoadingStarted(String imageUri, View view) {
if (frontBitamp != null) {
cubicPanorama.setImage(new PLImage(frontBitamp, false),
PLCubeFaceOrientation.PLCubeFaceOrientationFront);
}};
#Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
cubicPanorama.setImage(new PLImage(loadedImage, false),
PLCubeFaceOrientation.PLCubeFaceOrientationFront);
frontBitamp = loadedImage;}