Lazy loading GridView with images downloading from internet - android

I've been visiting Stack Overflow for many years and it is the first time that I can't find any post that can solve my problem (at least I didn't see any).
I have a GridView with a custom adapter, which I have overridden to return a custom view made by an ImageView and a TextView.
I load the images after JSON parsing them from URLs with an AsyncTask, storing all the info into an ArrayList in the doInBackground() method and calling notifyDataSetChanged() in the onPostExecute() method. Everything's fine.
Now my problem is that when I launch the activity it takes a time of 5-10 seconds before the grid view will create and present itself to the user in entity. I'm wondering if there is a way to show the grid view with the text info first and then each image will load. Is this possible or not because they are both created in the same method?
#Override
public View getView(int arg0, View arg1, ViewGroup arg2) {
View v = null;
if (arg1 == null) {
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
v = inflater.inflate(R.layout.custom_product_view, null);
} else {
v = arg1;
}
iv = (ImageView) v.findViewById(R.id.product_image);
imageLoader.DisplayImage(products.get(arg0).getImage(), iv);
TextView tv = (TextView) v.findViewById(R.id.product_price);
tv.setText(products.get(arg0).getPrice());
return v;
}
I also must inform you as you can see from the DisplayImage() method that I have implemented this lazy loading: Lazy load of images in ListView. It works fine but the thing is that it loads the whole view again. What I want to do is launch the activity, load caption first and then the image will load when it finishes downloading. With this code here, it just lazy loads the whole View that every cell of the grid view contains. I earned some seconds because I don't download all the images at once like before but still it's not what I'm searching for.
Thanks a lot.

Follow this approach.
First, create a custom WebImageView class as follows.
public class WebImageView extends ImageView {
private Drawable placeholder, image;
public WebImageView(Context context) {
super(context);
}
public WebImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public WebImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setPlaceholderImage(Drawable drawable) {
placeholder = drawable;
if (image == null) {
setImageDrawable(placeholder);
}
}
public void setPlaceholderImage(int resid) {
placeholder = getResources().getDrawable(resid);
if (image == null) {
setImageDrawable(placeholder);
}
}
public void setImageUrl(String url) {
DownloadTask task = new DownloadTask();
task.execute(url);
}
private class DownloadTask extends AsyncTask<String, Void, Bitmap> {
#Override
protected Bitmap doInBackground(String... params) {
String url = params[0];
try {
URLConnection conn = (new URL(url)).openConnection();
InputStream is = conn.getInputStream();
BufferedInputStream bis = new BufferedInputStream(is);
ByteArrayBuffer baf = new ByteArrayBuffer(50);
int current = 0;
while ((current=bis.read()) != -1) {
baf.append((byte)current);
}
byte[] imageData = baf.toByteArray();
return BitmapFactory.decodeByteArray(imageData, 0, imageData.length);
} catch (Exception e) {
return null;
}
}
#Override
protected void onPostExecute(Bitmap result) {
image = new BitmapDrawable(result);
if (image != null) {
setImageDrawable(image);
}
}
}
}
Next, in Activity use the above custom ImageView as follows:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
WebImageView imageView = (WebImageView) findViewById(R.id.webimage);
imageView.setPlaceholderImage(R.drawable.ic_launcher);
imageView.setImageUrl("http://www.google.co.in/images/srpr/logo3w.png");
}
In brief, you are setting a placeholder image for the ImageView which gets replaced by the actual image when download completes. So the GridView will render immediately without delay.
Implementation Details:
So in your custom view (with an image + text) instead of using a simple ImageView, use WebImageView as shown above. When you get the JSON response set the TextView with the caption and the WebImageView with the image url.
So the caption will display immediately and the Image will load lazily.

