I have a large list of objects, all of whom have a path to their image "ex. http://www.google.com/image.jpg" and I need to download the image and save the drawable to the object..
I was using AsyncTask, but even if I use my own threads I always end up with 'OutOfMemoryError' at some arbitrary point in the list. The images are never larger than 82Kb (Is this too big for android tablets?) in size, but I think the sheer number of images is causing the process as a whole to fail.
Here is what I'm currently doing.
class DownloadImageTask extends AsyncTask<ArrayList<Item>, Void, Void> {
private static int num =1;
#Override
protected Void doInBackground(ArrayList<Item>... items) {
try {
if(items.length == 0)
return null;
HttpURLConnection connection;
InputStream input;
for(ArrayList<Item> itemlist : items) {
for(Item i : itemlist) {
Log.d(JusTouchMenu.TAG,"[Item]Image request to url:"+i.getImagePath());
try {
connection = (HttpURLConnection)new URL(i.getImagePath()).openConnection();
connection.setRequestProperty("User-agent","Mozilla/4.0");
connection.connect();
input = connection.getInputStream();
i.setImage(new BitmapDrawable(BitmapFactory.decodeStream(input)));//Requires a drawable
connection.disconnect();
} catch(Exception e) {Log.e(JusTouchMenu.TAG,"[Item]Unable to download image # '"+i.getImagePath()+"'",e);}
Log.v(JusTouchMenu.TAG, "[Item]Image decoded # '"+i.getImagePath()+"' #"+num++);
for(Tag pt : i.tags()) {
Log.d(JusTouchMenu.TAG,"[Item->Tag]Image request to url:"+pt.getImagePath());
try {
connection = (HttpURLConnection)new URL(pt.getImagePath()).openConnection();
connection.setRequestProperty("User-agent","Mozilla/4.0");
connection.connect();
input = connection.getInputStream();
pt.setImage(new BitmapDrawable(BitmapFactory.decodeStream(input))); //Requires a drawable
connection.disconnect();
} catch(Exception e) {
Log.e(JusTouchMenu.TAG,"[Item->Tag]Unable to download image # '"+i.getImagePath()+"'",e);
}
Log.v(JusTouchMenu.TAG, "[Item->Tag]Image decoded # '"+pt.getImagePath()+"' #"+num++);
}
}
}
} catch(Exception e) {
Log.e(JusTouchMenu.TAG,"Error decoding image inside AsyncTask",e);
}
return null;
}
Thanks!
How many of these images are you trying to get at once? How are you using them?
You should probably consider using the capabilities of Gallery or GridView and thereby only be loading the images that are in view at the moment. You can even get fancy and cache them so scrolling is nice and smooth. Trying to load all of the images into an ArrayList when youre not likely to be able to display them all at once is not a mobile friendly approach.
Here are some decent references to caching approaches but you should probably get started with a basic gallery or grid view loading as needed and then add the caching on.
Lazy load of images in ListView
http://code.google.com/p/libs-for-android/wiki/ImageLoader
Related
I have an activity in which the user presses a button which fetches a JSON response from a URL, and then downloads and saves all of the image URLs in that JSON. The downloading takes place in a separate class which extends Thread:
downloadButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
DownloadTask task = new DownloadTask(handler);
task.start();
}
});
handler is an inner static Handler that holds a WeakReference to the activity (used for displaying progress).
In the DownloadTask:
public DownloadTask(Handler handler) {
this.handler = handler;
}
#Override
public void run() {
String jsonString = // gets JSON from server
urlsToDownload = new HashSet<String>();
// do some stuff with the JSON to put each URL into the Set
for (Iterator<String> i = urlsToDownload.iterator(); i.hasNext(); ) {
String urlString = i.next();
// the following takes place in two static method calls,
// but I've laid it all out here for easier interpretation.
// I'm also removing all try/catch blocks, if (x != null) checks etc
// first download the image from the web
URL url = new URL(urlString);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.connect();
BufferedInputStream bis = new BufferedInputStream(connection.getInputStream());
Bitmap bitmap = BitmapFactory.decodeStream(bis);
bis.close() // (done in try-with-resource)
connection.disconnect();
// then save the image on the device
File file = new File(App.context.getFilesDir(), "my/file/name.jpg");
FileOutputStream fos = new FileOutputStream(file);
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, fos);
fos.close() // (done in try-with-resource)
// make a Bundle, add some progress info and send it in a Message
handler.handleMessage(msg);
}
}
My problem is that this is using a very large amount of memory. When looking at the memory monitor in Android Studio, it spikes up to ~95MB when downloading/saving each image (~1.7MB). I used the allocation tracker to take a close look, and there is this one line which bothers me:
Can anyone help me figure out why this is happening? As far as I know, this is a "standard" way to download images in Android.
You make intermediate Bitmaps from your files. That takes a lot of memory. Do not use Bitmaps but save the image bytes directly to file. You are probably downloading a jpg file i think.
Just make a loop in which you read chunks from the input stream and write them to the file output stream.
I'm downloading an image from a URL and displaying it in an ImageView. I need to download the image at its full original size. I've tried Glide, Picasso and Universal Image Loader with no success. Is there any library or mehod out there to achieve this? I even tried making my own AsyncTask to do it, something like this:
public class ImageLoader extends AsyncTask<Void, Void, Bitmap> {
#Override
protected Bitmap doInBackground(Void... params) {
try {
URL url = new URL(bundle.getString("selectedImage"));
HttpURLConnection conn =
(HttpURLConnection)url.openConnection();
conn.setReadTimeout(6000);
conn.setConnectTimeout(6000);
conn.setRequestMethod("GET");
conn.setDoInput(true);
conn.connect();
int respose = conn.getResponseCode();
InputStream is = conn.getInputStream();
BufferedInputStream bufferedInputStream = new
BufferedInputStream(is);
bitmap = BitmapFactory.decodeStream(bufferedInputStream);
return bitmap;
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
but no success. Anyone have anything to help me?
1) Try to use Volley library.
https://developer.android.com/training/volley/request.html#request-image
2) Use WebView instead ImageView
I'm not really sure what you mean by "its full original size". I haven't experienced any automagic scaling of images simply by downloading them.
Maybe you could double-check that you have an appropriate android:scaleType on the target ImageView. You can read more on the different values of the scale type property here: http://developer.android.com/reference/android/widget/ImageView.ScaleType.html
If you want to pan the image, like an "unscaled" web page in the browser (typically when the image is bigger than the screen), you might need to add further logic to manage this. Maybe it could be as easy as having a ScrollView wrap your ImageView (which then would wrap its content, of course).
The error was down to photo bucket giving me a scaled down URL instead I used flikr and on my device I get an image almost identical to my original (Picasso limit is 2048x2048) but on other devices I still seem to get a 1080 x 910 image, will investigate further but it seems the answer is not to use photo bucket
I am having trouble loading a bitmap from a url on Android based on this answer: https://stackoverflow.com/a/8993175/1062794
I've simplified the case to the absolute minimum:
public void loadBitmap(View view) {
Bitmap b = getBitmapFromURL("http://upload.wikimedia.org/wikipedia/en/7/70/Example.png");
}
public Bitmap getBitmapFromURL(String src) {
try {
URL url = new URL(src);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setDoInput(true);
connection.connect();
InputStream input = connection.getInputStream();
Bitmap myBitmap = BitmapFactory.decodeStream(input);
return myBitmap;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
I have enabled internet access in manifest (I believe):
<uses-permission android:name="android.permission.INTERNET"/>
When I run the app it crashes with null details when it tries to run connection.connect(). Stepping through I see it tries to throw this error from StrictMode.class:
if ((mPolicyMask & PENALTY_DEATH_ON_NETWORK) != 0) {
throw new NetworkOnMainThreadException();
}
This is my first day trying to make an Android app so I could be making an obvious mistake. I am using the emulator and Win7.
Starting with Android 3.0, synchronous operations can no longer be run directly from a UI thread. If you try to call the loadBitmap(View view) method directly in your onCreate() method, your application will crash when it is run on a device running Android 3.0 and later. Because loadBitmap() method is synchronous - that is, it will not return control until the image is downloaded - calling it directly will freeze the UI of your activity. This is not allowed in Android 3.0 and later; all synchronous code must be wrapped using an AsyncTask class. Using AsyncTask enables you to perform background tasks in a separate thread and then return the result in a UI thread. That way, you can perform background operations without needing to handle complex threading issues. To call the loadBitamp() method asynchronously, you need to wrap the code in a subclass of the AsyncTask class, as shown here:
private class DownloadImage extends AsyncTask<String, Void, Bitmap> {
protected Bitmap doInBackground(String... urls) {
return getBitmapFromUrl(urls[0]);
}
protected void onPostExecute(Bitamp result) {
ImageView img = (ImageView) findViewById(R.id.img);
img.setImageBitmap(result);
}
}
Now in the onCreate() method create a new instance of AsyncTask class and execute it:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
new DownloadImage().execute("http://upload.wikimedia.org/wikipedia/en/7/70/Example.png");
}
this is because you are trying to access the internet from the ui thread (more info here) .
create a new thread (you can use an asyncTask if you wish, but any other thread creation method would suffice) in order to access the internet , and once the bitmap is ready , pass it to the ui thread if you wish to show it
also , for a nice sample of bitmap handling read this :
http://developer.android.com/training/displaying-bitmaps/index.html
Your void will crash when image size big 5 MG. Bitmap are stacking RAM so sample 20 images * 5 = 100 mb. When you re open activity another 100 mb spend your RAM and app will crash. You add follow lines your code;
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Config.RGB_565;
options.inSampleSize = 2;
Bitmap myBitmap = BitmapFactory.decodeStream(input,rect,options);
I would like to load images into a gallery view from a url?
I first make them a bitmap using this.
URL aURL = new URL(myRemoteImages[position]);
URLConnection conn = aURL.openConnection();
conn.setUseCaches(true);
conn.connect();
Object response = conn.getContent();
if (response instanceof Bitmap) {
Bitmap bm = (Bitmap)response;
InputStream is = conn.getInputStream();
/* Buffered is always good for a performance plus. */
BufferedInputStream bis = new BufferedInputStream(is);
/* Decode url-data to a bitmap. */
bm = BitmapFactory.decodeStream(bis);
bis.close();
is.close();
Log.v(imageUrl, "Retrieving image");
/* Apply the Bitmap to the ImageView that will be returned. */
i.setImageBitmap(bm);
How could I go about caching this bitmap? So when the user swipes the screen it doesn't reload over and over?
EDIT: I CALL getImage() to retreive the text url for each url.
i use both of these in a asyncTask. preExecute i call getImage()
and doInBackground i set the gallery to the imageAdapter.
private class MyTask extends AsyncTask<Void, Void, Void>{
#Override
protected Void doInBackground(Void... arg0) {try {
getImages();
Log.v("MyTask", "Image 1 retreived");
getImage2();
Log.v("MyTask", "Image 2 retreived");
getImage3();
Log.v("MyTask", "Image 3 retreived");
getImage4();
Log.v("MyTask", "Image 4 retreived");
} catch (IOException e) {
Log.e("MainMenu retreive image", "Image Retreival failed");
e.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(Void notUsed){
((Gallery) findViewById(R.id.gallery))
.setAdapter(new ImageAdapter(MainMenu.this));
}
}
EDIT: getView() method
public View getView(int position, View convertView, ViewGroup parent) {
ImageView i = new ImageView(this.myContext);
try {
URL aURL = new URL(myRemoteImages[position]);
URLConnection conn = aURL.openConnection();
conn.setUseCaches(true);
conn.connect();
Object response = conn.getContent();
if (response instanceof Bitmap) {
Bitmap bm = (Bitmap)response;
You could store your images on the SDCard and on the launch of your application you need to initialize a component that keeps a HashMap<String,Bitmap> and initialize the map with the contents of a folder from the SDCard.
When you will need an image, you will first check if your HashMap contains the key of that image, let say myMap.contains(myFileName) and if it does you will fetch the image from the map, and if the image is not contained in your map you will need to download it, store id on the SDCard and put in in your map.
I'm not sure if this is the best solution, since if you have a large number of Bitmaps your application can run out of resources. Also I think storing Drawable instead of Bitmap will be less memory consuming.
EDIT:For your problem you need create a custom class that has a member Drawable and execute the URLConnection just when you first create your objects. After that in the getView() method you will just use myObj.getMyDrawable() to access the drawable for that specific object.
Since Android 4 it is possible to cache HTTP responses directly by the HttpUrlConnection. See this article: http://practicaldroid.blogspot.de/2013/01/utilizing-http-response-cache.html
Caching images on android is an level oriented task:
Generally at Caching at two levels:
Runtime Heap memory in a form key-value, where key being a identifier for image and value is object of bitmap.(Refer Here)
Most optimised Way for implementing it is LRUCache:
Which Basically maintains a LinkedList for recently accessed items where dump the items accessed at earliest due to memory limitation.
As this backing pixel data for a bitmap is stored in native memory. It is separate from the bitmap itself, which is stored in the Dalvik heap. The pixel data in native memory is not released in a predictable manner, potentially causing an application to briefly exceed its memory limits and crash.
private LruCache<String, Bitmap> mMemoryCache;
#Override
protected void onCreate(Bundle savedInstanceState) {
// Get max available VM memory, exceeding this amount will throw an
// OutOfMemory exception. Stored in kilobytes as LruCache takes an
// int in its constructor.
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
// Use 1/8th of the available memory for this memory cache.
final int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
#Override
protected int sizeOf(String key, Bitmap bitmap) {
// The cache size will be measured in kilobytes rather than
// number of items.
return bitmap.getByteCount() / 1024;
}
};
}
public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
if (getBitmapFromMemCache(key) == null) {
mMemoryCache.put(key, bitmap);
}
}
public Bitmap getBitmapFromMemCache(String key) {
return mMemoryCache.get(key);
}
Disk Storage: As memory has limited storage with restricted lifecycle.
Memory Cache is good for speeding up in accessing recently viewed images but you can not rely on this for images available on this cache.
UIComponents with indefinite and large data set can easily fill up memory and resulting in loss of images.
Memory cache may get effected in situations like going in background while on call.
Disk Cache can help you to make images persist for longer.
One of it's Optimized way of using it is DiskLruCache
So When you look up for a bitmap in memory cache is result is nil you can try to access it from disk cache and incase you don't find it here too and then just load it from internet.
For Implementation Refer here
I've been looking everywhere to see if there is a standard way of achieving this but I find a different solution every-time.
Basically, I am trying to build a Custom ListView with an image and two-three lines of text besides it.
In order to optimize it, I understand that the following have to be used:
convertView: Basically if the view was already inflated, use it
Lazy-Loading: Do not load the content of the rows until they are called for
Background Downloading & Caching: Download images in their own threads and then update the row (and possible cache them?)
I could manage 1 and 2 but the third one is really confusing me.
Is there a best practice for this?
Thanks
Thanks to Mark for his help. This is one way of doing what he suggested (just in case some else is curious):
private class DownloadImageTask extends AsyncTask<Object, Integer, Bitmap> {
private ImageView iv;
protected Bitmap doInBackground(Object... params) {
try {
iv = (ImageView) params[0];
URL aURL = new URL("http://URLTOIMAGE/img" + params[1] + ".png" );
URLConnection conn = aURL.openConnection();
conn.connect();
InputStream is = conn.getInputStream();
BufferedInputStream bis = new BufferedInputStream(is);
Bitmap bm = BitmapFactory.decodeStream(bis);
bis.close();
is.close();
return bm;
} catch (IOException e) {
return null;
}
}
protected void onPostExecute(Bitmap result) {
iv.setImageBitmap((Bitmap) result);
}
}
And it would be used as follows:
new DownloadImageTask().execute(new Object[] {ImageViewHandle, position});
Note that this is not a working code. This was taken from a larger code base so you will have to make appropriate changes to make this work.
It may be a bit early to describe something as "best practice". AsyncTask or a thread monitoring a LinkedBlockingQueue are fine ways to offload something like image fetching.
You may be interested in my ThumbnailAdapter, which handles the background downloading and caching for you.