Lazy loading GridView with downloading images from the Internet

I have been visiting Stack Overflow for many years, and for the first time I can’t find a message that can solve my problem (at least I haven’t seen it).

I have a GridView with a custom adapter that I redefined to return the custom view made by ImageView and TextView .

I upload images after JSON parses them with URLs using AsyncTask , storing all the information in an ArrayList in the doInBackground() method and calling notifyDataSetChanged() in the onPostExecute() method. All perfectly.

Now my problem is that it takes 5-10 seconds to start an activity before the grid view creates and presents itself to the user in essence. I am wondering if there is a way to show a grid view with text information first, and then each image will load. Is this possible or not, because they are both created by 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 should also tell you, as you can see from the DisplayImage() method, that I implemented this lazy loading: Lazy loading of images in a ListView . It works great, but the thing is, it loads the whole view again. What I want to do is run the action, first upload the signature, and then upload the image when it finishes uploading. With this code, here it just lazily loads the whole view that each grid cell contains. I earned a few seconds because I do not download all the images at the same time as before, but still this is not what I am looking for.

Thank you very much.

+4
source share
3 answers

Follow this approach.

First , create your own 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 Office, 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 short, you set up a placeholder image for ImageView, which is replaced with the actual image when the download is completed. Therefore, the GridView will display immediately without delay.

Implementation Details : Therefore, in your custom view (with image + text), instead of a simple ImageView, use WebImageView, as shown above. When you get a JSON response, install a TextView with the caption and a WebImageView with the image URL. Therefore, the title will be displayed immediately and the image will load lazily.

+4
source

I used the class below to implement Lazy loading images, which it works great for me. You also try.

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 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 shown below:

First you need to put the ProgressBar in your own layout, where you have an ImageView , as shown 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 an instance of the ImageLoader class and use it, as shown below, in the 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); } 

Hope this helps you.

thanks

+1
source

The answer you mentioned is not good, in my opinion. For example, if you have 50 images, when the user scrolls up / down the entire list, 50 threads will be created in this model project. This is bad for mobile devices such as a mobile phone. Side notes, its concept of a "lazy list" is different from that defined by the Android SDK. For an example code for viewing a lazy boot list, see:

 [Android SDK]/samples/android-x/ApiDemos/src/com/example/android/apis/view/List13.java 

where x is the API level. You can test the compiled application in any emulators, open the API application Demos> Views> Lists> 13. Slow Adapter.

About your current approach. You should not use AsyncTask to download images. The documentation says:

AsyncTasks is ideal for short operations (just a few seconds).

Instead, you should:

  • Use service to download images in the background. Note that services start in the main user interface thread, so to avoid NetworkOnMainThreadException , you need something like Thread in your service.
  • Use the content provider to manage downloaded images on the SD card. For example, you save a map of the source URLs to the corresponding files you uploaded.
  • Along with the content provider, use the CursorAdapter for your grid view and loaders for your activity / fragment that hosts the grid view.

Basically, the first time a user opens your activity, you create a new adapter and install it as a grid. Thus, he has a relationship with the content provider. Then you start the service to check and download images. For each uploaded image, you insert it into the content provider. The provider notifies all observers of changes - your activity / fragment (bootloader) receives a notification and updates the user interface.

0
source

All Articles