I have used the below class to implement the Lazy loading of the images it works awesome for me . You try it also.
ImageLoader
/**
* This is class for display image in lazy-loading way.
*/
public class ImageLoader
{
private static final String TAG = ImageLoader.class.getSimpleName();
private InputStream m_is = null;
private OutputStream m_os = null;
private Bitmap m_bitmap = null;
private String m_imagePath;
private File m_cacheDir;
private WeakHashMap<String, Bitmap> m_cache = new WeakHashMap<String, Bitmap>();
/**
* Makes the background thread low priority. This way it will not affect the
* UI performance.<br>
* Checks the Device SD card exits or not and assign path according this
* condition.
*
* #param p_context
* activity context
*/
public ImageLoader(Context p_context)
{
/**
* Make the background thread low priority. This way it will not affect
* the UI performance
*/
m_imageLoaderThread.setPriority(Thread.NORM_PRIORITY - 1);
/**
* Check the Device SD card exits or not and assign path according this
* condition.
*/
if (android.os.Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED))
{
m_imagePath = Environment.getExternalStorageDirectory() + "/Android/data/" + p_context.getPackageName();
m_cacheDir = new File(m_imagePath);
}
else
{
m_cacheDir = new File(p_context.getDir("Cache", Context.MODE_PRIVATE), "Cache");
}
if (!m_cacheDir.exists())
m_cacheDir.mkdirs();
}
/**
* Check Image exits on HashMap or not.If exist then set image to ImageView
* else send request in the queue.
*
* #param p_url
* image Url
* #param p_imageView
* image container
* #param p_prgBar
* progressbar that is displayed till image is not download from
* server.
*/
public void DisplayImage(String p_url, ImageView p_imageView, ProgressBar p_prgBar) throws CustomException
{
if (m_cache.containsKey(p_url))
{
p_prgBar.setVisibility(View.GONE);
p_imageView.setVisibility(View.VISIBLE);
p_imageView.setImageBitmap(m_cache.get(p_url));
}
else
{
queueImage(p_url, p_imageView, p_prgBar);
}
}
/**
* Clear old task from the queue and add new image downloading in the queue.
*
* #param p_url
* image Url
* #param p_imageView
* image container
* #param p_prgBar
* progressbar that is displayed till image is not download from
* server.
*/
private void queueImage(String p_url, ImageView p_imageView, ProgressBar p_prgBar) throws CustomException
{
try
{
m_imagesQueue.Clean(p_imageView);
ImageToLoad m_photoObj = new ImageToLoad(p_url, p_imageView, p_prgBar);
synchronized (m_imagesQueue.m_imagesToLoad)
{
m_imagesQueue.m_imagesToLoad.push(m_photoObj);
m_imagesQueue.m_imagesToLoad.notifyAll();
}
/**
* start thread if it's not started yet
*/
if (m_imageLoaderThread.getState() == Thread.State.NEW)
m_imageLoaderThread.start();
}
catch (CustomException c)
{
throw c;
}
catch (Throwable t)
{
CustomLogHandler.printErrorlog(t);
throw new CustomException(TAG + " Error in queueImage(String p_url, ImageView p_imageView, ProgressBar p_prgBar) of ImageLoader", t);
}
}
/**
* Checks in SD card for cached file.If bitmap is not available then will
* download it from Url.
*
* #param p_url
* imgae Url
* #return bitmap from Cache or from server.
*/
private Bitmap getBitmap(String p_url) throws CustomException
{
System.gc();
String m_fileName = String.valueOf(p_url.hashCode());
File m_file = new File(m_cacheDir, m_fileName);
// from SD cache
m_bitmap = decodeFile(m_file);
if (m_bitmap != null)
return m_bitmap;
// from web
try
{
Bitmap m_bitmap = null;
int m_connectionCode = 0;
m_connectionCode = HttpConnection.getHttpUrlConnection(p_url).getResponseCode();
if (m_connectionCode == HttpURLConnection.HTTP_OK)
{
m_is = new URL(p_url).openStream();
m_os = new FileOutputStream(m_file);
FileIO.copyStream(m_is, m_os);
m_os.close();
m_os = null;
m_bitmap = decodeFile(m_file);
m_is.close();
m_is = null;
HttpConnection.getHttpUrlConnection(p_url).disconnect();
}
return m_bitmap;
}
catch (CustomException c)
{
throw c;
}
catch (Throwable t)
{
CustomLogHandler.printErrorlog(t);
throw new CustomException(TAG + " Error in getBitmap(String p_url) of ImageLoader", t);
}
}
/**
* Decodes the Image file to bitmap.
*
* #param p_file
* Image file object
* #return decoded bitmap
*/
private Bitmap decodeFile(File p_file) throws CustomException
{
try
{
// decode image size
Bitmap m_retBmp = null;
System.gc();
int m_scale = 1;
if (p_file.length() > 400000)
{
m_scale = 4;
}
else if (p_file.length() > 100000 && p_file.length() < 400000)
{
m_scale = 3;
}
// decode with inSampleSize
if (p_file.exists())
{
BitmapFactory.Options m_o2 = new BitmapFactory.Options();
m_o2.inSampleSize = m_scale;
m_retBmp = BitmapFactory.decodeFile(p_file.getPath(), m_o2);
}
return m_retBmp;
}
catch (Throwable t)
{
CustomLogHandler.printErrorlog(t);
throw new CustomException(TAG + " Error in decodeFile(File p_file) of ImageLoader", t);
}
}
/**
* Stores image information
*/
private class ImageToLoad
{
public String m_url;
public ImageView m_imageView;
public ProgressBar m_prgBar;
public ImageToLoad(String p_str, ImageView p_img, ProgressBar p_prgBar)
{
m_url = p_str;
m_imageView = p_img;
m_imageView.setTag(p_str);
m_prgBar = p_prgBar;
}
}
ImagesQueue m_imagesQueue = new ImagesQueue();
/**
* This is method to stop current running thread.
*/
public void stopThread()
{
m_imageLoaderThread.interrupt();
}
/**
* Stores list of image to be downloaded in stack.
*/
class ImagesQueue
{
private Stack<ImageToLoad> m_imagesToLoad = new Stack<ImageToLoad>();
/**
* Removes all instances of this ImageView
*
* #param p_ivImage
* imageView
*/
public void Clean(ImageView p_ivImage) throws CustomException
{
try
{
for (int m_i = 0; m_i < m_imagesToLoad.size();)
{
if (m_imagesToLoad.get(m_i).m_imageView == p_ivImage)
m_imagesToLoad.remove(m_i);
else
m_i++;
}
}
catch (Throwable t)
{
CustomLogHandler.printErrorlog(t);
throw new CustomException(TAG + " Error in Clean(ImageView p_image) of ImageLoader", t);
}
}
}
/**
*
* This is class waits until there are any images to load in the queue.
*/
class ImagesLoader extends Thread
{
public void run()
{
try
{
while (true)
{
if (m_imagesQueue.m_imagesToLoad.size() == 0)
synchronized (m_imagesQueue.m_imagesToLoad)
{
m_imagesQueue.m_imagesToLoad.wait();
}
if (m_imagesQueue.m_imagesToLoad.size() != 0)
{
ImageToLoad m_imageToLoadObj;
synchronized (m_imagesQueue.m_imagesToLoad)
{
m_imageToLoadObj = m_imagesQueue.m_imagesToLoad.pop();
}
Bitmap m_bmp = getBitmap(m_imageToLoadObj.m_url);
m_cache.put(m_imageToLoadObj.m_url, m_bmp);
if (((String) m_imageToLoadObj.m_imageView.getTag()).equals(m_imageToLoadObj.m_url))
{
BitmapDisplayer m_bmpdisplayer = new BitmapDisplayer(m_bmp, m_imageToLoadObj.m_imageView, m_imageToLoadObj.m_prgBar);
Activity m_activity = (Activity) m_imageToLoadObj.m_imageView.getContext();
m_activity.runOnUiThread(m_bmpdisplayer);
}
}
if (Thread.interrupted())
break;
}
}
catch (InterruptedException e)
{
/*
* allow thread to exit
*/
}
catch (Throwable t)
{
CustomLogHandler.printErrorlog(t);
}
}
}
ImagesLoader m_imageLoaderThread = new ImagesLoader();
/**
* This class Used to display bitmap in the UI thread
*/
class BitmapDisplayer implements Runnable
{
Bitmap m_bmp;
ImageView m_imageView;
ProgressBar m_prgBar;
public BitmapDisplayer(Bitmap p_bmp, ImageView p_imgview, ProgressBar p_prgBar)
{
m_bmp = p_bmp;
m_imageView = p_imgview;
m_prgBar = p_prgBar;
}
public void run()
{
if (m_bmp != null)
{
m_imageView.setImageBitmap(m_bmp);
m_prgBar.setVisibility(View.GONE);
m_imageView.setVisibility(View.VISIBLE);
}
}
}
}
Use the above class as below:
First you need to put the ProgressBar in your custom layout where you have your ImageView as below:
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/RelativeImagelayout">
<ProgressBar android:id="#+id/Progress"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_marginTop="10dp"/>
<ImageView
android:id="#+id/ivImage"
android:layout_width="80dp"
android:layout_height="90dp"
android:layout_marginTop="10dp"
android:clickable="false"/>
</RelativeLayout>
In your adapter class create the instance of the ImageLoader class and use it as below in your getView method:
ImageView m_ibImage = (ImageView) v.findViewById(R.id.ivImage);
ProgressBar m_pbProgress = (ProgressBar) v.findViewById(R.id.Progress);
if (products.get(arg0).getImage().toString().equals(null)
|| products.get(arg0).getImage().toString().equals(""))
{
m_pbProgress.setVisibility(View.INVISIBLE);
m_ibImage.setVisibility(View.VISIBLE);
}
else if (!products.get(arg0).getImage().toString().equals(null))
{
m_imgLoader.DisplayImage(products.get(arg0).getImage(), m_ibImage,
m_pbProgress);
}
I hope it will help you.
Thanks

The answer you mentioned of is not good, in my opinion. For example if you have 50 images, when the user scrolls up/ down the entire list, that sample project will spawn 50 threads. That is bad for mobile devices like a cell phone. A side note, his concept "lazy list" is different to the one that Android SDK defines. For a sample code of lazy loading list view, have a look at:
[Android SDK]/samples/android-x/ApiDemos/src/com/example/android/apis/view/List13.java
where x is API level. You can test the compiled app in any emulators, open the app API Demos > Views > Lists > 13. Slow Adapter.
About your current approach. You shouldn't use AsyncTask to download images. The documentation says:
AsyncTasks should ideally be used for short operations (a few seconds at the most.)
You should instead:
Use a service to download images in background. Note that services run on main UI thread, so to avoid of NetworkOnMainThreadException, you need something like Thread in your service.
Use a content provider to manage the downloaded images on SD card. For instance you keep the map of original URLs to corresponding files downloaded.
Along with the content provider, use a CursorAdapter for your grid view, and loaders for your activity/ fragment which hosts the grid view.
Basically, in the first time the user opens your activity, you create new adapter and set it to the grid view. So it has a connection with the content provider. Then you start the service to check and download the images. For every image downloaded, you insert it into the content provider. The provider notifies any observers about changes ― your activity/ fragment (the loader) receives the notification and updates UI.

