I'm a little bit confused about the caching procedure when the images are stored into a custom ArrayAdapter....
As far as I know you can use LruCache only on Activities, so how to cache images in an adapter?
A Cache is just a data structure that keeps track of Objects. You can create your own cache, then save, for example, bitmaps using URLs as keys. For example, take this Object:
public static Map<String, Object> cache = new HashMap<String, Object>();
This is your cache. You can now save images by their urls. For example, say you get a bitmap from http://www.example.com/img.png. A simple method like this will get the cached image if it exists, or get a new one if it does not:
public Bitmap getImage(String url)
{
synchronized(cache) {
Object o = cache.get(url);
if (o != null)
return (Bitmap) o;
//here, get the bitmap from the URL using whatever method you want, then save it and return it:
Bitmap bmp = getBitmapForURL(url);
cache.put(url, bmp);
return bmp;
}
}
So you just call:
myImageView.setImageBitmap(getImage("http://www.example.com/img.png"));
Store only image URIs in adapter not the images.
Related
I'm workin with Volley API in my WebServices, and then I'll write the data in with SQLITE.
Webservices that comes with a JSON with many itens, each of then has data and a image, and I need to keep this image in my cache to display offline in a ListView and later on a detail screen. In the future the user will clean these itens and clear the imagens of them too.
Well, how can I save these images in my local DB and link with each Item that I have from the JSON?
I will be, in 90% of the time, offline. I will only stay online for synchronizing and download the updated server items.
For dealing with images in Android the benchmark is to use Picasso library. It takes care of:
Handling ImageView recycling and download cancelation in an adapter;
Complex image transformations with minimal memory use;
Automatic memory and disk caching.
Besides that, if you're going to display images in lists and need animation to enhance your UI, I strongly recommend changing from ListView to RecyclerView. It is not recommended to store the images in the DB, you would lose time converting this from/to blobs (check here and here). Said that, what I suggest is:
Use Picasso to load the images from the URL provided in the JSON;
Implement a Custom target to handle the image file downloaded by Picasso;
Save the image in a folder inside your app directory;
Use RecyclerView to display the images; (optional)
If you need a project example where those things are done, you can check this. In this project I follow the approache I've described above. You can download the app from the store and see how it will download the images.
Quicky-guide:
To use Picasso add this to you module's gradle file: compile 'com.squareup.picasso:picasso:2.5.2'
Create a class that implements import com.squareup.picasso.Callback;:
public class ImageWarehouse implements Callback {
private static final String TAG = "ImageWarehouse";
private String mDirectory;
private String mFileName;
private ImageView mContainer;
#Inject
App mApplication;
public ImageWarehouse(String fileName, ImageView container, String directory) {
this.mFileName = fileName;
this.mContainer = container;
this.mDirectory = directory;
this.getStorageDir();
}
#Override
public void onSuccess() {
if (this.isExternalStorageWritable()) {
final Bitmap bitmap = ((BitmapDrawable) this.mContainer.getDrawable()).getBitmap();
new AsyncTask<Void, Void, File>() {
#Override
protected File doInBackground(Void... params) {
File file = null;
try {
file = new File(ImageWarehouse.this.getStorageDir().getPath().concat("/").concat(ImageWarehouse.this.mFileName.concat(Constants.MEDIA_EXTENSION)));
file.createNewFile();
FileOutputStream ostream = new FileOutputStream(file);
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, ostream);
ostream.close();
} catch (Exception e) {
Log.e(TAG, "External Storage is not available");
}
return file;
}
}.execute();
} else {
Log.e(TAG, "External Storage is not available");
}
}
#Override
public void onError() {
}
public boolean isExternalStorageWritable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
return true;
}
return false;
}
public File getStorageDir() {
File file = new File(Environment.getExternalStorageDirectory(), Constants.MEDIA_DIRECTORY.concat(this.mDirectory));
if (!file.mkdirs()) {
}
return file;
}
}
Call Picasso in order to display the image in the layout and save it to the specified path:
Picasso
.load(URL)
.fit()
.centerCrop()
.into(viewHolder.cover,
new ImageWarehouse(
name,
viewHolder.cover,
Constants.MEDIA_CHARACTER
)
);
You can user Android-Universal-Image-Loader it is also cache image in memory. it is show from cache when next time same url used in imageloader for get image
see below link for complete source code of Universal Image Loader Example.
Android - Universal Image Loader
You can save your images in Sqlite as blob and can retrieve it when required. Create table as
create table sometable(id integer primary key autoincrement,photo BLOB);
And save the image as
//convert image into byte[]
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Bitmap bitmap = ((BitmapDrawable)getResources().getDrawable(R.drawable.common)).getBitmap();
bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos);
byte[] photo = baos.toByteArray();
//And now store this image
ContentValues initialValues = new ContentValues();
initialValues.put("photo",photo);
return db.insert("sometable", null, initialValues);
And retrieve image as
Cursor cur=your query;
while(cur.moveToNext())
{
byte[] photo=cur.getBlob(index of blob cloumn);
}
Does it store the data in memory or in the disk?
Asked this because I am planning to convert my parse file into an image thumbnail in which i set it to my list items. If it's in memory there would be no need to decode it in a background thread and since the file is in memory it would be garbage collected say when the activity is closed right?
ParseFile imageFile = event.getThumbnailFile();
final ParseImageView imageView = (ParseImageView) convertView.findViewById(R.id.event_thumbnail);
imageView.setPlaceholder(getResources().getDrawable(R.drawable.ic_report_problem_black_48dp));
imageFile.getDataInBackground(new GetDataCallback() {
#Override
public void done(byte[] bytes, ParseException e) {
Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length); // will replace this to convert bigger images into small ones
imageView.setImageBitmap(bitmap);
}
});
It seems that if the getDataInBackground is successful it apparently stores the the ParseFile in a disk cache. Subsequent calls to the method updates the disk cache when the file is updated on the cloud.
I use the "imgUrl" to save the URL where I download the image and "BitmapFactory" to zoom out and not waste RAM, but it takes a lot because there are several pictures, Do you know a better way?.
public class DownloadImage extends AsyncTask<Object, Void, Bitmap> {
ImageView imagen;
String imgUrl="";
Bitmap bitm;
protected Bitmap doInBackground(Object... params){
imgUrl = (String) params[0];
imagen = (ImageView) params[1];
try {
URL imageUrl = new URL(imgUrl); /*Using the URL for download the image*/
HttpURLConnection conn = (HttpURLConnection) imageUrl.openConnection();
conn.connect();
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 3;
bitm = BitmapFactory.decodeStream(conn.getInputStream(), new Rect(0, 0, 0, 0), options);
} catch (IOException e) {
Log.e("catch", e+"");
}
return bitm;
}
protected void onPostExecute(Bitmap result){
imagen.setImageBitmap(result);
super.onPostExecute(result);
}
}
Not sure what do you want to optimize, but there are several ways to optimize the images used in an app which are fetched online.
If images are large and your app will show different sizes of it, you can store different sizes of the same image on the server, and based on the need you can download the size you need. This saves in data transfer, and makes the image loading (e.g. in lists which thumbnails) faster and more smooth.
You can obviously use a cache, usually a two level cache which uses both RAM and disk, to store downloaded image on the disk and not to download it again.
You can use different APIs to analyze the image before loading and load the size you need. This way you load the image faster and use less memory in your view/cache to hold it.
You can read more details about this here
I am downloading images from server into the ListView , now to perform this task i am using ImageDownloader example code. so far its working fine.
But i want to save images of ListView in a SD card but i am confused when to store the images as images are being downloaded Asynchronously and because of ViewHolder pattern its little tough for me to judge.
Once i stored it in a SD card next time i want to read it from memory only.
ImageDownload is storing bitmap in cache and fetching it from there once it gets downloaded.But the problem is its behavior is not predictable.
Sometimes it downloads from server and sometimes from cache.
so can anyone help me in finding what is the proper place to store the images in sd card once.
Modify your ImageDownloader class to save the image like this :
add a parameter to download method like :
download(String url, ImageView imageView, Boolean saveData)
make a global variable saveData in yout ID class :
private Boolean saveData;
and store in it the value given as parameter in download dmethod:
this.saveData = saveData;
and the BitmapDownloaderTask's onPostExecute method should look like this :
#Override
protected void onPostExecute(Bitmap bitmap) {
if (isCancelled()) {
bitmap = null;
}
addBitmapToCache(url, bitmap);
if (saveData == true) {
try {
FileOutputStream out = new FileOutputStream(path);
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, out);
} catch (Exception e) {
e.printStackTrace();
}
}
if (imageViewReference != null) {
ImageView imageView = imageViewReference.get();
BitmapDownloaderTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView);
// Change bitmap only if this process is still associated with it
if (this == bitmapDownloaderTask) {
imageView.setImageBitmap(bitmap);
}
}
}
where path is the path were you want to save the image .
and next time before you want to load the image you have to see if it is already downloaded and load it from the path otherwise call ImageDownloader.
that's it! enjoy!
I m using Fedor's lazy Loader Adapter for showing images on a list view.
I want to pass the image(already downloaded) of the row which is clicked to new activity. I dont want to re download the image again.
How can this be done ?
Ref : Lazy load of images in ListView
Fedor's Code (from what i've read) uses a File Cache aswell as a Memory Cache to Cache the downloaded files...
So Simply use
FileCache fileCache = new FileCache(context);
File bmp = fileCache.getFile(url);
//convert the File object to a Bitmap object using BitmapFactory (see decodeFile() method in ImageLoader)
and
MemoryCache memCache = new MemoryCache(context);
Bitmap bmp = memCache.get(url)
Note : If both return null, then you'll have to download it again.