Large amount of memory being allocated when downloading images from URL - android

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.

Related

Why is Android file creation suddenly failing when it worked on Android 11 previously?

In the last few days, my Android app is suddenly failing to download files from a web server to store in the app. This is the same for all users I have contacted. It was previously working in Android 11, so it's something that has only just changed. It's a (free) niche app for UK glider pilots to process NOTAMS, and has relatively large number of users who I don't want to let down.
The published app uses getExternalFilesDir(null) to return the directory in which to store the downloaded files, with android:requestLegacyExternalStorage set to "true" in the manifest.
I changed getExternalFilesDir(null) to getFilesDir() in Android Studio since that's what I understand should now be used for internal app data files. This returns /data/user/0/(my package name)/files. I'm running the Pixel 2 API 30 emulator for debugging, and the File Explorer shows that /data/data/(my package name)/files directory has been created. Everything I've read on here says that this is what is supposed to happen and it should all work. However no file was created when I attempted the download.
I changed android:requestLegacyExternalStorage to "false", and this time a file was created as expected. However it was empty and the download thread was giving an exception "unexpected end of stream on com.android.okhttp.Address#89599f3f".
This is the relevant code in my DownloadFile class which runs as a separate thread (comments removed for compactness):
public class DownloadFile implements Runnable
{
private String mUrlString;
private String mFileName;
private CountDownLatch mLatch;
public DownloadFile(String urlString, String fileName, CountDownLatch latch)
{
mUrlString = urlString;
mFileName = fileName;
mLatch = latch;
}
public void run()
{
HttpURLConnection urlConnection = null;
// Note for StackOverflow: following is a public static variable defined in the main activity
Spine.mDownloadStatus = false;
try
{
URL url = new URL(mUrlString);
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setRequestMethod("GET");
urlConnection.setDoOutput(true);
urlConnection.setUseCaches(false);
urlConnection.connect();
File file = new File(Spine.dataDir, mFileName);
FileOutputStream fileOutput = new FileOutputStream(file);
InputStream inputStream = urlConnection.getInputStream();
byte[] buffer = new byte[1024];
int bufferLength = 0; // used to store a temporary size of the buffer
while ((bufferLength = inputStream.read(buffer)) > 0)
{
fileOutput.write(buffer, 0, bufferLength);
}
fileOutput.close();
Spine.mDownloadStatus = true;
}
// catch some possible errors...
catch (IOException e)
{
Spine.mErrorString = e.getMessage();
}
if (urlConnection != null)
urlConnection.disconnect();
// Signal completion
mLatch.countDown();
}
}
I now believe the problem lies with the URL connection, rather than the changes to local file storage access which is what I first thought. Incidentally, if I enter the full URL into my web browser the complete text file is displayed OK, so it's not a problem with the server.
The problem has been narrowed down to changes to the functionality of the website that hosts the data files to be downloaded. It's been made https secure and they are currently working on further changes.
I temporarily moved the hosting to my own website in Android Studio and everything worked so it's down to those website changes and nothing to do with my code (at least it may need changing later to support the upgrade to the main hosting site).
Thanks to all for responding.

Why aren't all local files displaying when using ArrayList?

I'm downloading files with my app via an API (this is working perfectly). Later in my app I'm doing a call to my database to get the file info: id, name, location and return it as a HashMap:
HashMap<Integer, ArrayList<String>> imgTitle;
I take the returned HashMap, extract the ArrayList and use that to populate some buttons. the array list holds the filename and file location. When the page loads the names display correctly but the images don't always load or one loads. If I go back and reenter the activity (via a button press) I'll get different images showing up. I've sent my array to the log and the file location is present and correct for all arrays. Why are only some image showing?
for (Map.Entry<Integer, ArrayList<String>> e : imgTitle.entrySet()) {
...
ArrayList<String> catList = e.getValue();
final String catTitle = catList.get(0);
File indexImage = new File(catList.get(1));
// add images
ImageButton imgButton = new ImageButton(this);
imgButton.setImageURI(Uri.fromFile(indexImage));
...
}
The above is inside a method called on the onCreate. My assumption is maybe the setImageURI is too slow? Any ideas?
Prior to using the setImageURI I used
imgButton.setImageResource(R.drawable.test_vehicle);
as a placeholder it was working fine. However, now I have to use a file that was downloaded and saved locally.
You could try to decode a Bitmap from a stream, like this:
File indexImage = new File(catList.get(1));
InputStream imageStream = new FileInputStream(indexImage);
Bitmap backgroundImage = null;
try {
backgroundImage = BitmapFactory.decodeStream(imageStream);
}
catch(Exception e) {
e.printStackTrace();
}
finally {
try {
//Dispose of the temporary resources
imageStream.close();
imageStream = null //So that the stream is deleted on next GC
}
catch(IOException e) {
e.printStackTrace();
}
}
if (backgroundImage != null) {
ImageButton imgButton = new ImageButton(this);
imgButton.setImageBitmap(backgroundImage);
backgroundImage.recycle();
}
Note: This is pure speculation, I haven't tested any of this. It's simply the first solution that came to my mind

Android load from URL to Bitmap #2

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);

OutOfMemoryError on batch image download

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

android bitmap caching

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

Categories

Resources