Related

Downloading mutliple images in an AsyncTask thread cause error

I'm downloading multiple images through AsyncTask. It is working well when there are 2-3 images; but, when there are more images, many AsyncTask instances are created, and are causing errors. Do anyone have an idea how to overcome this problem?
Can you add your code here?
Maybe you can try to use onPostExecute function of your Asynctask to start a new one like:
protected void onPostExecute() {
doInBackground();
}
or you can trigger an activity function by your onPostExecute() function to start download " next picture " so it will only create one asynctask for one time.
From Android Doc: Common view components such as ListView and GridView
introduce another issue when used in conjunction with the AsyncTask as
demonstrated in the previous section. In order to be efficient with
memory, these components recycle child views as the user scrolls. If
each child view triggers an AsyncTask, there is no guarantee that when
it completes, the associated view has not already been recycled for
use in another child view. Furthermore, there is no guarantee that the
order in which asynchronous tasks are started is the order that they
complete.
Handle Concurrency
http://android-developers.blogspot.in/2010/07/multithreading-for-performance.html
public class ImageDownloader {
private static final String LOG_TAG = "ImageDownloader";
public enum Mode { NO_ASYNC_TASK, NO_DOWNLOADED_DRAWABLE, CORRECT }
private Mode mode = Mode.NO_ASYNC_TASK;
/**
* Download the specified image from the Internet and binds it to the provided ImageView. The
* binding is immediate if the image is found in the cache and will be done asynchronously
* otherwise. A null bitmap will be associated to the ImageView if an error occurs.
*
* #param url The URL of the image to download.
* #param imageView The ImageView to bind the downloaded image to.
*/
public void download(String url, ImageView imageView) {
resetPurgeTimer();
Bitmap bitmap = getBitmapFromCache(url);
if (bitmap == null) {
forceDownload(url, imageView);
} else {
cancelPotentialDownload(url, imageView);
imageView.setImageBitmap(bitmap);
}
}
/*
* Same as download but the image is always downloaded and the cache is not used.
* Kept private at the moment as its interest is not clear.
private void forceDownload(String url, ImageView view) {
forceDownload(url, view, null);
}
*/
/**
* Same as download but the image is always downloaded and the cache is not used.
* Kept private at the moment as its interest is not clear.
*/
private void forceDownload(String url, ImageView imageView) {
// State sanity: url is guaranteed to never be null in DownloadedDrawable and cache keys.
if (url == null) {
imageView.setImageDrawable(null);
return;
}
if (cancelPotentialDownload(url, imageView)) {
switch (mode) {
case NO_ASYNC_TASK:
Bitmap bitmap = downloadBitmap(url);
addBitmapToCache(url, bitmap);
imageView.setImageBitmap(bitmap);
break;
case NO_DOWNLOADED_DRAWABLE:
imageView.setMinimumHeight(156);
BitmapDownloaderTask task = new BitmapDownloaderTask(imageView);
task.execute(url);
break;
case CORRECT:
task = new BitmapDownloaderTask(imageView);
DownloadedDrawable downloadedDrawable = new DownloadedDrawable(task);
imageView.setImageDrawable(downloadedDrawable);
imageView.setMinimumHeight(156);
task.execute(url);
break;
}
}
}
/**
* Returns true if the current download has been canceled or if there was no download in
* progress on this image view.
* Returns false if the download in progress deals with the same url. The download is not
* stopped in that case.
*/
private static boolean cancelPotentialDownload(String url, ImageView imageView) {
BitmapDownloaderTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView);
if (bitmapDownloaderTask != null) {
String bitmapUrl = bitmapDownloaderTask.url;
if ((bitmapUrl == null) || (!bitmapUrl.equals(url))) {
bitmapDownloaderTask.cancel(true);
} else {
// The same URL is already being downloaded.
return false;
}
}
return true;
}
/**
* #param imageView Any imageView
* #return Retrieve the currently active download task (if any) associated with this imageView.
* null if there is no such task.
*/
private static BitmapDownloaderTask getBitmapDownloaderTask(ImageView imageView) {
if (imageView != null) {
Drawable drawable = imageView.getDrawable();
if (drawable instanceof DownloadedDrawable) {
DownloadedDrawable downloadedDrawable = (DownloadedDrawable)drawable;
return downloadedDrawable.getBitmapDownloaderTask();
}
}
return null;
}
Bitmap downloadBitmap(String url) {
final int IO_BUFFER_SIZE = 4 * 1024;
// AndroidHttpClient is not allowed to be used from the main thread
final HttpClient client = (mode == Mode.NO_ASYNC_TASK) ? new DefaultHttpClient() :
AndroidHttpClient.newInstance("Android");
final HttpGet getRequest = new HttpGet(url);
try {
HttpResponse response = client.execute(getRequest);
final int statusCode = response.getStatusLine().getStatusCode();
if (statusCode != HttpStatus.SC_OK) {
Log.w("ImageDownloader", "Error " + statusCode +
" while retrieving bitmap from " + url);
return null;
}
final HttpEntity entity = response.getEntity();
if (entity != null) {
InputStream inputStream = null;
try {
inputStream = entity.getContent();
// return BitmapFactory.decodeStream(inputStream);
// Bug on slow connections, fixed in future release.
return BitmapFactory.decodeStream(new FlushedInputStream(inputStream));
} finally {
if (inputStream != null) {
inputStream.close();
}
entity.consumeContent();
}
}
} catch (IOException e) {
getRequest.abort();
Log.w(LOG_TAG, "I/O error while retrieving bitmap from " + url, e);
} catch (IllegalStateException e) {
getRequest.abort();
Log.w(LOG_TAG, "Incorrect URL: " + url);
} catch (Exception e) {
getRequest.abort();
Log.w(LOG_TAG, "Error while retrieving bitmap from " + url, e);
} finally {
if ((client instanceof AndroidHttpClient)) {
((AndroidHttpClient) client).close();
}
}
return null;
}
/*
* An InputStream that skips the exact number of bytes provided, unless it reaches EOF.
*/
static class FlushedInputStream extends FilterInputStream {
public FlushedInputStream(InputStream inputStream) {
super(inputStream);
}
#Override
public long skip(long n) throws IOException {
long totalBytesSkipped = 0L;
while (totalBytesSkipped < n) {
long bytesSkipped = in.skip(n - totalBytesSkipped);
if (bytesSkipped == 0L) {
int b = read();
if (b < 0) {
break; // we reached EOF
} else {
bytesSkipped = 1; // we read one byte
}
}
totalBytesSkipped += bytesSkipped;
}
return totalBytesSkipped;
}
}
/**
* The actual AsyncTask that will asynchronously download the image.
*/
class BitmapDownloaderTask extends AsyncTask<String, Void, Bitmap> {
private String url;
private final WeakReference<ImageView> imageViewReference;
public BitmapDownloaderTask(ImageView imageView) {
imageViewReference = new WeakReference<ImageView>(imageView);
}
/**
* Actual download method.
*/
#Override
protected Bitmap doInBackground(String... params) {
url = params[0];
return downloadBitmap(url);
}
/**
* Once the image is downloaded, associates it to the imageView
*/
#Override
protected void onPostExecute(Bitmap bitmap) {
if (isCancelled()) {
bitmap = null;
}
addBitmapToCache(url, bitmap);
if (imageViewReference != null) {
ImageView imageView = imageViewReference.get();
BitmapDownloaderTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView);
// Change bitmap only if this process is still associated with it
// Or if we don't use any bitmap to task association (NO_DOWNLOADED_DRAWABLE mode)
if ((this == bitmapDownloaderTask) || (mode != Mode.CORRECT)) {
imageView.setImageBitmap(bitmap);
}
}
}
}
/**
* A fake Drawable that will be attached to the imageView while the download is in progress.
*
* <p>Contains a reference to the actual download task, so that a download task can be stopped
* if a new binding is required, and makes sure that only the last started download process can
* bind its result, independently of the download finish order.</p>
*/
static class DownloadedDrawable extends ColorDrawable {
private final WeakReference<BitmapDownloaderTask> bitmapDownloaderTaskReference;
public DownloadedDrawable(BitmapDownloaderTask bitmapDownloaderTask) {
super(Color.BLACK);
bitmapDownloaderTaskReference =
new WeakReference<BitmapDownloaderTask>(bitmapDownloaderTask);
}
public BitmapDownloaderTask getBitmapDownloaderTask() {
return bitmapDownloaderTaskReference.get();
}
}
public void setMode(Mode mode) {
this.mode = mode;
clearCache();
}
/*
* Cache-related fields and methods.
*
* We use a hard and a soft cache. A soft reference cache is too aggressively cleared by the
* Garbage Collector.
*/
private static final int HARD_CACHE_CAPACITY = 10;
private static final int DELAY_BEFORE_PURGE = 10 * 1000; // in milliseconds
// Hard cache, with a fixed maximum capacity and a life duration
private final HashMap<String, Bitmap> sHardBitmapCache =
new LinkedHashMap<String, Bitmap>(HARD_CACHE_CAPACITY / 2, 0.75f, true) {
#Override
protected boolean removeEldestEntry(LinkedHashMap.Entry<String, Bitmap> eldest) {
if (size() > HARD_CACHE_CAPACITY) {
// Entries push-out of hard reference cache are transferred to soft reference cache
sSoftBitmapCache.put(eldest.getKey(), new SoftReference<Bitmap>(eldest.getValue()));
return true;
} else
return false;
}
};
// Soft cache for bitmaps kicked out of hard cache
private final static ConcurrentHashMap<String, SoftReference<Bitmap>> sSoftBitmapCache =
new ConcurrentHashMap<String, SoftReference<Bitmap>>(HARD_CACHE_CAPACITY / 2);
private final Handler purgeHandler = new Handler();
private final Runnable purger = new Runnable() {
public void run() {
clearCache();
}
};
/**
* Adds this bitmap to the cache.
* #param bitmap The newly downloaded bitmap.
*/
private void addBitmapToCache(String url, Bitmap bitmap) {
if (bitmap != null) {
synchronized (sHardBitmapCache) {
sHardBitmapCache.put(url, bitmap);
}
}
}
/**
* #param url The URL of the image that will be retrieved from the cache.
* #return The cached bitmap or null if it was not found.
*/
private Bitmap getBitmapFromCache(String url) {
// First try the hard reference cache
synchronized (sHardBitmapCache) {
final Bitmap bitmap = sHardBitmapCache.get(url);
if (bitmap != null) {
// Bitmap found in hard cache
// Move element to first position, so that it is removed last
sHardBitmapCache.remove(url);
sHardBitmapCache.put(url, bitmap);
return bitmap;
}
}
// Then try the soft reference cache
SoftReference<Bitmap> bitmapReference = sSoftBitmapCache.get(url);
if (bitmapReference != null) {
final Bitmap bitmap = bitmapReference.get();
if (bitmap != null) {
// Bitmap found in soft cache
return bitmap;
} else {
// Soft reference has been Garbage Collected
sSoftBitmapCache.remove(url);
}
}
return null;
}
/**
* Clears the image cache used internally to improve performance. Note that for memory
* efficiency reasons, the cache will automatically be cleared after a certain inactivity delay.
*/
public void clearCache() {
sHardBitmapCache.clear();
sSoftBitmapCache.clear();
}
/**
* Allow a new delay before the automatic cache clear is done.
*/
private void resetPurgeTimer() {
purgeHandler.removeCallbacks(purger);
purgeHandler.postDelayed(purger, DELAY_BEFORE_PURGE);
}
}
you can use Vinci android Library its testet and support Concurrency and its build on top
Builder Pattern
Singleton Pattern
Callback Pattern
and its handle downloading multi Images by her self and its easy to use .
if you wanna get a result of every download request :
Vinci
.base(context)
.process()
.load(uri,
new Request() {
#Override
public void onSuccess(Bitmap bitmap) {
viewHolder.Writer.setImageBitmap(bitmap);
//or
//do some thing with bitmap
}
#Override
public void onFailure(Throwable e) {
Log.e(e.getClass().getSimpleName(), e.getMessage());
}
});
if you wanna show image in ImageView you can use it like this
Vinci
.base(context)
.process()
.load(uri)
.view(imageView);
for use it in RecycleView see this wiki page
Simply use the Picasso library, to load the images into the ImageViews, as this is very simple to use and also supports concurrency very easily.
Example:
Picasso.with(this.context).load(url_of_image).into(imageView);

