Question

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.

Was it helpful?

Solution

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.

OTHER TIPS

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top