Listview items with Animation do not render properly

have a gridView populated by bitmaps that animate in when the bitmap loads asynchronously.
Sometimes when flinging the gridView some of the items do not render properly. The animation will trigger but the bitmap will simply not show up.
I have confirmed that the bitmap is indeed there (at least the data) but it just doesn't render.
This happens when flinging the gridView quickly but also happens on a slow scroll as well.
It seems that the view recycling is not working properly.
Here is my code:
ListView adapter:
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
if(convertView == null){
convertView = new FlipAnimatedCacheableImage(mContext);
}
final ImageInfo info = mapItem(getItem(position));
FlipAnimatedCacheableImage image = (FlipAnimatedCacheableImage)convertView;
image.resetState();
image.setTitle(info.title);
image.setSubTitle(info.subTitle);
image.loadImage(info.imgURL, false);
return convertView;
}
Code for FlipAnimatedCacheableImage:
public class FlipAnimatedCacheableImage extends FrameLayout
{
private static final String TAG = FlipAnimatedCacheableImage.class.getCanonicalName();
protected static final long DURATION = 300;
private ImageView mPlaceHolder;
private NetworkedCacheableImageView mCacheableImage;
private View mTextContainer;
private TextView mTitleTv;
private TextView mSubTitleTv;
private View mProgress;
private ImageLoadListener mListener = new ImageLoadListener()
{
private boolean isShown;
#Override
public void onImageLoaded(boolean animate)
{
if(animate){
mProgress.setVisibility(View.GONE);
mPlaceHolder.setVisibility(View.VISIBLE);
mPlaceHolder.setRotationY(0);
mCacheableImage.setVisibility(View.VISIBLE);
mCacheableImage.setRotationY(-90);
mPlaceHolder.animate().rotationY(90).setDuration(DURATION).start();
mCacheableImage.animate().rotationY(0).setDuration(DURATION).setStartDelay(DURATION).start();
if(!TextUtils.isEmpty(mTitleTv.getText()) || !TextUtils.isEmpty(mSubTitleTv.getText())){
mTextContainer.setVisibility(View.VISIBLE);
mTextContainer.setAlpha(0);
mTextContainer.animate().alpha(1).setDuration(DURATION).setStartDelay(DURATION * 2).start();
}
else{
mTextContainer.setVisibility(View.GONE);
}
isShown = true;
FlipAnimatedCacheableImage.this.invalidate();
}
else{
mPlaceHolder.setVisibility(View.GONE);
mProgress.setVisibility(View.GONE);
mCacheableImage.setVisibility(View.VISIBLE);
mCacheableImage.setRotationY(0);
mCacheableImage.clearAnimation();
if(!TextUtils.isEmpty(mTitleTv.getText()) || !TextUtils.isEmpty(mSubTitleTv.getText())){
mTextContainer.setVisibility(View.VISIBLE);
mTextContainer.setAlpha(1);
}
else{
mTextContainer.setVisibility(View.GONE);
}
FlipAnimatedCacheableImage.this.invalidate();
}
}
};
public FlipAnimatedCacheableImage(Context context, boolean isLarge)
{
this(context, null, 0, isLarge);
}
public FlipAnimatedCacheableImage(Context context)
{
this(context, null);
}
public FlipAnimatedCacheableImage(Context context, AttributeSet attrs)
{
this(context, attrs, 0, false);
}
public FlipAnimatedCacheableImage(Context context, AttributeSet attrs, int defStyle, boolean isLarge)
{
super(context, attrs, defStyle);
LayoutInflater inflater = LayoutInflater.from(context);
inflater.inflate(R.layout.item_image_thumbnail, this);
mCacheableImage = (NetworkedCacheableImageView)this.findViewById(R.id.image_view);
mPlaceHolder = (ImageView)this.findViewById(R.id.place_holder);
mProgress = this.findViewById(R.id.progressBar);
// only used by small images
mTextContainer = this.findViewById(R.id.text_container);
mTitleTv = (TextView)this.findViewById(R.id.text_title);
mSubTitleTv = (TextView)this.findViewById(R.id.text_sub_title);
// listener to animate after loading
mCacheableImage.setLoadListener(mListener);
// set default state
mTextContainer.setVisibility(View.GONE);
mTitleTv.setVisibility(GONE);
mSubTitleTv.setVisibility(GONE);
if(isLarge){
// adjust the size to the correct dimensions, ignore titleAnd subTitle
FrameLayout.LayoutParams imageParams = new FrameLayout.LayoutParams((int)context.getResources()
.getDimension(R.dimen.grid_image_width_large), (int)context.getResources().getDimension(
R.dimen.grid_image_height_large));
findViewById(R.id.content_wrapper).setLayoutParams(imageParams);
}
// this.setOnClickListener(listener);
resetState();
}
public void resetState()
{
mCacheableImage.setVisibility(View.VISIBLE);
mCacheableImage.setRotationY(0);
mProgress.setVisibility(View.VISIBLE);
mPlaceHolder.setVisibility(View.VISIBLE);
mTextContainer.setVisibility(View.GONE);
mTitleTv.setVisibility(GONE);
mSubTitleTv.setVisibility(GONE);
}
public boolean loadImage(String url, boolean fullSize)
{
return mCacheableImage.loadImage(url, fullSize);
}
public void setTitle(String title)
{
mTitleTv.setText(title);
if(!TextUtils.isEmpty(title)){
mTitleTv.setVisibility(View.VISIBLE);
}
else{
mTitleTv.setVisibility(View.GONE);
}
}
public void setSubTitle(String subTitle)
{
mSubTitleTv.setText(subTitle);
if(!TextUtils.isEmpty(subTitle)){
mSubTitleTv.setVisibility(View.VISIBLE);
}
else{
mSubTitleTv.setVisibility(View.GONE);
}
}
}
FlipAnimatedCache will request the image from a cache written by Chris Banes here https://github.com/chrisbanes/Android-BitmapCache
Here is that code:
/*******************************************************************************
* Copyright 2011, 2013 Chris Banes.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/
/**
* Simple extension of CacheableImageView which allows downloading of Images of
* the Internet.
*
* This code isn't production quality, but works well enough for this sample.s
*
* #author Chris Banes
*
*/
public class NetworkedCacheableImageView extends CacheableImageView
{
private static final String TAG = NetworkedCacheableImageView.class.getCanonicalName();
public interface ImageLoadListener
{
public void onImageLoaded(boolean animate);
}
/**
* This task simply fetches an Bitmap from the specified URL and wraps it in
* a wrapper. This implementation is NOT 'best practice' or production ready
* code.
*/
private static class ImageUrlAsyncTask extends AsyncTask<String, Void, CacheableBitmapDrawable>
{
private final BitmapLruCache mCache;
private final WeakReference<ImageView> mImageViewRef;
private final WeakReference<NetworkedCacheableImageView> viewRef;
private final BitmapFactory.Options mDecodeOpts;
private final ImageLoadListener mLoadListener;
private boolean outOfMemoryFailure;
private String mURL;
ImageUrlAsyncTask(ImageView imageView, BitmapLruCache cache, BitmapFactory.Options decodeOpts,
ImageLoadListener listener, NetworkedCacheableImageView view)
{
mCache = cache;
mLoadListener = listener;
mImageViewRef = new WeakReference<ImageView>(imageView);
viewRef = new WeakReference<NetworkedCacheableImageView>(view);
mDecodeOpts = decodeOpts;
}
#Override
protected CacheableBitmapDrawable doInBackground(String... params)
{
try{
// Return early if the ImageView has disappeared.
if(null == mImageViewRef.get()){
return null;
}
mURL = params[0];
// Now we're not on the main thread we can check all caches
CacheableBitmapDrawable result = mCache.get(mURL, mDecodeOpts);
if(null == result){
Log.w("CACHE", "Downloading: " + mURL);
// The bitmap isn't cached so download from the web
HttpURLConnection conn = (HttpURLConnection)new URL(mURL).openConnection();
InputStream is = new BufferedInputStream(conn.getInputStream());
// Add to cache
result = mCache.put(mURL, is, mDecodeOpts);
}
else{
Log.w("CACHE", "Got from Disk Cache: " + mURL);
}
return result;
}
catch(IOException e){
Log.e("Error downloading image", e.toString());
}
catch(OutOfMemoryError e){
Log.e(TAG, "running out of memory, trimming image memory cache");
outOfMemoryFailure = true;
}
return null;
}
#Override
protected void onPostExecute(final CacheableBitmapDrawable result)
{
// super.onPostExecute(result);
if(outOfMemoryFailure || result == null || !result.hasValidBitmap()){
mCache.trimMemory();
// viewRef.get().loadImageAsync(mURL, mDecodeOpts);
Log.e(TAG, "image probably did not load::" + mURL);
}
else{
if(BuildConfig.DEBUG){
Log.d("bitmapCache", "NetworkedCacheableImageView.ImageUrlAsyncTask.onPostExecute() WIDTH:"
+ result.getBitmap().getWidth());
Log.d("bitmapCache", "NetworkedCacheableImageView.ImageUrlAsyncTask.onPostExecute() HEIGHT:"
+ result.getBitmap().getHeight());
}
Log.i(TAG, "RESULT for :" + mURL + " mImageViewRef::" + mImageViewRef + " outOfMemoryFailure::"
+ outOfMemoryFailure);
if(result != null){
Log.i(TAG, "RESULT object for :" + mURL + " result:hasValidBitmap" + result.hasValidBitmap()
+ " result:isReferencedByCache" + result.isReferencedByCache() + " result.isBeingDisplayed() "
+ result.isBeingDisplayed());
}
Runnable runnable = new Runnable()
{
#Override
public void run()
{
if(mImageViewRef != null){
final ImageView iv = mImageViewRef.get();
Log.e(TAG, "RESULT image view reference :" + mURL + " view ref::" + iv);
if(null != iv){
iv.setImageDrawable(result);
if(mLoadListener != null){
mLoadListener.onImageLoaded(true);
}
}
}
}
};
Handler handler = new Handler();
handler.postDelayed(runnable, 50);
}
super.onPostExecute(result);
}
}
private final BitmapLruCache mCache;
private ImageUrlAsyncTask mCurrentTask;
private ImageLoadListener mListener;
public NetworkedCacheableImageView(Context context, AttributeSet attrs)
{
super(context, attrs);
mCache = WatchApplication.getBitmapCache();
}
public void setLoadListener(ImageLoadListener listener)
{
mListener = listener;
}
public void removeListener()
{
mListener = null;
}
/**
* Loads the Bitmap.
*
* #param url
* - URL of image
* #param fullSize
* - Whether the image should be kept at the original size
* #return true if the bitmap was found in the cache
*/
public boolean loadImage(String url, final boolean fullSize)
{
setImageDrawable(null);
// First check whether there's already a task running, if so cancel it
if(TextUtils.isEmpty(url))
return false;
if(null != mCurrentTask){
mCurrentTask.cancel(false);
}
// Check to see if the memory cache already has the bitmap. We can
// safely do
// this on the main thread.
BitmapDrawable wrapper = mCache.getFromMemoryCache(url);
if(null != wrapper){
// The cache has it, so just display it
if(BuildConfig.DEBUG){
Log.w(TAG, "CACHE. FOUND IN MEMORY:" + url);
}
setImageDrawable(wrapper);
if(mListener != null){
mListener.onImageLoaded(false);
}
return true;
}
else{
// Memory Cache doesn't have the URL, do threaded request...
BitmapFactory.Options decodeOpts = null;
if(!fullSize){
decodeOpts = new BitmapFactory.Options();
// decodeOpts.inDensity = DisplayMetrics.DENSITY_XHIGH;
decodeOpts.inPurgeable = true;
decodeOpts.outHeight = this.getHeight();
decodeOpts.outWidth = this.getWidth();
}
loadImageAsync(url, decodeOpts);
return false;
}
}
public void loadImageAsync(String url, BitmapFactory.Options decodeOpts)
{
mCurrentTask = new ImageUrlAsyncTask(this, mCache, decodeOpts, mListener, this);
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB){
SDK11.executeOnThreadPool(mCurrentTask, url);
}
else{
mCurrentTask.execute(url);
}
}
}
Thanks in advance for any help!
Two things worth trying -
For chaining animation operations use a listener:
mPlaceHolder.animate()
.alpha(0f)
.scaleX(0.9f)
.scaleY(0.9f)
.rotationY(90)
.setDuration(DURATION)
.setListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
mCacheableImage.setImageDrawable(drawable);
mCacheableImage.animate()
.alpha(1f)
.scaleY(1f)
.scaleX(1f)
.rotationY(0)
.setDuration(DURATION)
.setListener(null);
}
});
Use setHasTransientState() to ensure the views are not recycled in the ListView/GridView. See this DevByte video for more info.

Android - Loading image using url but after they are loaded, the other image replaces the other

I'm wondering what happened to this on. My app loads image using the url.
I have successfully done the loading and displaying. The problem is,
the image when displayed, displays the wrong image...
It's better to show it in image:
At first, while other images are still being loaded, the correct images are displayed[TOP IMAGE].
But after all images are loaded, img2 duplicates and replaces img1 but still using the
dimensions of img1?
this is the code i use when loading the image:
public class DrawableBackgroundDownloader {
private final Map<String, Drawable> mCache = new HashMap<String, Drawable>();
private final LinkedList <Drawable> mChacheController = new LinkedList <Drawable> ();
private ExecutorService mThreadPool;
private final Map<ImageView, String> mImageViews = Collections.synchronizedMap(new WeakHashMap<ImageView, String>());
public static int MAX_CACHE_SIZE = 80;
public int THREAD_POOL_SIZE = 3;
/**
* Constructor
*/
public DrawableBackgroundDownloader() {
mThreadPool = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
}
/**
* Clears all instance data and stops running threads
*/
public void Reset() {
ExecutorService oldThreadPool = mThreadPool;
mThreadPool = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
oldThreadPool.shutdownNow();
mChacheController.clear();
mCache.clear();
mImageViews.clear();
}
/**
* Load the drawable associated to a url and assign it to an image, you can set a placeholder to replace this drawable.
* #param url Is the url of the image.
* #param imageView The image to assign the drawable.
* #param placeholder A drawable that is show during the image is downloading.
*/
public void loadDrawable(final String url, final ImageView imageView,Drawable placeholder) {
if(!mImageViews.containsKey(url))
mImageViews.put(imageView, url);
Drawable drawable = getDrawableFromCache(url);
// check in UI thread, so no concurrency issues
if (drawable != null) {
//Log.d(null, "Item loaded from mCache: " + url);
imageView.setImageDrawable(drawable);
} else {
imageView.setImageDrawable(placeholder);
queueJob(url, imageView, placeholder);
}
}
/**
* Return a drawable from the cache.
* #param url url of the image.
* #return a Drawable in case that the image exist in the cache, else returns null.
*/
public Drawable getDrawableFromCache(String url) {
if (mCache.containsKey(url)) {
return mCache.get(url);
}
return null;
}
/**
* Save the image to cache memory.
* #param url The image url
* #param drawable The drawable to save.
*/
private synchronized void putDrawableInCache(String url,Drawable drawable) {
int chacheControllerSize = mChacheController.size();
if (chacheControllerSize > MAX_CACHE_SIZE)
mChacheController.subList(0, MAX_CACHE_SIZE/2).clear();
mChacheController.addLast(drawable);
mCache.put(url, drawable);
}
/**
* Queue the job to download the image.
* #param url Image url.
* #param imageView The ImageView where is assigned the drawable.
* #param placeholder The drawable that is show during the image is downloading.
*/
private void queueJob(final String url, final ImageView imageView,final Drawable placeholder) {
/* Create handler in UI thread. */
final Handler handler = new Handler() {
#Override
public void handleMessage(Message msg) {
String tag = mImageViews.get(imageView);
if (tag != null && tag.equals(url)) {
if (imageView.isShown())
if (msg.obj != null) {
imageView.setImageDrawable((Drawable) msg.obj);
} else {
imageView.setImageDrawable(placeholder);
//Log.d(null, "fail " + url);
}
}
}
};
mThreadPool.submit(new Runnable() {
public void run() {
final Drawable bmp = downloadDrawable(url);
// if the view is not visible anymore, the image will be ready for next time in cache
if (imageView.isShown())
{
Message message = Message.obtain();
message.obj = bmp;
//Log.d(null, "Item downloaded: " + url);
handler.sendMessage(message);
}
}
});
}
/**
* Method that download the image
* #param url The url image.
* #return Returns the drawable associated to this image.
*/
private Drawable downloadDrawable(String url) {
try {
InputStream is = getInputStream(url);
Drawable drawable = Drawable.createFromStream(is, url);
putDrawableInCache(url,drawable);
return drawable;
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
/**
* This method manage the connection to download the image.
* #param urlString url of the image.
* #return Returns an InputStream associated with the url image.
* #throws MalformedURLException
* #throws IOException
*/
private InputStream getInputStream(String urlString) throws MalformedURLException, IOException {
URL url = new URL(urlString);
URLConnection connection;
connection = url.openConnection();
connection.setUseCaches(true);
connection.connect();
InputStream response = connection.getInputStream();
return response;
}
}
Also, when i used that code above to download my image, i've noticed, that in my other activity, after all the images are being loaded, the horizontal scroll (devsmart) no longer work.
When i tried scrolling before the images are loaded, it still work. So i tried to use another one, following this, the scrolling works but my images cant be loaded because of leak issues...
Can anyone share idea regarding this problems. Any help will be appreciated.
So, I've decided to use this library, UrlImageViewHelper by koush.
This solved my problem. The loading of image seems stable than mine.

How to efficiently store bitmaps in Android?

I'm building a relatively basic news-reader app that involves displaying news in a custom listview (Image + Title + Short Description per list element).
My question is How can I store the images I download from the server and then attach them to the listview? The images will be relatively small, 200 X 200 usually, in .jpeg format.
It's not so much a question of how as much as "how to do it efficiently", as I'm already noticing lag in lower-end phones when using the default "ic_launcher" icon instead of bitmaps.
Would it be faster to store them as files or into the news database along with other news data when the app starts and syncs up the news or cache them...?
How should I go about this?
better you can do it's use SoftReference via an ImageManager class.
In you ListAdpater getView() method call the displayImage() method of ImageManager.
ImageManager Coding Exemple :
public class ImageManagerExemple {
private static final String LOG_TAG = "ImageManager";
private static ImageManagerExemple instance = null;
public static ImageManagerExemple getInstance(Context context) {
if (instance == null) {
instance = new ImageManagerExemple(context);
}
return instance;
}
private HashMap<String, SoftReference<Bitmap>> imageMap = new HashMap<String, SoftReference<Bitmap>>();
private Context context;
private File cacheDir;
private ImageManagerExemple(Context context) {
this.context = context;
// Find the dir to save cached images
String sdState = android.os.Environment.getExternalStorageState();
if (sdState.equals(android.os.Environment.MEDIA_MOUNTED)) {
File sdDir = android.os.Environment.getExternalStorageDirectory();
cacheDir = new File(sdDir,"data/yourappname");
} else {
cacheDir = context.getCacheDir();
}
if(!cacheDir.exists()) {
cacheDir.mkdirs();
}
}
/**
* Display web Image loading thread
* #param imageUrl picture web url
* #param imageView target
* #param imageWaitRef picture during loading
*/
public void displayImage(String imageUrl, ImageView imageView, Integer imageWaitRef) {
String imageKey = imageUrl;
imageView.setTag(imageKey);
if(imageMap.containsKey(imageKey) && imageMap.get(imageKey).get() != null) {
Bitmap bmp = imageMap.get(imageKey).get();
imageView.setImageBitmap(bmp);
} else {
queueImage(imageUrl, imageView);
if(imageWaitRef != null)
imageView.setImageResource(imageWaitRef);
}
}
private void queueImage(String url, ImageView imageView) {
ImageRef imgRef=new ImageRef(url, imageView);
// Start thread
Thread imageLoaderThread = new Thread(new ImageQueueManager(imgRef));
// Make background thread low priority, to avoid affecting UI performance
imageLoaderThread.setPriority(Thread.NORM_PRIORITY-1);
imageLoaderThread.start();
}
private Bitmap getBitmap(String url) {
String filename = String.valueOf(url.hashCode());
File f = new File(cacheDir, filename);
try {
// Is the bitmap in our cache?
Bitmap bitmap = BitmapFactory.decodeFile(f.getPath());
if(bitmap != null) return bitmap;
// Nope, have to download it
bitmap = ImageServerUtils.pictureUrlToBitmap(url);
// save bitmap to cache for later
writeFile(bitmap, f);
return bitmap;
} catch (Exception ex) {
ex.printStackTrace();
Log.e(LOG_TAG, ""+ex.getLocalizedMessage());
return null;
} catch (OutOfMemoryError e) {
Log.e(LOG_TAG, "OutOfMemoryError : "+e.getLocalizedMessage());
e.printStackTrace();
return null;
}
}
private void writeFile(Bitmap bmp, File f) {
if (bmp != null && f != null) {
FileOutputStream out = null;
try {
out = new FileOutputStream(f);
//bmp.compress(Bitmap.CompressFormat.PNG, 80, out);
bmp.compress(Bitmap.CompressFormat.JPEG, 80, out);
} catch (Exception e) {
e.printStackTrace();
}
finally {
try { if (out != null ) out.close(); }
catch(Exception ex) {}
}
}
}
private class ImageRef {
public String imageUrl;
public ImageView imageView;
public ImageRef(String imageUrl, ImageView i) {
this.imageUrl=imageUrl;
this.imageView=i;
}
}
private class ImageQueueManager implements Runnable {
private ImageRef imageRef;
public ImageQueueManager(ImageRef imageRef) {
super();
this.imageRef = imageRef;
}
#Override
public void run() {
ImageRef imageToLoad = this.imageRef;
if (imageToLoad != null) {
Bitmap bmp = getBitmap(imageToLoad.imageUrl);
String imageKey = imageToLoad.imageUrl;
imageMap.put(imageKey, new SoftReference<Bitmap>(bmp));
Object tag = imageToLoad.imageView.getTag();
// Make sure we have the right view - thread safety defender
if (tag != null && ((String)tag).equals(imageKey)) {
BitmapDisplayer bmpDisplayer = new BitmapDisplayer(bmp, imageToLoad.imageView);
Activity a = (Activity)imageToLoad.imageView.getContext();
a.runOnUiThread(bmpDisplayer);
}
}
}
}
//Used to display bitmap in the UI thread
private class BitmapDisplayer implements Runnable {
Bitmap bitmap;
ImageView imageView;
public BitmapDisplayer(Bitmap b, ImageView i) {
bitmap=b;
imageView=i;
}
#Override
public void run() {
if(bitmap != null) {
imageView.setImageBitmap(bitmap);
}
}
}
The trick to getting smooth ListView scrolling without stutter is to not update it in any way, shape or form while the user is scrolling it. Afaik, this is essentially how iOS manages to get its ListViews that smooth: it disallows any changes to it (and the UI in general) while the user has his finger on it.
Just comment out any code that changes your ListView while leaving all the bitmap loading code intact, and you'll see that the actual loading of the bitmaps in the background doesn't really impact performance at all. The problem is that the UI thread can't keep up with view updates and scrolling at the same time.
You can achieve the same thing by using a OnScrollListener that blocks all updates to the ListView while the User is scrolling it. As soon as the user stops, you can sneak in all pending updates.
For added performance, try not to use notifyDataSetChanged but iterate over the views of the ListView and only update the views that have actually changed.

Android: Make App accessible without internet connection

so I made an app which communicates with JSON on the web. It fetches images and texts from the JSON.
And now I got a task to make this app accessible without an internet connection.
it should be like this:
The first time it's launched, the app has to check if there's any internet connection or not. If not, pop up a dialog box 'Please check your internet connection'. If there's any, the app is opened and it has to download the images and texts from the JSON and save them into an external storage
The next time when the app is opened, when there's no internet it will load the images and text files from the external storage. And each time it's connected to the internet, it will download the files and replace the previous files.
Can anybody provide me a solution by modifying these classes below maybe?
public class ImageThreadLoader {
private static final String TAG = "ImageThreadLoader";
// Global cache of images.
// Using SoftReference to allow garbage collector to clean cache if needed
private final HashMap<String, SoftReference<Bitmap>> Cache = new HashMap<String, SoftReference<Bitmap>>();
private final class QueueItem {
public URL url;
public ImageLoadedListener listener;
}
private final ArrayList<QueueItem> Queue = new ArrayList<QueueItem>();
private final Handler handler = new Handler(); // Assumes that this is started from the main (UI) thread
private Thread thread;
private QueueRunner runner = new QueueRunner();;
/** Creates a new instance of the ImageThreadLoader */
public ImageThreadLoader() {
thread = new Thread(runner);
}
/**
* Defines an interface for a callback that will handle
* responses from the thread loader when an image is done
* being loaded.
*/
public interface ImageLoadedListener {
public void imageLoaded(Bitmap imageBitmap );
}
/**
* Provides a Runnable class to handle loading
* the image from the URL and settings the
* ImageView on the UI thread.
*/
private class QueueRunner implements Runnable {
public void run() {
synchronized(this) {
while(Queue.size() > 0) {
final QueueItem item = Queue.remove(0);
// If in the cache, return that copy and be done
if( Cache.containsKey(item.url.toString()) && Cache.get(item.url.toString()) != null) {
// Use a handler to get back onto the UI thread for the update
handler.post(new Runnable() {
public void run() {
if( item.listener != null ) {
// NB: There's a potential race condition here where the cache item could get
// garbage collected between when we post the runnable and it's executed.
// Ideally we would re-run the network load or something.
SoftReference<Bitmap> ref = Cache.get(item.url.toString());
if( ref != null ) {
item.listener.imageLoaded(ref.get());
}
}
}
});
} else {
final Bitmap bmp = readBitmapFromNetwork(item.url);
if( bmp != null ) {
Cache.put(item.url.toString(), new SoftReference<Bitmap>(bmp));
// Use a handler to get back onto the UI thread for the update
handler.post(new Runnable() {
public void run() {
if( item.listener != null ) {
item.listener.imageLoaded(bmp);
}
}
});
}
}
}
}
}
}
/**
* Queues up a URI to load an image from for a given image view.
*
* #param uri The URI source of the image
* #param callback The listener class to call when the image is loaded
* #throws MalformedURLException If the provided uri cannot be parsed
* #return A Bitmap image if the image is in the cache, else null.
*/
public Bitmap loadImage( final String uri, final ImageLoadedListener listener) throws MalformedURLException {
// If it's in the cache, just get it and quit it
if( Cache.containsKey(uri)) {
SoftReference<Bitmap> ref = Cache.get(uri);
if( ref != null ) {
return ref.get();
}
}
QueueItem item = new QueueItem();
item.url = new URL(uri);
item.listener = listener;
Queue.add(item);
// start the thread if needed
if( thread.getState() == State.NEW) {
thread.start();
} else if( thread.getState() == State.TERMINATED) {
thread = new Thread(runner);
thread.start();
}
return null;
}
/**
* Convenience method to retrieve a bitmap image from
* a URL over the network. The built-in methods do
* not seem to work, as they return a FileNotFound
* exception.
*
* Note that this does not perform any threading --
* it blocks the call while retrieving the data.
*
* #param url The URL to read the bitmap from.
* #return A Bitmap image or null if an error occurs.
*/
public static Bitmap readBitmapFromNetwork( URL url ) {
InputStream is = null;
BufferedInputStream bis = null;
Bitmap bmp = null;
try {
URLConnection conn = url.openConnection();
conn.connect();
is = conn.getInputStream();
bis = new BufferedInputStream(is);
bmp = BitmapFactory.decodeStream(bis);
} catch (MalformedURLException e) {
Log.e(TAG, "Bad ad URL", e);
} catch (IOException e) {
Log.e(TAG, "Could not get remote ad image", e);
} finally {
try {
if( is != null )
is.close();
if( bis != null )
bis.close();
} catch (IOException e) {
Log.w(TAG, "Error closing stream.");
}
}
return bmp;
}
}
and
public class ProjectAdapter extends ArrayAdapter<Project> {
int resource;
String response;
Context context;
private final static String TAG = "MediaItemAdapter";
private ImageThreadLoader imageLoader = new ImageThreadLoader();
//Initialize adapter
public ProjectAdapter(Context context, int resource, List<Project> items) {
super(context, resource, items);
this.resource=resource;
}
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
TextView textTitle;
final ImageView image;
Project pro = getItem(position);
LinearLayout projectView;
//Inflate the view
if(convertView==null)
{
projectView = new LinearLayout(getContext());
String inflater = Context.LAYOUT_INFLATER_SERVICE;
LayoutInflater vi;
vi = (LayoutInflater)getContext().getSystemService(inflater);
vi.inflate(resource, projectView, true);
}
else
{
projectView = (LinearLayout) convertView;
}
try {
textTitle = (TextView)projectView.findViewById(R.id.txt_title);
image = (ImageView)projectView.findViewById(R.id.image);
} catch( ClassCastException e ) {
Log.e(TAG, "Your layout must provide an image and a text view with ID's icon and text.", e);
throw e;
}
Bitmap cachedImage = null;
try {
cachedImage = imageLoader.loadImage(pro.smallImageUrl, new ImageLoadedListener() {
public void imageLoaded(Bitmap imageBitmap) {
image.setImageBitmap(imageBitmap);
notifyDataSetChanged(); }
});
} catch (MalformedURLException e) {
Log.e(TAG, "Bad remote image URL: " + pro.smallImageUrl, e);
}
textTitle.setText(pro.project_title);
if( cachedImage != null ) {
image.setImageBitmap(cachedImage);
}
return projectView;
}
}
Thank you!
Create a database with the names and paths of the downloaded images. Upon onCreate() (or wherever you want to do the check), read the database and check if it's empty or not. If not, then use the images.

Categories

Resources